Skip to content

Commit e7f2378

Browse files
authored
🆕 #1639 微信支付增加v3图片上传接口
1. 实现v3上传图片功能 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/chapter3_1.shtml 2. 将接口获取到的证书保存到PayConfig中,v3接口中部分字段是敏感数据,在对这些数据加密时会用到
1 parent a9f9e30 commit e7f2378

16 files changed

+376
-32
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.github.binarywang.wxpay.bean.media;
2+
3+
import com.google.gson.annotations.SerializedName;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
7+
8+
/**
9+
* 媒体文件上传返回结果对象
10+
* @author zhouyongshen
11+
*/
12+
@NoArgsConstructor
13+
@Data
14+
public class ImageUploadResult {
15+
16+
public static ImageUploadResult fromJson(String json) {
17+
return WxGsonBuilder.create().fromJson(json, ImageUploadResult.class);
18+
}
19+
/**
20+
* 媒体文件标识 Id
21+
*
22+
* 微信返回的媒体文件标识Id。
23+
* 示例值:6uqyGjGrCf2GtyXP8bxrbuH9-aAoTjH-rKeSl3Lf4_So6kdkQu4w8BYVP3bzLtvR38lxt4PjtCDXsQpzqge_hQEovHzOhsLleGFQVRF-U_0
24+
*
25+
*/
26+
@SerializedName("media_id")
27+
private String mediaId;
28+
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
import com.github.binarywang.wxpay.exception.WxPayException;
44
import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder;
5-
import com.github.binarywang.wxpay.v3.auth.AutoUpdateCertificatesVerifier;
6-
import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
7-
import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
8-
import com.github.binarywang.wxpay.v3.auth.WxPayValidator;
5+
import com.github.binarywang.wxpay.v3.auth.*;
96
import com.github.binarywang.wxpay.v3.util.PemUtils;
107
import jodd.util.ResourcesUtil;
118
import lombok.Data;
@@ -153,6 +150,12 @@ public class WxPayConfig {
153150
private String httpProxyUsername;
154151
private String httpProxyPassword;
155152

153+
/**
154+
* v3接口下证书检验对象,通过改对象可以获取到X509Certificate,进一步对敏感信息加密
155+
* 文档见 https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/min-gan-xin-xi-jia-mi
156+
*/
157+
private Verifier verifier;
158+
156159
/**
157160
* 返回所设置的微信支付接口请求地址域名.
158161
*
@@ -297,14 +300,20 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
297300

298301
try {
299302
PrivateKey merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream);
303+
304+
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
305+
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
306+
apiV3Key.getBytes(StandardCharsets.UTF_8));
307+
308+
300309
CloseableHttpClient httpClient = WxPayV3HttpClientBuilder.create()
301310
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
302311
.withWechatpay(Collections.singletonList(PemUtils.loadCertificate(certInputStream)))
303-
.withValidator(new WxPayValidator(new AutoUpdateCertificatesVerifier(
304-
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
305-
apiV3Key.getBytes(StandardCharsets.UTF_8))))
312+
.withValidator(new WxPayValidator(verifier))
306313
.build();
307314
this.apiV3HttpClient = httpClient;
315+
this.verifier=verifier;
316+
308317
return httpClient;
309318
} catch (Exception e) {
310319
throw new WxPayException("v3请求构造异常!", e);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.github.binarywang.wxpay.service;
2+
3+
import com.github.binarywang.wxpay.bean.media.ImageUploadResult;
4+
import com.github.binarywang.wxpay.exception.WxPayException;
5+
6+
import java.io.File;
7+
import java.io.IOException;
8+
9+
/**
10+
* <pre>
11+
* 微信支付通用媒体接口.
12+
* </pre>
13+
*
14+
* @author zhouyongshen
15+
*/
16+
public interface MerchantMediaService {
17+
/**
18+
* <pre>
19+
* 通用接口-图片上传API
20+
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/chapter3_1.shtml
21+
* 接口链接:https://api.mch.weixin.qq.com/v3/merchant/media/upload
22+
* </pre>
23+
*
24+
* @param imageFile 需要上传的图片文件
25+
* @return ImageUploadResult 微信返回的媒体文件标识Id。示例值:6uqyGjGrCf2GtyXP8bxrbuH9-aAoTjH-rKeSl3Lf4_So6kdkQu4w8BYVP3bzLtvR38lxt4PjtCDXsQpzqge_hQEovHzOhsLleGFQVRF-U_0
26+
* @throws WxPayException the wx pay exception
27+
*/
28+
ImageUploadResult imageUploadV3(File imageFile) throws WxPayException, IOException;
29+
30+
31+
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.github.binarywang.wxpay.config.WxPayConfig;
1111
import com.github.binarywang.wxpay.constant.WxPayConstants;
1212
import com.github.binarywang.wxpay.exception.WxPayException;
13+
import org.apache.http.client.methods.HttpPost;
1314

1415
import java.io.File;
1516
import java.net.URI;
@@ -65,6 +66,16 @@ public interface WxPayService {
6566
*/
6667
String postV3(String url, String requestStr) throws WxPayException;
6768

69+
/**
70+
* 发送post请求,得到响应字符串.
71+
*
72+
* @param url 请求地址
73+
* @param httpPost 请求信息
74+
* @return 返回请求结果字符串 string
75+
* @throws WxPayException the wx pay exception
76+
*/
77+
String postV3(String url, HttpPost httpPost) throws WxPayException;
78+
6879
/**
6980
* 发送get V3请求,得到响应字符串.
7081
*
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.github.binarywang.wxpay.service.impl;
2+
3+
import com.github.binarywang.wxpay.bean.media.ImageUploadResult;
4+
import com.github.binarywang.wxpay.exception.WxPayException;
5+
import com.github.binarywang.wxpay.service.MerchantMediaService;
6+
import com.github.binarywang.wxpay.service.WxPayService;
7+
import com.github.binarywang.wxpay.v3.WechatPayUploadHttpPost;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.apache.commons.codec.digest.DigestUtils;
11+
12+
import java.io.File;
13+
import java.io.FileInputStream;
14+
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.net.URI;
17+
18+
/**
19+
* 微信支付-媒体文件上传service
20+
* @author zhouyongshen
21+
*/
22+
@Slf4j
23+
@RequiredArgsConstructor
24+
public class MerchantMediaServiceImpl implements MerchantMediaService {
25+
26+
private final WxPayService payService;
27+
28+
@Override
29+
public ImageUploadResult imageUploadV3(File imageFile) throws WxPayException,IOException {
30+
String url = String.format("%s/v3/merchant/media/upload", this.payService.getPayBaseUrl());
31+
32+
try (FileInputStream s1 = new FileInputStream(imageFile)) {
33+
String sha256 = DigestUtils.sha256Hex(s1);
34+
try (InputStream s2 = new FileInputStream(imageFile)) {
35+
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost.Builder(URI.create(url))
36+
.withImage(imageFile.getName(), sha256, s2)
37+
.build();
38+
String result = this.payService.postV3(url, request);
39+
return ImageUploadResult.fromJson(result);
40+
}
41+
}
42+
}
43+
44+
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,36 @@ public String postV3(String url, String requestStr) throws WxPayException {
116116

117117
}
118118

119+
@Override
120+
public String postV3(String url, HttpPost httpPost) throws WxPayException {
121+
122+
httpPost.setConfig(RequestConfig.custom()
123+
.setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
124+
.setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
125+
.setSocketTimeout(this.getConfig().getHttpTimeout())
126+
.build());
127+
128+
CloseableHttpClient httpClient = this.createApiV3HttpClient();
129+
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
130+
//v3已经改为通过状态码判断200 204 成功
131+
int statusCode = response.getStatusLine().getStatusCode();
132+
String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
133+
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
134+
this.log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString);
135+
return responseString;
136+
} else {
137+
//有错误提示信息返回
138+
JsonObject jsonObject = GsonParser.parse(responseString);
139+
throw new WxPayException(jsonObject.get("message").getAsString());
140+
}
141+
} catch (Exception e) {
142+
this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
143+
throw new WxPayException(e.getMessage(), e);
144+
} finally {
145+
httpPost.releaseConnection();
146+
}
147+
}
148+
119149
@Override
120150
public String getV3(URI url) throws WxPayException {
121151
CloseableHttpClient httpClient = this.createApiV3HttpClient();

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import jodd.http.net.SSLSocketHttpConnectionProvider;
1717
import jodd.http.net.SocketHttpConnectionProvider;
1818
import jodd.util.Base64;
19+
import org.apache.http.client.methods.HttpPost;
1920

2021
/**
2122
* 微信支付请求实现类,jodd-http实现.
@@ -65,6 +66,11 @@ public String postV3(String url, String requestStr) throws WxPayException {
6566
return null;
6667
}
6768

69+
@Override
70+
public String postV3(String url, HttpPost httpPost) throws WxPayException {
71+
return null;
72+
}
73+
6874
@Override
6975
public String getV3(URI url) throws WxPayException {
7076
return null;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.github.binarywang.wxpay.v3;
22

33
import java.io.IOException;
4-
import org.apache.http.client.methods.HttpUriRequest;
4+
5+
import org.apache.http.client.methods.HttpRequestWrapper;
56

67
public interface Credentials {
78

89
String getSchema();
910

10-
String getToken(HttpUriRequest request) throws IOException;
11+
String getToken(HttpRequestWrapper request) throws IOException;
1112
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SignatureExec.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import org.apache.http.HttpEntity;
5+
import org.apache.http.HttpEntityEnclosingRequest;
56
import org.apache.http.HttpException;
67
import org.apache.http.StatusLine;
78
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -12,6 +13,7 @@
1213
import org.apache.http.client.methods.RequestBuilder;
1314
import org.apache.http.client.protocol.HttpClientContext;
1415
import org.apache.http.conn.routing.HttpRoute;
16+
import org.apache.http.entity.BufferedHttpEntity;
1517
import org.apache.http.entity.ByteArrayEntity;
1618
import org.apache.http.impl.execchain.ClientExecChain;
1719
import org.apache.http.util.EntityUtils;
@@ -43,11 +45,11 @@ protected void convertToRepeatableResponseEntity(CloseableHttpResponse response)
4345
}
4446
}
4547

46-
protected void convertToRepeatableRequestEntity(HttpUriRequest request) throws IOException {
47-
if (request instanceof HttpEntityEnclosingRequestBase) {
48-
HttpEntity entity = ((HttpEntityEnclosingRequestBase) request).getEntity();
49-
if (entity != null && !entity.isRepeatable()) {
50-
((HttpEntityEnclosingRequestBase) request).setEntity(newRepeatableEntity(entity));
48+
protected void convertToRepeatableRequestEntity(HttpRequestWrapper request) throws IOException {
49+
if (request instanceof HttpEntityEnclosingRequest) {
50+
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
51+
if (entity != null) {
52+
((HttpEntityEnclosingRequest) request).setEntity(new BufferedHttpEntity(entity));
5153
}
5254
}
5355
}
@@ -64,15 +66,16 @@ public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request
6466

6567
private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestWrapper request,
6668
HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException {
67-
HttpUriRequest newRequest = RequestBuilder.copy(request.getOriginal()).build();
68-
convertToRepeatableRequestEntity(newRequest);
69+
// 上传类不需要消耗两次故不做转换
70+
if (!(request.getOriginal() instanceof WechatPayUploadHttpPost)) {
71+
convertToRepeatableRequestEntity(request);
72+
}
6973
// 添加认证信息
70-
newRequest.addHeader("Authorization",
71-
credentials.getSchema() + " " + credentials.getToken(newRequest));
74+
request.addHeader("Authorization",
75+
credentials.getSchema() + " " + credentials.getToken(request));
7276

7377
// 执行
74-
CloseableHttpResponse response = mainExec.execute(
75-
route, HttpRequestWrapper.wrap(newRequest), context, execAware);
78+
CloseableHttpResponse response = mainExec.execute(route, request, context, execAware);
7679

7780
// 对成功应答验签
7881
StatusLine statusLine = response.getStatusLine();
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.github.binarywang.wxpay.v3;
2+
3+
import org.apache.http.client.methods.HttpPost;
4+
import org.apache.http.entity.ContentType;
5+
import org.apache.http.entity.mime.HttpMultipartMode;
6+
import org.apache.http.entity.mime.MultipartEntityBuilder;
7+
8+
import java.io.InputStream;
9+
import java.net.URI;
10+
import java.net.URLConnection;
11+
12+
public class WechatPayUploadHttpPost extends HttpPost {
13+
14+
private String meta;
15+
16+
private WechatPayUploadHttpPost(URI uri, String meta) {
17+
super(uri);
18+
19+
this.meta = meta;
20+
}
21+
22+
public String getMeta() {
23+
return meta;
24+
}
25+
26+
public static class Builder {
27+
28+
private String fileName;
29+
private String fileSha256;
30+
private InputStream fileInputStream;
31+
private ContentType fileContentType;
32+
private URI uri;
33+
34+
public Builder(URI uri) {
35+
this.uri = uri;
36+
}
37+
38+
public Builder withImage(String fileName, String fileSha256, InputStream inputStream) {
39+
this.fileName = fileName;
40+
this.fileSha256 = fileSha256;
41+
this.fileInputStream = inputStream;
42+
43+
String mimeType = URLConnection.guessContentTypeFromName(fileName);
44+
if (mimeType == null) {
45+
// guess this is a video uploading
46+
this.fileContentType = ContentType.APPLICATION_OCTET_STREAM;
47+
} else {
48+
this.fileContentType = ContentType.create(mimeType);
49+
}
50+
return this;
51+
}
52+
53+
public WechatPayUploadHttpPost build() {
54+
if (fileName == null || fileSha256 == null || fileInputStream == null) {
55+
throw new IllegalArgumentException("缺少待上传图片文件信息");
56+
}
57+
58+
if (uri == null) {
59+
throw new IllegalArgumentException("缺少上传图片接口URL");
60+
}
61+
62+
String meta = String.format("{\"filename\":\"%s\",\"sha256\":\"%s\"}", fileName, fileSha256);
63+
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost(uri, meta);
64+
65+
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
66+
entityBuilder.setMode(HttpMultipartMode.RFC6532)
67+
.addBinaryBody("file", fileInputStream, fileContentType, fileName)
68+
.addTextBody("meta", meta, ContentType.APPLICATION_JSON);
69+
70+
request.setEntity(entityBuilder.build());
71+
request.addHeader("Accept", ContentType.APPLICATION_JSON.toString());
72+
73+
return request;
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)