From 04d870be1a0f6a53fd600f7cd6daefa5e5cd7c20 Mon Sep 17 00:00:00 2001 From: xiafang Date: Thu, 29 Oct 2020 09:29:30 +0800 Subject: [PATCH] 'init' --- payment-spring-boot-autoconfigure/pom.xml | 32 +++ .../payment/alipay/SignatureProvider.java | 91 +++++++ .../configuration/AliPayConfiguration.java | 13 + .../configuration/AliPayProperties.java | 51 ++++ .../autoconfigure/WechatPayConfiguration.java | 70 +++++ .../autoconfigure/WechatPayProperties.java | 51 ++++ .../payment/wechat/DefaultPayFilterChain.java | 32 +++ .../dianji/payment/wechat/KeyPairFactory.java | 58 ++++ .../dianji/payment/wechat/PayFilter.java | 23 ++ .../dianji/payment/wechat/PayFilterChain.java | 27 ++ .../payment/wechat/WechatPayV3Service.java | 104 ++++++++ .../payment/wechat/enumeration/BankCode.java | 209 +++++++++++++++ .../payment/wechat/enumeration/V2PayType.java | 83 ++++++ .../payment/wechat/enumeration/V3PayType.java | 108 ++++++++ .../wechat/enumeration/WeChatServer.java | 56 ++++ .../payment/wechat/v2/WechatPayV2Service.java | 26 ++ .../payment/wechat/v2/WechatResponseBody.java | 24 ++ .../payment/wechat/v2/model/BaseModel.java | 152 +++++++++++ .../wechat/v2/model/PayToWechatModel.java | 22 ++ .../payment/wechat/v3/SignatureProvider.java | 248 ++++++++++++++++++ .../payment/wechat/v3/WechatPayRequest.java | 118 +++++++++ .../wechat/v3/filter/BodyMergeFilter.java | 70 +++++ .../wechat/v3/filter/HeaderFilter.java | 48 ++++ .../wechat/v3/filter/HttpRequestFilter.java | 91 +++++++ .../wechat/v3/filter/WechatServerFilter.java | 25 ++ .../payment/wechat/v3/model/Amount.java | 14 + .../payment/wechat/v3/model/AppPayModel.java | 17 ++ .../payment/wechat/v3/model/BaseModel.java | 54 ++++ .../payment/wechat/v3/model/Detail.java | 25 ++ .../dianji/payment/wechat/v3/model/Goods.java | 28 ++ .../payment/wechat/v3/model/H5Info.java | 31 +++ .../dianji/payment/wechat/v3/model/Payer.java | 23 ++ .../payment/wechat/v3/model/SceneInfo.java | 27 ++ .../payment/wechat/v3/model/SettleInfo.java | 16 ++ .../payment/wechat/v3/model/StoreInfo.java | 27 ++ .../wechat/v3/model/WechatMetaBean.java | 27 ++ .../wechat/v3/service/WechatPayV3Api.java | 31 +++ payment-spring-boot-starter/pom.xml | 7 + pom.xml | 18 ++ 39 files changed, 2177 insertions(+) create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/SignatureProvider.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayConfiguration.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayProperties.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayConfiguration.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayProperties.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/DefaultPayFilterChain.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/KeyPairFactory.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilter.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilterChain.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/WechatPayV3Service.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/BankCode.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V2PayType.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V3PayType.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/WeChatServer.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatPayV2Service.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatResponseBody.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/BaseModel.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/PayToWechatModel.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/SignatureProvider.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayRequest.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/BodyMergeFilter.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HeaderFilter.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HttpRequestFilter.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/WechatServerFilter.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Amount.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/AppPayModel.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/BaseModel.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Detail.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Goods.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/H5Info.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Payer.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SceneInfo.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SettleInfo.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/StoreInfo.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/WechatMetaBean.java create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/service/WechatPayV3Api.java diff --git a/payment-spring-boot-autoconfigure/pom.xml b/payment-spring-boot-autoconfigure/pom.xml index b8ffbd5..0cf299e 100644 --- a/payment-spring-boot-autoconfigure/pom.xml +++ b/payment-spring-boot-autoconfigure/pom.xml @@ -6,6 +6,18 @@ com.enongm.dianji payment-spring-boot-autoconfigure 1.0.0.RELEASE + + + + org.apache.maven.plugins + maven-compiler-plugin + + 7 + 7 + + + + jar 4.0.0 @@ -21,6 +33,14 @@ + + 1.8 + 1.8 + UTF-8 + UTF-8 + 1.8 + + org.springframework.boot @@ -47,6 +67,18 @@ spring-boot-starter-web provided + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + com.squareup.okhttp3 + okhttp + + + org.bouncycastle + bcprov-jdk15to18 + \ No newline at end of file diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/SignatureProvider.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/SignatureProvider.java new file mode 100644 index 0000000..ec92b41 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/SignatureProvider.java @@ -0,0 +1,91 @@ +package com.enongm.dianji.payment.alipay; + +import org.springframework.util.CollectionUtils; + +import java.util.*; + + +/** + * The type Ali pay core. + */ +public class SignatureProvider { + + /** + * 生成签名结果 + * + * @param params 要签名的数组 + * @param key 签名密钥 + * @param signType 签名类型 + * @return 签名结果字符串 + */ + public static String requestSign(Map params, String key, String signType) { + String preStr = createLinkString(params); + /* if (SignType.MD5.getType().equals(signType)) { + return SecureUtil.md5(preStr.concat(key)); + }*/ + return null; + } + + /** + * 生成要请求给支付宝的参数数组 + * + * @param params 请求前的参数数组 + * @param key 商户的私钥 + * @param signType 签名类型 + * @return 要请求的参数数组 map + */ + public static Map buildRequestPara(Map params, String key, String signType) { + // 除去数组中的空值和签名参数 + Map tempMap = paraFilter(params); + // 生成签名结果 + String mySign = requestSign(params, key, signType); + + // 签名结果与签名方式加入请求提交参数组中 + tempMap.put("sign", mySign); + tempMap.put("sign_type", signType); + + return tempMap; + } + + /** + * 除去数组中的空值和签名参数 + * + * @param sArray 签名参数组 + * @return 去掉空值与签名参数后的新签名参数组 map + */ + public static Map paraFilter(Map sArray) { + Map result = new HashMap(sArray.size()); + if (CollectionUtils.isEmpty(sArray)) { + return Collections.emptyMap(); + } + + for (String key : sArray.keySet()) { + String value = sArray.get(key); + if (value == null || "".equals(value) || "sign".equalsIgnoreCase(key) + || "sign_type".equalsIgnoreCase(key)) { + continue; + } + result.put(key, value); + } + return result; + } + + /** + * 参数自然排序拼接 + * + * @param params {'k':'b','e':'f','c':'d'} -> {'c':'d','e':'f','k':'b'} + * @return 拼接后字符串 c=d&e=f&k=b + */ + public static String createLinkString(Map params) { + TreeMap treeMap = new TreeMap<>(params); + + Set keySet = treeMap.keySet(); + StringBuilder content = new StringBuilder(); + for (String key : keySet) { + content.append(key).append("=") + .append(treeMap.get(key)) + .append("&"); + } + return content.substring(0, content.length() - 1); + } +} \ No newline at end of file diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayConfiguration.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayConfiguration.java new file mode 100644 index 0000000..009cc06 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayConfiguration.java @@ -0,0 +1,13 @@ +package com.enongm.dianji.payment.alipay.configuration; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author Dax + * @since 14:35 + */ +@Configuration +@EnableConfigurationProperties(AliPayProperties.class) +public class AliPayConfiguration { +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayProperties.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayProperties.java new file mode 100644 index 0000000..fe58bae --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/alipay/configuration/AliPayProperties.java @@ -0,0 +1,51 @@ +package com.enongm.dianji.payment.alipay.configuration; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * @author Dax + * @since 14:13 + */ +@Data +@ConfigurationProperties("ali.pay") +public class AliPayProperties { + /** + * alipay api version 1.0 + */ + @NestedConfigurationProperty + private V1 v1; + + + @Data + public static class V1{ + /** + * alipay server + */ + private String serverUrl = "https://openapi.alipay.com/gateway.do"; + /** + * your app ID + */ + private String appId; + /** + * your app private key + */ + private String appPrivateKey; + /** + * sign type + */ + private String signType = "md5"; + /** + * alipay public cert path + */ + private String alipayPublicCertPath; + /** + * alipay root cert path + */ + private String alipayRootCertPath; + + } + + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayConfiguration.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayConfiguration.java new file mode 100644 index 0000000..77938e0 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayConfiguration.java @@ -0,0 +1,70 @@ +package com.enongm.dianji.payment.autoconfigure; + + +import com.enongm.dianji.payment.wechat.KeyPairFactory; +import com.enongm.dianji.payment.wechat.WechatPayV3Service; +import com.enongm.dianji.payment.wechat.v2.WechatPayV2Service; +import com.enongm.dianji.payment.wechat.v3.SignatureProvider; +import com.enongm.dianji.payment.wechat.v3.model.WechatMetaBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * The type Wechat pay configuration. + */ +@Configuration +@EnableConfigurationProperties(WechatPayProperties.class) +public class WechatPayConfiguration { + private static final String CERT_ALIAS = "Tenpay Certificate"; + + /** + * 微信支付公私钥 以及序列号. + * + * @param wechatPayProperties the wechat pay properties + * @return the wechat cert bean + */ + @Bean + WechatMetaBean wechatMetaBean(WechatPayProperties wechatPayProperties) { + + String certPath = wechatPayProperties.getV3().getCertPath(); + String mchId = wechatPayProperties.getV3().getMchId(); + WechatMetaBean wechatMetaBean = new KeyPairFactory().createPKCS12(certPath, CERT_ALIAS, mchId); + wechatMetaBean.setWechatPayProperties(wechatPayProperties); + return wechatMetaBean; + } + + /** + * Signature provider signature provider. + * + * @param wechatMetaBean the wechat meta bean + * @return the signature provider + */ + @Bean + public SignatureProvider signatureProvider(WechatMetaBean wechatMetaBean) { + return new SignatureProvider(wechatMetaBean); + } + + + /** + * 微信支付V2 只实现V3支付没有的支付业务。 + * + * @param wechatPayProperties the wechat pay properties + * @return the wechat pay v 2 service + */ + @Bean + public WechatPayV2Service wechatPayV2Service(WechatPayProperties wechatPayProperties) { + return new WechatPayV2Service(wechatPayProperties); + } + + /** + * Wechat pay service wechat pay service. + * + * @param signatureProvider the signature provider + * @return the wechat pay service + */ + @Bean + public WechatPayV3Service wechatPayService(SignatureProvider signatureProvider) { + return new WechatPayV3Service(signatureProvider); + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayProperties.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayProperties.java new file mode 100644 index 0000000..965d467 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/autoconfigure/WechatPayProperties.java @@ -0,0 +1,51 @@ +package com.enongm.dianji.payment.autoconfigure; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * The type Wechat pay properties. + */ +@Data +@ConfigurationProperties("wechat.pay") +public class WechatPayProperties { + /** + * wechat pay V3 properties + */ + @NestedConfigurationProperty + private V3 v3; + + @Data + public static class V3 { + /** + * app id for wechat pay is required + */ + private String appId; + /** + * app secret for wechat pay is required + */ + private String appSecret; + /** + * app V3 secret is required by wechat pay V3 + */ + private String appV3Secret; + /** + * mchId for wechat pay is required + */ + private String mchId; + /** + * partnerKey for wechat pay is optional + */ + private String partnerKey; + /** + * wechat pay certificate Path + */ + private String certPath; + /** + * your pay server domain + */ + private String domain; + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/DefaultPayFilterChain.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/DefaultPayFilterChain.java new file mode 100644 index 0000000..f4d111c --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/DefaultPayFilterChain.java @@ -0,0 +1,32 @@ +package com.enongm.dianji.payment.wechat; + + + +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Dax + * @since 16:16 + */ +public class DefaultPayFilterChain implements PayFilterChain { + private int pos = 0; + private final List filters = new ArrayList<>(); + + @Override + public void register(PayFilter filter) { + filters.add(filter); + } + + @Override + public void doChain(WechatPayRequest request) { + int size = filters.size(); + if (pos < size) { + PayFilter payFilter = filters.get(pos++); + payFilter.doFilter(request, this); + } + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/KeyPairFactory.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/KeyPairFactory.java new file mode 100644 index 0000000..7c16173 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/KeyPairFactory.java @@ -0,0 +1,58 @@ +package com.enongm.dianji.payment.wechat; + + +import com.enongm.dianji.payment.wechat.v3.model.WechatMetaBean; +import org.springframework.core.io.ClassPathResource; + +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; + +/** + * KeyPairFactory + * + * @author dax + * @since 13:41 + **/ +public class KeyPairFactory { + + private KeyStore store; + + private final Object lock = new Object(); + + /** + * 获取公私钥. + * + * @param keyPath the key path + * @param keyAlias the key alias + * @param keyPass password + * @return the key pair + */ + public WechatMetaBean createPKCS12(String keyPath, String keyAlias, String keyPass) { + ClassPathResource resource = new ClassPathResource(keyPath); + char[] pem = keyPass.toCharArray(); + try { + synchronized (lock) { + if (store == null) { + synchronized (lock) { + store = KeyStore.getInstance("PKCS12"); + store.load(resource.getInputStream(), pem); + } + } + } + X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias); + certificate.checkValidity(); + String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase(); + PublicKey publicKey = certificate.getPublicKey(); + PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem); + WechatMetaBean wechatMetaBean = new WechatMetaBean(); + wechatMetaBean.setKeyPair(new KeyPair(publicKey, storeKey)); + wechatMetaBean.setSerialNumber(serialNumber); + return wechatMetaBean; + } catch (Exception e) { + throw new IllegalStateException("Cannot load keys from store: " + resource, e); + } + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilter.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilter.java new file mode 100644 index 0000000..2989fa5 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilter.java @@ -0,0 +1,23 @@ +package com.enongm.dianji.payment.wechat; + + +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; + +/** + * The interface Pay filter. + * + * @author Dax + * @since 15 :08 + */ + +public interface PayFilter { + + /** + * Do filter. + * + * @param request the request + * @param chain the chain + */ + void doFilter(WechatPayRequest request, PayFilterChain chain); + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilterChain.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilterChain.java new file mode 100644 index 0000000..d3a4e0f --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/PayFilterChain.java @@ -0,0 +1,27 @@ +package com.enongm.dianji.payment.wechat; + + +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; + +/** + * The interface Pay filter chain. + * + * @author Dax + * @since 16 :11 + */ +public interface PayFilterChain { + + /** + * Do chain. + * + * @param request the request + */ + void doChain(WechatPayRequest request); + + /** + * Register. + * + * @param filter the filter + */ + void register(PayFilter filter); +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/WechatPayV3Service.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/WechatPayV3Service.java new file mode 100644 index 0000000..6fa834a --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/WechatPayV3Service.java @@ -0,0 +1,104 @@ +package com.enongm.dianji.payment.wechat; + + + +import com.enongm.dianji.payment.wechat.v3.SignatureProvider; +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; +import com.enongm.dianji.payment.wechat.v3.filter.BodyMergeFilter; +import com.enongm.dianji.payment.wechat.v3.filter.HeaderFilter; +import com.enongm.dianji.payment.wechat.v3.filter.HttpRequestFilter; +import com.enongm.dianji.payment.wechat.v3.filter.WechatServerFilter; +import com.enongm.dianji.payment.wechat.v3.model.BaseModel; +import com.enongm.dianji.payment.wechat.v3.model.WechatMetaBean; + +import java.util.function.Function; + +/** + * The type Wechat pay service. + * + * @author Dax + * @since 11 :43 + */ +public class WechatPayV3Service { + private final PayFilterChain payFilterChain; + + /** + * Instantiates a new Wechat pay service. + * + * @param payFilterChain the pay filter chain + */ + public WechatPayV3Service(PayFilterChain payFilterChain) { + this.payFilterChain = payFilterChain; + } + + /** + * Instantiates a new Wechat pay service. + * + * @param signatureProvider the signature provider + */ + public WechatPayV3Service(SignatureProvider signatureProvider) { + WechatMetaBean wechatMetaBean = signatureProvider.getWechatMetaBean(); + DefaultPayFilterChain defaultPayFilterChain = new DefaultPayFilterChain(); + // 微信服务器选择 + defaultPayFilterChain.register(new WechatServerFilter()); + // 对请求体注入业务无关参数 + defaultPayFilterChain.register(new BodyMergeFilter(wechatMetaBean)); + // 构造私钥签名 + defaultPayFilterChain.register(new HeaderFilter(signatureProvider)); + defaultPayFilterChain.register(new HttpRequestFilter(signatureProvider)); + + this.payFilterChain = defaultPayFilterChain; + } + + + /** + * Exec executor. + * + * @param the type parameter + * @param the type parameter + * @param requestFunction the request function + * @return the executor + */ + public Executor request(Function requestFunction) { + return new Executor<>(this.payFilterChain, requestFunction); + } + + + /** + * The type Executor. + * + * @param the type parameter + * @param the type parameter + */ + public static class Executor { + /** + * The Pay filter chain. + */ + PayFilterChain payFilterChain; + /** + * The Request function. + */ + Function requestFunction; + + /** + * Instantiates a new Executor. + * + * @param payFilterChain the pay filter chain + * @param requestFunction the request function + */ + public Executor(PayFilterChain payFilterChain, Function requestFunction) { + this.payFilterChain = payFilterChain; + this.requestFunction = requestFunction; + } + + /** + * With model. + * + * @param model the model + */ + public void withModel(M model) { + payFilterChain.doChain(requestFunction.apply(model)); + } + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/BankCode.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/BankCode.java new file mode 100644 index 0000000..d59bc07 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/BankCode.java @@ -0,0 +1,209 @@ +package com.enongm.dianji.payment.wechat.enumeration; + + +/** + * The enum Bank code. + */ +public enum BankCode { + /** + * 工商银行 + */ + BK_1002("1002","工商银行"), + /** + * 农业银行 + */ + BK_1005("1005","农业银行"), + /** + * 建设银行 + */ + BK_1003("1003","建设银行"), + /** + * 中国银行 + */ + BK_1026("1026","中国银行"), + /** + * 交通银行 + */ + BK_1020("1020","交通银行"), + /** + * 招商银行 + */ + BK_1001("1001","招商银行"), + /** + * 邮储银行 + */ + BK_1066("1066","邮储银行"), + /** + * 民生银行 + */ + BK_1006("1006","民生银行"), + /** + * 平安银行 + */ + BK_1010("1010","平安银行"), + /** + * 中信银行 + */ + BK_1021("1021","中信银行"), + /** + * 浦发银行 + */ + BK_1004("1004","浦发银行"), + /** + * 兴业银行 + */ + BK_1009("1009","兴业银行"), + /** + * 光大银行 + */ + BK_1022("1022","光大银行"), + /** + * 广发银行 + */ + BK_1027("1027","广发银行"), + /** + * 华夏银行 + */ + BK_1025("1025","华夏银行"), + /** + * 宁波银行 + */ + BK_1056("1056","宁波银行"), + /** + * 北京银行 + */ + BK_4836("4836","北京银行"), + /** + * 上海银行 + */ + BK_1024("1024","上海银行"), + /** + * 南京银行 + */ + BK_1054("1054","南京银行"), + /** + * 长子县融汇村镇银行 + */ + BK_4755("4755","长子县融汇村镇银行"), + /** + * 长沙银行 + */ + BK_4216("4216","长沙银行"), + /** + * 浙江泰隆商业银行 + */ + BK_4051("4051","浙江泰隆商业银行"), + /** + * 中原银行 + */ + BK_4753("4753","中原银行"), + /** + * 企业银行(中国) + */ + BK_4761("4761","企业银行(中国)"), + /** + * 顺德农商银行 + */ + BK_4036("4036","顺德农商银行"), + /** + * 衡水银行 + */ + BK_4752("4752","衡水银行"), + /** + * 长治银行 + */ + BK_4756("4756","长治银行"), + /** + * 大同银行 + */ + BK_4767("4767","大同银行"), + /** + * 河南省农村信用社 + */ + BK_4115("4115","河南省农村信用社"), + /** + * 宁夏黄河农村商业银行 + */ + BK_4150("4150","宁夏黄河农村商业银行"), + /** + * 山西省农村信用社 + */ + BK_4156("4156","山西省农村信用社"), + /** + * 安徽省农村信用社 + */ + BK_4166("4166","安徽省农村信用社"), + /** + * 甘肃省农村信用社 + */ + BK_4157("4157","甘肃省农村信用社"), + /** + * 天津农村商业银行 + */ + BK_4153("4153","天津农村商业银行"), + /** + * 广西壮族自治区农村信用社 + */ + BK_4113("4113","广西壮族自治区农村信用社"), + /** + * 陕西省农村信用社 + */ + BK_4108("4108","陕西省农村信用社"), + /** + * 深圳农村商业银行 + */ + BK_4076("4076","深圳农村商业银行"), + /** + * 宁波鄞州农村商业银行 + */ + BK_4052("4052","宁波鄞州农村商业银行"), + /** + * 浙江省农村信用社联合社 + */ + BK_4764("4764","浙江省农村信用社联合社"), + /** + * 江苏省农村信用社联合社 + */ + BK_4217("4217","江苏省农村信用社联合社"), + /** + * 江苏紫金农村商业银行股份有限公司 + */ + BK_4072("4072","江苏紫金农村商业银行"), + /** + * 北京中关村银行股份有限公司 + */ + BK_4769("4769","北京中关村银行"), + /** + * 星展银行(中国)有限公司 + */ + BK_4778("4778","星展银行(中国)"), + /** + * 枣庄银行股份有限公司 + */ + BK_4766("4766","枣庄银行"), + /** + * 海口联合农村商业银行股份有限公司 + */ + BK_4758("4758","海口联合农村商业银行"), + /** + * 南洋商业银行(中国)有限公司 + */ + BK_4763("4763","南洋商业银行(中国)"); + + + private final String code; + private final String bankName; + + BankCode(String code, String bankName) { + this.code = code; + this.bankName = bankName; + } + + public String code() { + return this.code; + } + + public String bankName() { + return this.bankName; + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V2PayType.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V2PayType.java new file mode 100644 index 0000000..affb538 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V2PayType.java @@ -0,0 +1,83 @@ +package com.enongm.dianji.payment.wechat.enumeration; + +/** + * @author Dax + * @since 10:33 + */ +public enum V2PayType { + + /** + * 企业向微信用户个人付款,目前支持向指定微信用户的openid付款。 + */ + PAY_TO_WECHAT("POST", "%s%s/mmpaymkttransfers/promotion/transfers"); + + private final String method; + private final String pattern; + + V2PayType(String method, String pattern) { + this.method = method; + this.pattern = pattern; + } + + /** + * Method string. + * + * @return the string + */ + public String method() { + return this.method; + } + + + /** + * 默认支付URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String defaultUri(WeChatServer weChatServer) { + return uri(weChatServer, false, false); + } + + /** + * 默认支付沙盒URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String defaultSandboxUri(WeChatServer weChatServer) { + return uri(weChatServer, true, false); + } + + /** + * 合作商支付URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String partnerUri(WeChatServer weChatServer) { + return uri(weChatServer, false, true); + } + + /** + * 合作商支付沙盒URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String partnerSandboxUri(WeChatServer weChatServer) { + return uri(weChatServer, true, true); + } + + /** + * @param isSandbox 是否是沙盒测试 + * @param isPartner 是否是合作商 + * @return uri + */ + private String uri(WeChatServer weChatServer, boolean isSandbox, boolean isPartner) { + + final String sandboxKey = isSandbox ? "/sandboxnew" : ""; + final String partnerKey = isPartner ? "/partner" : ""; + return String.format(this.pattern, weChatServer.domain(), sandboxKey, partnerKey); + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V3PayType.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V3PayType.java new file mode 100644 index 0000000..5013de5 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/V3PayType.java @@ -0,0 +1,108 @@ +package com.enongm.dianji.payment.wechat.enumeration; + +/** + * The enum Pay type. + * + * @author Dax + * @since 14 :25 + */ +public enum V3PayType { + /** + * 获取证书. + */ + CERT("GET", "%s/v3/certificates"), + + /** + * 微信公众号支付或者小程序支付 + */ + JSAPI("POST", "%s%s/v3/pay%s/transactions/jsapi"), + + /** + * 微信扫码支付 + */ + NATIVE("POST", "%s%s/v3/pay%s/transactions/native"), + + /** + * 微信APP支付 + */ + APP("POST", "%s%s/v3/pay%s/transactions/app"), + + /** + * H5支付 + */ + MWEB("POST", "%s%s/v3/pay%s/transactions/h5"); + + private final String pattern; + private final String method; + + V3PayType(String method, String pattern) { + this.method = method; + this.pattern = pattern; + } + + /** + * Method string. + * + * @return the string + */ + public String method() { + return this.method; + } + + + /** + * 默认支付URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String defaultUri(WeChatServer weChatServer) { + return uri(weChatServer, false, false); + } + + /** + * 默认支付沙盒URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String defaultSandboxUri(WeChatServer weChatServer) { + return uri(weChatServer, true, false); + } + + /** + * 合作商支付URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String partnerUri(WeChatServer weChatServer) { + return uri(weChatServer, false, true); + } + + /** + * 合作商支付沙盒URI. + * + * @param weChatServer the we chat server + * @return the string + */ + public String partnerSandboxUri(WeChatServer weChatServer) { + return uri(weChatServer, true, true); + } + + /** + * @param isSandbox 是否是沙盒测试 + * @param isPartner 是否是合作商 + * @return uri + */ + private String uri(WeChatServer weChatServer, boolean isSandbox, boolean isPartner) { + + if (this.equals(V3PayType.CERT)) { + return String.format(this.pattern, WeChatServer.CHINA.domain()); + } + + final String sandboxKey = isSandbox ? "/sandboxnew" : ""; + final String partnerKey = isPartner ? "/partner" : ""; + return String.format(this.pattern, weChatServer.domain(), sandboxKey, partnerKey); + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/WeChatServer.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/WeChatServer.java new file mode 100644 index 0000000..1138600 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/enumeration/WeChatServer.java @@ -0,0 +1,56 @@ +package com.enongm.dianji.payment.wechat.enumeration; + + +/** + * The enum We chat server domain. + */ +public enum WeChatServer { + /** + * 中国 + */ + CHINA("https://api.mch.weixin.qq.com"), + /** + * 中国国内(备用域名) + */ + CHINA2("https://api2.mch.weixin.qq.com"), + /** + * 香港 + */ + HK("https://apihk.mch.weixin.qq.com"), + /** + * 美国 + */ + US("https://apius.mch.weixin.qq.com"), + /** + * 获取公钥 + */ + FRAUD("https://fraud.mch.weixin.qq.com"), + /** + * 活动 + */ + ACTION("https://action.weixin.qq.com"); + + + /** + * 域名 + */ + private final String domain; + + WeChatServer(String domain) { + this.domain = domain; + } + + /** + * Gets type. + * + * @return the type + */ + public String domain() { + return domain; + } + + @Override + public String toString() { + return domain; + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatPayV2Service.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatPayV2Service.java new file mode 100644 index 0000000..be2726d --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatPayV2Service.java @@ -0,0 +1,26 @@ +package com.enongm.dianji.payment.wechat.v2; + + +import com.enongm.dianji.payment.autoconfigure.WechatPayProperties; +import com.enongm.dianji.payment.wechat.v2.model.BaseModel; + +/** + * @author Dax + * @since 15:15 + */ +public class WechatPayV2Service { + + private final WechatPayProperties wechatPayProperties; + + public WechatPayV2Service(WechatPayProperties wechatPayProperties) { + this.wechatPayProperties = wechatPayProperties; + } + + + public BaseModel model(M model) { + WechatPayProperties.V3 v3 = wechatPayProperties.getV3(); + return model.appId(v3.getAppId()) + .mchId(v3.getMchId()); + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatResponseBody.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatResponseBody.java new file mode 100644 index 0000000..7c2b601 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/WechatResponseBody.java @@ -0,0 +1,24 @@ +package com.enongm.dianji.payment.wechat.v2; + +import lombok.Data; + +/** + * @author Dax + * @since 15:28 + */ +@Data +public class WechatResponseBody { + + private String returnCode; + private String returnMsg; + private String mchAppid; + private String mchid; + private String deviceInfo; + private String nonceStr; + private String resultCode; + private String errCode; + private String errCodeDes; + private String partnerTradeNo; + private String paymentNo; + private String paymentTime; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/BaseModel.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/BaseModel.java new file mode 100644 index 0000000..fce9b48 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/BaseModel.java @@ -0,0 +1,152 @@ +package com.enongm.dianji.payment.wechat.v2.model; + + +import com.enongm.dianji.payment.wechat.enumeration.V2PayType; +import com.enongm.dianji.payment.wechat.enumeration.WeChatServer; +import com.enongm.dianji.payment.wechat.v2.WechatResponseBody; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +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; +import lombok.Getter; +import lombok.SneakyThrows; + +import okhttp3.*; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.MD5Digest; +import org.bouncycastle.util.encoders.Hex; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.IdGenerator; + + +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * The type Base model. + * + * @author Dax + * @since 16 :03 + */ +@Getter +public class BaseModel { + private static final XmlMapper MAPPER = new XmlMapper(); + + static { + // 忽略null + MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL) + // 属性使用 驼峰首字母小写 + .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + } + + + private static final OkHttpClient CLIENT = new OkHttpClient(); + private static final IdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator(); + private final String nonceStr = ID_GENERATOR.generateId() + .toString() + .replaceAll("-", ""); + private String mchAppid; + private String mchid; + private String sign; + @JsonIgnore + private String key; + @JsonIgnore + private V2PayType payType; + + + public BaseModel appId(String appId) { + this.mchAppid = appId; + return this; + } + + public BaseModel mchId(String mchId) { + this.mchid = mchId; + return this; + } + + public BaseModel payType(V2PayType payType) { + this.payType = payType; + return this; + } + + /** + * Xml string. + * + * @return the string + */ + @SneakyThrows + public String xml() { + + + + this.key = "Djkjchina19491001"; + String link = link(this, key); + + this.sign = this.bouncyCastleMD5(link); + + + return MAPPER.writer() + .withRootName("xml") + .writeValueAsString(this); + } + + /** + * md5摘要. + * + * @param src the src + * @return the string + */ + private String bouncyCastleMD5(String src) { + Digest digest = new MD5Digest(); + byte[] bytes = src.getBytes(StandardCharsets.UTF_8); + digest.update(bytes, 0, bytes.length); + byte[] md5Bytes = new byte[digest.getDigestSize()]; + digest.doFinal(md5Bytes, 0); + return Hex.toHexString(md5Bytes).toUpperCase(); + } + + /** + * 按照格式拼接参数以生成签名 + * + * @param the type parameter + * @param t the t + * @return the map + */ + @SneakyThrows + private String link(T t, String apiKey) { + + return new ObjectMapper() + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) + .writer() + .writeValueAsString(t) + .replaceAll("\":\"", "=") + .replaceAll("\",\"", "&") + .replaceAll("\\{\"", "") + .replaceAll("\"}", "") + .concat("&key=").concat(apiKey); + } + + + @SneakyThrows + public WechatResponseBody request() { + + Request request = new Request.Builder() + .method(payType.method(), RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), this.xml())) + .url(payType.defaultUri(WeChatServer.CHINA)) + .build(); + System.out.println("request.toString() = " + request.toString()); + Response response = CLIENT.newCall(request).execute(); + + ResponseBody body = response.body(); + if (Objects.nonNull(body)) { + return MAPPER.readValue(body.string(), WechatResponseBody.class); + } + throw new IllegalStateException("wechat pay response body is empty"); + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/PayToWechatModel.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/PayToWechatModel.java new file mode 100644 index 0000000..c1641ff --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v2/model/PayToWechatModel.java @@ -0,0 +1,22 @@ +package com.enongm.dianji.payment.wechat.v2.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author Dax + * @since 15:48 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PayToWechatModel extends BaseModel { + private String deviceInfo; + private String partnerTradeNo; + private String openid; + private String checkName = "NO_CHECK"; + private String reUserName; + private String amount; + private String desc; + private String spbillCreateIp; + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/SignatureProvider.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/SignatureProvider.java new file mode 100644 index 0000000..ef16b15 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/SignatureProvider.java @@ -0,0 +1,248 @@ +package com.enongm.dianji.payment.wechat.v3; + + +import com.enongm.dianji.payment.wechat.enumeration.V3PayType; +import com.enongm.dianji.payment.wechat.enumeration.WeChatServer; +import com.enongm.dianji.payment.wechat.v3.model.WechatMetaBean; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.SneakyThrows; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.Base64Utils; +import org.springframework.util.IdGenerator; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; +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.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 签名 加签 验签 + *

+ * 我方请求微信服务器时,需要根据我方的API证书对参数进行加签;微信服务器会根据我方签名验签以确定请求来自我方服务器; + *

+ * 然后微信服务器响应我方请求并在响应报文中使用【微信平台证书】加签 我方需要根据规则验签是否响应来自微信支付服务器 + *

+ * 其中【微信平台证书】定期会进行更新,不受我方管控,我方需要适当的时候获取最新的证书列表。 + * + * @author Dax + * @since 16 :48 + */ +public class SignatureProvider { + /** + * The constant APPLICATION_JSON. + */ + public static final String APPLICATION_JSON = "application/json"; + private static final IdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator(); + private static final String SCHEMA = "WECHATPAY2-SHA256-RSA2048 "; + /** + * The constant TOKEN_PATTERN. + */ + public static final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""; + private final WechatMetaBean wechatMetaBean; + /** + * 微信平台证书容器 key = 序列号 value = 证书对象 + */ + private static final Map CERTIFICATE_MAP = new ConcurrentHashMap<>(); + + /** + * Instantiates a new Signature provider. + * + * @param wechatMetaBean the wechat meta bean + */ + public SignatureProvider(WechatMetaBean wechatMetaBean) { + this.wechatMetaBean = wechatMetaBean; + } + + + /** + * 我方请求时加签,使用API证书. + * + * @param method the method + * @param canonicalUrl the canonical url + * @param body the body + * @return the string + */ + @SneakyThrows + public String requestSign(String method, String canonicalUrl, String body) { + Signature signer = Signature.getInstance("SHA256withRSA"); + signer.initSign(wechatMetaBean.getKeyPair().getPrivate()); + + long timestamp = System.currentTimeMillis() / 1000; + String nonceStr = ID_GENERATOR.generateId() + .toString() + .replaceAll("-", ""); + final String signatureStr = createSign(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body); + signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); + String encode = Base64Utils.encodeToString(signer.sign()); + + // 序列号 + String serialNo = wechatMetaBean.getSerialNumber(); + // 生成token + String token = String.format(TOKEN_PATTERN, + wechatMetaBean.getWechatPayProperties().getV3().getMchId(), + nonceStr, timestamp, serialNo, encode); + return SCHEMA.concat(token); + } + + /** + * 我方对响应验签,和应答签名做比较,使用微信平台证书. + * + * @param wechatpaySerial response.headers['Wechatpay-Serial'] 当前使用的微信平台证书序列号 + * @param wechatpaySignature response.headers['Wechatpay-Signature'] 微信平台签名 + * @param wechatpayTimestamp response.headers['Wechatpay-Timestamp'] 微信服务器的时间戳 + * @param wechatpayNonce response.headers['Wechatpay-Nonce'] 微信服务器提供的随机串 + * @param body response.body 微信服务器的响应体 + * @return the boolean + */ + @SneakyThrows + public boolean responseSignVerify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) { + + if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) { + refreshCertificate(); + } + Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial); + + final String signatureStr = createSign(wechatpayTimestamp, wechatpayNonce, body); + Signature signer = Signature.getInstance("SHA256withRSA"); + signer.initVerify(certificate); + signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); + + return signer.verify(Base64Utils.decodeFromString(wechatpaySignature)); + } + + + /** + * 当我方服务器不存在平台证书或者证书同当前响应报文中的证书序列号不一致时应当刷新 调用/v3/certificates + */ + @SneakyThrows + private synchronized void refreshCertificate() { + String url = V3PayType.CERT.defaultUri(WeChatServer.CHINA); + + HttpUrl httpUrl = HttpUrl.get(url); + + String canonicalUrl = httpUrl.encodedPath(); + String encodedQuery = httpUrl.encodedQuery(); + if (encodedQuery != null) { + canonicalUrl += "?" + encodedQuery; + } + // 签名 + String authorization = requestSign(V3PayType.CERT.method(), canonicalUrl, ""); + + Request request = new Request.Builder() + .get() + .url(httpUrl) + .addHeader("Accept", APPLICATION_JSON) + .addHeader("Authorization", authorization) + .addHeader("Content-Type", APPLICATION_JSON) + .addHeader("User-Agent", "pay-service") + .build(); + + + Response response = new OkHttpClient().newCall(request).execute(); + + if (Objects.isNull(response.body())) { + throw new RuntimeException("cant obtain the response body"); + } + + String body = response.body().string(); + ObjectMapper MAPPER = new ObjectMapper(); + ObjectNode bodyObjectNode = MAPPER.readValue(body, ObjectNode.class); + ArrayNode certificates = bodyObjectNode.withArray("data"); + + if (certificates.isArray() && certificates.size() > 0) { + final CertificateFactory cf = CertificateFactory.getInstance("X509"); + certificates.forEach(objectNode -> { + JsonNode encryptCertificate = objectNode.get("encrypt_certificate"); + String associatedData = encryptCertificate.get("associated_data").asText(); + String nonce = encryptCertificate.get("nonce").asText(); + String ciphertext = encryptCertificate.get("ciphertext").asText(); + String publicKey = decryptResponseBody(associatedData, nonce, ciphertext); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8)); + Certificate certificate = null; + try { + certificate = cf.generateCertificate(inputStream); + } catch (CertificateException e) { + e.printStackTrace(); + } + String responseSerialNo = objectNode.get("serial_no").asText(); + + CERTIFICATE_MAP.clear(); + CERTIFICATE_MAP.put(responseSerialNo, certificate); + }); + + } + + } + + + /** + * 解密响应体. + * + * @param associatedData the associated data + * @param nonce the nonce + * @param ciphertext the ciphertext + * @return the string + */ + public String decryptResponseBody(String associatedData, String nonce, String ciphertext) { + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + String apiV3Key = wechatMetaBean.getWechatPayProperties().getV3().getAppV3Secret(); + SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES"); + GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8)); + + cipher.init(Cipher.DECRYPT_MODE, key, spec); + cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8)); + + + byte[] bytes; + try { + bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext)); + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException(e); + } + + + return new String(bytes, StandardCharsets.UTF_8); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException(e); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException(e); + } + } + + public WechatMetaBean getWechatMetaBean() { + return wechatMetaBean; + } + + /** + * 请求时设置签名 组件 + * + * @param components the components + * @return string string + */ + private String createSign(String... components) { + return Arrays.stream(components) + .collect(Collectors.joining("\n", "", "\n")); + } + + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayRequest.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayRequest.java new file mode 100644 index 0000000..2d7e3e9 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayRequest.java @@ -0,0 +1,118 @@ +package com.enongm.dianji.payment.wechat.v3; + + +import com.enongm.dianji.payment.wechat.enumeration.V3PayType; +import com.enongm.dianji.payment.wechat.enumeration.WeChatServer; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * The type Wechat pay request. + * + * @author Dax + * @since 9 :18 + */ +@Getter +public class WechatPayRequest { + private V3PayType v3PayType; + private WeChatServer weChatServer; + private String body; + private final Map headers = new HashMap<>(); + private Consumer responseBodyConsumer; + + + /** + * Pay type wechat pay request. + * + * @param v3PayType the pay type + * @return the wechat pay request + */ + public WechatPayRequest v3PayType(V3PayType v3PayType) { + this.v3PayType = v3PayType; + return this; + } + + /** + * We chat server. + * + * @param weChatServer the we chat server + */ + public void weChatServer(WeChatServer weChatServer) { + this.weChatServer = weChatServer; + } + + + /** + * Body wechat pay request. + * + * @param body the body + * @return the wechat pay request + */ + public WechatPayRequest body(String body) { + this.body = body; + return this; + } + + /** + * Add header wechat pay request. + * + * @param name the name + * @param value the value + * @return the wechat pay request + */ + public WechatPayRequest addHeader(String name, String value) { + headers.put(name, value); + return this; + } + + /** + * Response consumer wechat pay request. + * + * @param responseConsumer the response consumer + * @return the wechat pay request + */ + public WechatPayRequest responseConsumer(Consumer responseConsumer) { + this.responseBodyConsumer = responseConsumer; + return this; + } + + /** + * Url string. + * + * @return the string + */ + public String url() { + return this.v3PayType.defaultUri(this.weChatServer); + } + + /** + * Default sandbox uri string. + * + * @return the string + */ + public String defaultSandboxUri() { + return this.v3PayType.defaultSandboxUri(this.weChatServer); + } + + /** + * Partner uri string. + * + * @return the string + */ + public String partnerUri() { + return this.v3PayType.partnerUri(this.weChatServer); + } + + /** + * Partner sandbox uri string. + * + * @return the string + */ + public String partnerSandboxUri() { + return this.v3PayType.partnerSandboxUri(this.weChatServer); + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/BodyMergeFilter.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/BodyMergeFilter.java new file mode 100644 index 0000000..28f612d --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/BodyMergeFilter.java @@ -0,0 +1,70 @@ +package com.enongm.dianji.payment.wechat.v3.filter; + + +import com.enongm.dianji.payment.autoconfigure.WechatPayProperties; +import com.enongm.dianji.payment.wechat.PayFilter; +import com.enongm.dianji.payment.wechat.PayFilterChain; +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; +import com.enongm.dianji.payment.wechat.v3.model.WechatMetaBean; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.SneakyThrows; +import org.springframework.util.StringUtils; + +/** + * The type Body merge filter. + * 2 + * + * @author Dax + * @since 11 :13 + */ +public class BodyMergeFilter implements PayFilter { + private static final ObjectMapper MAPPER = new ObjectMapper(); + private final WechatMetaBean wechatMetaBean; + + static { + MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + + public BodyMergeFilter(WechatMetaBean wechatMetaBean) { + this.wechatMetaBean = wechatMetaBean; + } + + @Override + public void doFilter(WechatPayRequest request, PayFilterChain chain) { + mergeAppIdAndMchIdIntoBody(request); + chain.doChain(request); + } + + + /** + * 将直连商户申请的公众号或移动应用appid 、商户号mchid 放入请求体 + */ + @SneakyThrows + private void mergeAppIdAndMchIdIntoBody(WechatPayRequest request) { + + String requestBody = request.getBody(); + if (StringUtils.hasText(requestBody)) { + WechatPayProperties wechatPayProperties = wechatMetaBean.getWechatPayProperties(); + String appId = wechatPayProperties.getV3().getAppId(); + String mchId = wechatPayProperties.getV3().getMchId(); + + ObjectNode jsonNodes = MAPPER.readValue(requestBody, ObjectNode.class); + JsonNode notify = jsonNodes.get("notify_url"); + String notifyUrl = wechatPayProperties.getV3().getDomain() + .concat(notify.asText()); + // ^https?://([^\\s/?#\\[\\]\\@]+\\@)?([^\\s/?#\\@:]+)(?::\\d{2,5})?([^\\s?#\\[\\]]*)$ + jsonNodes.put("notify_url", notifyUrl); + jsonNodes.put("appid", appId); + jsonNodes.put("mchid", mchId); + + request.body(MAPPER.writeValueAsString(jsonNodes)); + } + } + + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HeaderFilter.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HeaderFilter.java new file mode 100644 index 0000000..e208c8d --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HeaderFilter.java @@ -0,0 +1,48 @@ +package com.enongm.dianji.payment.wechat.v3.filter; + + +import com.enongm.dianji.payment.wechat.PayFilter; +import com.enongm.dianji.payment.wechat.PayFilterChain; +import com.enongm.dianji.payment.wechat.v3.SignatureProvider; +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; +import okhttp3.HttpUrl; + +/** + * 微信支付 给请求添加必要的请求头. + * 3 + * + * @author Dax + * @since 15 :12 + */ +public class HeaderFilter implements PayFilter { + private static final String APPLICATION_JSON = "application/json"; + private final SignatureProvider signatureProvider; + + public HeaderFilter(SignatureProvider signatureProvider) { + this.signatureProvider = signatureProvider; + } + + @Override + public void doFilter(WechatPayRequest request, PayFilterChain chain) { + + // 签名 + HttpUrl url = HttpUrl.get(request.url()); + + String canonicalUrl = url.encodedPath(); + String encodedQuery = url.encodedQuery(); + if (encodedQuery != null) { + canonicalUrl += "?" + encodedQuery; + } + String method = request.getV3PayType().method(); + String body = "GET".equals(method) ? "" : request.getBody(); + + String authorization = signatureProvider.requestSign(method, canonicalUrl, body); + request.addHeader("Accept", APPLICATION_JSON) + .addHeader("Authorization", authorization) + .addHeader("Content-Type", APPLICATION_JSON) + .addHeader("User-Agent", "pay-service"); + chain.doChain(request); + } + + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HttpRequestFilter.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HttpRequestFilter.java new file mode 100644 index 0000000..07f9f04 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/HttpRequestFilter.java @@ -0,0 +1,91 @@ +package com.enongm.dianji.payment.wechat.v3.filter; + + +import com.enongm.dianji.payment.wechat.PayFilter; +import com.enongm.dianji.payment.wechat.PayFilterChain; +import com.enongm.dianji.payment.wechat.v3.SignatureProvider; +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; + +import java.io.IOException; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * 微信支付用来请求微信支付服务器 + * + * @author Dax + * @since 10:42 + */ +@Slf4j +public class HttpRequestFilter implements PayFilter { + private final OkHttpClient client; + private final SignatureProvider signatureProvider; + + + public HttpRequestFilter(SignatureProvider signatureProvider) { + this.signatureProvider = signatureProvider; + this.client = new OkHttpClient(); + + } + + public HttpRequestFilter(SignatureProvider signatureProvider, OkHttpClient client) { + this.signatureProvider = signatureProvider; + this.client = client; + } + + + @Override + public void doFilter(WechatPayRequest request, PayFilterChain chain) { + + Request.Builder builder = new Request.Builder() + .url(request.url()) + .headers(Headers.of(request.getHeaders())); + + if (!request.getV3PayType().method().equals("GET")) { + RequestBody requestBody = + RequestBody.create(MediaType.parse("application/json"), request.getBody()); + builder.method(request.getV3PayType().method(), requestBody); + } + + Request httpRequest = builder.build(); + + try { + Response response = client.newCall(httpRequest).execute(); + + ResponseBody responseBody = response.body(); + + if (Objects.isNull(responseBody)) { + throw new IllegalStateException("cant obtain response body"); + } + // 微信请求回调id + String RequestId = response.header("Request-ID"); + // 微信平台证书序列号 用来取微信平台证书 + String wechatpaySerial = response.header("Wechatpay-Serial"); + //获取应答签名 + String wechatpaySignature = response.header("Wechatpay-Signature"); + String body = responseBody.string(); + + //构造验签名串 + String wechatpayTimestamp = response.header("Wechatpay-Timestamp"); + String wechatpayNonce = response.header("Wechatpay-Nonce"); + + // 验证微信服务器签名 + if (signatureProvider.responseSignVerify(wechatpaySerial, wechatpaySignature, wechatpayTimestamp, wechatpayNonce, body)) { + ; + Consumer responseConsumer = request.getResponseBodyConsumer(); + if (Objects.nonNull(responseConsumer)) { + // 验证通过消费 + responseConsumer.accept(body); + } + } else { + throw new IllegalArgumentException("签名验证失败"); + } + + + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/WechatServerFilter.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/WechatServerFilter.java new file mode 100644 index 0000000..efacc9a --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/filter/WechatServerFilter.java @@ -0,0 +1,25 @@ +package com.enongm.dianji.payment.wechat.v3.filter; + + +import com.enongm.dianji.payment.wechat.PayFilter; +import com.enongm.dianji.payment.wechat.PayFilterChain; +import com.enongm.dianji.payment.wechat.enumeration.V3PayType; +import com.enongm.dianji.payment.wechat.enumeration.WeChatServer; +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; + +/** + * 根据{@link V3PayType} 组装URL + * 1 + * + * @author Dax + * @since 9:43 + */ +public class WechatServerFilter implements PayFilter { + + @Override + public void doFilter(WechatPayRequest request, PayFilterChain chain) { + request.weChatServer(WeChatServer.CHINA); + chain.doChain(request); + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Amount.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Amount.java new file mode 100644 index 0000000..143f5df --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Amount.java @@ -0,0 +1,14 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import lombok.Data; + +/** + * 支付金额 货币单位【分】默认使用人民币标识CNY + * @author Dax + * @since 16:45 + */ +@Data +public class Amount { + private int total; + private String currency ="CNY"; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/AppPayModel.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/AppPayModel.java new file mode 100644 index 0000000..4a44fa4 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/AppPayModel.java @@ -0,0 +1,17 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author Dax + * @since 17:10 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AppPayModel extends BaseModel { + private Amount amount; + private Detail detail; + private SceneInfo sceneInfo; + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/BaseModel.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/BaseModel.java new file mode 100644 index 0000000..97e8640 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/BaseModel.java @@ -0,0 +1,54 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import lombok.Data; +import lombok.SneakyThrows; + +/** + * @author Dax + * @since 16:34 + */ +@Data +public class BaseModel { + + /** + * 商品描述 + * Image形象店-深圳腾大-QQ公仔 + */ + private String description; + /** + * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。 + * 示例值:1217752501201407033233368018 + */ + private String outTradeNo; + /** + * 订单失效时间 YYYY-MM-DDTHH:mm:ss+TIMEZONE + */ + private String timeExpire; + /** + * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 + */ + private String attach; + /** + * 通知URL必须为直接可访问的URL,不允许携带查询串。 + */ + private String notifyUrl; + /** + * 订单优惠标记 + */ + private String goodsTag; + /** + * 支付金额 + */ + private Amount amount; + + @SneakyThrows + public String jsonBody() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return objectMapper.writeValueAsString(this); + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Detail.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Detail.java new file mode 100644 index 0000000..a39b43c --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Detail.java @@ -0,0 +1,25 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import lombok.Data; + +import java.util.List; + +/** + * @author Dax + * @since 17:01 + */ +@Data +public class Detail { + /** + * 订单原价 + */ + private int costPrice; + /** + * 商品小票ID + */ + private String invoiceId; + /** + * 单品列表 + */ + private List goodsDetail; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Goods.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Goods.java new file mode 100644 index 0000000..b48d3f4 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Goods.java @@ -0,0 +1,28 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +/** + * @author Dax + * @since 17:02 + */ +public class Goods { + /** + * 商户侧商品编码 + */ + private String merchantGoodsId; + /** + * 微信侧商品编码 + */ + private String wechatpayGoodsId; + /** + * 商品名称 + */ + private String goodsName; + /** + * 商品数量 + */ + private int quantity; + /** + * 商品单价 + */ + private int unitPrice; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/H5Info.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/H5Info.java new file mode 100644 index 0000000..fdfa3af --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/H5Info.java @@ -0,0 +1,31 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import lombok.Data; + +/** + * @author Dax + * @since 17:03 + */ +@Data +public class H5Info { + /** + * 场景类型 + */ + private String type; + /** + * 应用名称 + */ + private String appName; + /** + * 网站URL + */ + private String appUrl; + /** + * IOS 平台 BundleID + */ + private String bundleId; + /** + * Android 平台 PackageName + */ + private String packageName; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Payer.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Payer.java new file mode 100644 index 0000000..16b0c28 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/Payer.java @@ -0,0 +1,23 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import lombok.Data; + +/** + * @author Dax + * @since 17:04 + */ +@Data +public class Payer { + /** + * 用户标识 + */ + private String openid; + /** + * 用户服务标识 + */ + private String spOpenid; + /** + * 用户子标识 + */ + private String subOpenid; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SceneInfo.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SceneInfo.java new file mode 100644 index 0000000..89eca91 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SceneInfo.java @@ -0,0 +1,27 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import lombok.Data; + +/** + * @author Dax + * @since 17:05 + */ +@Data +public class SceneInfo { + /** + * 用户终端IP + */ + private String payerClientIp; + /** + * 商户端设备号 + */ + private String deviceId; + /** + * 商户门店信息 + */ + private StoreInfo storeInfo; + /** + * H5 场景信息 + */ + private H5Info h5Info; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SettleInfo.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SettleInfo.java new file mode 100644 index 0000000..6daa722 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/SettleInfo.java @@ -0,0 +1,16 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +/** + * @author Dax + * @since 17:05 + */ +public class SettleInfo { + /** + * 是否指定分账 + */ + private boolean profitSharing; + /** + * 补差金额 + */ + private int subsidyAmount; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/StoreInfo.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/StoreInfo.java new file mode 100644 index 0000000..d262639 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/StoreInfo.java @@ -0,0 +1,27 @@ +package com.enongm.dianji.payment.wechat.v3.model; + +import lombok.Data; + +/** + * @author Dax + * @since 17:06 + */ +@Data +public class StoreInfo { + /** + * 门店编号 + */ + private String id; + /** + * 门店名称 + */ + private String name; + /** + * 地区编码 + */ + private String areaCode; + /** + * 详细地址 + */ + private String address; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/WechatMetaBean.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/WechatMetaBean.java new file mode 100644 index 0000000..8b84d58 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/model/WechatMetaBean.java @@ -0,0 +1,27 @@ +package com.enongm.dianji.payment.wechat.v3.model; + + +import com.enongm.dianji.payment.autoconfigure.WechatPayProperties; +import lombok.Data; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +import java.security.KeyPair; + +/** + * @author Dax + * @since 15:50 + */ +@Data +public class WechatMetaBean implements InitializingBean { + private KeyPair keyPair; + private String serialNumber; + private WechatPayProperties wechatPayProperties; + + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(wechatPayProperties, "wechatPayProperties must not be null"); + Assert.notNull(keyPair, "wechat pay p12 certificate has not loaded"); + Assert.hasText(serialNumber, "wechat pay p12 certificate SerialNumber must not null"); + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/service/WechatPayV3Api.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/service/WechatPayV3Api.java new file mode 100644 index 0000000..d76fa03 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/service/WechatPayV3Api.java @@ -0,0 +1,31 @@ +package com.enongm.dianji.payment.wechat.v3.service; + + +import com.enongm.dianji.payment.wechat.enumeration.V3PayType; +import com.enongm.dianji.payment.wechat.v3.WechatPayRequest; +import com.enongm.dianji.payment.wechat.v3.model.AppPayModel; + +/** + * The type Wechat pay v 3 api. + * + * @author Dax + * @since 17 :47 + */ +public class WechatPayV3Api { + + /** + * APP 支付. + * + * @param appPayModel the app pay model + * @return the wechat pay request + */ + public static WechatPayRequest appPay(AppPayModel appPayModel) { + + return new WechatPayRequest().responseConsumer(System.out::println) + // 如果需要支持容灾 需要对server 进行一些处理 + .v3PayType(V3PayType.APP) + .body(appPayModel.jsonBody()); + } + + +} diff --git a/payment-spring-boot-starter/pom.xml b/payment-spring-boot-starter/pom.xml index e4ffcb1..7169396 100644 --- a/payment-spring-boot-starter/pom.xml +++ b/payment-spring-boot-starter/pom.xml @@ -21,6 +21,13 @@ + + 1.8 + 1.8 + UTF-8 + UTF-8 + 1.8 + diff --git a/pom.xml b/pom.xml index 3c9f566..5941e7f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,9 @@ 4.10.167.ALL 1.0.0.RELEASE 1.18.12 + 2.9.10 + 1.66 + 3.14.9 @@ -66,6 +69,21 @@ lombok ${lombok.verison} + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + + + com.squareup.okhttp3 + okhttp + ${okhttp3.version} + + + org.bouncycastle + bcprov-jdk15to18 + ${bcprov.version} + com.enongm.dianji payment-spring-boot-autoconfigure