微信支付V3版本集成詳解【避坑指南】

最近對(duì)項(xiàng)目中的微信支付功能做了升級(jí),之前使用的是V2版本。V2版本目前還可以使用,但已暫停更新。V3版本的集成,官方文檔還是比較清晰的,但各類的配置,一個(gè)不小心就掉坑里半天爬不出來。趁著思路清晰,特此記錄一下。
V2版本參數(shù)格式是xml格式,不太好維護(hù),V3版本已改成json格式。
V2版本的簽名是拼在參數(shù)里面的,V3版本校驗(yàn)都放在配置類里面了,更加方便靈活。
前置條件
1、微信開放平臺(tái) – APP支付
- 注冊(cè)APP,獲取appId appSecret等信息
 
2、微信公眾平臺(tái) – (微信公眾號(hào) 小程序) 微信內(nèi)支付
- 開通賬號(hào),申請(qǐng)支付功能,綁定商戶平臺(tái)
 - 配置域名等
 
3、瀏覽器H5支付
- 申請(qǐng)權(quán)限:微信支付商戶平臺(tái)—>產(chǎn)品中心—>H5支付—>申請(qǐng)開通
 - 配置:產(chǎn)品中心—>開發(fā)配置—>H5支付
 
4、微信商戶平臺(tái)
- 商戶號(hào)
 - API證書密鑰及證書序列號(hào)
 

- API v3密鑰
 

代碼集成
微信提供兩種集成方式:wechatpay-java(推薦);wechatpay-apache-httpclient,以推薦的方式為例:
<dependency>
  <groupId>com.github.wechatpay-apiv3</groupId>
  <artifactId>wechatpay-java</artifactId>
  <version>0.2.5</version>
</dependency>配置初始化 – 加載微信支付平臺(tái)證書
使用自動(dòng)更新平臺(tái)證書的配置類 RSAAutoCertificateConfig。注:每個(gè)商戶號(hào)只能創(chuàng)建一個(gè) RSAAutoCertificateConfig。
代碼實(shí)現(xiàn),將配置交由Spring統(tǒng)一管理,單例模式保證初始化一次。
@Configuration
public class WXPayConfig {
    private Config config;
    @PostConstruct
    public void init(){
        config =
                new RSAAutoCertificateConfig.Builder()
                        .merchantId(WXPayConstants.MCHID)
                        .privateKey(WXPayConstants.PRIVATE_KEY)
                        .merchantSerialNumber(WXPayConstants.MERCHANT_SERIAL_NUMBER)
                        .apiV3Key(WXPayConstants.API_V3_KEY)
                        .build();
    }
    @Bean("h5Service")
    public H5Service getH5Service(){
        // H5支付
        return new H5Service.Builder().config(config).build();
    }
    @Bean("jsService")
    public JsapiServiceExtension getJsService(){
        // 微信js支付
        return new JsapiServiceExtension.Builder()
                        .config(config)
                        .signType("RSA") // 不填則默認(rèn)為RSA
                        .build();
    }
    @Bean("appService")
    public AppServiceExtension getAppService() {
        // App支付
        return new AppServiceExtension.Builder().config(config).build();
    }
    @Bean("NotificationParser")
    public NotificationParser getNotificationParser(){
        // 支付回調(diào)的解析器
        return new NotificationParser((NotificationConfig)config);
    }
}獲取支付請(qǐng)求信息
APP下單
/**
 * 獲取微信支付參數(shù)(APP)
 */
public WechatPayDTO getWechatAppPayParam(BigDecimal money, String orderNumber, String notifyUrl) throws Exception {
    // 下單
    com.wechat.pay.java.service.payments.app.model.PrepayRequest request = new com.wechat.pay.java.service.payments.app.model.PrepayRequest();
    com.wechat.pay.java.service.payments.app.model.Amount amount = new com.wechat.pay.java.service.payments.app.model.Amount();
    amount.setTotal(Integer.parseInt(totalFee(money)));
    amount.setCurrency("CNY");
    request.setAmount(amount);
    request.setAppid(WXPayConstants.APPID);
    request.setMchid(WXPayConstants.MCHID);
    request.setDescription("");
    request.setNotifyUrl(notifyUrl);
    request.setOutTradeNo(orderNumber);
    com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse response = appService.prepayWithRequestPayment(request);
    return WechatPayDTO.builder()
            .appid(response.getAppid())
            .partnerid(response.getPartnerId())
            .prepayid(response.getPrepayId())
            .packageVal(response.getPackageVal())
            .timestamp(response.getTimestamp())
            .noncestr(response.getNonceStr())
            .sign(response.getSign())
            .build();
}公眾號(hào) 小程序下單
/**
 * 獲取微信支付參數(shù)(公眾號(hào) 小程序)
 */
public WechatPayDTO getWechatJSAPIPayParam(String openid, BigDecimal money, String orderNumber, String notifyUrl) throws Exception {
    // 下單
    com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest request = new com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest();
    com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount();
    amount.setTotal(Integer.parseInt(totalFee(money)));
    amount.setCurrency("CNY");
    request.setAmount(amount);
    request.setAppid(WXPayConstants.PUBLIC_APPID);
    request.setMchid(WXPayConstants.MCHID);
    request.setDescription("");
    request.setNotifyUrl(notifyUrl);
    request.setOutTradeNo(orderNumber);
    Payer payer = new Payer();
    payer.setOpenid(openid);
    request.setPayer(payer);
    PrepayWithRequestPaymentResponse response = jsService.prepayWithRequestPayment(request);
    logger.info("JS支付參數(shù):{}", response.toString());
    return WechatPayDTO.builder()
            .appid(response.getAppId())
            .packageVal(response.getPackageVal())
            .timestamp(response.getTimeStamp())
            .noncestr(response.getNonceStr())
            .signType(response.getSignType())
            .paySign(response.getPaySign())
            .build();
}H5下單
/**
  * 獲取微信H5支付連接
	*/
public String getWechatH5PayUrl(BigDecimal money, String orderNumber, String notifyUrl) {
      // 下單
      PrepayRequest request = new PrepayRequest();
      Amount amount = new Amount();
      amount.setTotal(Integer.parseInt(totalFee(money)));
      amount.setCurrency("CNY");
      request.setAmount(amount);
      SceneInfo sceneInfo = new SceneInfo();
      sceneInfo.setPayerClientIp("");
      request.setSceneInfo(sceneInfo);
      request.setAppid(WXPayConstants.PUBLIC_APPID);
      request.setMchid(WXPayConstants.MCHID);
      request.setDescription("");
      request.setNotifyUrl(notifyUrl);
      request.setOutTradeNo(orderNumber);
      // 調(diào)用接口
      PrepayResponse response = h5Service.prepay(request);
      return response.getH5Url();
}支付回調(diào)
獲取 HTTP 請(qǐng)求頭中的以下值,構(gòu)建 RequestParam 。
- Wechatpay-Signature
 - Wechatpay-Nonce
 - Wechatpay-Timestamp
 - Wechatpay-Serial
 - Wechatpay-Signature-Type
 
獲取 HTTP 請(qǐng)求體 body。切記不要用 JSON 對(duì)象序列化后的字符串,避免驗(yàn)簽的 body 和原文不一致。
根據(jù)解密后的通知數(shù)據(jù)數(shù)據(jù)結(jié)構(gòu),構(gòu)造解密對(duì)象類 DecryptObject 。支付結(jié)果通知解密對(duì)象類為 Transaction,退款結(jié)果通知解密對(duì)象類為 RefundNotification。
初始化 RSAAutoCertificateConfig(已在前文統(tǒng)一初始化)。
初始化 NotificationParser(已在前文統(tǒng)一初始化)。
使用請(qǐng)求參數(shù) requestParam 和 DecryptObject.class ,調(diào)用 parser.parse 驗(yàn)簽并解密報(bào)文。
RequestParam requestParam = new RequestParam.Builder()
      .serialNumber(request.getHeader("Wechatpay-Serial"))
      .nonce(request.getHeader("Wechatpay-Nonce"))
      .signature(request.getHeader("Wechatpay-Signature"))
      .timestamp(request.getHeader("Wechatpay-Timestamp"))
      .signType(request.getHeader("Wechatpay-Signature-Type"))
      .body(body)
      .build();
Transaction transaction = notificationParser.parse(requestParam, Transaction.class);
if (Objects.equals(transaction.getTradeState(), Transaction.TradeStateEnum.SUCCESS)){
  //處理業(yè)務(wù)邏輯
  //通知微信支付成功
  wechatPayUtil.paySuccessful(response);
}














 
 
 






 
 
 
 