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