前言
微信新出的V3支付接口,还没玩过,正好有做支付的需求,于是就看看怎么搞了,下面是集成第三方jar来调用的微信支付,如何使用可以查看我的另一篇文章,不过方式跟v2也是有些区别的。
微信jar V2支付(含内网穿透工具)
V3接口规则(个人理解)
微信文档
发送请求前,构造签名串,使用证书和密钥文件签名
将生成签名按照规则组装后,作为请求头,发送请求
整合binarywang.weixin-java-pay实现v3支付
jar
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.0.0</version>
</dependency>
下单
该库现在支持使用V3方式构造Http Client,从而使用该Client发送请求时,我们就无需关心签名的生成,只需要设置接口所需参数即可,不过这个Client也需要我们来生成!!
生成Http Client
/**
* <p>
* 生成v3 http client并返回WxPayConfig
* </p>
* @param mchId 商户号
* @param apiV3Key api V3 密钥
* @param privateKey 私钥
* @param privateCert 证书
* @param isUrl 私钥和证书是否为url资源,false则表示文件资源
* @return com.github.binarywang.wxpay.config.WxPayConfig
* @author guohaibin
* @date 2020-12-02 13:53:14
*/
private WxPayConfig createV3WxPayConfig(String mchId, String apiV3Key,
String privateKey, String privateCert, boolean isUrl) {
WxPayConfig wxPayConfig = new WxPayConfig();
if (isUrl) {
// 手动构建http client - 通过url的方式来读取证书/密钥
Credentials credentials = new WxPayCredentials(
mchId,
new PrivateKeySigner(
PemUtils.loadCertificate(URLUtil.getStream(URLUtil.url(privateCert))).getSerialNumber().toString(16),
PemUtils.loadPrivateKey(URLUtil.getStream(URLUtil.url(privateKey)))
)
);
Validator validator = new WxPayValidator(
new AutoUpdateCertificatesVerifier(
credentials, apiV3Key.getBytes()
)
);
CloseableHttpClient client = WxPayV3HttpClientBuilder.create()
.withCredentials(credentials)
.withValidator(validator)
.build();
wxPayConfig.setApiV3HttpClient(client);
} else {
// 自动构建apiV3HttpClient时以下参数必须设置
// com.github.binarywang.wxpay.config.WxPayConfig.initApiV3HttpClient
wxPayConfig.setMchId(mchId);
wxPayConfig.setPrivateKeyPath(privateKey);
wxPayConfig.setPrivateCertPath(privateCert);
wxPayConfig.setApiV3Key(apiV3Key);
}
return wxPayConfig;
}
这里我使用两种方式来生成,一种是读文件的方式,一种是文件是url时的方式,因为我们项目时代小程序开发,所以如果用了V3,证书文件就会存在服务器上,因此考虑两种方式;但实际不适用,因为回调无法满足需求。
一般单独小程序使用,读文件的方式就足以使用了!
下单
微信文档
/**
* <p>
* JSAPI支付
* </p>
* @param appId 小程序appId
* @param mchId 小程序绑定商户号
* @param apiV3Key api V3 密钥
* @param description 商品描述(128)
* @param total 订单总金额(分)
* @param outTradeNo 订单号(需要生成)
* @param openId 用户openId
* @param privateKey 私钥
* @param privateCert 证书
* @param isUrl 私钥和证书是否为url资源,false则表示文件资源
* @return com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult
* @author guohaibin
* @date 2020-12-02 13:56:02
*/
public WxPayMpOrderResult createOrderJSAPI(String appId, String mchId, String apiV3Key,
String description, BigDecimal total, String outTradeNo,
String openId, String privateKey, String privateCert, boolean isUrl) throws Exception {
WxPayService wxPayService = new WxPayServiceImpl();
WxPayConfig wxPayConfig = createV3WxPayConfig(mchId, apiV3Key, privateKey, privateCert, isUrl);
wxPayService.setConfig(wxPayConfig);
// 设置参数
JSONObject data = new JSONObject();
data.set("appid", appId);
data.set("mchid", mchId);
data.set("description", description);
data.set("out_trade_no", outTradeNo);
data.set("notify_url", v3PayNotifyUrl);
JSONObject amount = new JSONObject();
amount.set("total", BaseWxPayRequest.yuanToFen(total.toString()));
data.set("amount", amount);
JSONObject payer = new JSONObject();
payer.set("openid", openId);
data.set("payer", payer);
// 调接口
String result = wxPayService.postV3("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi",
data.toString());
JSONObject prepayObject = JSONUtil.parseObj(result);
// 获取paySign
if (prepayObject.containsKey("prepay_id")) {
// 预支付签名
String packageStr = "prepay_id=" + JSONUtil.parseObj(result).getStr("prepay_id");
String nonceStr = SignUtils.genRandomStr();
String timeStamp = String.valueOf(System.currentTimeMillis());
String signType = "RSA";
// 构造paySign
String signStr = appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n";
String paySign = SignUtils.sign(signStr, getPrivateKey(privateKey, isUrl));
return new WxPayMpOrderResult(null, timeStamp, nonceStr, packageStr, signType, paySign);
} else {
// TODO 报错并打印有用信息
throw new WxPayException("v3获取预支付异常");
}
}
后续的流程就是设置参数,调接口,再签名出paySign,和V2类似,不过这里再次签名方式不一样,要先构造签名串,用V3的方式签名。
签名文档
支付回调
controller
因为V3都是JSON做参和返回值了,所以直接接收即可。
@PostMapping("/payNotify")
public String payNotify(@RequestBody PayV3Notify payV3Notify) {
// 需要考虑恶意调用,非法调用可以不考虑,因为有签名验证,但是要避免多次恶意调用
return payV3Service.payNotify(payV3Notify);
}
service
V2:返回值中会包含信息,然后再用商户密钥去校验签名,可利用信息
V3:返回值中不包含任何明文,需要ApiV3密钥解密后,才可解析出订单号等信息
因此,在我们项目中,我们可以通过appId来获取商户信息,但由于V3不再提供信息,导致我们无法根据商户号/订单号来查找商户信息,那么就无法使用V3来实现我们的逻辑,并且,V3暂未提供退款接口,因此后续的退款我们也实现不了,综上,最后我还是选择使用V2来实现支付。
public String payNotify(PayV3Notify payV3Notify) {
try {
// 解密
String decryptToString = AesUtils.decryptToString(payV3Notify.getResource().getAssociated_data(),
payV3Notify.getResource().getNonce(), payV3Notify.getResource().getCiphertext(),
wxConfig.getApiV3Key());
PayV3NotifyDecrypt payV3NotifyDecrypt = JSONUtil.toBean(decryptToString, PayV3NotifyDecrypt.class);
// 订单号
String outTradeNo = payV3NotifyDecrypt.getOut_trade_no();
// 判断订单状态
// 修改订单状态
return WxPayNotifyResponse.success("处理成功!");
} catch (Exception e) {
log.error("微信回调结果异常,异常原因{}", e.getMessage());
return WxPayNotifyResponse.fail(e.getMessage());
}
}
小结
就接口来说,V3调用的方式复杂了些,每次都需要构造签名,有点麻烦,不过配置好通用一下就行;
参数方面比V2好些,就不需要string -> xml或是xml -> string了,可以自定义对象来接收回调数据了
就支付回调来说,V2是先拿到数据,再校验数据,而V3是先解密,才能获取数据(个人感觉这样在代小程序开发中就不好用V3来搞,因为小程序是动态的,V2就能获取appId再连查密钥,而V3需要密钥才能获取信息,就不符合需求感觉!)
最后,用别人封装好的还是香啊,这个库后续估计还会对V3进行优化,到时候用起来就更滋润了!!
最后附上自己的demo,也包含V2接口!
demo