发布于 

记录使用微信支付 APIv3 Java SDK的使用和踩的一些坑

接入使用

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.8</version>
</dependency>

构建全局自动更新平台证书的RSA配置

我这里是多商户配置逻辑,所以会根据商户id作为配置的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private Map<String, RSAAutoCertificateConfig> configMap;

public RSAAutoCertificateConfig getConfig(BesaPayReqDTO besaPayReqDTO) {

if (configMap == null) {
configMap = new HashMap<String, RSAAutoCertificateConfig>();
}

RSAAutoCertificateConfig config = configMap.get(besaPayReqDTO.getMerchantId());
if (ObjectUtil.isNotEmpty(config)) {
return config;
}

config =
new RSAAutoCertificateConfig.Builder()
.merchantId(besaPayReqDTO.getMerchantId())
.privateKey(besaPayReqDTO.getPrivateKey())
.merchantSerialNumber(besaPayReqDTO.getMerchantSerialNumber())
.apiV3Key(besaPayReqDTO.getApiV3key())
.build();

configMap.put(besaPayReqDTO.getMerchantId(), config);

return config;

}

构建h5网页支付请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    /**
* wap类型支付方式
*
* @param prepayRequest 支付参数
* @param besaPayReqDTO 通用支付配置
* @return
*/
public String wapPay(PrepayRequest prepayRequest, BesaPayReqDTO besaPayReqDTO, Long merchantId) {

RSAAutoCertificateConfig config = getConfig(besaPayReqDTO);

// 构建service
H5Service service = new H5Service.Builder().config(config).build();
PrepayResponse prepay = null;
try {
prepay = service.prepay(prepayRequest);
} catch (Exception e) {
log.error("订单:{},调用微信网页支付失败", prepayRequest.getOutTradeNo());
e.printStackTrace();
throw new RuntimeException("订单拉起微信支付失败");
}

// 缓存支付配置,用于回调验签
redisService.setCacheObject(RedisWechatPayKeyConstant.WECHAT_PAY_CONFIG_CACHE + merchantId, besaPayReqDTO, 3L, TimeUnit.DAYS);
return prepay.getH5Url();

}

返回h5支付链接给前端,等待用户完成支付

处理订单支付回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* 订单支付结果回调
*/
@PostMapping("/pay/wap/{merchantId}")
public CallbackRespDTO orderWapPayResult(HttpServletRequest request, HttpServletResponse response, @PathVariable("merchantId") Long merchantId) {

Transaction transaction = null;
try {
transaction = callbackCheckSign(request, Transaction.class, merchantId);
} catch (Exception e) {
log.error("微信支付回调处理异常,异常原因:{}", e.getMessage());
e.printStackTrace();
return CallbackRespDTO.fail(response);
}

if (ObjectUtil.isEmpty(transaction)) {
log.error("微信支付回调参数解析失败!");
return CallbackRespDTO.fail(response);
}

// 处理回调信息
xxxx.xxxxxxx(transaction)

return CallbackRespDTO.success();
}


/**
* 回调验签转换
*
* @param <T>
* @param request 回调
* @param clazz 参数转换类型
* @param merchantId 商户id
* @return
*/
private <T> T callbackCheckSign(HttpServletRequest request, Class<T> clazz, Long merchantId) {
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");

//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");

//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");

//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");

// 签名类型
String signatureType = request.getHeader("Wechatpay-Signature-Type");

//获取报文
String body = getRequestBody(request);
log.info("微信支付回调验签nonce:{},signature:{},serialNo:{},timestamp:{},body:{}", nonceStr, signature, serialNo, timestamp, body);


// 缓存获取配置
BesaPayReqDTO besaPayReqDTO = redisService.getCacheObject(RedisWechatPayKeyConstant.WECHAT_PAY_CONFIG_CACHE + merchantId);

// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonceStr)
.signature(signature)
.timestamp(timestamp)
.signType(signatureType)
.body(body)
.build();

NotificationConfig config = wechatPayService.getConfig(besaPayReqDTO);
NotificationParser parser = new NotificationParser(config);

return parser.parse(requestParam, clazz);
}


/**
* 读取请求数据流
*
* @param request
* @return
*/
private String getRequestBody(HttpServletRequest request) {

try {
ByteArrayOutputStream result = new ByteArrayOutputStream();
ServletInputStream inputStream = request.getInputStream();
byte[] buffer = new byte[1024];
for (int length; (length = inputStream.read(buffer)) != -1; ) {
result.write(buffer, 0, length);
}
return result.toString("UTF-8");
} catch (Exception e) {
log.error("读取数据流异常:{}", e);
}

return null;

}

订单请求退款

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 退款
*
* @param createRequest 支付参数
* @param besaPayReqDTO 通用支付配置
* @return
*/
public Refund refund(CreateRequest createRequest, BesaPayReqDTO besaPayReqDTO, Long merchantId) {
RSAAutoCertificateConfig config = getConfig(besaPayReqDTO);
// 构建service
RefundService refundService = new RefundService.Builder().config(config).build();
Refund refund = null;
try {
refund = refundService.create(createRequest);
} catch (Exception e) {
log.error("退款订单:{},退款失败,退款单号:{}", createRequest.getOutTradeNo(), createRequest.getOutRefundNo());
throw new RuntimeException(e);
}
redisService.setCacheObject(RedisWechatPayKeyConstant.WECHAT_PAY_CONFIG_CACHE + merchantId, besaPayReqDTO, 3L, TimeUnit.DAYS);
return refund;
}

退款回调处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 退款结果回调
*/
@PostMapping("/refund/{merchantId}")
public CallbackRespDTO orderRefundResult(HttpServletRequest request, HttpServletResponse response, @PathVariable("merchantId") Long merchantId) {
//获取报文
RefundNotification refundNotification = null;
try {
refundNotification = callbackCheckSign(request, RefundNotification.class, merchantId);
} catch (Exception e) {
log.error("微信退款回调处理异常,异常原因:{}}", e.getMessage());
e.printStackTrace();
return CallbackRespDTO.fail(response);
}

// 处理回调信息
xxxx.xxxxxxx(refundNotification)

return CallbackRespDTO.success();
}

使用中碰到的一些问题

RSAAutoCertificateConfig配置多次初始化问题

原写法是参考官方文档上的内容直接进行构建,导致在第二次请求支付时处出现证书配置初始化错误问题

github类似问题issues

https://github.com/wechatpay-apiv3/wechatpay-java/issues/130

错误的H5支付构建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* wap类型支付方式
* @param prepayRequest 支付参数
* @param besaPayReqDTO 通用支付配置
* @return
*/
public String wapPay(PrepayRequest prepayRequest, BesaPayReqDTO besaPayReqDTO,Long merchantId) {

Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(besaPayReqDTO.getMerchantId())
.privateKey(besaPayReqDTO.getPrivateKey())
.merchantSerialNumber(besaPayReqDTO.getMerchantSerialNumber())
.apiV3Key(besaPayReqDTO.getApiV3key())
.build();
// 构建service
H5Service service = new H5Service.Builder().config(config).build();
PrepayResponse prepay = null;
try {
prepay = service.prepay(prepayRequest);
} catch (Exception e) {
log.error("订单:{},调用微信网页支付失败", prepayRequest.getOutTradeNo());
e.printStackTrace();
throw new RuntimeException("订单拉起微信支付失败");
}
return prepay.getH5Url();

}

支付状态回调验签问题

github类似问题issues

https://github.com/wechatpay-apiv3/wechatpay-java/issues/120

回调验签的请求体的读取方式错误导致缺少空格、换行等字符或者进行了JSON序列化导致验签失败

错误代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 读取请求数据流
*
* @param request
* @return
*/
private String getRequestBody(HttpServletRequest request) {

StringBuffer sb = new StringBuffer();

try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;

while ((line = reader.readLine()) != null) {
sb.append(line);
}

} catch (IOException e) {
log.error("读取数据流异常:{}", e);
}

return sb.toString();

}

构建验签requestParam时serialNumber使用了配置里面的证书编号导致验签出错,构建时需要使用请求头中返回的证书编号;在guthub的issues中存在非常多的同类型错误问题一定要注意,天坑!!!

错误代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 回调验签转换
*
* @param <T>
* @param request 回调
* @param clazz 参数转换类型
* @param merchantId 商户id
* @param body
* @return
*/
private <T> T callbackCheckSign(HttpServletRequest request, Class<T> clazz, Long merchantId, String body) {
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");

//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");

//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");

//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");

// 缓存获取配置
BesaPayReqDTO besaPayReqDTO = redisService.getCacheObject(RedisWechatPayKeyConstant.WECHAT_PAY_CONFIG_CACHE + merchantId);

if (besaPayReqDTO.getMerchantSerialNumber().equals(serialNo)) {
throw new RuntimeException("回调证书序列号和请求序列号不一致!");
}

// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(besaPayReqDTO.getMerchantSerialNumber())
.nonce(nonceStr)
.signature(signature)
.timestamp(timestamp)
.body(body)
.build();

NotificationConfig config = wechatPayService.getConfig(besaPayReqDTO);
NotificationParser parser = new NotificationParser(config);

return parser.parse(requestParam, clazz);
}

本站由 @binvv 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。