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