From 0e2e91c0f48127254c4ae6af20b413933166321c Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Tue, 28 Jun 2022 17:09:22 +0800
Subject: [PATCH 1/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81=E5=99=A8?=
=?UTF-8?q?=E5=92=8CHttpClient=20API=E5=AD=97=E5=85=B8=E5=92=8C=E5=B7=A5?=
=?UTF-8?q?=E5=85=B7=E7=B1=BB=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
payment-demo/pom.xml | 5 +
.../paymentdemo/config/WxPayConfig.java | 46 ++++-
.../controller/ProductController.java | 1 +
.../hongyi/paymentdemo/enums/OrderStatus.java | 49 +++++
.../com/hongyi/paymentdemo/enums/PayType.java | 24 +++
.../paymentdemo/enums/wxpay/WxApiType.java | 55 ++++++
.../paymentdemo/enums/wxpay/WxNotifyType.java | 30 ++++
.../enums/wxpay/WxRefundStatus.java | 34 ++++
.../paymentdemo/enums/wxpay/WxTradeState.java | 34 ++++
.../paymentdemo/util/HttpClientUtils.java | 168 ++++++++++++++++++
.../hongyi/paymentdemo/util/HttpUtils.java | 39 ++++
.../hongyi/paymentdemo/util/OrderNoUtils.java | 46 +++++
.../util/WechatPay2ValidatorForRequest.java | 114 ++++++++++++
.../PaymentDemoApplicationTests.java | 6 +-
14 files changed, 647 insertions(+), 4 deletions(-)
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/enums/OrderStatus.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/enums/PayType.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxApiType.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxNotifyType.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxRefundStatus.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxTradeState.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpClientUtils.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpUtils.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/util/OrderNoUtils.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/util/WechatPay2ValidatorForRequest.java
diff --git a/payment-demo/pom.xml b/payment-demo/pom.xml
index 4e98468..aabdd14 100644
--- a/payment-demo/pom.xml
+++ b/payment-demo/pom.xml
@@ -61,6 +61,11 @@
0.3.0
+
+ com.google.code.gson
+ gson
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
index c8b29c5..3509f37 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
@@ -1,13 +1,21 @@
package com.hongyi.paymentdemo.config;
+import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
+import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
+import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
+import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
@@ -43,7 +51,7 @@ public class WxPayConfig {
* @param filename
* @return
*/
- public PrivateKey getPrivateKey(String filename) {
+ private PrivateKey getPrivateKey(String filename) {
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
@@ -51,4 +59,40 @@ public class WxPayConfig {
}
}
+ /**
+ * 获取签名验证器
+ * @return
+ */
+ @Bean
+ public ScheduledUpdateCertificatesVerifier getVerifier() {
+ // 获取商户私钥
+ PrivateKey privateKey = getPrivateKey(privateKeyPath);
+ // 私钥签名
+ PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
+ // 身份认证对象
+ WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
+ // 使用定时更新的签名验证器,不需要传入证书
+ ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
+ wechatPay2Credentials,
+ apiV3Key.getBytes(StandardCharsets.UTF_8));
+ return verifier;
+ }
+
+ /**
+ * 获取http请求对象
+ * @param verifier
+ * @return
+ */
+ @Bean
+ public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
+ // 获取商户私钥
+ PrivateKey privateKey = getPrivateKey(privateKeyPath);
+ WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
+ .withMerchant(mchId, mchSerialNo, privateKey)
+ .withValidator(new WechatPay2Validator(verifier));
+ // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
+ CloseableHttpClient httpClient = builder.build();
+ return httpClient;
+ }
+
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/ProductController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/ProductController.java
index 39b4b96..128557c 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/ProductController.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/ProductController.java
@@ -19,6 +19,7 @@ import java.util.List;
* @Date 2022/5/31 18:18
* @Version 1.0
*/
+
@Api(tags = "商品管理")
@RestController
@RequestMapping("/api/product")
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/OrderStatus.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/OrderStatus.java
new file mode 100644
index 0000000..757401c
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/OrderStatus.java
@@ -0,0 +1,49 @@
+package com.hongyi.paymentdemo.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum OrderStatus {
+ /**
+ * 未支付
+ */
+ NOTPAY("未支付"),
+
+
+ /**
+ * 支付成功
+ */
+ SUCCESS("支付成功"),
+
+ /**
+ * 已关闭
+ */
+ CLOSED("超时已关闭"),
+
+ /**
+ * 已取消
+ */
+ CANCEL("用户已取消"),
+
+ /**
+ * 退款中
+ */
+ REFUND_PROCESSING("退款中"),
+
+ /**
+ * 已退款
+ */
+ REFUND_SUCCESS("已退款"),
+
+ /**
+ * 退款异常
+ */
+ REFUND_ABNORMAL("退款异常");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/PayType.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/PayType.java
new file mode 100644
index 0000000..b75fa3f
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/PayType.java
@@ -0,0 +1,24 @@
+package com.hongyi.paymentdemo.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum PayType {
+ /**
+ * 微信
+ */
+ WXPAY("微信"),
+
+
+ /**
+ * 支付宝
+ */
+ ALIPAY("支付宝");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxApiType.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxApiType.java
new file mode 100644
index 0000000..31b4fe8
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxApiType.java
@@ -0,0 +1,55 @@
+package com.hongyi.paymentdemo.enums.wxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum WxApiType {
+
+ /**
+ * Native下单
+ */
+ NATIVE_PAY("/v3/pay/transactions/native"),
+
+ /**
+ * Native下单
+ */
+ NATIVE_PAY_V2("/pay/unifiedorder"),
+
+ /**
+ * 查询订单
+ */
+ ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),
+
+ /**
+ * 关闭订单
+ */
+ CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),
+
+ /**
+ * 申请退款
+ */
+ DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),
+
+ /**
+ * 查询单笔退款
+ */
+ DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),
+
+ /**
+ * 申请交易账单
+ */
+ TRADE_BILLS("/v3/bill/tradebill"),
+
+ /**
+ * 申请资金账单
+ */
+ FUND_FLOW_BILLS("/v3/bill/fundflowbill");
+
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxNotifyType.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxNotifyType.java
new file mode 100644
index 0000000..5980fc0
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxNotifyType.java
@@ -0,0 +1,30 @@
+package com.hongyi.paymentdemo.enums.wxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum WxNotifyType {
+
+ /**
+ * 支付通知
+ */
+ NATIVE_NOTIFY("/api/wx-pay/native/notify"),
+
+ /**
+ * 支付通知
+ */
+ NATIVE_NOTIFY_V2("/api/wx-pay-v2/native/notify"),
+
+
+ /**
+ * 退款结果通知
+ */
+ REFUND_NOTIFY("/api/wx-pay/refunds/notify");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxRefundStatus.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxRefundStatus.java
new file mode 100644
index 0000000..9709f02
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxRefundStatus.java
@@ -0,0 +1,34 @@
+package com.hongyi.paymentdemo.enums.wxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum WxRefundStatus {
+
+ /**
+ * 退款成功
+ */
+ SUCCESS("SUCCESS"),
+
+ /**
+ * 退款关闭
+ */
+ CLOSED("CLOSED"),
+
+ /**
+ * 退款处理中
+ */
+ PROCESSING("PROCESSING"),
+
+ /**
+ * 退款异常
+ */
+ ABNORMAL("ABNORMAL");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxTradeState.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxTradeState.java
new file mode 100644
index 0000000..f711340
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/enums/wxpay/WxTradeState.java
@@ -0,0 +1,34 @@
+package com.hongyi.paymentdemo.enums.wxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum WxTradeState {
+
+ /**
+ * 支付成功
+ */
+ SUCCESS("SUCCESS"),
+
+ /**
+ * 未支付
+ */
+ NOTPAY("NOTPAY"),
+
+ /**
+ * 已关闭
+ */
+ CLOSED("CLOSED"),
+
+ /**
+ * 转入退款
+ */
+ REFUND("REFUND");
+
+ /**
+ * 类型
+ */
+ private final String type;
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpClientUtils.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpClientUtils.java
new file mode 100644
index 0000000..3a4d735
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpClientUtils.java
@@ -0,0 +1,168 @@
+package com.hongyi.paymentdemo.util;
+
+import org.apache.http.Consts;
+import org.apache.http.HttpEntity;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.*;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLContextBuilder;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * http请求客户端
+ */
+public class HttpClientUtils {
+ private String url;
+ private Map param;
+ private int statusCode;
+ private String content;
+ private String xmlParam;
+ private boolean isHttps;
+
+ public boolean isHttps() {
+ return isHttps;
+ }
+
+ public void setHttps(boolean isHttps) {
+ this.isHttps = isHttps;
+ }
+
+ public String getXmlParam() {
+ return xmlParam;
+ }
+
+ public void setXmlParam(String xmlParam) {
+ this.xmlParam = xmlParam;
+ }
+
+ public HttpClientUtils(String url, Map param) {
+ this.url = url;
+ this.param = param;
+ }
+
+ public HttpClientUtils(String url) {
+ this.url = url;
+ }
+
+ public void setParameter(Map map) {
+ param = map;
+ }
+
+ public void addParameter(String key, String value) {
+ if (param == null)
+ param = new HashMap();
+ param.put(key, value);
+ }
+
+ public void post() throws ClientProtocolException, IOException {
+ HttpPost http = new HttpPost(url);
+ setEntity(http);
+ execute(http);
+ }
+
+ public void put() throws ClientProtocolException, IOException {
+ HttpPut http = new HttpPut(url);
+ setEntity(http);
+ execute(http);
+ }
+
+ public void get() throws ClientProtocolException, IOException {
+ if (param != null) {
+ StringBuilder url = new StringBuilder(this.url);
+ boolean isFirst = true;
+ for (String key : param.keySet()) {
+ if (isFirst) {
+ url.append("?");
+ isFirst = false;
+ }else {
+ url.append("&");
+ }
+ url.append(key).append("=").append(param.get(key));
+ }
+ this.url = url.toString();
+ }
+ HttpGet http = new HttpGet(url);
+ execute(http);
+ }
+
+ /**
+ * set http post,put param
+ */
+ private void setEntity(HttpEntityEnclosingRequestBase http) {
+ if (param != null) {
+ List nvps = new LinkedList();
+ for (String key : param.keySet())
+ nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
+ http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
+ }
+ if (xmlParam != null) {
+ http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
+ }
+ }
+
+ private void execute(HttpUriRequest http) throws ClientProtocolException,
+ IOException {
+ CloseableHttpClient httpClient = null;
+ try {
+ if (isHttps) {
+ SSLContext sslContext = new SSLContextBuilder()
+ .loadTrustMaterial(null, new TrustStrategy() {
+ // 信任所有
+ public boolean isTrusted(X509Certificate[] chain,
+ String authType)
+ throws CertificateException {
+ return true;
+ }
+ }).build();
+ SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
+ sslContext);
+ httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
+ .build();
+ } else {
+ httpClient = HttpClients.createDefault();
+ }
+ CloseableHttpResponse response = httpClient.execute(http);
+ try {
+ if (response != null) {
+ if (response.getStatusLine() != null)
+ statusCode = response.getStatusLine().getStatusCode();
+ HttpEntity entity = response.getEntity();
+ // 响应内容
+ content = EntityUtils.toString(entity, Consts.UTF_8);
+ }
+ } finally {
+ response.close();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ httpClient.close();
+ }
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ public String getContent() throws ParseException, IOException {
+ return content;
+ }
+
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpUtils.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpUtils.java
new file mode 100644
index 0000000..dfdcacb
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/HttpUtils.java
@@ -0,0 +1,39 @@
+package com.hongyi.paymentdemo.util;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.IOException;
+
+
+public class HttpUtils {
+
+ /**
+ * 将通知参数转化为字符串
+ * @param request
+ * @return
+ */
+ public static String readData(HttpServletRequest request) {
+ BufferedReader br = null;
+ try {
+ StringBuilder result = new StringBuilder();
+ br = request.getReader();
+ for (String line; (line = br.readLine()) != null; ) {
+ if (result.length() > 0) {
+ result.append("\n");
+ }
+ result.append(line);
+ }
+ return result.toString();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/util/OrderNoUtils.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/OrderNoUtils.java
new file mode 100644
index 0000000..18542e0
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/OrderNoUtils.java
@@ -0,0 +1,46 @@
+package com.hongyi.paymentdemo.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * 订单号工具类
+ *
+ * @author qy
+ * @since 1.0
+ */
+public class OrderNoUtils {
+
+ /**
+ * 获取订单编号
+ * @return
+ */
+ public static String getOrderNo() {
+ return "ORDER_" + getNo();
+ }
+
+ /**
+ * 获取退款单编号
+ * @return
+ */
+ public static String getRefundNo() {
+ return "REFUND_" + getNo();
+ }
+
+ /**
+ * 获取编号
+ * @return
+ */
+ public static String getNo() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+ String newDate = sdf.format(new Date());
+ String result = "";
+ Random random = new Random();
+ for (int i = 0; i < 3; i++) {
+ result += random.nextInt(10);
+ }
+ return newDate + result;
+ }
+
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/util/WechatPay2ValidatorForRequest.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/WechatPay2ValidatorForRequest.java
new file mode 100644
index 0000000..311b86b
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/util/WechatPay2ValidatorForRequest.java
@@ -0,0 +1,114 @@
+package com.hongyi.paymentdemo.util;
+
+
+import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+
+import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
+
+/**
+ * @author xy-peng
+ */
+public class WechatPay2ValidatorForRequest {
+
+ protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
+ /**
+ * 应答超时时间,单位为分钟
+ */
+ protected static final long RESPONSE_EXPIRED_MINUTES = 5;
+ protected final Verifier verifier;
+ protected final String requestId;
+ protected final String body;
+
+
+ public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
+ this.verifier = verifier;
+ this.requestId = requestId;
+ this.body = body;
+ }
+
+ protected static IllegalArgumentException parameterError(String message, Object... args) {
+ message = String.format(message, args);
+ return new IllegalArgumentException("parameter error: " + message);
+ }
+
+ protected static IllegalArgumentException verifyFail(String message, Object... args) {
+ message = String.format(message, args);
+ return new IllegalArgumentException("signature verify fail: " + message);
+ }
+
+ public final boolean validate(HttpServletRequest request) throws IOException {
+ try {
+ //处理请求参数
+ validateParameters(request);
+
+ //构造验签名串
+ String message = buildMessage(request);
+
+ String serial = request.getHeader(WECHAT_PAY_SERIAL);
+ String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
+
+ //验签
+ if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
+ throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
+ serial, message, signature, requestId);
+ }
+ } catch (IllegalArgumentException e) {
+ log.warn(e.getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ protected final void validateParameters(HttpServletRequest request) {
+
+ // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
+ String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
+
+ String header = null;
+ for (String headerName : headers) {
+ header = request.getHeader(headerName);
+ if (header == null) {
+ throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
+ }
+ }
+
+ //判断请求是否过期
+ String timestampStr = header;
+ try {
+ Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
+ // 拒绝过期请求
+ if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
+ throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
+ }
+ } catch (DateTimeException | NumberFormatException e) {
+ throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
+ }
+ }
+
+ protected final String buildMessage(HttpServletRequest request) throws IOException {
+ String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
+ String nonce = request.getHeader(WECHAT_PAY_NONCE);
+ return timestamp + "\n"
+ + nonce + "\n"
+ + body + "\n";
+ }
+
+ protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
+ HttpEntity entity = response.getEntity();
+ return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
+ }
+
+}
diff --git a/payment-demo/src/test/java/com/hongyi/paymentdemo/PaymentDemoApplicationTests.java b/payment-demo/src/test/java/com/hongyi/paymentdemo/PaymentDemoApplicationTests.java
index 4dd25fa..4fe1379 100644
--- a/payment-demo/src/test/java/com/hongyi/paymentdemo/PaymentDemoApplicationTests.java
+++ b/payment-demo/src/test/java/com/hongyi/paymentdemo/PaymentDemoApplicationTests.java
@@ -15,9 +15,9 @@ class PaymentDemoApplicationTests {
@Test
void testGetPrivateKey() {
- String privateKeyPath = wxPayConfig.getPrivateKeyPath();
- PrivateKey privateKey = wxPayConfig.getPrivateKey(privateKeyPath);
- System.out.println(privateKey);
+// String privateKeyPath = wxPayConfig.getPrivateKeyPath();
+// PrivateKey privateKey = wxPayConfig.getPrivateKey(privateKeyPath);
+// System.out.println(privateKey);
}
}
--
Gitee
From 9f13cb2c214a3cd893b8fb64ff357c34015f377c Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Tue, 28 Jun 2022 20:50:49 +0800
Subject: [PATCH 2/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=B8=8B=E5=8D=95api?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
payment-demo/pom.xml | 5 +
.../paymentdemo/config/WxPayConfig.java | 2 +-
.../controller/WxPayController.java | 37 ++++++
.../paymentdemo/service/WxPayService.java | 13 ++
.../service/impl/WxPayServiceImpl.java | 113 ++++++++++++++++++
.../java/com/hongyi/paymentdemo/vo/R.java | 2 +
.../src/main/resources/wxpay.properties | 2 +-
7 files changed, 172 insertions(+), 2 deletions(-)
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
diff --git a/payment-demo/pom.xml b/payment-demo/pom.xml
index aabdd14..00d8f76 100644
--- a/payment-demo/pom.xml
+++ b/payment-demo/pom.xml
@@ -61,6 +61,11 @@
0.3.0
+
+ org.springframework.boot
+ spring-boot-devtools
+
+
com.google.code.gson
gson
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
index 3509f37..e5200a7 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
@@ -38,7 +38,7 @@ public class WxPayConfig {
private String apiV3Key;
// APPID
- private String appid;
+ private String appId;
// 微信服务器地址
private String domain;
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
new file mode 100644
index 0000000..6c988cb
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
@@ -0,0 +1,37 @@
+package com.hongyi.paymentdemo.controller;
+
+import com.hongyi.paymentdemo.service.WxPayService;
+import com.hongyi.paymentdemo.vo.R;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+/**
+ * @Author Kisugi Takumi
+ * @Date 2022/6/28 17:18
+ * @Version 1.0
+ */
+@CrossOrigin
+@RestController
+@RequestMapping("/api/wx-pay")
+@Api(tags = "网站微信支付api")
+@Slf4j
+public class WxPayController {
+
+ @Resource // 这个是java规范的注解,Spring的为@Autowired
+ private WxPayService wxPayService;
+
+ @ApiOperation("调用统一下单API,生成支付二维码")
+ @PostMapping("/native/{productId}")
+ public R nativePay(@PathVariable Long productId) throws Exception {
+ log.info("发起支付请求");
+ // 返回支付二维码连接和订单号
+ Map map = wxPayService.nativePay(productId);
+ return R.ok().setData(map);
+ }
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
new file mode 100644
index 0000000..b09ad7c
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
@@ -0,0 +1,13 @@
+package com.hongyi.paymentdemo.service;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @Author Kisugi Takumi
+ * @Date 2022/6/28 17:19
+ * @Version 1.0
+ */
+public interface WxPayService {
+ Map nativePay(Long productId) throws Exception;
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
new file mode 100644
index 0000000..d0fb8f4
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
@@ -0,0 +1,113 @@
+package com.hongyi.paymentdemo.service.impl;
+
+import com.google.gson.Gson;
+import com.hongyi.paymentdemo.config.WxPayConfig;
+import com.hongyi.paymentdemo.entity.OrderInfo;
+import com.hongyi.paymentdemo.enums.OrderStatus;
+import com.hongyi.paymentdemo.enums.wxpay.WxApiType;
+import com.hongyi.paymentdemo.enums.wxpay.WxNotifyType;
+import com.hongyi.paymentdemo.service.WxPayService;
+import com.hongyi.paymentdemo.util.OrderNoUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Author Kisugi Takumi
+ * @Date 2022/6/28 17:19
+ * @Version 1.0
+ */
+@Service
+@Slf4j
+public class WxPayServiceImpl implements WxPayService {
+
+ @Resource
+ private WxPayConfig wxPayConfig;
+
+ @Resource
+ private CloseableHttpClient httpClient;
+
+ /**
+ * 创建订单,调用native支付接口
+ * @param productId 购买产品的id
+ * @return code_url和订单号
+ * @throws Exception
+ */
+ @Override
+ public Map nativePay(Long productId) throws Exception {
+ log.info("生成订单");
+ // 生成订单
+ OrderInfo orderInfo = new OrderInfo();
+ orderInfo.setTitle("test");
+ orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); // 订单号
+ orderInfo.setProductId(productId);
+ orderInfo.setTotalFee(99); // 价格,单位为分
+ orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
+ // TODO: 存入数据库
+
+ log.info("调用统一下单API");
+ // 调用统一下单API,构造Post请求
+ HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
+
+ // 请求body参数
+ Gson gson = new Gson();
+ Map paramsMap = new HashMap<>();
+ paramsMap.put("appid", wxPayConfig.getAppId());
+ paramsMap.put("mchid", wxPayConfig.getMchId());
+ paramsMap.put("description", orderInfo.getTitle());
+ paramsMap.put("out_trade_no", orderInfo.getOrderNo());
+ paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
+ // 注意查阅文档得知,amount为嵌套结构
+ Map amountMap = new HashMap<>();
+ amountMap.put("total", orderInfo.getTotalFee());
+ amountMap.put("currency", "CNY");
+ paramsMap.put("amount", amountMap);
+ // 将Map对象转换为json
+ String jsonParams = gson.toJson(paramsMap);
+ log.info("请求参数: " + jsonParams);
+
+ // 将请求参数放入请求体中,设置请求和响应类型为json
+ StringEntity entity = new StringEntity(jsonParams,"utf-8");
+ entity.setContentType("application/json");
+ httpPost.setEntity(entity);
+ httpPost.setHeader("Accept", "application/json");
+
+ // 完成签名并执行请求
+ CloseableHttpResponse response = httpClient.execute(httpPost);
+
+ try {
+ // 获取响应体
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ // 获取响应状态码
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) { //处理成功
+ log.info("success,return body = " + bodyAsString);
+ } else if (statusCode == 204) { //处理成功,无返回Body
+ log.info("success");
+ } else {
+ log.info("failed,resp code = " + statusCode+ ",return body = " + bodyAsString);
+ throw new IOException("request failed");
+ }
+ // 响应结果
+ HashMap resultMap = gson.fromJson(bodyAsString, HashMap.class);
+ // 二维码
+ String codeUrl = resultMap.get("code_url");
+ HashMap map = new HashMap<>();
+ map.put("codeUrl", codeUrl);
+ // 订单编号
+ map.put("orderNo", orderInfo.getOrderNo());
+ return map;
+ } finally {
+ response.close();
+ }
+ }
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/vo/R.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/vo/R.java
index d92595d..01a013d 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/vo/R.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/vo/R.java
@@ -1,6 +1,7 @@
package com.hongyi.paymentdemo.vo;
import lombok.Data;
+import lombok.experimental.Accessors;
import java.util.HashMap;
import java.util.Map;
@@ -11,6 +12,7 @@ import java.util.Map;
* @Version 1.0
*/
@Data // 生成get和set方法
+@Accessors(chain = true)
public class R {
// 响应码
private Integer code;
diff --git a/payment-demo/src/main/resources/wxpay.properties b/payment-demo/src/main/resources/wxpay.properties
index 8fad36c..c7bbe01 100644
--- a/payment-demo/src/main/resources/wxpay.properties
+++ b/payment-demo/src/main/resources/wxpay.properties
@@ -8,7 +8,7 @@ wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
-wxpay.appid=wx74862e0dfcf69954
+wxpay.app-id=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
--
Gitee
From 5e87d578f053a6186e5517459d5a4443d5936667 Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Wed, 29 Jun 2022 15:28:37 +0800
Subject: [PATCH 3/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E4=BF=9D=E5=AD=98=E8=AE=A2=E5=8D=95=20=E7=BC=93=E5=AD=98?=
=?UTF-8?q?=E4=BA=8C=E7=BB=B4=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../paymentdemo/config/WxPayConfig.java | 10 ++--
.../paymentdemo/service/OrderInfoService.java | 12 ++++
.../service/impl/OrderInfoServiceImpl.java | 56 +++++++++++++++++++
.../service/impl/WxPayServiceImpl.java | 30 +++++++---
.../src/main/resources/application.yml | 5 +-
5 files changed, 99 insertions(+), 14 deletions(-)
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
index e5200a7..354bcca 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/config/WxPayConfig.java
@@ -7,6 +7,7 @@ import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -23,6 +24,7 @@ import java.security.PrivateKey;
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
+@Slf4j
public class WxPayConfig {
// 商户号
@@ -65,6 +67,7 @@ public class WxPayConfig {
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier() {
+ log.info("获取签名验证器");
// 获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
// 私钥签名
@@ -72,10 +75,9 @@ public class WxPayConfig {
// 身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
- ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
+ return new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
- return verifier;
}
/**
@@ -85,14 +87,14 @@ public class WxPayConfig {
*/
@Bean
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
+ log.info("获取HttpClient");
// 获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
- CloseableHttpClient httpClient = builder.build();
- return httpClient;
+ return builder.build();
}
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
index cd06466..1b7794c 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
@@ -4,5 +4,17 @@ import com.hongyi.paymentdemo.entity.OrderInfo;
import com.baomidou.mybatisplus.extension.service.IService;
public interface OrderInfoService extends IService {
+ /**
+ * 根据商品id生成订单
+ * @param productId
+ * @return
+ */
+ OrderInfo createOrderByProductById(Long productId);
+ /**
+ * 根据订单编号存储二维码地址
+ * @param orderNo
+ * @param codeUrl
+ */
+ void saveCodeUrl(String orderNo, String codeUrl);
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
index c93e860..6c39492 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
@@ -1,12 +1,68 @@
package com.hongyi.paymentdemo.service.impl;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hongyi.paymentdemo.entity.OrderInfo;
+import com.hongyi.paymentdemo.entity.Product;
+import com.hongyi.paymentdemo.enums.OrderStatus;
import com.hongyi.paymentdemo.mapper.OrderInfoMapper;
+import com.hongyi.paymentdemo.mapper.ProductMapper;
import com.hongyi.paymentdemo.service.OrderInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.hongyi.paymentdemo.util.OrderNoUtils;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+
@Service
+@Slf4j
public class OrderInfoServiceImpl extends ServiceImpl implements OrderInfoService {
+ @Resource
+ private ProductMapper productMapper;
+
+ @Resource
+ private OrderInfoMapper orderInfoMapper;
+
+ @Override
+ public OrderInfo createOrderByProductById(Long productId) {
+ // 查找已存在但未支付的订单
+ OrderInfo orderInfo = this.getNoPayOrderByProductById(productId);
+ if (orderInfo != null) {
+ return orderInfo;
+ }
+ // 获取商品信息
+ Product product = productMapper.selectById(productId);
+ // 生成订单
+ orderInfo = new OrderInfo();
+ orderInfo.setTitle(product.getTitle());
+ orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); // 订单号
+ orderInfo.setProductId(productId);
+ orderInfo.setTotalFee(product.getPrice()); // 价格,单位为分
+ orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
+ orderInfoMapper.insert(orderInfo);
+ return orderInfo;
+ }
+
+ @Override
+ public void saveCodeUrl(String orderNo, String codeUrl) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+ OrderInfo orderInfo = new OrderInfo();
+ orderInfo.setCodeUrl(codeUrl);
+ orderInfoMapper.update(orderInfo, queryWrapper);
+ }
+
+ /**
+ * 根据商品id查询未支付的订单
+ * @param productId
+ * @return
+ */
+ private OrderInfo getNoPayOrderByProductById(Long productId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("product_id", productId)
+ .eq("order_status", OrderStatus.NOTPAY.getType());
+ return orderInfoMapper.selectOne(queryWrapper);
+ }
+
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
index d0fb8f4..5d20638 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
@@ -6,6 +6,7 @@ import com.hongyi.paymentdemo.entity.OrderInfo;
import com.hongyi.paymentdemo.enums.OrderStatus;
import com.hongyi.paymentdemo.enums.wxpay.WxApiType;
import com.hongyi.paymentdemo.enums.wxpay.WxNotifyType;
+import com.hongyi.paymentdemo.service.OrderInfoService;
import com.hongyi.paymentdemo.service.WxPayService;
import com.hongyi.paymentdemo.util.OrderNoUtils;
import lombok.extern.slf4j.Slf4j;
@@ -15,6 +16,7 @@ import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.io.IOException;
@@ -36,6 +38,9 @@ public class WxPayServiceImpl implements WxPayService {
@Resource
private CloseableHttpClient httpClient;
+ @Resource
+ private OrderInfoService orderInfoService;
+
/**
* 创建订单,调用native支付接口
* @param productId 购买产品的id
@@ -45,14 +50,19 @@ public class WxPayServiceImpl implements WxPayService {
@Override
public Map nativePay(Long productId) throws Exception {
log.info("生成订单");
+
// 生成订单
- OrderInfo orderInfo = new OrderInfo();
- orderInfo.setTitle("test");
- orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); // 订单号
- orderInfo.setProductId(productId);
- orderInfo.setTotalFee(99); // 价格,单位为分
- orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
- // TODO: 存入数据库
+ OrderInfo orderInfo = orderInfoService.createOrderByProductById(productId);
+ String codeUrl = orderInfo.getCodeUrl();
+ if (orderInfo != null && !StringUtils.isEmpty(codeUrl)) {
+ log.info("订单已存在,二维码已保存");
+ // 返回二维码
+ HashMap map = new HashMap<>();
+ map.put("codeUrl", codeUrl);
+ // 订单编号
+ map.put("orderNo", orderInfo.getOrderNo());
+ return map;
+ }
log.info("调用统一下单API");
// 调用统一下单API,构造Post请求
@@ -100,7 +110,11 @@ public class WxPayServiceImpl implements WxPayService {
// 响应结果
HashMap resultMap = gson.fromJson(bodyAsString, HashMap.class);
// 二维码
- String codeUrl = resultMap.get("code_url");
+ codeUrl = resultMap.get("code_url");
+ // 保存二维码
+ String orderNo = orderInfo.getOrderNo();
+ orderInfoService.saveCodeUrl(orderNo, codeUrl);
+ // 返回二维码
HashMap map = new HashMap<>();
map.put("codeUrl", codeUrl);
// 订单编号
diff --git a/payment-demo/src/main/resources/application.yml b/payment-demo/src/main/resources/application.yml
index 19090ad..fd60c70 100644
--- a/payment-demo/src/main/resources/application.yml
+++ b/payment-demo/src/main/resources/application.yml
@@ -15,5 +15,6 @@ mybatis-plus:
configuration: #sql日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:com/hongyi/paymentdemo/mapper/xml/*.xml
-
-
+#logging:
+# level:
+# root: debug
--
Gitee
From e35a89d73e61fcfd4110a5a97fb7aea955562838 Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Wed, 29 Jun 2022 17:33:59 +0800
Subject: [PATCH 4/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E6=94=AF=E4=BB=98=E9=80=9A=E7=9F=A5api=20=E5=86=85=E7=BD=91?=
=?UTF-8?q?=E7=A9=BF=E9=80=8F=20=E9=80=9A=E7=9F=A5=E6=96=B9=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controller/OrderInfoController.java | 33 +++++++++++++++++++
.../controller/WxPayController.java | 27 +++++++++++++++
.../paymentdemo/service/OrderInfoService.java | 8 +++++
.../service/impl/OrderInfoServiceImpl.java | 8 +++++
.../src/main/resources/wxpay.properties | 3 +-
5 files changed, 78 insertions(+), 1 deletion(-)
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java
new file mode 100644
index 0000000..afe8b32
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java
@@ -0,0 +1,33 @@
+package com.hongyi.paymentdemo.controller;
+
+import com.hongyi.paymentdemo.entity.OrderInfo;
+import com.hongyi.paymentdemo.service.OrderInfoService;
+import com.hongyi.paymentdemo.vo.R;
+import io.swagger.annotations.Api;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @Author Kisugi Takumi
+ * @Date 2022/6/29 15:33
+ * @Version 1.0
+ */
+@Api(tags = "商品订单管理")
+@RestController
+@CrossOrigin
+@RequestMapping("/api/order-info")
+public class OrderInfoController {
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ @GetMapping("/list")
+ public R list() {
+ List list = orderInfoService.listOrderByCreateTimeDesc();
+ return R.ok().data("list", list);
+ }
+}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
index 6c988cb..e03be91 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
@@ -1,6 +1,8 @@
package com.hongyi.paymentdemo.controller;
+import com.google.gson.Gson;
import com.hongyi.paymentdemo.service.WxPayService;
+import com.hongyi.paymentdemo.util.HttpUtils;
import com.hongyi.paymentdemo.vo.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -9,6 +11,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
import java.util.Map;
/**
@@ -34,4 +39,26 @@ public class WxPayController {
Map map = wxPayService.nativePay(productId);
return R.ok().setData(map);
}
+
+ @PostMapping("/native/notify")
+ public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
+ Gson gson = new Gson();
+ // 应答对象
+ Map map = new HashMap<>();
+ // 处理通知参数
+ String body = HttpUtils.readData(request);
+ Map bodyMap = gson.fromJson(body, HashMap.class);
+ log.info("支付通知的id ====> {}", bodyMap.get("id"));
+ log.info("支付通知完整的数据 ====> {}", body);
+ Object id = bodyMap.get("id");
+ // TODO: 签名的验证
+
+ // TODO: 处理订单
+
+ // 成功应答
+ response.setStatus(200);
+ map.put("code", "SUCCESS");
+ map.put("message", "成功");
+ return gson.toJson(map);
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
index 1b7794c..ae670d5 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
@@ -3,6 +3,8 @@ package com.hongyi.paymentdemo.service;
import com.hongyi.paymentdemo.entity.OrderInfo;
import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+
public interface OrderInfoService extends IService {
/**
* 根据商品id生成订单
@@ -17,4 +19,10 @@ public interface OrderInfoService extends IService {
* @param codeUrl
*/
void saveCodeUrl(String orderNo, String codeUrl);
+
+ /**
+ * 查询订单列表,并按时间倒序查询
+ * @return
+ */
+ List listOrderByCreateTimeDesc();
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
index 6c39492..41ff390 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
@@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
+import java.util.List;
@Service
@Slf4j
@@ -53,6 +54,13 @@ public class OrderInfoServiceImpl extends ServiceImpl listOrderByCreateTimeDesc() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.orderByDesc("create_time");
+ return orderInfoMapper.selectList(queryWrapper);
+ }
+
/**
* 根据商品id查询未支付的订单
* @param productId
diff --git a/payment-demo/src/main/resources/wxpay.properties b/payment-demo/src/main/resources/wxpay.properties
index c7bbe01..dbae34f 100644
--- a/payment-demo/src/main/resources/wxpay.properties
+++ b/payment-demo/src/main/resources/wxpay.properties
@@ -12,4 +12,5 @@ wxpay.app-id=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
-wxpay.notify-domain=https://7d92-115-171-63-135.ngrok.io
+# 注意:每次重新启动ngrok,都需要修改这个配置
+wxpay.notify-domain=https://c257-117-174-85-8.ap.ngrok.io
--
Gitee
From ca481af37145a53845b36e9c6447080406f03ecc Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Fri, 1 Jul 2022 16:17:11 +0800
Subject: [PATCH 5/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E6=94=AF=E4=BB=98=E9=80=9A=E7=9F=A5=20=E9=AA=8C=E7=AD=BE=20?=
=?UTF-8?q?=E8=A7=A3=E5=AF=86=20=E5=A4=84=E7=90=86=E8=AE=A2=E5=8D=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controller/WxPayController.java | 45 ++++++++++++++----
.../paymentdemo/service/OrderInfoService.java | 8 ++++
.../service/PaymentInfoService.java | 8 +++-
.../paymentdemo/service/WxPayService.java | 3 ++
.../service/impl/OrderInfoServiceImpl.java | 10 ++++
.../service/impl/PaymentInfoServiceImpl.java | 40 ++++++++++++++++
.../service/impl/WxPayServiceImpl.java | 46 +++++++++++++++++++
.../src/main/resources/wxpay.properties | 2 +-
8 files changed, 151 insertions(+), 11 deletions(-)
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
index e03be91..b81aa2e 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
@@ -3,7 +3,9 @@ package com.hongyi.paymentdemo.controller;
import com.google.gson.Gson;
import com.hongyi.paymentdemo.service.WxPayService;
import com.hongyi.paymentdemo.util.HttpUtils;
+import com.hongyi.paymentdemo.util.WechatPay2ValidatorForRequest;
import com.hongyi.paymentdemo.vo.R;
+import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -13,6 +15,8 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
@@ -31,6 +35,9 @@ public class WxPayController {
@Resource // 这个是java规范的注解,Spring的为@Autowired
private WxPayService wxPayService;
+ @Resource
+ private Verifier verifier;
+
@ApiOperation("调用统一下单API,生成支付二维码")
@PostMapping("/native/{productId}")
public R nativePay(@PathVariable Long productId) throws Exception {
@@ -41,7 +48,7 @@ public class WxPayController {
}
@PostMapping("/native/notify")
- public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
+ public String nativeNotify(HttpServletRequest request, HttpServletResponse response) throws IOException, GeneralSecurityException {
Gson gson = new Gson();
// 应答对象
Map map = new HashMap<>();
@@ -50,15 +57,35 @@ public class WxPayController {
Map bodyMap = gson.fromJson(body, HashMap.class);
log.info("支付通知的id ====> {}", bodyMap.get("id"));
log.info("支付通知完整的数据 ====> {}", body);
- Object id = bodyMap.get("id");
- // TODO: 签名的验证
+ String requestId = (String) bodyMap.get("id");
+ // 签名的验证
+ WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
+ = new WechatPay2ValidatorForRequest(verifier, requestId, body);
+ if (!wechatPay2ValidatorForRequest.validate(request)) {
+ log.info("通知验签失败");
+ // 失败应答
+ response.setStatus(500);
+ map.put("code", "ERROR");
+ map.put("message", "通知验签失败");
+ return gson.toJson(map);
+ }
+ log.info("通知验签成功");
- // TODO: 处理订单
+ // 处理订单
+ wxPayService.processOrder(bodyMap);
- // 成功应答
- response.setStatus(200);
- map.put("code", "SUCCESS");
- map.put("message", "成功");
- return gson.toJson(map);
+ try {
+ // 成功应答
+ response.setStatus(200);
+ map.put("code", "SUCCESS");
+ map.put("message", "成功");
+ return gson.toJson(map);
+ } catch (Exception e) {
+ // 失败应答
+ response.setStatus(500);
+ map.put("code", "ERROR");
+ map.put("message", "失败");
+ return gson.toJson(map);
+ }
}
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
index ae670d5..b2fd0ae 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
@@ -2,6 +2,7 @@ package com.hongyi.paymentdemo.service;
import com.hongyi.paymentdemo.entity.OrderInfo;
import com.baomidou.mybatisplus.extension.service.IService;
+import com.hongyi.paymentdemo.enums.OrderStatus;
import java.util.List;
@@ -25,4 +26,11 @@ public interface OrderInfoService extends IService {
* @return
*/
List listOrderByCreateTimeDesc();
+
+ /**
+ * 根据订单号更新订单状态
+ * @param orderNo
+ * @param orderStatus
+ */
+ void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus);
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/PaymentInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/PaymentInfoService.java
index 0d56fe9..0b2e26a 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/PaymentInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/PaymentInfoService.java
@@ -1,5 +1,11 @@
package com.hongyi.paymentdemo.service;
-public interface PaymentInfoService {
+import java.util.HashMap;
+public interface PaymentInfoService {
+ /**
+ * 根据解密的明文创建支付日志
+ * @param plainTextMap
+ */
+ void createPaymentInfo(HashMap plainTextMap);
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
index b09ad7c..7775038 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
@@ -1,6 +1,7 @@
package com.hongyi.paymentdemo.service;
import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.Map;
/**
@@ -10,4 +11,6 @@ import java.util.Map;
*/
public interface WxPayService {
Map nativePay(Long productId) throws Exception;
+
+ void processOrder(Map bodyMap) throws GeneralSecurityException;
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
index 41ff390..422ef61 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
@@ -61,6 +61,16 @@ public class OrderInfoServiceImpl extends ServiceImpl {}", orderStatus.getType());
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+ OrderInfo orderInfo = new OrderInfo();
+ orderInfo.setOrderStatus(orderStatus.getType());
+ orderInfoMapper.update(orderInfo, queryWrapper);
+ }
+
/**
* 根据商品id查询未支付的订单
* @param productId
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/PaymentInfoServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/PaymentInfoServiceImpl.java
index a28a68b..c29d8d5 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/PaymentInfoServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/PaymentInfoServiceImpl.java
@@ -1,12 +1,52 @@
package com.hongyi.paymentdemo.service.impl;
+import com.google.gson.Gson;
import com.hongyi.paymentdemo.entity.PaymentInfo;
+import com.hongyi.paymentdemo.enums.PayType;
import com.hongyi.paymentdemo.mapper.PaymentInfoMapper;
import com.hongyi.paymentdemo.service.PaymentInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
@Service
+@Slf4j
public class PaymentInfoServiceImpl extends ServiceImpl implements PaymentInfoService {
+ @Resource
+ private PaymentInfoMapper paymentInfoMapper;
+
+
+ @Override
+ public void createPaymentInfo(HashMap plainTextMap) {
+ log.info("创建支付日志");
+ // 订单号
+ String orderNo = (String)plainTextMap.get("out_trade_no");
+ // 微信端的业务编号
+ String transactionId = (String)plainTextMap.get("transaction_id");
+ // 交易类型
+ String tradeType = (String)plainTextMap.get("trade_type");
+ // 交易状态
+ String tradeState = (String)plainTextMap.get("trade_state");
+ // 用户支付的金额
+ Map amount = (Map)plainTextMap.get("amount");
+ Integer payerTotal = ((Double) amount.get("payer_total")).intValue();
+ // 新建支付订单
+ PaymentInfo paymentInfo = new PaymentInfo();
+ paymentInfo.setOrderNo(orderNo);
+ paymentInfo.setPaymentType(PayType.WXPAY.getType());
+ paymentInfo.setTransactionId(transactionId);
+ paymentInfo.setTradeType(tradeType);
+ paymentInfo.setTradeState(tradeState);
+ paymentInfo.setPayerTotal(payerTotal);
+ // 将所有数据存在一个字段里
+ Gson gson = new Gson();
+ String plainText = gson.toJson(plainTextMap);
+ paymentInfo.setContent(plainText);
+ paymentInfoMapper.insert(paymentInfo);
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
index 5d20638..ddc6fd2 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
@@ -7,8 +7,10 @@ import com.hongyi.paymentdemo.enums.OrderStatus;
import com.hongyi.paymentdemo.enums.wxpay.WxApiType;
import com.hongyi.paymentdemo.enums.wxpay.WxNotifyType;
import com.hongyi.paymentdemo.service.OrderInfoService;
+import com.hongyi.paymentdemo.service.PaymentInfoService;
import com.hongyi.paymentdemo.service.WxPayService;
import com.hongyi.paymentdemo.util.OrderNoUtils;
+import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
@@ -20,6 +22,8 @@ import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
@@ -41,6 +45,9 @@ public class WxPayServiceImpl implements WxPayService {
@Resource
private OrderInfoService orderInfoService;
+ @Resource
+ private PaymentInfoService paymentInfoService;
+
/**
* 创建订单,调用native支付接口
* @param productId 购买产品的id
@@ -124,4 +131,43 @@ public class WxPayServiceImpl implements WxPayService {
response.close();
}
}
+
+ @Override
+ public void processOrder(Map bodyMap) throws GeneralSecurityException {
+ log.info("处理订单");
+ // 解密报文
+ String plainText = decryptFromResource(bodyMap);
+ // 将明文转换为map
+ Gson gson = new Gson();
+ HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
+ String orderNo = (String)plainTextMap.get("out_trade_no"); // 订单号
+ // 更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
+ // 记录支付日志
+ paymentInfoService.createPaymentInfo(plainTextMap);
+ }
+
+ /**
+ * 对称解密
+ * @param bodyMap
+ * @return
+ */
+ private String decryptFromResource(Map bodyMap) throws GeneralSecurityException {
+ log.info("密文解密");
+ // 通知数据
+ Map resourceMap = (Map) bodyMap.get("resource");
+ // 数据密文
+ String ciphertext = resourceMap.get("ciphertext");
+ // 随机串
+ String nonce = resourceMap.get("nonce");
+ // 附加数据
+ String associatedData = resourceMap.get("associated_data");
+ AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
+ String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
+ nonce.getBytes(StandardCharsets.UTF_8),
+ ciphertext);
+ log.info("密文 ===> {}", ciphertext);
+ log.info("明文 ===> {}", plainText);
+ return plainText;
+ }
}
diff --git a/payment-demo/src/main/resources/wxpay.properties b/payment-demo/src/main/resources/wxpay.properties
index dbae34f..eb68ba0 100644
--- a/payment-demo/src/main/resources/wxpay.properties
+++ b/payment-demo/src/main/resources/wxpay.properties
@@ -13,4 +13,4 @@ wxpay.app-id=wx74862e0dfcf69954
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要修改这个配置
-wxpay.notify-domain=https://c257-117-174-85-8.ap.ngrok.io
+wxpay.notify-domain=https://d892-101-206-166-241.jp.ngrok.io
--
Gitee
From 5362828bce5bb199ae0f66ddb9d8333f8ae50c00 Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Fri, 1 Jul 2022 17:48:32 +0800
Subject: [PATCH 6/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E5=A4=84=E7=90=86=E9=87=8D=E5=A4=8D=E9=80=9A=E7=9F=A5=20?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=94=81=20=E5=AE=9A=E6=97=B6=E6=9F=A5?=
=?UTF-8?q?=E8=AF=A2=E8=AE=A2=E5=8D=95=E7=8A=B6=E6=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controller/OrderInfoController.java | 21 ++++++++++++---
.../paymentdemo/service/OrderInfoService.java | 7 +++++
.../service/impl/OrderInfoServiceImpl.java | 11 ++++++++
.../service/impl/WxPayServiceImpl.java | 27 ++++++++++++++++---
4 files changed, 58 insertions(+), 8 deletions(-)
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java
index afe8b32..a4593f3 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/OrderInfoController.java
@@ -1,13 +1,11 @@
package com.hongyi.paymentdemo.controller;
import com.hongyi.paymentdemo.entity.OrderInfo;
+import com.hongyi.paymentdemo.enums.OrderStatus;
import com.hongyi.paymentdemo.service.OrderInfoService;
import com.hongyi.paymentdemo.vo.R;
import io.swagger.annotations.Api;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@@ -30,4 +28,19 @@ public class OrderInfoController {
List list = orderInfoService.listOrderByCreateTimeDesc();
return R.ok().data("list", list);
}
+
+ /**
+ * 查询订单状态
+ * @param orderNo
+ * @return
+ */
+ @GetMapping("/query-order-status/{orderNo}")
+ public R queryOrderStatus(@PathVariable String orderNo) {
+ String orderStatus = orderInfoService.getOrderStatus(orderNo);
+ if (OrderStatus.SUCCESS.getType().equals(orderStatus)) {
+ return R.ok().setMessage("支付成功"); //支付成功
+ }
+ // code: 1为支付成功,0为支付失败,101为任意值
+ return R.ok().setCode(101).setMessage("用户支付中....");
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
index b2fd0ae..b92626a 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
@@ -33,4 +33,11 @@ public interface OrderInfoService extends IService {
* @param orderStatus
*/
void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus);
+
+ /**
+ * 根据订单号查询订单状态
+ * @param orderNo
+ * @return
+ */
+ String getOrderStatus(String orderNo);
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
index 422ef61..2c292b1 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
@@ -71,6 +71,17 @@ public class OrderInfoServiceImpl extends ServiceImpl queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+ OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);
+ if (orderInfo == null) {
+ return null;
+ }
+ return orderInfo.getOrderStatus();
+ }
+
/**
* 根据商品id查询未支付的订单
* @param productId
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
index ddc6fd2..d1625e8 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
@@ -26,6 +26,7 @@ import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
/**
* @Author Kisugi Takumi
@@ -48,6 +49,8 @@ public class WxPayServiceImpl implements WxPayService {
@Resource
private PaymentInfoService paymentInfoService;
+ private final ReentrantLock lock = new ReentrantLock();
+
/**
* 创建订单,调用native支付接口
* @param productId 购买产品的id
@@ -141,10 +144,26 @@ public class WxPayServiceImpl implements WxPayService {
Gson gson = new Gson();
HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
String orderNo = (String)plainTextMap.get("out_trade_no"); // 订单号
- // 更新订单状态
- orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
- // 记录支付日志
- paymentInfoService.createPaymentInfo(plainTextMap);
+ // 数据锁:成功获取则返回true,失败获取则立即返回false,不会一直等待锁的释放
+ if (lock.tryLock()) {
+ try {
+ // 处理重复的通知
+ // 保证接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的
+ String orderStatus = orderInfoService.getOrderStatus(orderNo);
+ // 如果当前订单已经支付,则不做处理
+ if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
+ return ;
+ }
+ // 如果用户尚未订单,则将数据插入数据库
+ // 更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
+ // 记录支付日志
+ paymentInfoService.createPaymentInfo(plainTextMap);
+ } finally {
+ // 释放锁
+ lock.unlock();
+ }
+ }
}
/**
--
Gitee
From df735e622e08e4690684115ace1fd6d9cbf05a33 Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Mon, 4 Jul 2022 11:03:11 +0800
Subject: [PATCH 7/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E5=8F=96=E6=B6=88=E8=AE=A2=E5=8D=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controller/WxPayController.java | 7 +++
.../paymentdemo/service/WxPayService.java | 2 +
.../service/impl/WxPayServiceImpl.java | 49 +++++++++++++++++++
.../src/main/resources/wxpay.properties | 2 +-
4 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
index b81aa2e..2b55166 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
@@ -88,4 +88,11 @@ public class WxPayController {
return gson.toJson(map);
}
}
+
+ @PostMapping("/cancel/{orderNo}")
+ public R cancel(@PathVariable String orderNo) throws IOException {
+ log.info("取消订单");
+ wxPayService.cancelOrder(orderNo);
+ return R.ok().setMessage("订单已取消");
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
index 7775038..a4de494 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
@@ -13,4 +13,6 @@ public interface WxPayService {
Map nativePay(Long productId) throws Exception;
void processOrder(Map bodyMap) throws GeneralSecurityException;
+
+ void cancelOrder(String orderNo) throws IOException;
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
index d1625e8..6084ae5 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
@@ -166,6 +166,55 @@ public class WxPayServiceImpl implements WxPayService {
}
}
+ @Override
+ public void cancelOrder(String orderNo) throws IOException {
+ // 调用微信支付的关闭订单接口
+ this.closeOrder(orderNo);
+ // 更新商户端的订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
+ }
+
+ /**
+ * 关闭订单接口的调用
+ * @param orderNo
+ */
+ private void closeOrder(String orderNo) throws IOException {
+ log.info("关单接口的调用,订单号 ====> {}", orderNo);
+ // 创建远程请求对象
+ String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
+ url = wxPayConfig.getDomain().concat(url);
+ HttpPost httpPost = new HttpPost(url);
+ // 组装json请求体
+ Gson gson = new Gson();
+ HashMap paramsMap = new HashMap<>();
+ paramsMap.put("mchid", wxPayConfig.getMchId());
+ String jsonParams = gson.toJson(paramsMap);
+ log.info("请求参数 ====> {}", jsonParams);
+ // 将请求参数设置到请求对象中
+ StringEntity entity = new StringEntity(jsonParams,"utf-8");
+ entity.setContentType("application/json");
+ httpPost.setEntity(entity);
+ httpPost.setHeader("Accept", "application/json");
+
+ // 完成签名并执行请求
+ CloseableHttpResponse response = httpClient.execute(httpPost);
+
+ try {
+ // 获取响应状态码
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) { //处理成功
+ log.info("success200");
+ } else if (statusCode == 204) { //处理成功,无返回Body
+ log.info("success204");
+ } else {
+ log.info("failed,resp code = " + statusCode);
+ throw new IOException("request failed");
+ }
+ } finally {
+ response.close();
+ }
+ }
+
/**
* 对称解密
* @param bodyMap
diff --git a/payment-demo/src/main/resources/wxpay.properties b/payment-demo/src/main/resources/wxpay.properties
index eb68ba0..a25d3e7 100644
--- a/payment-demo/src/main/resources/wxpay.properties
+++ b/payment-demo/src/main/resources/wxpay.properties
@@ -13,4 +13,4 @@ wxpay.app-id=wx74862e0dfcf69954
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要修改这个配置
-wxpay.notify-domain=https://d892-101-206-166-241.jp.ngrok.io
+wxpay.notify-domain=https://84bb-117-174-85-8.ap.ngrok.io
--
Gitee
From 7fe376b322e3af44b96968b26830811a94c38aa1 Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Mon, 4 Jul 2022 17:22:20 +0800
Subject: [PATCH 8/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E6=9F=A5=E5=8D=95API=20=E8=B6=85=E6=97=B6=E8=AE=A2=E5=8D=95=20?=
=?UTF-8?q?=E7=94=B3=E8=AF=B7=E9=80=80=E6=AC=BEAPI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../paymentdemo/PaymentDemoApplication.java | 3 +
.../controller/WxPayController.java | 14 +++
.../paymentdemo/service/OrderInfoService.java | 14 +++
.../service/RefundInfoService.java | 12 ++
.../paymentdemo/service/WxPayService.java | 28 +++++
.../service/impl/OrderInfoServiceImpl.java | 20 ++++
.../service/impl/RefundInfoServiceImpl.java | 55 +++++++++
.../service/impl/WxPayServiceImpl.java | 111 ++++++++++++++++++
.../hongyi/paymentdemo/task/WxPayTask.java | 56 +++++++++
.../src/main/resources/wxpay.properties | 2 +-
10 files changed, 314 insertions(+), 1 deletion(-)
create mode 100644 payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/PaymentDemoApplication.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/PaymentDemoApplication.java
index 8616977..5ea4681 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/PaymentDemoApplication.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/PaymentDemoApplication.java
@@ -2,8 +2,11 @@ package com.hongyi.paymentdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
+// 引入Spring Task
+@EnableScheduling
public class PaymentDemoApplication {
public static void main(String[] args) {
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
index 2b55166..5c69971 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
@@ -95,4 +95,18 @@ public class WxPayController {
wxPayService.cancelOrder(orderNo);
return R.ok().setMessage("订单已取消");
}
+
+ @GetMapping("/query/{orderNo}")
+ public R queryOrder(@PathVariable String orderNo) throws IOException {
+ log.info("查询订单");
+ String result = wxPayService.queryOrder(orderNo);
+ return R.ok().setMessage("查询成功").data("result", result);
+ }
+
+ @PostMapping("/refunds/{orderNo}/{reason}")
+ public R refunds(@PathVariable String orderNo, @PathVariable String reason) throws IOException {
+ log.info("申请退款");
+ wxPayService.refund(orderNo, reason);
+ return R.ok();
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
index b92626a..ebd9041 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/OrderInfoService.java
@@ -40,4 +40,18 @@ public interface OrderInfoService extends IService {
* @return
*/
String getOrderStatus(String orderNo);
+
+ /**
+ * 查询超过minutes分钟的未支付订单
+ * @param minutes
+ * @return
+ */
+ List getNoPayOrderByDuration(int minutes);
+
+ /**
+ * 根据订单号获得订单
+ * @param orderNo
+ * @return
+ */
+ OrderInfo getOrderByOrderNo(String orderNo);
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java
index ff21f56..ce4c550 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java
@@ -4,5 +4,17 @@ import com.hongyi.paymentdemo.entity.RefundInfo;
import com.baomidou.mybatisplus.extension.service.IService;
public interface RefundInfoService extends IService {
+ /**
+ * 根据订单号和退款原因创建退款单记录
+ * @param orderNo
+ * @param reason
+ * @return
+ */
+ RefundInfo createRefundByOrderNo(String orderNo, String reason);
+ /**
+ * 更新退款单
+ * @param content
+ */
+ void updateRefund(String content);
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
index a4de494..3cd8224 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
@@ -14,5 +14,33 @@ public interface WxPayService {
void processOrder(Map bodyMap) throws GeneralSecurityException;
+ /**
+ * 通过订单号取消订单
+ * @param orderNo
+ * @throws IOException
+ */
void cancelOrder(String orderNo) throws IOException;
+
+ /**
+ * 通过订单号查询订单
+ * @param orderNo
+ * @return
+ * @throws IOException
+ */
+ String queryOrder(String orderNo) throws IOException;
+
+ /**
+ * 根据订单号查询微信支付查单接口,核实订单状态
+ * 如果订单已经支付,则更新商户端订单状态
+ * 如果订单未支付,则调用关单接口,并更新商户端订单状态
+ * @param orderNo
+ */
+ void checkOrderStatus(String orderNo) throws IOException;
+
+ /**
+ * 根据订单号退款
+ * @param orderNo
+ * @param reason
+ */
+ void refund(String orderNo, String reason) throws IOException;
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
index 2c292b1..f7fbad3 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/OrderInfoServiceImpl.java
@@ -13,6 +13,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.Instant;
import java.util.List;
@Service
@@ -82,6 +84,22 @@ public class OrderInfoServiceImpl extends ServiceImpl getNoPayOrderByDuration(int minutes) {
+ Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType())
+ .le("create_time", instant);
+ return orderInfoMapper.selectList(queryWrapper);
+ }
+
+ @Override
+ public OrderInfo getOrderByOrderNo(String orderNo) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("order_no", orderNo);
+ return orderInfoMapper.selectOne(queryWrapper);
+ }
+
/**
* 根据商品id查询未支付的订单
* @param productId
@@ -94,4 +112,6 @@ public class OrderInfoServiceImpl extends ServiceImpl implements RefundInfoService {
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ @Resource
+ private RefundInfoMapper refundInfoMapper;
+
+ @Override
+ public RefundInfo createRefundByOrderNo(String orderNo, String reason) {
+ // 根据订单号获取订单信息
+ OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
+ // 根据订单号生成退款单
+ RefundInfo refundInfo = new RefundInfo();
+ refundInfo.setOrderNo(orderNo);
+ refundInfo.setRefundNo(OrderNoUtils.getRefundNo());
+ refundInfo.setTotalFee(orderInfo.getTotalFee());
+ refundInfo.setRefund(orderInfo.getTotalFee());
+ refundInfo.setReason(reason);
+ // 保存
+ refundInfoMapper.insert(refundInfo);
+ return refundInfo;
+ }
+
+ @Override
+ public void updateRefund(String content) {
+ //将json字符串转换成Map
+ Gson gson = new Gson();
+ Map resultMap = gson.fromJson(content, HashMap.class);
+ //根据退款单编号修改退款单
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));
+ //设置要修改的字段
+ RefundInfo refundInfo = new RefundInfo();
+ refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号
+ //查询退款和申请退款中的返回参数
+ if(resultMap.get("status") != null){
+ refundInfo.setRefundStatus(resultMap.get("status"));//退款状态
+ refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
+ }
+ //退款回调中的回调参数
+ if(resultMap.get("refund_status") != null){
+ refundInfo.setRefundStatus(resultMap.get("refund_status")); //退款状态
+ refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段
+ }
+ //更新退款单
+ refundInfoMapper.update(refundInfo, queryWrapper);
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
index 6084ae5..8c6603a 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
@@ -3,9 +3,11 @@ package com.hongyi.paymentdemo.service.impl;
import com.google.gson.Gson;
import com.hongyi.paymentdemo.config.WxPayConfig;
import com.hongyi.paymentdemo.entity.OrderInfo;
+import com.hongyi.paymentdemo.entity.RefundInfo;
import com.hongyi.paymentdemo.enums.OrderStatus;
import com.hongyi.paymentdemo.enums.wxpay.WxApiType;
import com.hongyi.paymentdemo.enums.wxpay.WxNotifyType;
+import com.hongyi.paymentdemo.enums.wxpay.WxTradeState;
import com.hongyi.paymentdemo.service.OrderInfoService;
import com.hongyi.paymentdemo.service.PaymentInfoService;
import com.hongyi.paymentdemo.service.WxPayService;
@@ -13,11 +15,13 @@ import com.hongyi.paymentdemo.util.OrderNoUtils;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
@@ -49,6 +53,9 @@ public class WxPayServiceImpl implements WxPayService {
@Resource
private PaymentInfoService paymentInfoService;
+ @Resource
+ private RefundInfoServiceImpl refundInfoService;
+
private final ReentrantLock lock = new ReentrantLock();
/**
@@ -174,6 +181,110 @@ public class WxPayServiceImpl implements WxPayService {
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
}
+ @Override
+ public String queryOrder(String orderNo) throws IOException {
+ log.info("查单接口调用 ====> {}", orderNo);
+ String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
+ url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
+ HttpGet httpGet = new HttpGet(url);
+ httpGet.setHeader("Accept", "application/json");
+ // 完成签名并执行请求
+ CloseableHttpResponse response = httpClient.execute(httpGet);
+ try {
+ // 获取响应体
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ // 获取响应状态码
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) { //处理成功
+ log.info("success,return body = " + bodyAsString);
+ } else if (statusCode == 204) { //处理成功,无返回Body
+ log.info("success");
+ } else {
+ log.info("failed,resp code = " + statusCode+ ",return body = " + bodyAsString);
+ throw new IOException("request failed");
+ }
+ return bodyAsString;
+ } finally {
+ response.close();
+ }
+ }
+
+ @Override
+ public void checkOrderStatus(String orderNo) throws IOException {
+ log.warn("根据订单号核实订单状态 ====> {}", orderNo);
+ // 调用查单接口
+ String result = this.queryOrder(orderNo);
+ Gson gson = new Gson();
+ Map resultMap = gson.fromJson(result, HashMap.class);
+ // 获取微信支付端的订单状态
+ Object tradeState = resultMap.get("trade_state");
+ // 判断订单状态
+ if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
+ log.warn("核实订单已支付 ====> {}", orderNo);
+ // 更新本地的订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
+ // 记录支付日志
+ paymentInfoService.createPaymentInfo(gson.fromJson(result, HashMap.class));
+ }
+ if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
+ log.info("核实订单未支付 ====> {}", tradeState);
+ // 调用关闭订单接口
+ this.closeOrder(orderNo);
+ // 更新本地的订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
+ }
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void refund(String orderNo, String reason) throws IOException {
+ log.info("创建退款单记录");
+ //根据订单编号创建退款单
+ RefundInfo refundsInfo = refundInfoService.createRefundByOrderNo(orderNo, reason);
+ log.info("调用退款API");
+ //调用统一下单API
+ String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
+ HttpPost httpPost = new HttpPost(url);
+ // 请求body参数
+ Gson gson = new Gson();
+ Map paramsMap = new HashMap();
+ paramsMap.put("out_trade_no", orderNo);//订单编号
+ paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
+ paramsMap.put("reason",reason);//退款原因
+ paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址
+ Map amountMap = new HashMap();
+ amountMap.put("refund", refundsInfo.getRefund());//退款金额
+ amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
+ amountMap.put("currency", "CNY");//退款币种
+ paramsMap.put("amount", amountMap); //将参数转换成json字符串
+ String jsonParams = gson.toJson(paramsMap);
+ log.info("请求参数 ===> {}" + jsonParams);
+ StringEntity entity = new StringEntity(jsonParams,"utf-8");
+ entity.setContentType("application/json");//设置请求报文格式
+ httpPost.setEntity(entity);//将请求报文放入请求对象
+ httpPost.setHeader("Accept", "application/json");//设置响应报文格式
+ //完成签名并执行请求,并完成验签
+ CloseableHttpResponse response = httpClient.execute(httpPost);
+ try {
+ //解析响应结果
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) {
+ log.info("成功, 退款返回结果 = " + bodyAsString);
+ } else if (statusCode == 204) {
+ log.info("成功");
+ } else {
+ throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款 返回结果 = " + bodyAsString);
+ }
+ //更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);
+ //更新退款单
+ refundInfoService.updateRefund(bodyAsString);
+ } finally {
+ response.close();
+ }
+ }
+
/**
* 关闭订单接口的调用
* @param orderNo
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java
new file mode 100644
index 0000000..4f62794
--- /dev/null
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java
@@ -0,0 +1,56 @@
+package com.hongyi.paymentdemo.task;
+
+import com.hongyi.paymentdemo.entity.OrderInfo;
+import com.hongyi.paymentdemo.service.OrderInfoService;
+import com.hongyi.paymentdemo.service.WxPayService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @Author Kisugi Takumi
+ * @Date 2022/7/4 14:34
+ * @Version 1.0
+ */
+@Component
+@Slf4j
+public class WxPayTask {
+
+ @Resource
+ private OrderInfoService orderInfoService;
+
+ @Resource
+ private WxPayService wxPayService;
+
+ /**
+ * 秒 分 时 日 月 周
+ * * 每秒都执行
+ * 1-3: 从第1s开始执行,到第3s结束执行
+ * 0/3: 从第0s开始,每隔3s执行1次
+ * 1,2,3: 在指定的第1,2,3s执行1次
+ * 日和周不能同时指定,?为不指定
+ */
+// @Scheduled(cron = "0/3 * * * * ?")
+// public void task1() {
+// log.info("task1被执行...");
+// }
+
+ /**
+ * 每隔30s执行一次,查询创建超过5分钟,并且未支付的订单
+ */
+ @Scheduled(cron = "0/30 * * * * ?")
+ public void orderConfirm() throws IOException {
+ log.info("orderConfirm被执行...");
+ List orderInfoList = orderInfoService.getNoPayOrderByDuration(1);
+ for (OrderInfo orderInfo : orderInfoList) {
+ String orderNo = orderInfo.getOrderNo();
+ log.warn("超时订单 ====> {}", orderNo);
+ // 核实订单状态:调用微信支付查单接口
+ wxPayService.checkOrderStatus(orderNo);
+ }
+ }
+}
diff --git a/payment-demo/src/main/resources/wxpay.properties b/payment-demo/src/main/resources/wxpay.properties
index a25d3e7..2df1e31 100644
--- a/payment-demo/src/main/resources/wxpay.properties
+++ b/payment-demo/src/main/resources/wxpay.properties
@@ -13,4 +13,4 @@ wxpay.app-id=wx74862e0dfcf69954
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要修改这个配置
-wxpay.notify-domain=https://84bb-117-174-85-8.ap.ngrok.io
+wxpay.notify-domain=https://4fcc-117-174-85-8.ap.ngrok.io
--
Gitee
From 7b9b61d58e9faf7c5fe5ce61687a64deeb9b3d2f Mon Sep 17 00:00:00 2001
From: zenghongyi <277382367@qq.com>
Date: Mon, 4 Jul 2022 20:46:25 +0800
Subject: [PATCH 9/9] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=20?=
=?UTF-8?q?=E9=80=80=E6=AC=BE=E9=80=9A=E7=9F=A5=20=E8=B4=A6=E5=8D=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controller/WxPayController.java | 60 +++++++
.../service/RefundInfoService.java | 9 ++
.../paymentdemo/service/WxPayService.java | 36 +++++
.../service/impl/RefundInfoServiceImpl.java | 15 ++
.../service/impl/WxPayServiceImpl.java | 146 +++++++++++++++++-
.../hongyi/paymentdemo/task/WxPayTask.java | 21 +++
6 files changed, 285 insertions(+), 2 deletions(-)
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
index 5c69971..78ef693 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/controller/WxPayController.java
@@ -109,4 +109,64 @@ public class WxPayController {
wxPayService.refund(orderNo, reason);
return R.ok();
}
+
+ @GetMapping("/query-refund/{refundNo}")
+ public R queryRefund(@PathVariable String refundNo) throws Exception {
+ log.info("查询退款");
+ String result = wxPayService.queryRefund(refundNo);
+ return R.ok().setMessage("查询成功").data("result", result);
+ }
+
+ @PostMapping("/refunds/notify")
+ public String refundsNotify(HttpServletRequest request, HttpServletResponse response){
+ log.info("退款通知执行");
+ Gson gson = new Gson();
+ Map map = new HashMap<>();
+ //应答对象
+ try {
+ //处理通知参数
+ String body = HttpUtils.readData(request);
+ Map bodyMap = gson.fromJson(body, HashMap.class);
+ String requestId = (String)bodyMap.get("id");
+ log.info("支付通知的id ===> {}", requestId);
+ //签名的验证
+ WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);
+ if(!wechatPay2ValidatorForRequest.validate(request)){
+ log.error("通知验签失败");
+ //失败应答
+ response.setStatus(500);
+ map.put("code", "ERROR");
+ map.put("message", "通知验签失败");
+ return gson.toJson(map);
+ }
+ log.info("通知验签成功");
+ //处理退款单
+ wxPayService.processRefund(bodyMap);
+ //成功应答
+ response.setStatus(200);
+ map.put("code", "SUCCESS");
+ map.put("message", "成功");
+ return gson.toJson(map);
+ } catch (Exception e) {
+ e.printStackTrace();
+ //失败应答
+ response.setStatus(500);
+ map.put("code", "ERROR");
+ map.put("message", "失败"); return gson.toJson(map);
+ }
+ }
+
+ @GetMapping("/querybill/{billDate}/{type}")
+ public R queryTradeBill( @PathVariable String billDate, @PathVariable String type) throws Exception {
+ log.info("获取账单url");
+ String downloadUrl = wxPayService.queryBill(billDate, type);
+ return R.ok().setMessage("获取账单url成功").data("downloadUrl", downloadUrl);
+ }
+
+ @GetMapping("/downloadbill/{billDate}/{type}")
+ public R downloadBill( @PathVariable String billDate, @PathVariable String type) throws Exception {
+ log.info("下载账单");
+ String result = wxPayService.downloadBill(billDate, type);
+ return R.ok().data("result", result);
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java
index ce4c550..05c868e 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/RefundInfoService.java
@@ -3,6 +3,8 @@ package com.hongyi.paymentdemo.service;
import com.hongyi.paymentdemo.entity.RefundInfo;
import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+
public interface RefundInfoService extends IService {
/**
* 根据订单号和退款原因创建退款单记录
@@ -17,4 +19,11 @@ public interface RefundInfoService extends IService {
* @param content
*/
void updateRefund(String content);
+
+ /**
+ * 找出申请退款超过minutes分钟并且未成功的退款单
+ * @param minutes
+ * @return
+ */
+ List getNoRefundOrderByDuration(int minutes);
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
index 3cd8224..ef21138 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/WxPayService.java
@@ -43,4 +43,40 @@ public interface WxPayService {
* @param reason
*/
void refund(String orderNo, String reason) throws IOException;
+
+ /**
+ * 根据退款单号核实退款单状态
+ * @param refundNo
+ * @return
+ */
+ String queryRefund(String refundNo) throws IOException;
+
+ /**
+ * 根据退款单号核实退款单状态
+ * @param refundNo
+ */
+ void checkRefundStatus(String refundNo) throws IOException;
+
+ /**
+ * 退款结果通知
+ * 退款状态改变后,微信会把相关退款结果发送给商户。
+ * @param bodyMap
+ */
+ void processRefund(Map bodyMap) throws Exception;
+
+ /**
+ * 申请账单
+ * @param billDate
+ * @param type
+ * @return
+ */
+ String queryBill(String billDate, String type) throws Exception;
+
+ /**
+ * 下载账单
+ * @param billDate
+ * @param type
+ * @return
+ */
+ String downloadBill(String billDate, String type) throws Exception;
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/RefundInfoServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/RefundInfoServiceImpl.java
index f443e9b..6cb75ee 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/RefundInfoServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/RefundInfoServiceImpl.java
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.Gson;
import com.hongyi.paymentdemo.entity.OrderInfo;
import com.hongyi.paymentdemo.entity.RefundInfo;
+import com.hongyi.paymentdemo.enums.wxpay.WxRefundStatus;
import com.hongyi.paymentdemo.mapper.RefundInfoMapper;
import com.hongyi.paymentdemo.service.OrderInfoService;
import com.hongyi.paymentdemo.service.RefundInfoService;
@@ -12,7 +13,10 @@ import com.hongyi.paymentdemo.util.OrderNoUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.Instant;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
@Service
@@ -64,4 +68,15 @@ public class RefundInfoServiceImpl extends ServiceImpl getNoRefundOrderByDuration(int minutes) {
+ //minutes分钟之前的时间
+ Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("refund_status", WxRefundStatus.PROCESSING.getType());
+ queryWrapper.le("create_time", instant);
+ List refundInfoList = refundInfoMapper.selectList(queryWrapper);
+ return refundInfoList;
+ }
}
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
index 8c6603a..587d19f 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/service/impl/WxPayServiceImpl.java
@@ -7,11 +7,12 @@ import com.hongyi.paymentdemo.entity.RefundInfo;
import com.hongyi.paymentdemo.enums.OrderStatus;
import com.hongyi.paymentdemo.enums.wxpay.WxApiType;
import com.hongyi.paymentdemo.enums.wxpay.WxNotifyType;
+import com.hongyi.paymentdemo.enums.wxpay.WxRefundStatus;
import com.hongyi.paymentdemo.enums.wxpay.WxTradeState;
import com.hongyi.paymentdemo.service.OrderInfoService;
import com.hongyi.paymentdemo.service.PaymentInfoService;
+import com.hongyi.paymentdemo.service.RefundInfoService;
import com.hongyi.paymentdemo.service.WxPayService;
-import com.hongyi.paymentdemo.util.OrderNoUtils;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -54,7 +55,7 @@ public class WxPayServiceImpl implements WxPayService {
private PaymentInfoService paymentInfoService;
@Resource
- private RefundInfoServiceImpl refundInfoService;
+ private RefundInfoService refundInfoService;
private final ReentrantLock lock = new ReentrantLock();
@@ -285,6 +286,147 @@ public class WxPayServiceImpl implements WxPayService {
}
}
+ @Override
+ public String queryRefund(String refundNo) throws IOException {
+ log.info("查询退款接口调用 ===> {}", refundNo);
+ String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
+ url = wxPayConfig.getDomain().concat(url);
+ //创建远程Get 请求对象
+ HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Accept", "application/json");
+ //完成签名并执行请求
+ CloseableHttpResponse response = httpClient.execute(httpGet);
+ try {
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) {
+ log.info("成功, 查询退款返回结果 = " + bodyAsString);
+ } else if (statusCode == 204) {
+ log.info("成功");
+ } else {
+ throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);
+ }
+ return bodyAsString;
+ } finally {
+ response.close();
+ }
+ }
+
+ @Override
+ public void checkRefundStatus(String refundNo) throws IOException {
+ log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);
+ //调用查询退款单接口
+ String result = this.queryRefund(refundNo);
+ //组装json请求体字符串
+ Gson gson = new Gson();
+ Map resultMap = gson.fromJson(result, HashMap.class);
+ //获取微信支付端退款状态
+ String status = resultMap.get("status");
+ String orderNo = resultMap.get("out_trade_no");
+ if (WxRefundStatus.SUCCESS.getType().equals(status)) {
+ log.warn("核实订单已退款成功 ===> {}", refundNo);
+ //如果确认退款成功,则更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS); //更新退款单
+ refundInfoService.updateRefund(result);
+ }
+ if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
+ log.warn("核实订单退款异常 ===> {}", refundNo);
+ //如果确认退款成功,则更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
+ //更新退款单
+ refundInfoService.updateRefund(result);
+ }
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void processRefund(Map bodyMap) throws Exception {
+ log.info("退款单");
+ //解密报文
+ String plainText = decryptFromResource(bodyMap);
+ //将明文转换成map
+ Gson gson = new Gson();
+ HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
+ String orderNo = (String)plainTextMap.get("out_trade_no");
+ if(lock.tryLock()){
+ try {
+ String orderStatus = orderInfoService.getOrderStatus(orderNo);
+ if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
+ return;
+ }
+ //更新订单状态
+ orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
+ //更新退款单
+ refundInfoService.updateRefund(plainText);
+ } finally {
+ //要主动释放锁
+ lock.unlock();
+ }
+ }
+ }
+
+ @Override
+ public String queryBill(String billDate, String type) throws Exception {
+ log.warn("申请账单接口调用 {}", billDate);
+ String url = "";
+ if("tradebill".equals(type)){
+ url = WxApiType.TRADE_BILLS.getType();
+ }else if("fundflowbill".equals(type)){
+ url = WxApiType.FUND_FLOW_BILLS.getType();
+ }else{
+ throw new RuntimeException("不支持的账单类型");
+ }
+ url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
+ //创建远程Get 请求对象
+ HttpGet httpGet = new HttpGet(url);
+ httpGet.addHeader("Accept", "application/json");
+ //使用wxPayClient发送请求得到响应
+ CloseableHttpResponse response = httpClient.execute(httpGet);
+ try {
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) {
+ log.info("成功, 申请账单返回结果 = " + bodyAsString);
+ } else if (statusCode == 204) {
+ log.info("成功");
+ }
+ else {
+ throw new RuntimeException("申请账单异常, 响应码 = " + statusCode+ ", 申请账单返回结果 = " + bodyAsString);
+ }
+ //获取账单下载地址
+ Gson gson = new Gson();
+ Map resultMap = gson.fromJson(bodyAsString, HashMap.class);
+ return resultMap.get("download_url");
+ } finally {
+ response.close();
+ }
+ }
+
+ @Override
+ public String downloadBill(String billDate, String type) throws Exception {
+ log.warn("下载账单接口调用 {}, {}", billDate, type);
+ //获取账单url地址
+ String downloadUrl = this.queryBill(billDate, type);
+ //创建远程Get 请求对象
+ HttpGet httpGet = new HttpGet(downloadUrl);
+ httpGet.addHeader("Accept", "application/json");
+ //使用wxPayClient发送请求得到响应
+ CloseableHttpResponse response = httpClient.execute(httpGet);
+ try {
+ String bodyAsString = EntityUtils.toString(response.getEntity());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) {
+ log.info("成功, 下载账单返回结果 = " + bodyAsString);
+ } else if (statusCode == 204) {
+ log.info("成功");
+ } else {
+ throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + bodyAsString);
+ }
+ return bodyAsString;
+ } finally {
+ response.close();
+ }
+ }
+
/**
* 关闭订单接口的调用
* @param orderNo
diff --git a/payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java b/payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java
index 4f62794..7b7b01f 100644
--- a/payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java
+++ b/payment-demo/src/main/java/com/hongyi/paymentdemo/task/WxPayTask.java
@@ -1,7 +1,9 @@
package com.hongyi.paymentdemo.task;
import com.hongyi.paymentdemo.entity.OrderInfo;
+import com.hongyi.paymentdemo.entity.RefundInfo;
import com.hongyi.paymentdemo.service.OrderInfoService;
+import com.hongyi.paymentdemo.service.RefundInfoService;
import com.hongyi.paymentdemo.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
@@ -26,6 +28,9 @@ public class WxPayTask {
@Resource
private WxPayService wxPayService;
+ @Resource
+ private RefundInfoService refundInfoService;
+
/**
* 秒 分 时 日 月 周
* * 每秒都执行
@@ -53,4 +58,20 @@ public class WxPayTask {
wxPayService.checkOrderStatus(orderNo);
}
}
+
+ /**
+ * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未成功的退款单
+ */
+ @Scheduled(cron = "0/30 * * * * ?")
+ public void refundConfirm() throws Exception {
+ log.info("refundConfirm 被执行......");
+ //找出申请退款超过5分钟并且未成功的退款单
+ List refundInfoList = refundInfoService.getNoRefundOrderByDuration(5);
+ for (RefundInfo refundInfo : refundInfoList) {
+ String refundNo = refundInfo.getRefundNo();
+ log.warn("超时未退款的退款单号 ===> {}", refundNo);
+ //核实订单状态:调用微信支付查询退款接口
+ wxPayService.checkRefundStatus(refundNo);
+ }
+ }
}
--
Gitee