mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2026-03-10 00:13:40 +08:00
🆕 #1639 微信支付增加v3图片上传接口
1. 实现v3上传图片功能 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/chapter3_1.shtml 2. 将接口获取到的证书保存到PayConfig中,v3接口中部分字段是敏感数据,在对这些数据加密时会用到
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
package com.github.binarywang.wxpay.v3;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
|
||||
import org.apache.http.client.methods.HttpRequestWrapper;
|
||||
|
||||
public interface Credentials {
|
||||
|
||||
String getSchema();
|
||||
|
||||
String getToken(HttpUriRequest request) throws IOException;
|
||||
String getToken(HttpRequestWrapper request) throws IOException;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.github.binarywang.wxpay.v3;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
@@ -12,6 +13,7 @@ import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.conn.routing.HttpRoute;
|
||||
import org.apache.http.entity.BufferedHttpEntity;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.execchain.ClientExecChain;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
@@ -43,11 +45,11 @@ public class SignatureExec implements ClientExecChain {
|
||||
}
|
||||
}
|
||||
|
||||
protected void convertToRepeatableRequestEntity(HttpUriRequest request) throws IOException {
|
||||
if (request instanceof HttpEntityEnclosingRequestBase) {
|
||||
HttpEntity entity = ((HttpEntityEnclosingRequestBase) request).getEntity();
|
||||
if (entity != null && !entity.isRepeatable()) {
|
||||
((HttpEntityEnclosingRequestBase) request).setEntity(newRepeatableEntity(entity));
|
||||
protected void convertToRepeatableRequestEntity(HttpRequestWrapper request) throws IOException {
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
|
||||
if (entity != null) {
|
||||
((HttpEntityEnclosingRequest) request).setEntity(new BufferedHttpEntity(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,15 +66,16 @@ public class SignatureExec implements ClientExecChain {
|
||||
|
||||
private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestWrapper request,
|
||||
HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException {
|
||||
HttpUriRequest newRequest = RequestBuilder.copy(request.getOriginal()).build();
|
||||
convertToRepeatableRequestEntity(newRequest);
|
||||
// 上传类不需要消耗两次故不做转换
|
||||
if (!(request.getOriginal() instanceof WechatPayUploadHttpPost)) {
|
||||
convertToRepeatableRequestEntity(request);
|
||||
}
|
||||
// 添加认证信息
|
||||
newRequest.addHeader("Authorization",
|
||||
credentials.getSchema() + " " + credentials.getToken(newRequest));
|
||||
request.addHeader("Authorization",
|
||||
credentials.getSchema() + " " + credentials.getToken(request));
|
||||
|
||||
// 执行
|
||||
CloseableHttpResponse response = mainExec.execute(
|
||||
route, HttpRequestWrapper.wrap(newRequest), context, execAware);
|
||||
CloseableHttpResponse response = mainExec.execute(route, request, context, execAware);
|
||||
|
||||
// 对成功应答验签
|
||||
StatusLine statusLine = response.getStatusLine();
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.github.binarywang.wxpay.v3;
|
||||
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.mime.HttpMultipartMode;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class WechatPayUploadHttpPost extends HttpPost {
|
||||
|
||||
private String meta;
|
||||
|
||||
private WechatPayUploadHttpPost(URI uri, String meta) {
|
||||
super(uri);
|
||||
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
public String getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private String fileName;
|
||||
private String fileSha256;
|
||||
private InputStream fileInputStream;
|
||||
private ContentType fileContentType;
|
||||
private URI uri;
|
||||
|
||||
public Builder(URI uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public Builder withImage(String fileName, String fileSha256, InputStream inputStream) {
|
||||
this.fileName = fileName;
|
||||
this.fileSha256 = fileSha256;
|
||||
this.fileInputStream = inputStream;
|
||||
|
||||
String mimeType = URLConnection.guessContentTypeFromName(fileName);
|
||||
if (mimeType == null) {
|
||||
// guess this is a video uploading
|
||||
this.fileContentType = ContentType.APPLICATION_OCTET_STREAM;
|
||||
} else {
|
||||
this.fileContentType = ContentType.create(mimeType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WechatPayUploadHttpPost build() {
|
||||
if (fileName == null || fileSha256 == null || fileInputStream == null) {
|
||||
throw new IllegalArgumentException("缺少待上传图片文件信息");
|
||||
}
|
||||
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("缺少上传图片接口URL");
|
||||
}
|
||||
|
||||
String meta = String.format("{\"filename\":\"%s\",\"sha256\":\"%s\"}", fileName, fileSha256);
|
||||
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost(uri, meta);
|
||||
|
||||
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
|
||||
entityBuilder.setMode(HttpMultipartMode.RFC6532)
|
||||
.addBinaryBody("file", fileInputStream, fileContentType, fileName)
|
||||
.addTextBody("meta", meta, ContentType.APPLICATION_JSON);
|
||||
|
||||
request.setEntity(entityBuilder.build());
|
||||
request.addHeader("Accept", ContentType.APPLICATION_JSON.toString());
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,14 @@ public class AutoUpdateCertificatesVerifier implements Verifier {
|
||||
|
||||
@Override
|
||||
public boolean verify(String serialNumber, byte[] message, String signature) {
|
||||
checkAndAutoUpdateCert();
|
||||
return verifier.verify(serialNumber, message, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查证书是否在有效期内,如果不在有效期内则进行更新
|
||||
*/
|
||||
private void checkAndAutoUpdateCert() {
|
||||
if (instant == null || Minutes.minutesBetween(instant, Instant.now()).getMinutes() >= minutesInterval) {
|
||||
if (lock.tryLock()) {
|
||||
try {
|
||||
@@ -113,7 +121,6 @@ public class AutoUpdateCertificatesVerifier implements Verifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
return verifier.verify(serialNumber, message, signature);
|
||||
}
|
||||
|
||||
private void autoUpdateCert() throws IOException, GeneralSecurityException {
|
||||
@@ -179,4 +186,11 @@ public class AutoUpdateCertificatesVerifier implements Verifier {
|
||||
|
||||
return newCertList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate getValidCertificate() {
|
||||
checkAndAutoUpdateCert();
|
||||
return verifier.getValidCertificate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,10 +5,13 @@ import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class CertificatesVerifier implements Verifier {
|
||||
private final HashMap<BigInteger, X509Certificate> certificates = new HashMap<>();
|
||||
@@ -40,4 +43,21 @@ public class CertificatesVerifier implements Verifier {
|
||||
BigInteger val = new BigInteger(serialNumber, 16);
|
||||
return certificates.containsKey(val) && verify(certificates.get(val), message, signature);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public X509Certificate getValidCertificate() {
|
||||
for (X509Certificate x509Cert : certificates.values()) {
|
||||
try {
|
||||
x509Cert.checkValidity();
|
||||
|
||||
return x509Cert;
|
||||
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NoSuchElementException("没有有效的微信支付平台证书");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.github.binarywang.wxpay.v3.auth;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
public interface Verifier {
|
||||
boolean verify(String serialNumber, byte[] message, String signature);
|
||||
|
||||
|
||||
X509Certificate getValidCertificate();
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package com.github.binarywang.wxpay.v3.auth;
|
||||
|
||||
|
||||
import com.github.binarywang.wxpay.v3.Credentials;
|
||||
import com.github.binarywang.wxpay.v3.WechatPayUploadHttpPost;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.client.methods.HttpRequestWrapper;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import com.github.binarywang.wxpay.v3.Credentials;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
@Slf4j
|
||||
public class WxPayCredentials implements Credentials {
|
||||
private static final String SYMBOLS =
|
||||
@@ -46,14 +48,14 @@ public class WxPayCredentials implements Credentials {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getToken(HttpUriRequest request) throws IOException {
|
||||
public final String getToken(HttpRequestWrapper request) throws IOException {
|
||||
String nonceStr = generateNonceStr();
|
||||
long timestamp = generateTimestamp();
|
||||
|
||||
String message = buildMessage(nonceStr, timestamp, request);
|
||||
log.debug("authorization message=[{}]", message);
|
||||
|
||||
Signer.SignatureResult signature = signer.sign(message.getBytes("utf-8"));
|
||||
Signer.SignatureResult signature = signer.sign(message.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
String token = "mchid=\"" + getMerchantId() + "\","
|
||||
+ "nonce_str=\"" + nonceStr + "\","
|
||||
@@ -65,7 +67,7 @@ public class WxPayCredentials implements Credentials {
|
||||
return token;
|
||||
}
|
||||
|
||||
protected final String buildMessage(String nonce, long timestamp, HttpUriRequest request)
|
||||
protected final String buildMessage(String nonce, long timestamp, HttpRequestWrapper request)
|
||||
throws IOException {
|
||||
URI uri = request.getURI();
|
||||
String canonicalUrl = uri.getRawPath();
|
||||
@@ -75,8 +77,10 @@ public class WxPayCredentials implements Credentials {
|
||||
|
||||
String body = "";
|
||||
// PATCH,POST,PUT
|
||||
if (request instanceof HttpEntityEnclosingRequestBase) {
|
||||
body = EntityUtils.toString(((HttpEntityEnclosingRequestBase) request).getEntity());
|
||||
if (request.getOriginal() instanceof WechatPayUploadHttpPost) {
|
||||
body = ((WechatPayUploadHttpPost) request.getOriginal()).getMeta();
|
||||
} else if (request instanceof HttpEntityEnclosingRequest) {
|
||||
body = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity());
|
||||
}
|
||||
|
||||
return request.getRequestLine().getMethod() + "\n"
|
||||
|
||||
Reference in New Issue
Block a user