diff --git a/README.md b/README.md index 1d7c7df..c24ba4f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ cn.felord payment-spring-boot-starter - 1.0.11.RELEASE + 1.0.12.RELEASE ``` diff --git a/docs/README.md b/docs/README.md index 2e5d488..c5c7126 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,7 +35,7 @@ cn.felord payment-spring-boot-starter - 1.0.11.RELEASE + 1.0.12.RELEASE ``` ## 采用技术 diff --git a/docs/changelog.md b/docs/changelog.md index 73f06ad..232e929 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,10 @@ +## 1.0.12.RELEASE +- 微信支付 + - fix: 修复多租户配置下,平台证书刷新错误的问题([#49](https://github.com/NotFound403/payment-spring-boot/issues/49)) + - fix: 分账API描述符错误([#48](https://github.com/NotFound403/payment-spring-boot/issues/48)) 。 + - refactor: 避免受jackson类库xml模块的影响 + - refactor: V2签名优化 + ## 1.0.11.RELEASE - 微信支付 @@ -13,7 +20,7 @@ - fix: 修复查询代金券参数的错误 - 支付宝 - feat: 支付宝增加字段`classpathUsed`来标识是否使用类路径,默认`true`。证书路径可依此来决定是使用绝对路径还是类路径 - + ## 1.0.10.RELEASE - 微信支付 diff --git a/docs/quick_start.md b/docs/quick_start.md index c61df5f..43b0a51 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -4,7 +4,7 @@ cn.felord payment-spring-boot-starter - 1.0.11.RELEASE + 1.0.12.RELEASE ``` > 基于 **Spring Boot 2.x** diff --git a/payment-spring-boot-autoconfigure/pom.xml b/payment-spring-boot-autoconfigure/pom.xml index 83fb1c6..8585112 100644 --- a/payment-spring-boot-autoconfigure/pom.xml +++ b/payment-spring-boot-autoconfigure/pom.xml @@ -5,11 +5,11 @@ cn.felord payment-spring-boot - 1.0.11.RELEASE + 1.0.12.RELEASE payment-spring-boot-autoconfigure - 1.0.11.RELEASE + 1.0.12.RELEASE jar 4.0.0 diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java index 654ef68..52528e2 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java @@ -622,7 +622,7 @@ public enum WechatPayV3Type { * * @since 1.0.11.RELEASE */ - PROFITSHARING_RECEIVERS_DELETE(HttpMethod.POST, "%s/v3/profitsharing/receivers/add"); + PROFITSHARING_RECEIVERS_DELETE(HttpMethod.POST, "%s/v3/profitsharing/receivers/delete"); /** * The Pattern. * diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/model/BaseModel.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/model/BaseModel.java index 1f6380f..e115e7b 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/model/BaseModel.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/model/BaseModel.java @@ -21,8 +21,8 @@ package cn.felord.payment.wechat.v2.model; import cn.felord.payment.PayException; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.dataformat.xml.XmlMapper; @@ -56,6 +56,8 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.cert.CertificateException; +import java.util.TreeMap; +import java.util.stream.Collectors; /** * The type Base model. @@ -74,7 +76,8 @@ public abstract class BaseModel { XML_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL) // 属性使用 驼峰首字母小写 .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); - OBJECT_MAPPER.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + OBJECT_MAPPER +// .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) .setSerializationInclusion(JsonInclude.Include.NON_NULL) .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); } @@ -149,10 +152,11 @@ public abstract class BaseModel { */ @SneakyThrows private String hmacSha256(String src) { - Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); - SecretKeySpec secret_key = new SecretKeySpec(appSecret.getBytes(),"HmacSHA256"); - sha256_HMAC.init(secret_key); - byte[] bytes = sha256_HMAC.doFinal(src.getBytes(StandardCharsets.UTF_8)); + String algorithm = "HmacSHA256"; + Mac sha256HMAC = Mac.getInstance(algorithm,"BC"); + SecretKeySpec secretKeySpec = new SecretKeySpec(appSecret.getBytes(), algorithm); + sha256HMAC.init(secretKeySpec); + byte[] bytes = sha256HMAC.doFinal(src.getBytes(StandardCharsets.UTF_8)); return Hex.toHexString(bytes).toUpperCase(); } @@ -166,13 +170,17 @@ public abstract class BaseModel { @SneakyThrows private String link(T t) { Assert.hasText(appSecret, "wechat pay appSecret is required"); - String link = OBJECT_MAPPER - .writer() - .writeValueAsString(t) - .replaceAll("\":\"", "=") - .replaceAll("\",\"", "&") - .replaceAll("\\\\\"", "\""); - return link.substring(2, link.length() - 2).concat("&key=").concat(this.appSecret); + String json = OBJECT_MAPPER + .writeValueAsString(t); + + TreeMap map = OBJECT_MAPPER.readValue(json, new TypeReference>() { + }); + + String query = map.entrySet() + .stream() + .map(entry -> entry.getKey().concat("=").concat(entry.getValue())) + .collect(Collectors.joining("&")); + return query.concat("&key=").concat(this.appSecret); } diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java index f3af733..e4f034d 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java @@ -30,7 +30,12 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.http.*; -import org.springframework.util.*; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; +import org.springframework.util.IdGenerator; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; @@ -49,10 +54,7 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -86,7 +88,7 @@ public class SignatureProvider { /** * 微信平台证书容器 key = 序列号 value = 证书对象 */ - private static final Map CERTIFICATE_MAP = new ConcurrentHashMap<>(); + private static final Map CERTIFICATE_MAP = new ConcurrentHashMap<>(); /** * 加密算法提供方 - BouncyCastle */ @@ -96,7 +98,7 @@ public class SignatureProvider { /** * The Rest operations. */ - private final RestOperations restOperations = new RestTemplate(); + private final RestOperations restOperations; /** * The Wechat meta container. */ @@ -110,6 +112,11 @@ public class SignatureProvider { public SignatureProvider(WechatMetaContainer wechatMetaContainer) { Provider bouncyCastleProvider = new BouncyCastleProvider(); Security.addProvider(bouncyCastleProvider); + RestTemplate restOperations = new RestTemplate(); + List> messageConverters = restOperations.getMessageConverters(); + messageConverters.removeIf(httpMessageConverter -> httpMessageConverter instanceof MappingJackson2XmlHttpMessageConverter); + restOperations.setMessageConverters(messageConverters); + this.restOperations = restOperations; this.wechatMetaContainer = wechatMetaContainer; wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate); } @@ -174,10 +181,10 @@ public class SignatureProvider { if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) { wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate); } - Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial); + Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial).getX509Certificate(); final String signatureStr = createSign(true, params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody()); - Signature signer = Signature.getInstance("SHA256withRSA"); + Signature signer = Signature.getInstance("SHA256withRSA", BC_PROVIDER); signer.initVerify(certificate); signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); @@ -234,7 +241,11 @@ public class SignatureProvider { try { Certificate certificate = certificateFactory.generateCertificate(inputStream); String responseSerialNo = objectNode.get("serial_no").asText(); - CERTIFICATE_MAP.put(responseSerialNo, certificate); + X509WechatCertificateInfo x509WechatCertificateInfo = new X509WechatCertificateInfo(); + x509WechatCertificateInfo.setWechatPaySerial(responseSerialNo); + x509WechatCertificateInfo.setTenantId(tenantId); + x509WechatCertificateInfo.setX509Certificate((X509Certificate) certificate); + CERTIFICATE_MAP.put(responseSerialNo, x509WechatCertificateInfo); } catch (CertificateException e) { throw new PayException("An error occurred while generating the wechat v3 certificate, reason : " + e.getMessage()); } @@ -307,21 +318,23 @@ public class SignatureProvider { /** * Get certificate x 509 wechat certificate info. * + * @param tenantId the tenant id * @return the x 509 wechat certificate info */ - public X509WechatCertificateInfo getCertificate() { + public X509WechatCertificateInfo getCertificate(String tenantId) { for (String serial : CERTIFICATE_MAP.keySet()) { - X509Certificate x509Cert = (X509Certificate) CERTIFICATE_MAP.get(serial); - try { - x509Cert.checkValidity(); - X509WechatCertificateInfo x509WechatCertificateInfo = new X509WechatCertificateInfo(); - x509WechatCertificateInfo.setWechatPaySerial(serial); - x509WechatCertificateInfo.setX509Certificate(x509Cert); - return x509WechatCertificateInfo; - } catch (Exception e) { - log.warn("the wechat certificate is invalid , {}", e.getMessage()); - // Async? - wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate); + X509WechatCertificateInfo wechatCertificateInfo = CERTIFICATE_MAP.get(serial); + X509Certificate x509Cert = wechatCertificateInfo.getX509Certificate(); + if (wechatCertificateInfo.getTenantId().equals(tenantId)){ + try { + x509Cert.checkValidity(); + + return wechatCertificateInfo; + } catch (Exception e) { + log.warn("the wechat certificate is invalid , {}", e.getMessage()); + // Async? + wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate); + } } } throw new PayException("failed to obtain wechat pay x509Certificate "); diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java index 198586a..8484ed3 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java @@ -77,7 +77,7 @@ public class WechatBatchTransferApi extends AbstractApi { List transferDetailList = createBatchTransferParams.getTransferDetailList(); SignatureProvider signatureProvider = this.client().signatureProvider(); - X509WechatCertificateInfo certificate = signatureProvider.getCertificate(); + X509WechatCertificateInfo certificate = signatureProvider.getCertificate(this.wechatMetaBean().getTenantId()); final X509Certificate x509Certificate = certificate.getX509Certificate(); List encrypted = transferDetailList.stream() .peek(transferDetailListItem -> { diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPartnerProfitsharingApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPartnerProfitsharingApi.java index 3504cf5..08d9be5 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPartnerProfitsharingApi.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPartnerProfitsharingApi.java @@ -56,7 +56,7 @@ public class WechatPartnerProfitsharingApi extends AbstractApi { .function((wechatPayV3Type, params) -> { WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); SignatureProvider signatureProvider = this.client().signatureProvider(); - X509WechatCertificateInfo certificate = signatureProvider.getCertificate(); + X509WechatCertificateInfo certificate = signatureProvider.getCertificate(this.wechatMetaBean().getTenantId()); final X509Certificate x509Certificate = certificate.getX509Certificate(); params.setAppid(v3.getAppId()); List receivers = params.getReceivers(); @@ -272,7 +272,7 @@ public class WechatPartnerProfitsharingApi extends AbstractApi { .function((wechatPayV3Type, params) -> { WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); SignatureProvider signatureProvider = this.client().signatureProvider(); - X509WechatCertificateInfo certificate = signatureProvider.getCertificate(); + X509WechatCertificateInfo certificate = signatureProvider.getCertificate(this.wechatMetaBean().getTenantId()); final X509Certificate x509Certificate = certificate.getX509Certificate(); params.setAppid(v3.getAppId()); String name = params.getName(); diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java index fb69c71..deab527 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java @@ -29,6 +29,7 @@ import org.springframework.http.*; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.util.Assert; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestOperations; @@ -351,7 +352,7 @@ public class WechatPayClient { DefaultResponseErrorHandler errorHandler = new WechatPayResponseErrorHandler(); restTemplate.setErrorHandler(errorHandler); List> messageConverters = restTemplate.getMessageConverters(); - + messageConverters.removeIf(httpMessageConverter -> httpMessageConverter instanceof MappingJackson2XmlHttpMessageConverter); messageConverters.removeIf(httpMessageConverter -> httpMessageConverter instanceof AllEncompassingFormHttpMessageConverter); messageConverters.add(new ExtensionFormHttpMessageConverter()); restTemplate.setMessageConverters(messageConverters); diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatProfitsharingApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatProfitsharingApi.java index 3158ae6..3f3c0f0 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatProfitsharingApi.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatProfitsharingApi.java @@ -53,7 +53,7 @@ public class WechatProfitsharingApi extends AbstractApi { .function((wechatPayV3Type, params) -> { WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); SignatureProvider signatureProvider = this.client().signatureProvider(); - X509WechatCertificateInfo certificate = signatureProvider.getCertificate(); + X509WechatCertificateInfo certificate = signatureProvider.getCertificate(this.wechatMetaBean().getTenantId()); final X509Certificate x509Certificate = certificate.getX509Certificate(); params.setAppid(v3.getAppId()); List receivers = params.getReceivers(); @@ -239,7 +239,7 @@ public class WechatProfitsharingApi extends AbstractApi { .function((wechatPayV3Type, params) -> { WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); SignatureProvider signatureProvider = this.client().signatureProvider(); - X509WechatCertificateInfo certificate = signatureProvider.getCertificate(); + X509WechatCertificateInfo certificate = signatureProvider.getCertificate(this.wechatMetaBean().getTenantId()); final X509Certificate x509Certificate = certificate.getX509Certificate(); params.setAppid(v3.getAppId()); String name = params.getName(); diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/X509WechatCertificateInfo.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/X509WechatCertificateInfo.java index 7d62e5e..9604e7a 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/X509WechatCertificateInfo.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/X509WechatCertificateInfo.java @@ -33,6 +33,10 @@ public class X509WechatCertificateInfo { * wechatPaySerial */ private String wechatPaySerial; + /** + * tenantId + */ + private String tenantId; /** * X509Certificate */ diff --git a/payment-spring-boot-starter/pom.xml b/payment-spring-boot-starter/pom.xml index 6e742a5..eb718bf 100644 --- a/payment-spring-boot-starter/pom.xml +++ b/payment-spring-boot-starter/pom.xml @@ -5,11 +5,11 @@ cn.felord payment-spring-boot - 1.0.11.RELEASE + 1.0.12.RELEASE payment-spring-boot-starter - 1.0.11.RELEASE + 1.0.12.RELEASE jar 4.0.0 diff --git a/pom.xml b/pom.xml index f827c72..0193578 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> cn.felord payment-spring-boot - 1.0.11.RELEASE + 1.0.12.RELEASE pom 4.0.0