微信支付V3版本集成詳解【避坑指南】
最近對項目中的微信支付功能做了升級,之前使用的是V2版本。V2版本目前還可以使用,但已暫停更新。V3版本的集成,官方文檔還是比較清晰的,但各類的配置,一個不小心就掉坑里半天爬不出來。趁著思路清晰,特此記錄一下。
V2版本參數格式是xml格式,不太好維護,V3版本已改成json格式。
V2版本的簽名是拼在參數里面的,V3版本校驗都放在配置類里面了,更加方便靈活。
前置條件
1、微信開放平臺 – APP支付
- 注冊APP,獲取appId appSecret等信息
2、微信公眾平臺 – (微信公眾號 小程序) 微信內支付
- 開通賬號,申請支付功能,綁定商戶平臺
- 配置域名等
3、瀏覽器H5支付
- 申請權限:微信支付商戶平臺—>產品中心—>H5支付—>申請開通
- 配置:產品中心—>開發配置—>H5支付
4、微信商戶平臺
- 商戶號
- API證書密鑰及證書序列號
- API v3密鑰
代碼集成
微信提供兩種集成方式:wechatpay-java(推薦);wechatpay-apache-httpclient,以推薦的方式為例:
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.5</version>
</dependency>
配置初始化 – 加載微信支付平臺證書
使用自動更新平臺證書的配置類 RSAAutoCertificateConfig。注:每個商戶號只能創建一個 RSAAutoCertificateConfig。
代碼實現,將配置交由Spring統一管理,單例模式保證初始化一次。
@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") // 不填則默認為RSA
.build();
}
@Bean("appService")
public AppServiceExtension getAppService() {
// App支付
return new AppServiceExtension.Builder().config(config).build();
}
@Bean("NotificationParser")
public NotificationParser getNotificationParser(){
// 支付回調的解析器
return new NotificationParser((NotificationConfig)config);
}
}
獲取支付請求信息
APP下單
/**
* 獲取微信支付參數(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();
}
公眾號 小程序下單
/**
* 獲取微信支付參數(公眾號 小程序)
*/
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支付參數:{}", 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);
// 調用接口
PrepayResponse response = h5Service.prepay(request);
return response.getH5Url();
}
支付回調
獲取 HTTP 請求頭中的以下值,構建 RequestParam 。
- Wechatpay-Signature
- Wechatpay-Nonce
- Wechatpay-Timestamp
- Wechatpay-Serial
- Wechatpay-Signature-Type
獲取 HTTP 請求體 body。切記不要用 JSON 對象序列化后的字符串,避免驗簽的 body 和原文不一致。
根據解密后的通知數據數據結構,構造解密對象類 DecryptObject 。支付結果通知解密對象類為 Transaction,退款結果通知解密對象類為 RefundNotification。
初始化 RSAAutoCertificateConfig(已在前文統一初始化)。
初始化 NotificationParser(已在前文統一初始化)。
使用請求參數 requestParam 和 DecryptObject.class ,調用 parser.parse 驗簽并解密報文。
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)){
//處理業務邏輯
//通知微信支付成功
wechatPayUtil.paySuccessful(response);
}