| @@ -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; | ||||
| } | } | ||||
| } | } | ||||