最近对项目中的微信支付功能做了升级,之前使用的是V2版本。V2版本目前还可以使用,但已暂停更新。V3版本的集成,官方文档还是比较清晰的,但各类的配置,一个不小心就掉坑里半天爬不出来。趁着思路清晰,特此记录一下。
V2版本参数格式是xml格式,不太好维护,V3版本已改成json格式。
V2版本的签名是拼在参数里面的,V3版本校验都放在配置类里面了,更加方便灵活。
前置条件
官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
1、微信开放平台 – APP支付
- 注册APP,获取appId appSecret等信息
2、微信公众平台 – (微信公众号 小程序) 微信内支付
- 开通账号,申请支付功能,绑定商户平台
- 配置域名等
3、浏览器H5支付
- 申请权限:微信支付商户平台—>产品中心—>H5支付—>申请开通
- 配置:产品中心—>开发配置—>H5支付
4、微信商户平台
- 商户号
- API证书密钥及证书序列号
- API v3密钥
代码集成
微信提供两种集成方式:wechatpay-java(推荐);wechatpay-apache-httpclient,以推荐的方式为例:
com.github.wechatpay-apiv3
wechatpay-java
0.2.5
配置初始化 – 加载微信支付平台证书
使用自动更新平台证书的配置类 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);
}