@@ -82,6 +82,11 @@ | |||||
<groupId>org.projectlombok</groupId> | <groupId>org.projectlombok</groupId> | ||||
<artifactId>lombok</artifactId> | <artifactId>lombok</artifactId> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>com.fasterxml.jackson.core</groupId> | |||||
<artifactId>jackson-databind</artifactId> | |||||
<version>2.9.7</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>com.google.code.gson</groupId> | <groupId>com.google.code.gson</groupId> | ||||
<artifactId>gson</artifactId> | <artifactId>gson</artifactId> | ||||
@@ -1,12 +1,9 @@ | |||||
package com.github.binarywang.wxpay.service.impl; | package com.github.binarywang.wxpay.service.impl; | ||||
import java.net.URI; | |||||
import java.nio.charset.StandardCharsets; | |||||
import javax.net.ssl.SSLContext; | |||||
import com.alibaba.fastjson.JSONObject; | |||||
import com.github.binarywang.wxpay.bean.WxPayApiData; | import com.github.binarywang.wxpay.bean.WxPayApiData; | ||||
import com.github.binarywang.wxpay.exception.WxPayException; | import com.github.binarywang.wxpay.exception.WxPayException; | ||||
import com.google.gson.JsonObject; | |||||
import com.google.gson.JsonParser; | |||||
import jodd.util.Base64; | import jodd.util.Base64; | ||||
import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
import org.apache.http.HttpHost; | import org.apache.http.HttpHost; | ||||
@@ -28,6 +25,10 @@ import org.apache.http.impl.client.HttpClientBuilder; | |||||
import org.apache.http.impl.client.HttpClients; | import org.apache.http.impl.client.HttpClients; | ||||
import org.apache.http.util.EntityUtils; | import org.apache.http.util.EntityUtils; | ||||
import javax.net.ssl.SSLContext; | |||||
import java.net.URI; | |||||
import java.nio.charset.StandardCharsets; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 微信支付请求实现类,apache httpclient实现. | * 微信支付请求实现类,apache httpclient实现. | ||||
@@ -37,6 +38,8 @@ import org.apache.http.util.EntityUtils; | |||||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | * @author <a href="https://github.com/binarywang">Binary Wang</a> | ||||
*/ | */ | ||||
public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { | public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { | ||||
private final static JsonParser JSON_PARSER = new JsonParser(); | |||||
@Override | @Override | ||||
public byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException { | public byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException { | ||||
try { | try { | ||||
@@ -92,18 +95,17 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { | |||||
HttpPost httpPost = this.createHttpPost(url, requestStr); | HttpPost httpPost = this.createHttpPost(url, requestStr); | ||||
httpPost.addHeader("Accept", "application/json"); | httpPost.addHeader("Accept", "application/json"); | ||||
httpPost.addHeader("Content-Type", "application/json"); | httpPost.addHeader("Content-Type", "application/json"); | ||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)){ | |||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) { | |||||
//v3已经改为通过状态码判断200 204 成功 | //v3已经改为通过状态码判断200 204 成功 | ||||
int statusCode = response.getStatusLine().getStatusCode(); | int statusCode = response.getStatusLine().getStatusCode(); | ||||
String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); | String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); | ||||
if (HttpStatus.SC_OK==statusCode || HttpStatus.SC_NO_CONTENT==statusCode){ | |||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { | |||||
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString); | this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString); | ||||
return responseString; | return responseString; | ||||
}else { | |||||
} else { | |||||
//有错误提示信息返回 | //有错误提示信息返回 | ||||
JSONObject jsonObject = JSONObject.parseObject(responseString); | |||||
String message = jsonObject.getString("message"); | |||||
throw new WxPayException(message); | |||||
JsonObject jsonObject = JSON_PARSER.parse(responseString).getAsJsonObject(); | |||||
throw new WxPayException(jsonObject.get("message").getAsString()); | |||||
} | } | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); | this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); | ||||
@@ -113,8 +115,6 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { | |||||
} | } | ||||
} | } | ||||
@Override | @Override | ||||
@@ -123,18 +123,17 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { | |||||
HttpGet httpGet = new HttpGet(url); | HttpGet httpGet = new HttpGet(url); | ||||
httpGet.addHeader("Accept", "application/json"); | httpGet.addHeader("Accept", "application/json"); | ||||
httpGet.addHeader("Content-Type", "application/json"); | httpGet.addHeader("Content-Type", "application/json"); | ||||
try (CloseableHttpResponse response = httpClient.execute(httpGet)){ | |||||
try (CloseableHttpResponse response = httpClient.execute(httpGet)) { | |||||
//v3已经改为通过状态码判断200 204 成功 | //v3已经改为通过状态码判断200 204 成功 | ||||
int statusCode = response.getStatusLine().getStatusCode(); | int statusCode = response.getStatusLine().getStatusCode(); | ||||
String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); | String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); | ||||
if (HttpStatus.SC_OK==statusCode || HttpStatus.SC_NO_CONTENT==statusCode){ | |||||
this.log.info("\n【请求地址】:{}\n【响应数据】:{}", url , responseString); | |||||
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { | |||||
this.log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString); | |||||
return responseString; | return responseString; | ||||
}else { | |||||
} else { | |||||
//有错误提示信息返回 | //有错误提示信息返回 | ||||
JSONObject jsonObject = JSONObject.parseObject(responseString); | |||||
String message = jsonObject.getString("message"); | |||||
throw new WxPayException(message); | |||||
JsonObject jsonObject = JSON_PARSER.parse(responseString).getAsJsonObject(); | |||||
throw new WxPayException(jsonObject.get("message").getAsString()); | |||||
} | } | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage()); | this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage()); | ||||
@@ -142,25 +141,19 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { | |||||
} finally { | } finally { | ||||
httpGet.releaseConnection(); | httpGet.releaseConnection(); | ||||
} | } | ||||
} | } | ||||
private CloseableHttpClient createApiV3HttpClient() throws WxPayException { | private CloseableHttpClient createApiV3HttpClient() throws WxPayException { | ||||
CloseableHttpClient apiv3HttpClient = this.getConfig().getApiV3HttpClient(); | CloseableHttpClient apiv3HttpClient = this.getConfig().getApiV3HttpClient(); | ||||
if (null==apiv3HttpClient){ | |||||
if (null == apiv3HttpClient) { | |||||
return this.getConfig().initApiV3HttpClient(); | return this.getConfig().initApiV3HttpClient(); | ||||
} | } | ||||
return apiv3HttpClient; | return apiv3HttpClient; | ||||
} | } | ||||
private StringEntity createEntry(String requestStr) { | private StringEntity createEntry(String requestStr) { | ||||
return new StringEntity(requestStr, ContentType.create("application/json", "utf-8")); | |||||
//return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); | |||||
return new StringEntity(requestStr, ContentType.create("application/json", "utf-8")); | |||||
//return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); | |||||
} | } | ||||
private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException { | private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException { | ||||
@@ -1,8 +1,22 @@ | |||||
package com.github.binarywang.wxpay.v3.auth; | package com.github.binarywang.wxpay.v3.auth; | ||||
import com.fasterxml.jackson.databind.JsonNode; | |||||
import com.fasterxml.jackson.databind.ObjectMapper; | |||||
import com.github.binarywang.wxpay.v3.Credentials; | |||||
import com.github.binarywang.wxpay.v3.WechatPayHttpClientBuilder; | |||||
import com.github.binarywang.wxpay.v3.util.AesUtils; | |||||
import com.github.binarywang.wxpay.v3.util.PemUtils; | |||||
import lombok.Getter; | |||||
import lombok.RequiredArgsConstructor; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | |||||
import org.apache.http.client.methods.HttpGet; | |||||
import org.apache.http.impl.client.CloseableHttpClient; | |||||
import org.apache.http.util.EntityUtils; | |||||
import java.io.ByteArrayInputStream; | import java.io.ByteArrayInputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.charset.StandardCharsets; | |||||
import java.security.GeneralSecurityException; | import java.security.GeneralSecurityException; | ||||
import java.security.cert.CertificateExpiredException; | import java.security.cert.CertificateExpiredException; | ||||
import java.security.cert.CertificateNotYetValidException; | import java.security.cert.CertificateNotYetValidException; | ||||
@@ -13,57 +27,56 @@ import java.util.ArrayList; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.concurrent.locks.ReentrantLock; | import java.util.concurrent.locks.ReentrantLock; | ||||
import com.fasterxml.jackson.databind.JsonNode; | |||||
import com.fasterxml.jackson.databind.ObjectMapper; | |||||
import com.github.binarywang.wxpay.v3.Credentials; | |||||
import com.github.binarywang.wxpay.v3.WechatPayHttpClientBuilder; | |||||
import com.github.binarywang.wxpay.v3.util.AesUtils; | |||||
import com.github.binarywang.wxpay.v3.util.PemUtils; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | |||||
import org.apache.http.client.methods.HttpGet; | |||||
import org.apache.http.impl.client.CloseableHttpClient; | |||||
import org.apache.http.util.EntityUtils; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
/** | /** | ||||
* 在原有CertificatesVerifier基础上,增加自动更新证书功能 | * 在原有CertificatesVerifier基础上,增加自动更新证书功能 | ||||
* | |||||
* @author doger.wang | |||||
*/ | */ | ||||
@Slf4j | |||||
public class AutoUpdateCertificatesVerifier implements Verifier { | public class AutoUpdateCertificatesVerifier implements Verifier { | ||||
/** | |||||
* 证书下载地址 | |||||
*/ | |||||
private static final String CERT_DOWNLOAD_PATH = "https://api.mch.weixin.qq.com/v3/certificates"; | |||||
private static final Logger log = LoggerFactory.getLogger(AutoUpdateCertificatesVerifier.class); | |||||
//证书下载地址 | |||||
private static final String CertDownloadPath = "https://api.mch.weixin.qq.com/v3/certificates"; | |||||
//上次更新时间 | |||||
/** | |||||
* 上次更新时间 | |||||
*/ | |||||
private volatile Instant instant; | private volatile Instant instant; | ||||
//证书更新间隔时间,单位为分钟 | |||||
private int minutesInterval; | |||||
/** | |||||
* 证书更新间隔时间,单位为分钟 | |||||
*/ | |||||
private final int minutesInterval; | |||||
private CertificatesVerifier verifier; | private CertificatesVerifier verifier; | ||||
private Credentials credentials; | |||||
private final Credentials credentials; | |||||
private byte[] apiV3Key; | |||||
private final byte[] apiV3Key; | |||||
private ReentrantLock lock = new ReentrantLock(); | |||||
private final ReentrantLock lock = new ReentrantLock(); | |||||
//时间间隔枚举,支持一小时、六小时以及十二小时 | |||||
/** | |||||
* 时间间隔枚举,支持一小时、六小时以及十二小时 | |||||
*/ | |||||
@Getter | |||||
@RequiredArgsConstructor | |||||
public enum TimeInterval { | public enum TimeInterval { | ||||
OneHour(60), SixHours(60 * 6), TwelveHours(60 * 12); | |||||
private int minutes; | |||||
TimeInterval(int minutes) { | |||||
this.minutes = minutes; | |||||
} | |||||
public int getMinutes() { | |||||
return minutes; | |||||
} | |||||
/** | |||||
* 一小时 | |||||
*/ | |||||
OneHour(60), | |||||
/** | |||||
* 六小时 | |||||
*/ | |||||
SixHours(60 * 6), | |||||
/** | |||||
* 十二小时 | |||||
*/ | |||||
TwelveHours(60 * 12); | |||||
private final int minutes; | |||||
} | } | ||||
public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key) { | public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key) { | ||||
@@ -103,11 +116,11 @@ public class AutoUpdateCertificatesVerifier implements Verifier { | |||||
private void autoUpdateCert() throws IOException, GeneralSecurityException { | private void autoUpdateCert() throws IOException, GeneralSecurityException { | ||||
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create() | CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create() | ||||
.withCredentials(credentials) | |||||
.withValidator(verifier == null ? (response) -> true : new WechatPay2Validator(verifier)) | |||||
.build(); | |||||
.withCredentials(credentials) | |||||
.withValidator(verifier == null ? (response) -> true : new WechatPay2Validator(verifier)) | |||||
.build(); | |||||
HttpGet httpGet = new HttpGet(CertDownloadPath); | |||||
HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH); | |||||
httpGet.addHeader("Accept", "application/json"); | httpGet.addHeader("Accept", "application/json"); | ||||
CloseableHttpResponse response = httpClient.execute(httpGet); | CloseableHttpResponse response = httpClient.execute(httpGet); | ||||
@@ -125,12 +138,10 @@ public class AutoUpdateCertificatesVerifier implements Verifier { | |||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* 反序列化证书并解密 | * 反序列化证书并解密 | ||||
*/ | */ | ||||
private List<X509Certificate> deserializeToCerts(byte[] apiV3Key, String body) | |||||
throws GeneralSecurityException, IOException { | |||||
private List<X509Certificate> deserializeToCerts(byte[] apiV3Key, String body) throws GeneralSecurityException, IOException { | |||||
AesUtils decryptor = new AesUtils(apiV3Key); | AesUtils decryptor = new AesUtils(apiV3Key); | ||||
ObjectMapper mapper = new ObjectMapper(); | ObjectMapper mapper = new ObjectMapper(); | ||||
JsonNode dataNode = mapper.readTree(body).get("data"); | JsonNode dataNode = mapper.readTree(body).get("data"); | ||||
@@ -140,14 +151,14 @@ public class AutoUpdateCertificatesVerifier implements Verifier { | |||||
JsonNode encryptCertificateNode = dataNode.get(i).get("encrypt_certificate"); | JsonNode encryptCertificateNode = dataNode.get(i).get("encrypt_certificate"); | ||||
//解密 | //解密 | ||||
String cert = decryptor.decryptToString( | String cert = decryptor.decryptToString( | ||||
encryptCertificateNode.get("associated_data").toString().replaceAll("\"", "") | |||||
.getBytes("utf-8"), | |||||
encryptCertificateNode.get("nonce").toString().replaceAll("\"", "") | |||||
.getBytes("utf-8"), | |||||
encryptCertificateNode.get("ciphertext").toString().replaceAll("\"", "")); | |||||
encryptCertificateNode.get("associated_data").toString().replaceAll("\"", "") | |||||
.getBytes(StandardCharsets.UTF_8), | |||||
encryptCertificateNode.get("nonce").toString().replaceAll("\"", "") | |||||
.getBytes(StandardCharsets.UTF_8), | |||||
encryptCertificateNode.get("ciphertext").toString().replaceAll("\"", "")); | |||||
X509Certificate x509Cert = PemUtils | X509Certificate x509Cert = PemUtils | ||||
.loadCertificate(new ByteArrayInputStream(cert.getBytes("utf-8"))); | |||||
.loadCertificate(new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8))); | |||||
try { | try { | ||||
x509Cert.checkValidity(); | x509Cert.checkValidity(); | ||||
} catch (CertificateExpiredException | CertificateNotYetValidException e) { | } catch (CertificateExpiredException | CertificateNotYetValidException e) { | ||||
@@ -156,6 +167,7 @@ public class AutoUpdateCertificatesVerifier implements Verifier { | |||||
newCertList.add(x509Cert); | newCertList.add(x509Cert); | ||||
} | } | ||||
} | } | ||||
return newCertList; | return newCertList; | ||||
} | } | ||||
} | } |