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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package com.qujie.mintwo.system.wechatpay;

import com.alibaba.fastjson.JSON;
import com.qujie.mintwo.config.WechatConfig;
import com.wechat.pay.contrib.apache.httpclient.Validator;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import static org.apache.http.HttpStatus.SC_OK;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
* @author 夏天
* @version 1.0
* @date 2021/11/24
*/
@Component
public class WechatPayApiUtil {
private final Logger logger = LoggerFactory.getLogger(WechatPayApiUtil.class);
/**
* 参数配置
*/
@Resource
private WechatConfig wechatConfig;


public CloseableHttpResponse v3Execution(HttpUriRequest request) throws IOException, GeneralSecurityException {
request.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
request.addHeader(ACCEPT, APPLICATION_JSON.toString());
FileInputStream privateKey;
try {
privateKey = new FileInputStream("D:\\apiClient_key.pem");
}catch (FileNotFoundException e){
logger.error("微信支付接口调用所需的私钥文件不存在,路径【{}】",wechatConfig.getPrivateKeyPath());
throw new FileNotFoundException("微信支付接口调用所需的私钥文件不存在");
}
PrivateKey merChantPrivateKey = PemUtil.loadPrivateKey(privateKey);
List<X509Certificate> certs = new ArrayList<>();
try {
FileInputStream certKey = new FileInputStream("D:\\api\\apiClient_cert.pem");
certs.add(PemUtil.loadCertificate(certKey));
}catch (FileNotFoundException e){
logger.error("微信支付接口调用所需的平台证书文件不存在,路径【{}】",wechatConfig.getCertKeyPath());
certs = downloadCertificate();
}
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(wechatConfig.getMerchantId(), new PrivateKeySigner(wechatConfig.getSerialNumber(), merChantPrivateKey)),
wechatConfig.getApiKey().getBytes(StandardCharsets.UTF_8));

WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(wechatConfig.getMerchantId(),wechatConfig.getSerialNumber(),merChantPrivateKey).withValidator(new WechatPay2Validator(verifier));
try (CloseableHttpClient client = builder.build()) {
CloseableHttpResponse response = client.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
String body = EntityUtils.toString(response.getEntity());
if (SC_OK == statusCode) {
logger.info("微信支付商家平台接口调用成功,接口响应信息:【{}】",body);
if(validate(response,certs)){
logger.info("微信支付商家平台响应信息验证签名成功");
return response;
}else {
logger.error("微信支付商家平台响应信息验证签名失败");
throw new IOException("微信支付商家平台响应信息验证签名失败");
}
}else {
logger.error("微信支付商家平台接口调用失败,错误码【{}】,错误信息【{}】",statusCode,body);
return response;
}
}
}
private boolean validate(CloseableHttpResponse response,List<X509Certificate> x509Certs) throws IOException {
//从下载的证书中,获取对报文签名的私钥所对应的证书,并进行验签来验证证书准确
Verifier verifier = new CertificatesVerifier(x509Certs);
Validator validator = new WechatPay2Validator(verifier);
return validator.validate(response);
}
public List<X509Certificate> downloadCertificate() throws IOException, GeneralSecurityException {
FileInputStream privateKey;
try {
privateKey = new FileInputStream("D:\\apiClient_key.pem");
}catch (FileNotFoundException e){
logger.error("微信支付接口调用所需的私钥文件路径【{}】不存在",wechatConfig.getPrivateKeyPath());
throw new FileNotFoundException("微信支付接口调用所需的私钥文件不存在");
}
PrivateKey merChantPrivateKey = PemUtil.loadPrivateKey(privateKey);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(wechatConfig.getMerchantId(),wechatConfig.getSerialNumber(),merChantPrivateKey).withValidator(response -> true);
HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");
httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
try (CloseableHttpClient client = builder.build()) {
CloseableHttpResponse response = client.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
String body = EntityUtils.toString(response.getEntity());
if (SC_OK != statusCode) {
logger.error("微信支付商家平台接口调用失败,接口响应码:【{}】,接口响应信息:【{}】",statusCode,body);
throw new IOException("微信支付商家平台接口调用失败");
}
logger.info("微信支付平台证书获取成功,接口响应信息:【{}】",body);
CertificateList certList = JSON.parseObject(body, CertificateList.class);
List<X509Certificate> x509Certs = new ArrayList<>();
AesUtil aesUtil = new AesUtil(wechatConfig.getApiKey().getBytes(StandardCharsets.UTF_8));
CertificateItem certificateItem = certList.getCerts().stream().max(Comparator.comparing(CertificateItem::getExpireTime)).orElse(null);
if(certificateItem == null){
logger.error("微信支付获取平台证书接口响应信息获取证书为空");
throw new IOException("微信支付获取平台证书接口响应信息获取证书为空");
}
String plainCertificate = aesUtil.decryptToString(certificateItem.getEncryptCertificate().getAssociatedData().getBytes(StandardCharsets.UTF_8),
certificateItem.getEncryptCertificate().getNonce().getBytes(StandardCharsets.UTF_8),
certificateItem.getEncryptCertificate().getCiphertext());
ByteArrayInputStream inputStream = new ByteArrayInputStream(plainCertificate.getBytes(StandardCharsets.UTF_8));
X509Certificate x509Cert = PemUtil.loadCertificate(inputStream);
x509Certs.add(x509Cert);
//从下载的证书中,获取对报文签名的私钥所对应的证书,并进行验签来验证证书准确
boolean isCorrectCert = validate(response,x509Certs);
logger.info(isCorrectCert ? "=== validate success ===" : "=== validate failed ===");
if(!isCorrectCert){
logger.error("微信支付平台证书响应结果验证失败");
throw new IOException("微信支付平台证书响应结果验证失败");
}
File file = new File("D:\\api");
if(!file.exists()){
boolean result = file.mkdirs();
logger.info("目录【{}】不存在需要创建目录,创建结果:【{}】",file.getAbsolutePath(),result);
}
String outputAbsoluteFilename = file.getAbsolutePath() + File.separator + "apiClient_cert.pem";
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(outputAbsoluteFilename), StandardCharsets.UTF_8))) {
writer.write(plainCertificate);
}
logger.info("save cert file absolute path {} " , outputAbsoluteFilename);
return x509Certs;
}
}

}