From 6d321273be7e0a77e8c12247274fe4e28a44f484 Mon Sep 17 00:00:00 2001 From: DaxPay Date: Mon, 24 Mar 2025 14:06:51 +0800 Subject: [PATCH 1/9] =?UTF-8?q?doc=20=E6=96=87=E6=A1=A3=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f9c5b84..389688c 100644 --- a/README.md +++ b/README.md @@ -55,23 +55,23 @@ Starter,支持微信优惠券,代金券、商家券、智慧商圈、商家 ## 文档地址 -- [payment-spring-boot GitHub文档](https://dromara.github.io/payment-spring-boot) +- ~~[payment-spring-boot GitHub文档](https://dromara.github.io/payment-spring-boot) (暂时不可用)~~ ## API清单 -目前已经实现绝大部分微信支付直连商户和服务商的接口,具体的API明细可查看[API清单](https://dromara.github.io/payment-spring-boot/#/wechat_v3_api) +目前已经实现绝大部分微信支付直连商户和服务商的接口,具体的API明细可查看[API清单(暂时不可用)](https://dromara.github.io/payment-spring-boot/#/wechat_v3_api) > 随着版本迭代功能会增加,也可通过API注册表类`WechatPayV3Type`进行API接口检索。 ## CHANGELOG -更新日志[CHANGELOG](https://dromara.github.io/payment-spring-boot/#/changelog) +~~更新日志[CHANGELOG](https://dromara.github.io/payment-spring-boot/#/changelog) (暂时不可用)~~ ## 使用入门 ### 集成配置 -关于集成配置请详细阅读[payment-spring-boot GitHub文档](https://dromara.github.io/payment-spring-boot) -中[快速接入](https://dromara.github.io/payment-spring-boot/#/quick_start)章节 +~~关于集成配置请详细阅读[payment-spring-boot GitHub文档](https://dromara.github.io/payment-spring-boot) +中[快速接入](https://dromara.github.io/payment-spring-boot/#/quick_start)章节 (暂时不可用)~~ ### 调用示例 From 7388a7545572a45365ed02ac02a8d93b7778d4f2 Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 17:09:53 +0800 Subject: [PATCH 2/9] =?UTF-8?q?:sparkles:=20=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E5=93=8D=E5=BA=94=E9=AA=8C=E7=AD=BE=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20=E5=BE=AE=E4=BF=A1=E5=85=AC=E9=92=A5=E9=AA=8C=E7=AD=BE?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- payment-spring-boot-autoconfigure/pom.xml | 4 +- .../wechat/InMemoryWechatTenantService.java | 32 +++++++- .../payment/wechat/WechatPayProperties.java | 21 ++++++ .../payment/wechat/v3/SignatureProvider.java | 73 ++++++++++++++++++- .../wechat/v3/WeChatPublicKeyInfo.java | 26 +++++++ .../payment/wechat/v3/WechatMetaBean.java | 2 + .../payment/wechat/v3/WechatPayClient.java | 7 ++ payment-spring-boot-starter/pom.xml | 2 +- pom.xml | 2 +- 9 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java diff --git a/payment-spring-boot-autoconfigure/pom.xml b/payment-spring-boot-autoconfigure/pom.xml index 86214da..3009ace 100644 --- a/payment-spring-boot-autoconfigure/pom.xml +++ b/payment-spring-boot-autoconfigure/pom.xml @@ -22,11 +22,11 @@ cn.felord payment-spring-boot - 1.0.20.RELEASE + 1.0.21.RELEASE payment-spring-boot-autoconfigure - 1.0.20.RELEASE + 1.0.21.RELEASE jar 4.0.0 diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java index f0de581..2b361b5 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java @@ -17,15 +17,22 @@ package cn.felord.payment.wechat; -import cn.felord.payment.wechat.v3.KeyPairFactory; -import cn.felord.payment.wechat.v3.WechatMetaBean; +import cn.felord.payment.PayException; +import cn.felord.payment.wechat.v3.*; import lombok.AllArgsConstructor; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.CollectionUtils; import org.springframework.util.ResourceUtils; +import java.io.FileReader; +import java.io.InputStreamReader; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.X509EncodedKeySpec; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -62,6 +69,7 @@ public class InMemoryWechatTenantService implements WechatTenantService { WechatMetaBean wechatMetaBean = keyPairFactory.initWechatMetaBean(resource, mchId); wechatMetaBean.setV3(v3); wechatMetaBean.setTenantId(tenantId); + SignatureProvider.addWeChatPublicKey(initWeChatPublicKeyInfo(wechatMetaBean)); return wechatMetaBean; }) .collect(Collectors.toSet()); @@ -69,4 +77,24 @@ public class InMemoryWechatTenantService implements WechatTenantService { } return cache; } + + private WeChatPublicKeyInfo initWeChatPublicKeyInfo(WechatMetaBean meta) { + try { + String certPath=meta.getV3().getWeChatPayPublicKeyPath(); + Resource resource = + resourceLoader.getResource(certPath == null ? "classpath:wechat/pub_key.pem" : + certPath.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) ? certPath : ResourceUtils.CLASSPATH_URL_PREFIX + certPath); + PemReader pemReader = new PemReader(new InputStreamReader(resource.getInputStream())); + PemObject pemObject = pemReader.readPemObject(); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pemObject.getContent()); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec); + // 生成公钥 + WeChatPublicKeyInfo keyInfo = new WeChatPublicKeyInfo(publicKey, meta.getV3().getWeChatPayPublicKeyId(), meta.getTenantId()); + return keyInfo; + }catch (Exception e){ + e.printStackTrace(); + throw new PayException("An error occurred while generating the public key,Please check the format and content of the configured public key"); + } + } } diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java index 05de2d9..2d60692 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java @@ -76,5 +76,26 @@ public class WechatPayProperties { * your pay server domain */ private String domain; + + /** + * wechat pay public key id + */ + private String weChatPayPublicKeyId; + + /** + * see + * + * wechat pay public key + */ + private String weChatPayPublicKeyPath; + + /** + * + * see + * Indicate whether to switch from the platform certificate to the WeChat Pay public key + * + */ + private Boolean switchVerifySignMethod = false; + } } 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 b949cd6..9b1c573 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 @@ -27,13 +27,15 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.http.*; 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.util.*; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; @@ -44,12 +46,16 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileReader; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -85,6 +91,11 @@ public class SignatureProvider { * 微信平台证书容器 key = 序列号 value = 证书对象 */ private static final Set CERTIFICATE_SET = Collections.synchronizedSet(new HashSet<>()); + + private static final Set PUBLIC_KEY_SET = Collections.synchronizedSet(new HashSet<>()); + + private static final String PUBLIC_KYE_ID_PREFIX = "PUB_KEY_ID"; + /** * 加密算法提供方 - BouncyCastle */ @@ -117,6 +128,10 @@ public class SignatureProvider { wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate); } + public static void addWeChatPublicKey(WeChatPublicKeyInfo weChatPublicKeyInfo) { + PUBLIC_KEY_SET.add(weChatPublicKeyInfo); + } + /** * 我方请求前用 SHA256withRSA 加签,使用API证书. @@ -172,7 +187,20 @@ public class SignatureProvider { */ public boolean responseSignVerify(ResponseSignVerifyParams params) { + log.debug("responseSignVerify: {}", params); + log.debug("wechatpaySerial: {}", params.getWechatpaySerial()); + + boolean verifyResult= params.getWechatpaySerial().startsWith(PUBLIC_KYE_ID_PREFIX)? + responseSignVerifyWithWeChatPublicKeyInfo(params): + responseSignVerifyWithX509WechatCertificate(params); + log.debug("responseSignVerify: {}", verifyResult); + return verifyResult; + } + + private boolean responseSignVerifyWithX509WechatCertificate(ResponseSignVerifyParams params){ + log.debug("responseSignVerifyWithX509WechatCertificate: {}", params); String wechatpaySerial = params.getWechatpaySerial(); + X509WechatCertificateInfo certificate = CERTIFICATE_SET.stream() .filter(cert -> Objects.equals(wechatpaySerial, cert.getWechatPaySerial())) .findAny() @@ -195,6 +223,31 @@ public class SignatureProvider { } } + private boolean responseSignVerifyWithWeChatPublicKeyInfo(ResponseSignVerifyParams params){ + log.debug("responseSignVerifyWithWeChatPublicKeyInfo: {}", params); + + String wechatpaySerial = params.getWechatpaySerial(); + + log.debug("wechatpaySerial: {}", wechatpaySerial); + + if (wechatpaySerial.startsWith(PUBLIC_KYE_ID_PREFIX)){ + WeChatPublicKeyInfo info = PUBLIC_KEY_SET.stream() + .filter(key -> Objects.equals(wechatpaySerial, key.getPublicKeyId())) + .findAny() + .orElseThrow(() -> new PayException("cannot obtain the public key")); + + try { + final String signatureStr = createSign(params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody()); + Signature signer = Signature.getInstance("SHA256withRSA", BC_PROVIDER); + signer.initVerify(info.getPublicKey()); + signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); + return signer.verify(Base64Utils.decodeFromString(params.getWechatpaySignature())); + } catch (Exception e) { + throw new PayException("An exception occurred during the response verification, the cause: " + e.getMessage()); + } + } + return false; + } /** * 当我方服务器不存在平台证书或者证书同当前响应报文中的证书序列号不一致时应当刷新 调用/v3/certificates @@ -395,4 +448,16 @@ public class SignatureProvider { .collect(Collectors.joining("\n", "", "\n")); } + public boolean isSwitchVerifySignMethod(String tenantId) { + + String publicKeyId=wechatMetaContainer.getWechatMeta(tenantId).getV3().getWeChatPayPublicKeyId(); + + Boolean switchVerifySignMethod = wechatMetaContainer.getWechatMeta(tenantId).getV3().getSwitchVerifySignMethod(); + + return switchVerifySignMethod && StringUtils.hasLength(publicKeyId); + } + + public String getWechatPublicKeyId(String tenantId) { + return wechatMetaContainer.getWechatMeta(tenantId).getV3().getWeChatPayPublicKeyId(); + } } diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java new file mode 100644 index 0000000..128038d --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java @@ -0,0 +1,26 @@ +package cn.felord.payment.wechat.v3; + +import lombok.Data; + +import java.security.interfaces.RSAPublicKey; + +@Data +public class WeChatPublicKeyInfo { + + private RSAPublicKey publicKey; + + private String publicKeyId; + + private String tenantId; + + public WeChatPublicKeyInfo(RSAPublicKey publicKey, String publicKeyId, String tenantId) { + + this.publicKeyId = publicKeyId; + this.tenantId = tenantId; + this.publicKey = publicKey; + + } + + public WeChatPublicKeyInfo() { + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatMetaBean.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatMetaBean.java index 6e1cd8e..4ed15e5 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatMetaBean.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatMetaBean.java @@ -46,5 +46,7 @@ public class WechatMetaBean { * The V3. */ private WechatPayProperties.V3 v3; + + private WeChatPublicKeyInfo publicKeyInfo; } 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 6c987ba..1bc3f90 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 @@ -224,6 +224,8 @@ public class WechatPayClient { String tenantId = Objects.requireNonNull(headers.get("Pay-TenantId")).get(0); String authorization = signatureProvider.requestSign(tenantId, httpMethod.name(), canonicalUrl, body); + + HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.addAll(headers); httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); @@ -232,6 +234,11 @@ public class WechatPayClient { // 避免出现因为中文导致的 HttpRetryException httpHeaders.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8")); } + + if (signatureProvider.isSwitchVerifySignMethod(tenantId)){ + httpHeaders.add("Wechatpay-Serial", signatureProvider.getWechatPublicKeyId(tenantId)); + } + httpHeaders.add("Authorization", authorization); httpHeaders.add("User-Agent", "X-Pay-Service"); httpHeaders.remove("Meta-Info"); diff --git a/payment-spring-boot-starter/pom.xml b/payment-spring-boot-starter/pom.xml index 52620ab..92afff8 100644 --- a/payment-spring-boot-starter/pom.xml +++ b/payment-spring-boot-starter/pom.xml @@ -26,7 +26,7 @@ payment-spring-boot-starter - 1.0.20.RELEASE + 1.0.21.RELEASE jar 4.0.0 diff --git a/pom.xml b/pom.xml index 4c8b172..0a8671e 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,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.20.RELEASE + 1.0.21.RELEASE pom 4.0.0 From 7be5971ae31323fc9b9859335d1dff55db3d199f Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 18:05:54 +0800 Subject: [PATCH 3/9] =?UTF-8?q?:sparkles:=20=E5=A2=9E=E5=8A=A0=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=95=86=E6=88=B7=E6=A8=A1=E5=BC=8F=E4=B8=8BV3?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E7=9A=84=E4=BB=98=E6=AC=BE=E7=A0=81=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E4=B8=8E=E5=AF=B9=E5=BA=94=E7=9A=84=E6=92=A4=E9=94=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wechat/enumeration/WechatPayV3Type.java | 15 +++++ .../payment/wechat/v3/WechatDirectPayApi.java | 64 ++++++++++++++++--- .../felord/payment/wechat/v3/model/Payer.java | 2 + 3 files changed, 73 insertions(+), 8 deletions(-) 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 c62871d..2195a86 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 @@ -66,6 +66,13 @@ public enum WechatPayV3Type { MERCHANT_MEDIA_VIDEO(HttpMethod.POST, "%s/v3/merchant/media/video_upload"), //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + /** + * 付款码支付 + * + * @since 1.0.0.RELEASE + */ + CODE(HttpMethod.POST, "%s/v3/pay/transactions/codepay"), + /** * 微信公众号支付或者小程序支付. * @@ -99,6 +106,13 @@ public enum WechatPayV3Type { * @since 1.0.0.RELEASE */ CLOSE(HttpMethod.POST, "%s/v3/pay/transactions/out-trade-no/{out_trade_no}/close"), + /** + * 关闭订单. + * + * @since 1.0.0.RELEASE + */ + REVERSE(HttpMethod.POST, "%s/v3/pay/transactions/out-trade-no/{out_trade_no}/reverse"), + /** * 微信支付订单号查询API. * @@ -633,6 +647,7 @@ public enum WechatPayV3Type { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + /** * 服务商APP下单API. * diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java index 3814f6a..fb53076 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java @@ -56,12 +56,29 @@ public class WechatDirectPayApi extends AbstractApi { super(wechatPayClient, tenantId); } - /** - * APP下单API - * - * @param payParams the pay params - * @return the wechat response entity - */ + public WechatResponseEntity codePay(PayParams payParams) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.CODE, payParams) + .function(this::payFunction) + .consumer(responseEntity -> { + ObjectNode body = responseEntity.getBody(); + if (Objects.isNull(body)) { + throw new PayException("response body cannot be resolved"); + } + wechatResponseEntity.setHttpStatus(responseEntity.getStatusCodeValue()); + wechatResponseEntity.setBody(body); + }) + .request(); + return wechatResponseEntity; + } + + + /** + * APP下单API + * + * @param payParams the pay params + * @return the wechat response entity + */ public WechatResponseEntity appPay(PayParams payParams) { WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); this.client().withType(WechatPayV3Type.APP, payParams) @@ -185,8 +202,10 @@ public class WechatDirectPayApi extends AbstractApi { WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); payParams.setAppid(v3.getAppId()); payParams.setMchid(v3.getMchId()); - String notifyUrl = v3.getDomain().concat(payParams.getNotifyUrl()); - payParams.setNotifyUrl(notifyUrl); + if (!type.equals(WechatPayV3Type.CODE)){ + String notifyUrl = v3.getDomain().concat(payParams.getNotifyUrl()); + payParams.setNotifyUrl(notifyUrl); + } URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA)) .build() .toUri(); @@ -252,6 +271,21 @@ public class WechatDirectPayApi extends AbstractApi { return wechatResponseEntity; } + /** + * 撤销API + * + * @param outTradeNo the out trade no + * @return the wechat response entity + */ + public WechatResponseEntity reverse(String outTradeNo) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.REVERSE, outTradeNo) + .function(this::reverseOutTradeNoFunction) + .consumer(wechatResponseEntity::convert) + .request(); + return wechatResponseEntity; + } + private RequestEntity closeByOutTradeNoFunction(WechatPayV3Type type, String outTradeNo) { WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); @@ -265,6 +299,20 @@ public class WechatDirectPayApi extends AbstractApi { return Post(uri, queryParams); } + private RequestEntity reverseOutTradeNoFunction(WechatPayV3Type type, String outTradeNo) { + WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); + + Map queryParams = new HashMap<>(1); + queryParams.put("mchid", v3.getMchId()); + queryParams.put("appid", v3.getAppId()); + + URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA)) + .build() + .expand(outTradeNo) + .toUri(); + return Post(uri, queryParams); + } + /** * 申请退款API * diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/Payer.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/Payer.java index 1b29fc4..b928056 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/Payer.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/Payer.java @@ -38,4 +38,6 @@ public class Payer { * 用户子标识 */ private String subOpenid; + + private String authCode; } From 20f80c16dbde6188cbc49eea85a1692572e9735b Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 18:46:26 +0800 Subject: [PATCH 4/9] =?UTF-8?q?:arrow=5Fup:=20=E5=8D=87=E7=BA=A7=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BE=9D=E8=B5=96=E7=9A=84=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0a8671e..0f89ec2 100644 --- a/pom.xml +++ b/pom.xml @@ -88,8 +88,8 @@ UTF-8 UTF-8 1.8 - 2.7.7 - 4.31.7.ALL + 2.7.18 + 4.40.251.ALL 1.78 From a4fa017cece1981828a026fc7b22ac8865a44fa3 Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 18:56:04 +0800 Subject: [PATCH 5/9] =?UTF-8?q?:bug:=20=E8=A7=A3=E5=86=B3=E6=9C=AA?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E5=85=AC?= =?UTF-8?q?=E9=92=A5=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../felord/payment/wechat/InMemoryWechatTenantService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java index 2b361b5..b8e1c1c 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java @@ -27,6 +27,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.CollectionUtils; import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; import java.io.FileReader; import java.io.InputStreamReader; @@ -79,6 +80,10 @@ public class InMemoryWechatTenantService implements WechatTenantService { } private WeChatPublicKeyInfo initWeChatPublicKeyInfo(WechatMetaBean meta) { + boolean enablePublicKey=StringUtils.hasLength(meta.getV3().getWeChatPayPublicKeyId()) && StringUtils.hasLength(meta.getV3().getWeChatPayPublicKeyPath()); + if (!enablePublicKey) { + return null; + } try { String certPath=meta.getV3().getWeChatPayPublicKeyPath(); Resource resource = From 62eaf468fb61181eb4c57505d406979b76e18e89 Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 19:02:01 +0800 Subject: [PATCH 6/9] =?UTF-8?q?:art:=20=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/wechat/v3/SignatureProvider.java | 14 ++++++-------- .../payment/wechat/v3/WeChatPublicKeyInfo.java | 2 -- .../payment/wechat/v3/WechatDirectPayApi.java | 6 ++++++ .../felord/payment/wechat/v3/WechatPayClient.java | 2 -- 4 files changed, 12 insertions(+), 12 deletions(-) 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 9b1c573..98c3058 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 @@ -186,10 +186,7 @@ public class SignatureProvider { * @return the boolean */ public boolean responseSignVerify(ResponseSignVerifyParams params) { - - log.debug("responseSignVerify: {}", params); log.debug("wechatpaySerial: {}", params.getWechatpaySerial()); - boolean verifyResult= params.getWechatpaySerial().startsWith(PUBLIC_KYE_ID_PREFIX)? responseSignVerifyWithWeChatPublicKeyInfo(params): responseSignVerifyWithX509WechatCertificate(params); @@ -197,10 +194,12 @@ public class SignatureProvider { return verifyResult; } + /*** + *通过平台证书进行验签 + */ private boolean responseSignVerifyWithX509WechatCertificate(ResponseSignVerifyParams params){ log.debug("responseSignVerifyWithX509WechatCertificate: {}", params); String wechatpaySerial = params.getWechatpaySerial(); - X509WechatCertificateInfo certificate = CERTIFICATE_SET.stream() .filter(cert -> Objects.equals(wechatpaySerial, cert.getWechatPaySerial())) .findAny() @@ -223,13 +222,12 @@ public class SignatureProvider { } } + /*** + *通过微信支付公钥进行验签 + */ private boolean responseSignVerifyWithWeChatPublicKeyInfo(ResponseSignVerifyParams params){ log.debug("responseSignVerifyWithWeChatPublicKeyInfo: {}", params); - String wechatpaySerial = params.getWechatpaySerial(); - - log.debug("wechatpaySerial: {}", wechatpaySerial); - if (wechatpaySerial.startsWith(PUBLIC_KYE_ID_PREFIX)){ WeChatPublicKeyInfo info = PUBLIC_KEY_SET.stream() .filter(key -> Objects.equals(wechatpaySerial, key.getPublicKeyId())) diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java index 128038d..73b347e 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WeChatPublicKeyInfo.java @@ -14,11 +14,9 @@ public class WeChatPublicKeyInfo { private String tenantId; public WeChatPublicKeyInfo(RSAPublicKey publicKey, String publicKeyId, String tenantId) { - this.publicKeyId = publicKeyId; this.tenantId = tenantId; this.publicKey = publicKey; - } public WeChatPublicKeyInfo() { diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java index fb53076..2c4cbdc 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatDirectPayApi.java @@ -56,6 +56,12 @@ public class WechatDirectPayApi extends AbstractApi { super(wechatPayClient, tenantId); } + /** + * 付款码支付API + * + * @param payParams the pay params + * @return the wechat response entity + */ public WechatResponseEntity codePay(PayParams payParams) { WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); this.client().withType(WechatPayV3Type.CODE, payParams) 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 1bc3f90..61212d6 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 @@ -224,8 +224,6 @@ public class WechatPayClient { String tenantId = Objects.requireNonNull(headers.get("Pay-TenantId")).get(0); String authorization = signatureProvider.requestSign(tenantId, httpMethod.name(), canonicalUrl, body); - - HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.addAll(headers); httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); From b4ff87b80b9156f6faf52f141f45573086a0eba3 Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 19:27:40 +0800 Subject: [PATCH 7/9] =?UTF-8?q?:pencil:=20=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BF=AE=E6=94=B9=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.md | 10 ++++++++++ docs/quick_start.md | 7 +++++++ .../payment/wechat/InMemoryWechatTenantService.java | 13 +++++++------ .../felord/payment/wechat/WechatPayProperties.java | 6 ++++-- .../felord/payment/wechat/v3/SignatureProvider.java | 4 ++-- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 5d27d83..34ba68d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,13 @@ +## 1.0.20.RELEASE +### 微信支付 + +- enhance: 增加了通过微信公钥对微信支付相关接口的响应内容或微信回调通知的参数进行验签的支持。 + - 微信配置项增加了:``` wechat-pay-public-key-id: 微信支付公钥的ID、 wechat-pay-public-key-path:微信支付公钥的路径、wechat-pay-public-key-absolute-path: 微信支付公钥的绝对路径、 switch-verify-sign-method: 是否启用从平台证书切换到微信支公钥``` + - `wechat-pay-public-key-id ` 与`wechat-pay-public-key-path或wechat-pay-public-key-absolute-path`同时正确配置,才会启用微信支付公钥验签,否则默认使用平台证书进行验签。 + - 如果需要[从平台证书切换成微信支付公钥](https://pay.weixin.qq.com/doc/v3/merchant/4012154180#5.-%E6%B2%A1%E6%9C%89%E4%BD%BF%E7%94%A8%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98SDK%E7%9A%84%E5%95%86%E6%88%B7%E5%A6%82%E4%BD%95%E5%B0%86%E5%B9%B3%E5%8F%B0%E8%AF%81%E4%B9%A6%E5%88%87%E6%8D%A2%E6%88%90%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98%E5%85%AC%E9%92%A5),请启用`switch-verify-sign-method`参数 +- enhance: 增加了微信支付V3版本的付款码支付``codePay``与撤销API``reverse``(仅支持普通商户模式,服务商模式暂不支持) +- factor: 升级了spring-boot-parent版本从 2.7.7 到2.7.18 +- factor: 升级了Alipay SDK版本从4.31.7.ALL到4.40.251.ALL ## 1.0.18.RELEASE ### 微信支付 diff --git a/docs/quick_start.md b/docs/quick_start.md index 768dff4..292cf51 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -86,6 +86,13 @@ wechat: mch-id: 1603337223 domain: https://felord.cn/miniapp cert-path: miniapp/apiclient_cert.p12 + #微信公钥ID + wechat-pay-public-key-id: PUB_KEY_ID_0116278111111115222222501 + #微信公钥 + wechat-pay-public-key-path: pub_key.pem + wechat-pay-public-key-absolute-path: D:\\felord\\wechat\\cert\\pub_key.pem + #是否启用从平台证书切换成微信支付公钥 不填默认为false + switch-verify-sign-method: true ``` > ❗注意:在一套系统中需要开发者保证`tentanID`唯一。 diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java index b8e1c1c..8ff21f1 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/InMemoryWechatTenantService.java @@ -80,14 +80,17 @@ public class InMemoryWechatTenantService implements WechatTenantService { } private WeChatPublicKeyInfo initWeChatPublicKeyInfo(WechatMetaBean meta) { - boolean enablePublicKey=StringUtils.hasLength(meta.getV3().getWeChatPayPublicKeyId()) && StringUtils.hasLength(meta.getV3().getWeChatPayPublicKeyPath()); + boolean enablePublicKey=StringUtils.hasLength(meta.getV3().getWechatPayPublicKeyId()) && + (StringUtils.hasLength(meta.getV3().getWechatPayPublicKeyPath())||StringUtils.hasLength(meta.getV3().getWechatPayPublicKeyAbsolutePath())); if (!enablePublicKey) { return null; } try { - String certPath=meta.getV3().getWeChatPayPublicKeyPath(); + String certPath=meta.getV3().getWechatPayPublicKeyPath(); + String certAbsolutePath = meta.getV3().getWechatPayPublicKeyAbsolutePath(); Resource resource = - resourceLoader.getResource(certPath == null ? "classpath:wechat/pub_key.pem" : + StringUtils.hasLength(certAbsolutePath) ? new FileSystemResource(certAbsolutePath) : + resourceLoader.getResource(!StringUtils.hasLength(certPath) ? "classpath:wechat/pub_key.pem" : certPath.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) ? certPath : ResourceUtils.CLASSPATH_URL_PREFIX + certPath); PemReader pemReader = new PemReader(new InputStreamReader(resource.getInputStream())); PemObject pemObject = pemReader.readPemObject(); @@ -95,10 +98,8 @@ public class InMemoryWechatTenantService implements WechatTenantService { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec); // 生成公钥 - WeChatPublicKeyInfo keyInfo = new WeChatPublicKeyInfo(publicKey, meta.getV3().getWeChatPayPublicKeyId(), meta.getTenantId()); - return keyInfo; + return new WeChatPublicKeyInfo(publicKey, meta.getV3().getWechatPayPublicKeyId(), meta.getTenantId()); }catch (Exception e){ - e.printStackTrace(); throw new PayException("An error occurred while generating the public key,Please check the format and content of the configured public key"); } } diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java index 2d60692..18b353f 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/WechatPayProperties.java @@ -80,14 +80,16 @@ public class WechatPayProperties { /** * wechat pay public key id */ - private String weChatPayPublicKeyId; + private String wechatPayPublicKeyId; /** * see * * wechat pay public key */ - private String weChatPayPublicKeyPath; + private String wechatPayPublicKeyPath; + + private String wechatPayPublicKeyAbsolutePath; /** * 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 98c3058..3b8a2c5 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 @@ -448,7 +448,7 @@ public class SignatureProvider { public boolean isSwitchVerifySignMethod(String tenantId) { - String publicKeyId=wechatMetaContainer.getWechatMeta(tenantId).getV3().getWeChatPayPublicKeyId(); + String publicKeyId=wechatMetaContainer.getWechatMeta(tenantId).getV3().getWechatPayPublicKeyId(); Boolean switchVerifySignMethod = wechatMetaContainer.getWechatMeta(tenantId).getV3().getSwitchVerifySignMethod(); @@ -456,6 +456,6 @@ public class SignatureProvider { } public String getWechatPublicKeyId(String tenantId) { - return wechatMetaContainer.getWechatMeta(tenantId).getV3().getWeChatPayPublicKeyId(); + return wechatMetaContainer.getWechatMeta(tenantId).getV3().getWechatPayPublicKeyId(); } } From f0e2495bfd56152eed612843005c5f8a4f6f4f11 Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 19:37:23 +0800 Subject: [PATCH 8/9] =?UTF-8?q?:pencil:=20=20=E4=BF=AE=E6=94=B9=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 34ba68d..0f1ec2c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,4 +1,4 @@ -## 1.0.20.RELEASE +## 1.0.21.RELEASE ### 微信支付 - enhance: 增加了通过微信公钥对微信支付相关接口的响应内容或微信回调通知的参数进行验签的支持。 From dedde9515d07e80b9d3f16272c2f5aa0c75ab2c5 Mon Sep 17 00:00:00 2001 From: xucun Date: Wed, 11 Jun 2025 19:39:07 +0800 Subject: [PATCH 9/9] =?UTF-8?q?:pencil:=20=E4=BF=AE=E6=94=B9=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/quick_start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_start.md b/docs/quick_start.md index 292cf51..926bd09 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -4,7 +4,7 @@ cn.felord payment-spring-boot-starter - 1.0.20.RELEASE + 1.0.21.RELEASE ``` > 基于 **Spring Boot 2.x**