springboot微信支付

微信支付官網(wǎng)api:https://pay.weixin.qq.com/docs/merchant/products/jsapi-payment/introduction.html

微信支付在Java中的應(yīng)用已經(jīng)非常流行,今天讓我們一起來寫一篇實(shí)戰(zhàn)篇的微信支付;

1.開發(fā)參數(shù)的準(zhǔn)備:https://pay.weixin.qq.com/docs/merchant/development/development-preparation/download-configure-merchant-certificates.html;

2.獲取到商戶證書以后,把壓縮包里的apiclient_key 文件放在項(xiàng)目的根目錄下,方便使用和讀取,如下:

image.png
image.png

3.創(chuàng)建加載商戶私鑰、加載平臺證書、初始化httpClient的通用方法。
這里我自己寫了一個(gè)配置文件
config配置文件會(huì)在項(xiàng)目啟動(dòng)時(shí)被加載(使用簽名驗(yàn)證器在每次使用微信支付的接口時(shí)會(huì)自動(dòng)驗(yàn)證身份)
3.1wxpay.propertie配置文件

#商戶號
wxpay.mch-id=替換為你自己的商戶號
#API
wxpay.mch-serial-no=替換為你自己的商戶號序列號
#商戶私鑰文件,放在工程目錄下
wxpay.private-key-path=替換為你自己的apiclient_key.pem
#APIv3密鑰
wxpay.api-v3-key=替換為你自己的APIv3密鑰
#APPID
wxpay.appid=替換為你自己的appid
#微信服務(wù)器地
wxpay.domain=替換為你自己的微信服務(wù)器地
# 接收結(jié)果通知地址
wxpay.notify-domain=替換為你自己的接收結(jié)果通知地址

3.2config配置文件

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;

@Configuration
@PropertySource("classpath:wxpay.properties") //讀取配置文件
@ConfigurationProperties(prefix = "wxpay") //讀取wxpay節(jié)點(diǎn)
@Data //使用set方法將wxpay節(jié)點(diǎn)中的值填充到當(dāng)前類的屬性中
@Slf4j
public class WxPayConfig {

    @Resource
    private WxPayConfig wxPayConfig;

    // 商戶號
    private String mchId;

    // 商戶API證書序列號
    private String mchSerialNo;

    // 商戶私鑰文件
    private String privateKeyPath;

    // APIv3密鑰
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服務(wù)器地址
    private String domain;

    // 接收結(jié)果通知地址
    private String notifyDomain;


    /**
     * 獲取商戶的私鑰文件
     *
     * @param filename
     * @return
     */
    public PrivateKey getPrivateKey(String filename) {

        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私鑰文件不存在", e);
        }
    }

    /**
     * 獲取簽名驗(yàn)證器
     *
     * @return
     */
    @Bean
    public verifier  getVerifier() throws HttpCodeException, GeneralSecurityException, IOException, NotFoundException {
        log.info("獲取簽名驗(yàn)證器");

        //獲取商戶私鑰
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //私鑰簽名對象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        //身份認(rèn)證對象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 獲取證書管理器實(shí)例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向證書管理器增加需要自動(dòng)更新平臺證書的商戶信息
        certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));

        //若有多個(gè)商戶號,可繼續(xù)調(diào)用putMerchant添加商戶信息
        certificatesManager.putMerchant(wxPayConfig.getMchId(), wechatPay2CredentialsDc, wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));

        // 從證書管理器中獲取verifier
        Verifier verifier = certificatesManager.getVerifier(mchId);
        return verifier ;
    }


    /**
     * 獲取http請求對象
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClientverifier  verifier){
        log.info("獲取httpClient");

        //獲取商戶私鑰
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier ));
        // ... 接下來,你仍然可以通過builder設(shè)置各種參數(shù),來配置你的HttpClient

        // 通過WechatPayHttpClientBuilder構(gòu)造的HttpClient,會(huì)自動(dòng)的處理簽名和驗(yàn)簽,并進(jìn)行證書自動(dòng)更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }

    /**
     * 獲取HttpClient,無需進(jìn)行應(yīng)答簽名驗(yàn)證,跳過驗(yàn)簽的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient() {

        //獲取商戶私鑰
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //用于構(gòu)造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //設(shè)置商戶信息
                .withMerchant(mchId, mchSerialNo, privateKey)
                //無需進(jìn)行簽名驗(yàn)證、通過withValidator((response) -> true)實(shí)現(xiàn)
                .withValidator((response) -> true);

        // 通過WechatPayHttpClientBuilder構(gòu)造的HttpClient,會(huì)自動(dòng)的處理簽名和驗(yàn)簽,并進(jìn)行證書自動(dòng)更新
        CloseableHttpClient httpClient = builder.build();

        log.info("== getWxPayNoSignClient END ==");

        return httpClient;
    }

}

4.調(diào)用支付接口,發(fā)起支付(此時(shí)需要對數(shù)據(jù)進(jìn)行簽名,簽名工具類在支付方法下;具體業(yè)務(wù)邏輯和參數(shù)自己添加)

    @PostMapping("/pay")
    public HashMap<String, Object> outpatientPlaceOrderNew() throws Exception {

        // 開始支付
        long timestamp = new Date().getTime();
        log.info("訂單開始支付,調(diào)用統(tǒng)一下單API");
        //調(diào)用統(tǒng)一下單API
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        // 請求body參數(shù)
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", "支付");
        paramsMap.put("out_trade_no", wjOrder.getId().toString());
        paramsMap.put("notify_url", "https://www.wjgzzdyy.mil.cn" + "/api/wxPay/notice");
        Map amountMap = new HashMap();
        amountMap.put("total", wjOrder.getAmount().multiply(new BigDecimal(100)).intValue());
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);
        Map payer = new HashMap();
        payer.put("openid", wjOrder.getOpenId());
        paramsMap.put("payer", payer);
        //將參數(shù)轉(zhuǎn)換成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("請求參數(shù) ===> {}" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成簽名并執(zhí)行請求
        CloseableHttpResponse response = dcWxPayClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//響應(yīng)體
            int statusCode = response.getStatusLine().getStatusCode();//響應(yīng)狀態(tài)碼
            log.info("響應(yīng)結(jié)果 = " + bodyAsString);
            if (statusCode == 200) {
                log.info("成功, 返回結(jié)果 = " + bodyAsString);
            } else if (statusCode == 204) { //處理成功,無返回Body
                log.info("成功");
            } else {
            log.info("失敗");
            }
            //響應(yīng)結(jié)果
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            String prepayId = resultMap.get("prepay_id");
            //組裝前端拉起支付需要的參數(shù)
            String nonceStr = signUtil.makeString();
            String signType = "RSA";
            String packageId = "prepay_id=" + prepayId;
            String source = appid + "\n";
            source += timestamp + "\n";
            source += nonceStr + "\n";
            source += packageId + "\n";
            //簽名
            String paySign = signUtil.signBySHA256WithRSAOrder(source, "UTF-8");
            HashMap<String, Object> res = new HashMap<>();
            res.put("timestamp", timestamp);
            res.put("nonceStr", nonceStr);
            res.put("package", packageId);
            res.put("signType", signType);
            res.put("paySign", paySign);
      
        } finally {
            response.close();
        }
        return res;
    }    

5.簽名工具類SignUtil


import com.colorful.hospital.config.WxPayConfig;
import com.colorful.hospital.config.WxPayDcConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.security.PrivateKey;
import java.security.Signature;

@Component
public class SignUtil {
    @Autowired
    WxPayConfig wxPayConfig;

    /**
     * SHA256withRSA簽名
     * @author xpl
     * @param content
     * @param charset
     * @return
     */
    public String signBySHA256WithRSA(String content, String charset){
        try {
            PrivateKey privateKey = wxPayConfig.getPrivateKey("apiclient_key.pem");
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(privateKey);
            signature.update(content.getBytes(charset));
            return org.apache.commons.codec.binary.Base64.encodeBase64String(signature.sign());
        } catch (Exception e) {
            //簽名失敗
            return null;
        }
    }

    public String makeString() {
        String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random1 = new Random();
        //指定字符串長度,拼接字符并toString
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            //獲取指定長度的字符串中任意一個(gè)字符的索引值int number=random1.nextInt(str.length());
            //根據(jù)索引值獲取對應(yīng)的字符
            char charAt = str.charAt(i);
            sb.append(charAt);
        }
        return sb.toString();
    }
}

6.微信支付通知(此處沒有做簽名驗(yàn)證)

     @ApiOperation("支付通知")
    @PostMapping("/notice")
    public Map wxJsApiCallback (@RequestBody Map body, HttpServletRequest request){
        log.info("進(jìn)入微信支付通知");
        Map<String, Object> result = new HashMap();
        //1:獲取微信支付回調(diào)的獲取簽名信息
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            // 2: 開始解析報(bào)文體
            String data = objectMapper.writeValueAsString(body);
            String message = timestamp + "\n" + nonce + "\n" + data + "\n";
            //3:獲取應(yīng)答簽名
            String sign = request.getHeader("Wechatpay-Signature");
            //4:獲取平臺對應(yīng)的證書
            String serialNo = request.getHeader("Wechatpay-Serial");
            Map<String, String> resource = (Map) body.get("resource");
            // 5:回調(diào)報(bào)文解密
            AesUtil aesUtil = new AesUtil(privateApiV3Key.getBytes());
            //解密后json字符串
            String decryptToString = aesUtil.decryptToString(
                    resource.get("associated_data").getBytes(),
                    resource.get("nonce").getBytes(),
                    resource.get("ciphertext"));
            //6:獲取微信支付返回的信息
            com.alibaba.fastjson.JSONObject jsonData = com.alibaba.fastjson.JSONObject.parseObject(decryptToString);
            log.info("wxJsApiCallback responseJson:" + jsonData.toJSONString());
            //7: 支付狀態(tài)的判斷 如果是success就代表支付成功
            if ("SUCCESS".equals(jsonData.get("trade_state"))) {
                // 8:獲取支付的交易單號,流水號,和附屬參數(shù)
                String out_trade_no = jsonData.get("out_trade_no").toString();
                String transaction_id = jsonData.get("transaction_id").toString();
                String success_time = jsonData.get("success_time").toString();
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
                Date parse = df.parse(success_time);
                com.alibaba.fastjson.JSONObject amount = jsonData.getJSONObject("amount");// 訂單金額信息
                int payMoney = amount.getIntValue("payer_total"); //實(shí)際支付金額
                // 成功處理,加自己的的業(yè)務(wù)
            
            } else {
                String out_trade_no = jsonData.get("out_trade_no").toString();
                // 失敗處理,加自己的的業(yè)務(wù)
            }
            result.put("code", "SUCCESS");
            result.put("message", "成功");
        } catch (Exception e) {
            result.put("code", "FAIL");
            result.put("message", "系統(tǒng)錯(cuò)誤");
            e.printStackTrace();
        }
        return result;
    }

7.退款

 @Resource
    private WxPayConfig wxPayConfig;
    @PostMapping("/refund")
    public AjaxResult refund1(@RequestBody Order Order) throws Exception {
        AjaxResult ajaxResult = new AjaxResult();
        //數(shù)據(jù)庫自行計(jì)算金額
        BigDecimal refundAmount = new BigDecimal(0);
        //商戶退款單號
            String outRefundNo = "R" + SnowFlake.nextId().toString();
            Long orderId = order.getId();
            // 退款金額
            int refund = refundAmount.multiply(new BigDecimal(100)).intValue();
            log.info("調(diào)用退款A(yù)PI");
            //調(diào)用統(tǒng)一下單API
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
            // 請求Body參數(shù)
            Gson gson = new Gson();
            Map<String, Object> paramsMap = new HashMap<>();
            paramsMap.put("out_trade_no", orderId.toString());
            paramsMap.put("out_refund_no", outRefundNo);
            Map amountMap = new HashMap<>();
            amountMap.put("total", order.getAmount().multiply(new BigDecimal(100)).intValue());
            amountMap.put("refund", refund);
            amountMap.put("currency", "CNY");
            paramsMap.put("amount", amountMap);
            // 將參數(shù)轉(zhuǎn)換成json字符串
            String jsonParams = gson.toJson(paramsMap);
            log.info("請求參數(shù) ===> {} " + jsonParams);

            StringEntity entity = new StringEntity(jsonParams, "utf-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");
            //完成簽名并執(zhí)行請求
            CloseableHttpResponse response = wxPayClient.execute(httpPost);
            try {
                //響應(yīng)體
                String bodyAsString = EntityUtils.toString(response.getEntity());
                // 響應(yīng)狀態(tài)碼
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == 200) {
                    log.info("成功 退款返回結(jié)果 = " + bodyAsString);
                   
                } else if (statusCode == 204) {
                    // 處理成功,無返回body
                    log.info("成功");
                } else {
                    throw new RuntimeException(" 退款異常,響應(yīng)碼 = " + statusCode + ", 退款返回結(jié)果 = " + bodyAsString);
                }
                ajaxResult = AjaxResult.success();

            } finally {
                response.close();
            }
        return ajaxResult;
    }

只介紹了一些基礎(chǔ)用法,如有其他更復(fù)雜的需求,請參考官網(wǎng)(開頭)
這里只是我個(gè)人理解的用法,如有理解不當(dāng)?shù)牡胤秸堉赋觯?/h5>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容