From 37c501ebfd6d78c2e049714b423fda14b2608f79 Mon Sep 17 00:00:00 2001 From: chen-xin-zhi <3588068430@qq.com> Date: Tue, 10 Dec 2024 09:25:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E5=95=86=E5=93=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 12 +- .../cultural/heritage/config/WxPayConfig.java | 156 +++--- .../good/AppointmentDateController.java | 4 +- .../controller/wx/WeChatPayController.java | 240 ++++---- .../controller/wxPayDemo/HttpUtils.java | 43 ++ .../WechatPay2ValidatorForRequest.java | 110 ++++ .../controller/wxPayDemo/WxApiType.java | 55 ++ .../controller/wxPayDemo/WxNotifyType.java | 26 + .../controller/wxPayDemo/WxPayConfig.java | 142 +++++ .../controller/wxPayDemo/WxPayController.java | 361 ++++++++++++ .../wxPayDemo/WxPayNotifyController.java | 124 +++++ .../heritage/service/wxpay/WeChatService.java | 106 ++-- .../service/wxpay/impl/WeChatServiceImpl.java | 526 +++++++++--------- src/main/resources/application.yml | 10 +- 14 files changed, 1393 insertions(+), 522 deletions(-) create mode 100644 src/main/java/com/cultural/heritage/controller/wxPayDemo/HttpUtils.java create mode 100644 src/main/java/com/cultural/heritage/controller/wxPayDemo/WechatPay2ValidatorForRequest.java create mode 100644 src/main/java/com/cultural/heritage/controller/wxPayDemo/WxApiType.java create mode 100644 src/main/java/com/cultural/heritage/controller/wxPayDemo/WxNotifyType.java create mode 100644 src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayConfig.java create mode 100644 src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayController.java create mode 100644 src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayNotifyController.java diff --git a/pom.xml b/pom.xml index 327420a..c523c8b 100644 --- a/pom.xml +++ b/pom.xml @@ -178,12 +178,20 @@ + + + + + + + com.github.wechatpay-apiv3 - wechatpay-java - 0.2.10 + wechatpay-apache-httpclient + 0.3.0 + diff --git a/src/main/java/com/cultural/heritage/config/WxPayConfig.java b/src/main/java/com/cultural/heritage/config/WxPayConfig.java index 338b774..87306ad 100644 --- a/src/main/java/com/cultural/heritage/config/WxPayConfig.java +++ b/src/main/java/com/cultural/heritage/config/WxPayConfig.java @@ -1,78 +1,78 @@ -package com.cultural.heritage.config; - -import com.wechat.pay.java.core.RSAAutoCertificateConfig; -import com.wechat.pay.java.core.util.IOUtil; -import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; -import com.wechat.pay.java.service.refund.RefundService; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -@Data -@Slf4j -@Configuration -@Component("WxPayConfig") -@ConfigurationProperties(prefix = "wx.pay") -public class WxPayConfig { - - private String appId; - - private String apiV3Key; - - private String notifyUrl; - - private String merchantId; - - private String privateKeyPath; - - private String merchantSerialNumber; - - // RSA配置 - private RSAAutoCertificateConfig RSAConfig; - - // JSAPI支付 - private JsapiServiceExtension jsapiServiceExtension; - - // 退款 - private RefundService refundService; - - /** - * 初始化配置 - */ - @Bean - public boolean initWxPayConfig() throws IOException { - this.RSAConfig = buildRSAAutoCertificateConfig(); - this.jsapiServiceExtension = buildJsapiServiceExtension(RSAConfig); - this.refundService = buildRefundService(RSAConfig); - return true; - } - - // 构建并使用自动更新平台证书的RSA配置,一个商户号只能初始化一个配置,否则会因为重复的下载任务报错 - private RSAAutoCertificateConfig buildRSAAutoCertificateConfig() throws IOException { - // 将 resource 目录下的文件转为 InputStream,然后利用 IOUtil.toString(inputStream) 转化为密钥 - String privateKey = IOUtil.toString(new ClassPathResource(privateKeyPath).getInputStream()); - return new RSAAutoCertificateConfig.Builder() - .merchantId(merchantId) - .privateKey(privateKey) - .merchantSerialNumber(merchantSerialNumber) - .apiV3Key(apiV3Key) - .build(); - } - - // 构建JSAPI支付 - private JsapiServiceExtension buildJsapiServiceExtension(RSAAutoCertificateConfig config) { - return new JsapiServiceExtension.Builder().config(config).build(); - } - - // 构建退款 - private RefundService buildRefundService(RSAAutoCertificateConfig config) { - return new RefundService.Builder().config(config).build(); - } - -} +//package com.cultural.heritage.config; +// +//import com.wechat.pay.java.core.RSAAutoCertificateConfig; +//import com.wechat.pay.java.core.util.IOUtil; +//import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; +//import com.wechat.pay.java.service.refund.RefundService; +//import lombok.Data; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.boot.context.properties.ConfigurationProperties; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.core.io.ClassPathResource; +//import org.springframework.stereotype.Component; +// +//import java.io.IOException; +// +//@Data +//@Slf4j +//@Configuration +//@Component("WxPayConfig") +//@ConfigurationProperties(prefix = "wx.pay") +//public class WxPayConfig { +// +// private String appId; +// +// private String apiV3Key; +// +// private String notifyUrl; +// +// private String merchantId; +// +// private String privateKeyPath; +// +// private String merchantSerialNumber; +// +// // RSA配置 +// private RSAAutoCertificateConfig RSAConfig; +// +// // JSAPI支付 +// private JsapiServiceExtension jsapiServiceExtension; +// +// // 退款 +// private RefundService refundService; +// +// /** +// * 初始化配置 +// */ +// @Bean +// public boolean initWxPayConfig() throws IOException { +// this.RSAConfig = buildRSAAutoCertificateConfig(); +// this.jsapiServiceExtension = buildJsapiServiceExtension(RSAConfig); +// this.refundService = buildRefundService(RSAConfig); +// return true; +// } +// +// // 构建并使用自动更新平台证书的RSA配置,一个商户号只能初始化一个配置,否则会因为重复的下载任务报错 +// private RSAAutoCertificateConfig buildRSAAutoCertificateConfig() throws IOException { +// // 将 resource 目录下的文件转为 InputStream,然后利用 IOUtil.toString(inputStream) 转化为密钥 +// String privateKey = IOUtil.toString(new ClassPathResource(privateKeyPath).getInputStream()); +// return new RSAAutoCertificateConfig.Builder() +// .merchantId(merchantId) +// .privateKey(privateKey) +// .merchantSerialNumber(merchantSerialNumber) +// .apiV3Key(apiV3Key) +// .build(); +// } +// +// // 构建JSAPI支付 +// private JsapiServiceExtension buildJsapiServiceExtension(RSAAutoCertificateConfig config) { +// return new JsapiServiceExtension.Builder().config(config).build(); +// } +// +// // 构建退款 +// private RefundService buildRefundService(RSAAutoCertificateConfig config) { +// return new RefundService.Builder().config(config).build(); +// } +// +//} diff --git a/src/main/java/com/cultural/heritage/controller/good/AppointmentDateController.java b/src/main/java/com/cultural/heritage/controller/good/AppointmentDateController.java index 54eb20f..6ed598e 100644 --- a/src/main/java/com/cultural/heritage/controller/good/AppointmentDateController.java +++ b/src/main/java/com/cultural/heritage/controller/good/AppointmentDateController.java @@ -160,7 +160,7 @@ public class AppointmentDateController { @Operation(summary = "Web端管理员添加预约时间段", description = "参数:预约时间段添加请求体,权限:管理员(admin, boss),方法名:addAppointmentDate") @Transactional(rollbackFor = Exception.class) @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) - public BaseResponse addTimePeriod(@RequestBody TimePeriodSingleAddRequest timePeriodSingleAddRequest) { + public BaseResponse addTimePeriod(@RequestBody TimePeriodSingleAddRequest timePeriodSingleAddRequest) { if (timePeriodSingleAddRequest == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } @@ -168,7 +168,7 @@ public class AppointmentDateController { BeanUtils.copyProperties(timePeriodSingleAddRequest, timePeriod); boolean save = timePeriodService.save(timePeriod); ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "预约时间段添加失败"); - return ResultUtils.success(true); + return ResultUtils.success(timePeriod.getId()); } diff --git a/src/main/java/com/cultural/heritage/controller/wx/WeChatPayController.java b/src/main/java/com/cultural/heritage/controller/wx/WeChatPayController.java index f80ff1d..35fa36e 100644 --- a/src/main/java/com/cultural/heritage/controller/wx/WeChatPayController.java +++ b/src/main/java/com/cultural/heritage/controller/wx/WeChatPayController.java @@ -1,120 +1,120 @@ -package com.cultural.heritage.controller.wx; - - -import com.cultural.heritage.annotation.AuthCheck; -import com.cultural.heritage.common.BaseResponse; -import com.cultural.heritage.common.ErrorCode; -import com.cultural.heritage.common.ResultUtils; -import com.cultural.heritage.constant.UserConstant; -import com.cultural.heritage.exception.BusinessException; -import com.cultural.heritage.exception.ThrowUtils; -import com.cultural.heritage.model.dto.CommonRequest; -import com.cultural.heritage.model.entity.Order; -import com.cultural.heritage.model.entity.User; -import com.cultural.heritage.service.order.OrderService; -import com.cultural.heritage.service.user.UserService; -import com.cultural.heritage.service.wxpay.WeChatService; -import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; -import com.wechat.pay.java.service.payments.model.Transaction; -import com.wechat.pay.java.service.refund.model.Refund; -import com.wechat.pay.java.service.refund.model.RefundNotification; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; - -/** - * 微信小程序相关接口 - **/ -@Slf4j -@RestController -@Tag(name = "微信支付接口") -@RequestMapping("/wechat") -public class WeChatPayController { - - @Resource - private UserService userService; - - @Resource - private OrderService ordersService; - - @Resource - private WeChatService weChatService; - - - /** - * JSAPI 下单 - */ - @PostMapping("/payment/create") - public BaseResponse createPayment(@RequestBody CommonRequest commonRequest, HttpServletRequest request) { - User loginUser = userService.getLoginUser(request); - String miniOpenId = loginUser.getMiniOpenId(); - ThrowUtils.throwIf(miniOpenId == null, ErrorCode.NOT_FOUND_ERROR, "不是小程序用户"); - Long orderId = commonRequest.getId(); - Order order = ordersService.getById(orderId); - ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在"); -// ThrowUtils.throwIf(order.getState() != 0, ErrorCode.OPERATION_ERROR, "订单状态错误"); - if (!loginUser.getId().equals(order.getUserId())) { - throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "你不是该订单用户!"); - } - PrepayWithRequestPaymentResponse response = weChatService.createPayment(String.valueOf(orderId), miniOpenId, order.getTotalAmount()); - return ResultUtils.success(response); - } - - /** - * JSAPI 下单回调 - */ - @PostMapping("/payment/callback") - @Transactional(rollbackFor = Exception.class) - public synchronized BaseResponse callbackPayment(HttpServletRequest request) throws IOException { - // 获取下单信息 - Transaction transaction = weChatService.getTransactionInfo(request); - System.out.println("下单信息:" + transaction); - // 支付回调 - boolean result = weChatService.paymentCallback(transaction); - ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "微信支付回调失败"); - return ResultUtils.success(true); - } - - /** - * 退款(仅管理员和商家) - */ - @PostMapping("/refund/create") - @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) - public BaseResponse createRefund(@RequestBody CommonRequest commonRequest) { - Long orderId = commonRequest.getId(); - Order order = ordersService.getById(orderId); - ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在"); - Refund refund = weChatService.refundPayment(String.valueOf(orderId), order.getTotalAmount()); - return ResultUtils.success(refund); - } - - /** - * 退款回调 - */ - @PostMapping("/refund/callback") - public BaseResponse callbackRefund(HttpServletRequest request) { - // 获取退款信息 - RefundNotification refundNotification = weChatService.getRefundInfo(request); - // 退款回调 - boolean result = weChatService.refundCallback(refundNotification); - ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "退款回调失败"); - return ResultUtils.success(true); - } - - /** - * 发送订阅消息 - */ - @GetMapping("/refund/callback") - public BaseResponse testSendMessage() { - String miniOpenId = "o0o_B5CMLFiOs96dJZwtkyHcJzcM"; - String templateId = "MK13FfX0XxsPV6m1vi6J8_8Bf7JT8rsayFs9q3f4FW4"; - boolean subscribeMessage = weChatService.sendSubscribeMessage(miniOpenId, templateId); - return ResultUtils.success(subscribeMessage); - } - -} +//package com.cultural.heritage.controller.wx; +// +// +//import com.cultural.heritage.annotation.AuthCheck; +//import com.cultural.heritage.common.BaseResponse; +//import com.cultural.heritage.common.ErrorCode; +//import com.cultural.heritage.common.ResultUtils; +//import com.cultural.heritage.constant.UserConstant; +//import com.cultural.heritage.exception.BusinessException; +//import com.cultural.heritage.exception.ThrowUtils; +//import com.cultural.heritage.model.dto.CommonRequest; +//import com.cultural.heritage.model.entity.Order; +//import com.cultural.heritage.model.entity.User; +//import com.cultural.heritage.service.order.OrderService; +//import com.cultural.heritage.service.user.UserService; +//import com.cultural.heritage.service.wxpay.WeChatService; +//import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; +//import com.wechat.pay.java.service.payments.model.Transaction; +//import com.wechat.pay.java.service.refund.model.Refund; +//import com.wechat.pay.java.service.refund.model.RefundNotification; +//import io.swagger.v3.oas.annotations.tags.Tag; +//import jakarta.annotation.Resource; +//import jakarta.servlet.http.HttpServletRequest; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.web.bind.annotation.*; +// +//import java.io.IOException; +// +///** +// * 微信小程序相关接口 +// **/ +//@Slf4j +//@RestController +//@Tag(name = "微信支付接口") +//@RequestMapping("/wechat") +//public class WeChatPayController { +// +// @Resource +// private UserService userService; +// +// @Resource +// private OrderService ordersService; +// +// @Resource +// private WeChatService weChatService; +// +// +// /** +// * JSAPI 下单 +// */ +// @PostMapping("/payment/create") +// public BaseResponse createPayment(@RequestBody CommonRequest commonRequest, HttpServletRequest request) { +// User loginUser = userService.getLoginUser(request); +// String miniOpenId = loginUser.getMiniOpenId(); +// ThrowUtils.throwIf(miniOpenId == null, ErrorCode.NOT_FOUND_ERROR, "不是小程序用户"); +// Long orderId = commonRequest.getId(); +// Order order = ordersService.getById(orderId); +// ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在"); +//// ThrowUtils.throwIf(order.getState() != 0, ErrorCode.OPERATION_ERROR, "订单状态错误"); +// if (!loginUser.getId().equals(order.getUserId())) { +// throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "你不是该订单用户!"); +// } +// PrepayWithRequestPaymentResponse response = weChatService.createPayment(String.valueOf(orderId), miniOpenId, order.getTotalAmount()); +// return ResultUtils.success(response); +// } +// +// /** +// * JSAPI 下单回调 +// */ +// @PostMapping("/payment/callback") +// @Transactional(rollbackFor = Exception.class) +// public synchronized BaseResponse callbackPayment(HttpServletRequest request) throws IOException { +// // 获取下单信息 +// Transaction transaction = weChatService.getTransactionInfo(request); +// System.out.println("下单信息:" + transaction); +// // 支付回调 +// boolean result = weChatService.paymentCallback(transaction); +// ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "微信支付回调失败"); +// return ResultUtils.success(true); +// } +// +// /** +// * 退款(仅管理员和商家) +// */ +// @PostMapping("/refund/create") +// @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) +// public BaseResponse createRefund(@RequestBody CommonRequest commonRequest) { +// Long orderId = commonRequest.getId(); +// Order order = ordersService.getById(orderId); +// ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在"); +// Refund refund = weChatService.refundPayment(String.valueOf(orderId), order.getTotalAmount()); +// return ResultUtils.success(refund); +// } +// +// /** +// * 退款回调 +// */ +// @PostMapping("/refund/callback") +// public BaseResponse callbackRefund(HttpServletRequest request) { +// // 获取退款信息 +// RefundNotification refundNotification = weChatService.getRefundInfo(request); +// // 退款回调 +// boolean result = weChatService.refundCallback(refundNotification); +// ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "退款回调失败"); +// return ResultUtils.success(true); +// } +// +// /** +// * 发送订阅消息 +// */ +// @GetMapping("/refund/callback") +// public BaseResponse testSendMessage() { +// String miniOpenId = "o0o_B5CMLFiOs96dJZwtkyHcJzcM"; +// String templateId = "MK13FfX0XxsPV6m1vi6J8_8Bf7JT8rsayFs9q3f4FW4"; +// boolean subscribeMessage = weChatService.sendSubscribeMessage(miniOpenId, templateId); +// return ResultUtils.success(subscribeMessage); +// } +// +//} diff --git a/src/main/java/com/cultural/heritage/controller/wxPayDemo/HttpUtils.java b/src/main/java/com/cultural/heritage/controller/wxPayDemo/HttpUtils.java new file mode 100644 index 0000000..d9e3bf7 --- /dev/null +++ b/src/main/java/com/cultural/heritage/controller/wxPayDemo/HttpUtils.java @@ -0,0 +1,43 @@ +package com.cultural.heritage.controller.wxPayDemo; + + +import jakarta.servlet.http.HttpServletRequest; + +import java.io.BufferedReader; +import java.io.IOException; + +/** + * @author weikai + */ +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(); + } + } + } + } +} diff --git a/src/main/java/com/cultural/heritage/controller/wxPayDemo/WechatPay2ValidatorForRequest.java b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WechatPay2ValidatorForRequest.java new file mode 100644 index 0000000..fc57693 --- /dev/null +++ b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WechatPay2ValidatorForRequest.java @@ -0,0 +1,110 @@ +package com.cultural.heritage.controller.wxPayDemo; + + +import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; +import jakarta.servlet.http.HttpServletRequest; +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 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.*; + +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/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxApiType.java b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxApiType.java new file mode 100644 index 0000000..314aeec --- /dev/null +++ b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxApiType.java @@ -0,0 +1,55 @@ +package com.cultural.heritage.controller.wxPayDemo; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum WxApiType { + + /** + * + */ + + /** + * Native下单 + */ + NATIVE_PAY("/v3/pay/transactions/native"), + + /** + * 查询订单 + */ + 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/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxNotifyType.java b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxNotifyType.java new file mode 100644 index 0000000..44dfa26 --- /dev/null +++ b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxNotifyType.java @@ -0,0 +1,26 @@ +package com.cultural.heritage.controller.wxPayDemo; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum WxNotifyType { + + /** + * 支付通知 + */ + NATIVE_NOTIFY("/api/wx-pay/notify/native"), + + + /** + * 退款结果通知 + */ + REFUND_NOTIFY("/api/wx-pay/notify/refunds"); + + /** + * 类型 + */ + private final String type; +} diff --git a/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayConfig.java b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayConfig.java new file mode 100644 index 0000000..ca711a3 --- /dev/null +++ b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayConfig.java @@ -0,0 +1,142 @@ +package com.cultural.heritage.controller.wxPayDemo; + + +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 lombok.extern.slf4j.Slf4j; +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 java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; + +@Configuration +@ConfigurationProperties(prefix = "wx.pay") //读取wxpay节点 +@Data //使用set方法将wxpay节点中的值填充到当前类的属性中 +@Slf4j +public class WxPayConfig { + + // 商户号 + private String mchId; + + // 商户API证书序列号 + private String mchSerialNo; + + // 商户私钥文件 + private String privateKeyPath; + + // APIv3密钥 + private String apiV3Key; + + // APPID + private String appid; + + // 微信服务器地址 + private String domain; + + // 接收结果通知地址 + private String notifyDomain; + + + /** + * 获取商户的私钥文件 + * + * @param filename + * @return + */ + private PrivateKey getPrivateKey(String filename) { + + try { + return PemUtil.loadPrivateKey(new FileInputStream(filename)); + } catch (FileNotFoundException e) { + throw new RuntimeException("私钥文件不存在", e); + } + } + + /** + * 获取签名验证器 + * + * @return + */ + @Bean + public ScheduledUpdateCertificatesVerifier getVerifier() { + + log.info("获取签名验证器"); + + //获取商户私钥 + 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(name = "wxPayClient") + public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) { + + log.info("获取httpClient"); + + //获取商户私钥 + PrivateKey privateKey = getPrivateKey(privateKeyPath); + + WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() + .withMerchant(mchId, mchSerialNo, privateKey) + .withValidator(new WechatPay2Validator(verifier)); + // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient + + // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新 + CloseableHttpClient httpClient = builder.build(); + + return httpClient; + } + + /** + * 获取HttpClient,无需进行应答签名验证,跳过验签的流程 + */ + @Bean(name = "wxPayNoSignClient") + public CloseableHttpClient getWxPayNoSignClient() { + + log.info("无需进行应答签名验证,获取httpClient"); + + + //获取商户私钥 + PrivateKey privateKey = getPrivateKey(privateKeyPath); + + //用于构造HttpClient + WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() + //设置商户信息 + .withMerchant(mchId, mchSerialNo, privateKey) + //无需进行签名验证、通过withValidator((response) -> true)实现 + .withValidator((response) -> true); + + // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新 + CloseableHttpClient httpClient = builder.build(); + + return httpClient; + } + +} diff --git a/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayController.java b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayController.java new file mode 100644 index 0000000..95f470b --- /dev/null +++ b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayController.java @@ -0,0 +1,361 @@ +package com.cultural.heritage.controller.wxPayDemo; + + +import com.google.gson.Gson; +import jakarta.annotation.Resource; +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.web.bind.annotation.*; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/wx-pay") +@Slf4j +public class WxPayController { + + @Resource + private CloseableHttpClient wxPayClient; + + @Resource + //无需应答签名 + private CloseableHttpClient wxPayNoSignClient; + + @Resource + private WxPayConfig wxPayConfig; + + /** + * Native下单 + */ + @PostMapping("/native") + public void nativePay() throws Exception { + + log.info("发起支付请求 v3"); + + log.info("调用统一下单API"); + + //调用统一下单API + HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType())); + + // 请求body参数 + Gson gson = new Gson(); + HashMap paramsMap = new HashMap<>(); + paramsMap.put("appid", wxPayConfig.getAppid());// APPID + paramsMap.put("mchid", wxPayConfig.getMchId());// 商户id + paramsMap.put("description", "魏凯的小商铺"); // 订单描述 + paramsMap.put("out_trade_no", "123456789987"); // 订单号 + paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));// 二维码扫描支付成功后进行回调 + + Map amountMap = new HashMap<>(); + amountMap.put("total", 1); // 金额 以分为单位 这里写1分钱 订单号123456789987对应的金额为1分 + 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"); + + //完成签名并执行请求(这个地方就是mchId与商户证书密钥进行校验) + try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) { + String bodyAsString = EntityUtils.toString(response.getEntity());//响应体 + int statusCode = response.getStatusLine().getStatusCode();//响应状态码 + if (statusCode == 200) { //处理成功 + log.info("成功, 返回结果 = " + bodyAsString); + } else if (statusCode == 204) { //处理成功,无返回Body + log.info("成功"); + } else { + log.info("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString); + throw new IOException("request failed"); + } + //响应结果 + HashMap resultMap = gson.fromJson(bodyAsString, HashMap.class); + //二维码 + String codeUrl = (String) resultMap.get("code_url"); + + log.info("二维码为: " + codeUrl); + + } + } + + /** + * 查询订单 + */ + @GetMapping("/query/{orderNo}") + public void queryOrder(@PathVariable String orderNo) throws Exception { + + log.info("查询订单"); + + log.info("查单接口调用 ===> {}", orderNo); + + // 拼接url + 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"); + + //完成签名并执行请求 + try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) { + String bodyAsString = EntityUtils.toString(response.getEntity());//响应体 + int statusCode = response.getStatusLine().getStatusCode();//响应状态码 + if (statusCode == 200) { //处理成功 + log.info("成功, 返回结果 = " + bodyAsString); + } else if (statusCode == 204) { //处理成功,无返回Body + log.info("成功"); + } else { + log.info("查单接口调用,响应码 = " + statusCode + ",返回结果 = " + bodyAsString); + throw new IOException("request failed"); + } + } + } + + + /** + * 用户取消订单 + */ + @PostMapping("/cancel/{orderNo}") + public void cancel(@PathVariable String orderNo) throws Exception { + + 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(); + Map 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"); + + //完成签名并执行请求 + try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) { + int statusCode = response.getStatusLine().getStatusCode();//响应状态码 + if (statusCode == 200) { //处理成功 + log.info("成功200"); + } else if (statusCode == 204) { //处理成功,无返回Body + log.info("成功204"); + } else { + log.info("Native下单失败,响应码 = " + statusCode); + throw new IOException("request failed"); + } + } + } + + /** + * 用户申请退款 + */ + @PostMapping("/refunds/{orderNo}") + public void refunds(@PathVariable String orderNo) throws Exception { + + log.info("申请退款"); + 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", "123456");//退款单编号 随便填 + paramsMap.put("reason", "随便填一个");//退款原因 + paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款成功通知地址 + + Map amountMap = new HashMap(); + amountMap.put("refund", 1);//退款金额 这里的金额应该根据订单id查询出来 + amountMap.put("total", 1);//原订单金额 这里的金额应该根据订单id查询出来 + 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");//设置响应报文格式 + + //完成签名并执行请求,并完成验签 + try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) { + //解析响应结果 + 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); + } + } + } + + /** + * 查询退款 + */ + @GetMapping("/query-refund/{refundNo}") + public void queryRefund(@PathVariable String refundNo) throws Exception { + + log.info("查询退款"); + 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"); + + //完成签名并执行请求 + try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) { + 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); + } + } + } + + /** + * 获取账单url 这个url无法在浏览器打开 + */ + @GetMapping("/querybill/{billDate}/{type}") + public void queryTradeBill(@PathVariable String billDate, @PathVariable String type) throws Exception { + + log.info("获取账单url"); + + 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发送请求得到响应 + try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) { + 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); + log.info("账单链接;" + resultMap.get("download_url")); + } + } + + /** + * 下载账单 + */ + @GetMapping("/downloadbill/{billDate}/{type}") + public void downloadBill(@PathVariable String billDate, @PathVariable String type) throws Exception { + + log.info("下载账单"); + log.warn("下载账单接口调用 {}, {}", billDate, type); + + //获取账单url地址 + String downloadUrl = this.queryBill(billDate, type); + //创建远程Get 请求对象 + HttpGet httpGet = new HttpGet(downloadUrl); + httpGet.addHeader("Accept", "application/json"); + + //使用wxPayClient发送请求得到响应 + try (CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet)) { + 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); + } + } + + } + + /** + * 申请账单 + */ + 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发送请求得到响应 + try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) { + 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"); + } + } + +} diff --git a/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayNotifyController.java b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayNotifyController.java new file mode 100644 index 0000000..6d6abb6 --- /dev/null +++ b/src/main/java/com/cultural/heritage/controller/wxPayDemo/WxPayNotifyController.java @@ -0,0 +1,124 @@ +package com.cultural.heritage.controller.wxPayDemo; + + +import com.google.gson.Gson; +import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@RestController +@RequestMapping("/api/wx-pay/notify") +@Slf4j +public class WxPayNotifyController { + @Resource + private Verifier verifier; + + /** + * 订单支付成功回调 + */ + @PostMapping("/native") + public String nativeNotify(HttpServletRequest request, HttpServletResponse response) { + + 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("通知验签成功"); + + //处理订单 这里可以对订单进行处理 比如订单支付成功 修改数据库订单表订单状态为已支付 +// processOrder(bodyMap); + + //应答超时 + //模拟接收微信端的重复通知 + TimeUnit.SECONDS.sleep(5); + + //成功应答 + 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); + } + } + + @PostMapping("/refunds") + 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("通知验签成功"); + + //处理退款单 订单退款成功 修改订单表中订单状态为已退款 +// 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); + } + } +} diff --git a/src/main/java/com/cultural/heritage/service/wxpay/WeChatService.java b/src/main/java/com/cultural/heritage/service/wxpay/WeChatService.java index b163dad..304854b 100644 --- a/src/main/java/com/cultural/heritage/service/wxpay/WeChatService.java +++ b/src/main/java/com/cultural/heritage/service/wxpay/WeChatService.java @@ -1,53 +1,53 @@ -package com.cultural.heritage.service.wxpay; - - -import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; -import com.wechat.pay.java.service.payments.model.Transaction; -import com.wechat.pay.java.service.refund.model.Refund; -import com.wechat.pay.java.service.refund.model.RefundNotification; -import jakarta.servlet.http.HttpServletRequest; - -import java.io.IOException; -import java.math.BigDecimal; - -/** - * @author 玄德 - */ -public interface WeChatService { - - /** - * 微信支付 - */ - PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount); - - /** - * 获取支付回调信息 - */ - Transaction getTransactionInfo(HttpServletRequest request); - - /** - * 支付回调 - */ - boolean paymentCallback(Transaction transaction) throws IOException; - - /** - * 退款申请 - */ - Refund refundPayment(String orderId, BigDecimal amount); - - /** - * 获取退款回调信息 - */ - RefundNotification getRefundInfo(HttpServletRequest request); - - /** - * 退款回调 - */ - boolean refundCallback(RefundNotification refundNotification); - - /** - * 发送订阅模板消息 - */ - boolean sendSubscribeMessage(String openid, String templateId); - -} +//package com.cultural.heritage.service.wxpay; +// +// +//import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; +//import com.wechat.pay.java.service.payments.model.Transaction; +//import com.wechat.pay.java.service.refund.model.Refund; +//import com.wechat.pay.java.service.refund.model.RefundNotification; +//import jakarta.servlet.http.HttpServletRequest; +// +//import java.io.IOException; +//import java.math.BigDecimal; +// +///** +// * @author 玄德 +// */ +//public interface WeChatService { +// +// /** +// * 微信支付 +// */ +// PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount); +// +// /** +// * 获取支付回调信息 +// */ +// Transaction getTransactionInfo(HttpServletRequest request); +// +// /** +// * 支付回调 +// */ +// boolean paymentCallback(Transaction transaction) throws IOException; +// +// /** +// * 退款申请 +// */ +// Refund refundPayment(String orderId, BigDecimal amount); +// +// /** +// * 获取退款回调信息 +// */ +// RefundNotification getRefundInfo(HttpServletRequest request); +// +// /** +// * 退款回调 +// */ +// boolean refundCallback(RefundNotification refundNotification); +// +// /** +// * 发送订阅模板消息 +// */ +// boolean sendSubscribeMessage(String openid, String templateId); +// +//} diff --git a/src/main/java/com/cultural/heritage/service/wxpay/impl/WeChatServiceImpl.java b/src/main/java/com/cultural/heritage/service/wxpay/impl/WeChatServiceImpl.java index fe1bcbb..d5a5cd8 100644 --- a/src/main/java/com/cultural/heritage/service/wxpay/impl/WeChatServiceImpl.java +++ b/src/main/java/com/cultural/heritage/service/wxpay/impl/WeChatServiceImpl.java @@ -1,265 +1,265 @@ -package com.cultural.heritage.service.wxpay.impl; - - -import cn.binarywang.wx.miniapp.api.WxMaMsgService; -import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; -import cn.hutool.core.util.RandomUtil; -import com.cultural.heritage.common.ErrorCode; -import com.cultural.heritage.config.WxOpenConfig; -import com.cultural.heritage.config.WxPayConfig; -import com.cultural.heritage.exception.BusinessException; -import com.cultural.heritage.model.entity.Order; -import com.cultural.heritage.service.order.OrderService; -import com.cultural.heritage.service.wxpay.WeChatService; -import com.wechat.pay.java.core.notification.NotificationParser; -import com.wechat.pay.java.core.notification.RequestParam; -import com.wechat.pay.java.service.payments.jsapi.model.Amount; -import com.wechat.pay.java.service.payments.jsapi.model.Payer; -import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest; -import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; -import com.wechat.pay.java.service.payments.model.Transaction; -import com.wechat.pay.java.service.refund.model.AmountReq; -import com.wechat.pay.java.service.refund.model.CreateRequest; -import com.wechat.pay.java.service.refund.model.Refund; -import com.wechat.pay.java.service.refund.model.RefundNotification; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.io.BufferedReader; -import java.io.IOException; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.ArrayList; -import java.util.List; - -/** - * @author 玄德 - */ -@Slf4j -@Service -public class WeChatServiceImpl implements WeChatService { - - @Resource - private WxPayConfig wxPayConfig; - - @Resource - private WxOpenConfig wxOpenConfig; - - @Resource - private OrderService ordersService; - - /** - * 请求参数 - */ - public static RequestParam requestParam = null; - - @Override - @Transactional(rollbackFor = Exception.class) - public synchronized PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount) { - // request.setXxx(val)设置所需参数,具体参数可见Request定义 - PrepayRequest request = new PrepayRequest(); - // 金额 - Amount WxAmount = new Amount(); - WxAmount.setTotal(amount.movePointRight(2).intValue()); - WxAmount.setCurrency("CNY"); - request.setAmount(WxAmount); - // 公众号id - request.setAppid(wxPayConfig.getAppId()); - // 商户号 - request.setMchid(wxPayConfig.getMerchantId()); - // 支付者信息 - Payer payer = new Payer(); - payer.setOpenid(miniOpenId); - request.setPayer(payer); - // 描述 - request.setDescription("订单号:" + orderId); - // 微信回调地址 - request.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/payment/callback"); - // 商户订单号 - request.setOutTradeNo(RandomUtil.randomNumbers(12)); - //返回数据,前端调起支付 - return wxPayConfig.getJsapiServiceExtension().prepayWithRequestPayment(request); - } - - @Override - public Transaction getTransactionInfo(HttpServletRequest request) { - NotificationParser notificationParser = getNotificationParser(request); - return notificationParser.parse(requestParam, Transaction.class); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public synchronized boolean paymentCallback(Transaction transaction) throws IOException { - System.out.println("---------------------------微信支付回调(开始)-------------------------------"); - // 获取订单信息 - String orderIdByString = transaction.getOutTradeNo(); - Order order = ordersService.getById(orderIdByString); - if (order == null) { - log.error("订单不存在"); - throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "订单不存在,订单号:" + transaction.getOutTradeNo()); - } - // 生成取餐码 - // 获取当日的日期 - LocalDate today = LocalDate.now(); - // 获取当日的 00:00 时间 - LocalDateTime startTime = today.atStartOfDay(); - // 获取当日的 23:59 时间 - LocalDateTime endTime = today.atTime(LocalTime.MAX); +//package com.cultural.heritage.service.wxpay.impl; +// +// +//import cn.binarywang.wx.miniapp.api.WxMaMsgService; +//import cn.binarywang.wx.miniapp.api.WxMaService; +//import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; +//import cn.hutool.core.util.RandomUtil; +//import com.cultural.heritage.common.ErrorCode; +//import com.cultural.heritage.config.WxOpenConfig; +//import com.cultural.heritage.config.WxPayConfig; +//import com.cultural.heritage.exception.BusinessException; +//import com.cultural.heritage.model.entity.Order; +//import com.cultural.heritage.service.order.OrderService; +//import com.cultural.heritage.service.wxpay.WeChatService; +//import com.wechat.pay.java.core.notification.NotificationParser; +//import com.wechat.pay.java.core.notification.RequestParam; +//import com.wechat.pay.java.service.payments.jsapi.model.Amount; +//import com.wechat.pay.java.service.payments.jsapi.model.Payer; +//import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest; +//import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; +//import com.wechat.pay.java.service.payments.model.Transaction; +//import com.wechat.pay.java.service.refund.model.AmountReq; +//import com.wechat.pay.java.service.refund.model.CreateRequest; +//import com.wechat.pay.java.service.refund.model.Refund; +//import com.wechat.pay.java.service.refund.model.RefundNotification; +//import jakarta.annotation.Resource; +//import jakarta.servlet.http.HttpServletRequest; +//import lombok.SneakyThrows; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +// +//import java.io.BufferedReader; +//import java.io.IOException; +//import java.math.BigDecimal; +//import java.time.LocalDate; +//import java.time.LocalDateTime; +//import java.time.LocalTime; +//import java.util.ArrayList; +//import java.util.List; +// +///** +// * @author 玄德 +// */ +//@Slf4j +//@Service +//public class WeChatServiceImpl implements WeChatService { +// +// @Resource +// private WxPayConfig wxPayConfig; +// +// @Resource +// private WxOpenConfig wxOpenConfig; +// +// @Resource +// private OrderService ordersService; +// +// /** +// * 请求参数 +// */ +// public static RequestParam requestParam = null; +// +// @Override +// @Transactional(rollbackFor = Exception.class) +// public synchronized PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount) { +// // request.setXxx(val)设置所需参数,具体参数可见Request定义 +// PrepayRequest request = new PrepayRequest(); +// // 金额 +// Amount WxAmount = new Amount(); +// WxAmount.setTotal(amount.movePointRight(2).intValue()); +// WxAmount.setCurrency("CNY"); +// request.setAmount(WxAmount); +// // 公众号id +// request.setAppid(wxPayConfig.getAppId()); +// // 商户号 +// request.setMchid(wxPayConfig.getMerchantId()); +// // 支付者信息 +// Payer payer = new Payer(); +// payer.setOpenid(miniOpenId); +// request.setPayer(payer); +// // 描述 +// request.setDescription("订单号:" + orderId); +// // 微信回调地址 +// request.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/payment/callback"); +// // 商户订单号 +// request.setOutTradeNo(RandomUtil.randomNumbers(12)); +// //返回数据,前端调起支付 +// return wxPayConfig.getJsapiServiceExtension().prepayWithRequestPayment(request); +// } +// +// @Override +// public Transaction getTransactionInfo(HttpServletRequest request) { +// NotificationParser notificationParser = getNotificationParser(request); +// return notificationParser.parse(requestParam, Transaction.class); +// } +// +// @Override +// @Transactional(rollbackFor = Exception.class) +// public synchronized boolean paymentCallback(Transaction transaction) throws IOException { +// System.out.println("---------------------------微信支付回调(开始)-------------------------------"); +// // 获取订单信息 +// String orderIdByString = transaction.getOutTradeNo(); +// Order order = ordersService.getById(orderIdByString); +// if (order == null) { +// log.error("订单不存在"); +// throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "订单不存在,订单号:" + transaction.getOutTradeNo()); +// } // // 生成取餐码 -// QueryWrapper queryWrapper = new QueryWrapper<>(); -// queryWrapper.eq("businessId", order.getBusinessId()); -// queryWrapper.between("createTime", startTime, endTime); -// queryWrapper.in("state", 1, 2); -// long count = ordersService.count(queryWrapper); -// String pickupCode = String.format("%04d", count + 1); +// // 获取当日的日期 +// LocalDate today = LocalDate.now(); +// // 获取当日的 00:00 时间 +// LocalDateTime startTime = today.atStartOfDay(); +// // 获取当日的 23:59 时间 +// LocalDateTime endTime = today.atTime(LocalTime.MAX); +//// // 生成取餐码 +//// QueryWrapper queryWrapper = new QueryWrapper<>(); +//// queryWrapper.eq("businessId", order.getBusinessId()); +//// queryWrapper.between("createTime", startTime, endTime); +//// queryWrapper.in("state", 1, 2); +//// long count = ordersService.count(queryWrapper); +//// String pickupCode = String.format("%04d", count + 1); +//// // 修改订单信息 +//// order.setState(1); +//// order.setPickupCode(pickupCode); +//// Date date = new Date(); +//// order.setUpdateTime(date); +//// boolean update = ordersService.updateById(order); +//// ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "修改订单状态失败"); +//// // 通知商家新订单 +//// Business business = businessService.getById(order.getBusinessId()); +//// Long userId = order.getUserId(); +//// Long businessId = business.getUserId(); +//// MessageVO messageVO = new MessageVO(); +//// messageVO.setType(1); +//// messageVO.setUserId(userId); +//// messageVO.setToUserId(businessId); +//// messageVO.setContent(orderIdByString); +//// String message = JSONUtil.toJsonStr(messageVO); +//// // 通知商家新订单 +//// webSocketServer.onMessage(message); +// System.out.println("---------------------------微信支付回调(结束)-------------------------------"); +// return true; +// } +// +// @Override +// public Refund refundPayment(String orderId, BigDecimal amount) { +// // 退款请求 +// CreateRequest createRequest = new CreateRequest(); +// // 商户订单号 +// createRequest.setOutTradeNo(orderId); +// // 商户退款单号 +// createRequest.setOutRefundNo(orderId); +// // 退款结果回调 +// createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback"); +// // 退款金额 +// AmountReq amountReq = new AmountReq(); +// long refundAmount = amount.movePointRight(2).intValue(); +// amountReq.setRefund(refundAmount); +// amountReq.setTotal(refundAmount); +// amountReq.setCurrency("CNY"); +// createRequest.setAmount(amountReq); +// // 申请退款 +// System.out.println("退款请求:" + createRequest); +// Refund refund = wxPayConfig.getRefundService().create(createRequest); +// System.out.println("退款申请结果:" + refund); +// return refund; +// } +// +// @Override +// public RefundNotification getRefundInfo(HttpServletRequest request) { +// NotificationParser notificationParser = getNotificationParser(request); +// return notificationParser.parse(requestParam, RefundNotification.class); +// } +// +// @Override +// @Transactional(rollbackFor = Exception.class) +// public synchronized boolean refundCallback(RefundNotification refundNotification) { +// System.out.println("---------------------------微信退款回调(开始)-------------------------------"); +// // 获取订单信息 +// String orderIdByString = refundNotification.getOutTradeNo(); +// Order order = ordersService.getById(orderIdByString); // // 修改订单信息 -// order.setState(1); -// order.setPickupCode(pickupCode); -// Date date = new Date(); -// order.setUpdateTime(date); -// boolean update = ordersService.updateById(order); -// ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "修改订单状态失败"); -// // 通知商家新订单 -// Business business = businessService.getById(order.getBusinessId()); -// Long userId = order.getUserId(); -// Long businessId = business.getUserId(); -// MessageVO messageVO = new MessageVO(); -// messageVO.setType(1); -// messageVO.setUserId(userId); -// messageVO.setToUserId(businessId); -// messageVO.setContent(orderIdByString); -// String message = JSONUtil.toJsonStr(messageVO); -// // 通知商家新订单 -// webSocketServer.onMessage(message); - System.out.println("---------------------------微信支付回调(结束)-------------------------------"); - return true; - } - - @Override - public Refund refundPayment(String orderId, BigDecimal amount) { - // 退款请求 - CreateRequest createRequest = new CreateRequest(); - // 商户订单号 - createRequest.setOutTradeNo(orderId); - // 商户退款单号 - createRequest.setOutRefundNo(orderId); - // 退款结果回调 - createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback"); - // 退款金额 - AmountReq amountReq = new AmountReq(); - long refundAmount = amount.movePointRight(2).intValue(); - amountReq.setRefund(refundAmount); - amountReq.setTotal(refundAmount); - amountReq.setCurrency("CNY"); - createRequest.setAmount(amountReq); - // 申请退款 - System.out.println("退款请求:" + createRequest); - Refund refund = wxPayConfig.getRefundService().create(createRequest); - System.out.println("退款申请结果:" + refund); - return refund; - } - - @Override - public RefundNotification getRefundInfo(HttpServletRequest request) { - NotificationParser notificationParser = getNotificationParser(request); - return notificationParser.parse(requestParam, RefundNotification.class); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public synchronized boolean refundCallback(RefundNotification refundNotification) { - System.out.println("---------------------------微信退款回调(开始)-------------------------------"); - // 获取订单信息 - String orderIdByString = refundNotification.getOutTradeNo(); - Order order = ordersService.getById(orderIdByString); - // 修改订单信息 - order.setOrderStatus("已退款"); - ordersService.updateById(order); - System.out.println("---------------------------微信退款回调(结束)-------------------------------"); - return true; - } - - @Override - public boolean sendSubscribeMessage(String openid, String templateId) { - try { - // 获取小程序对象 - WxMaService wxMaService = wxOpenConfig.getWxMaService(); - // 获取消息接口 - WxMaMsgService msgService = wxMaService.getMsgService(); - // 构建订阅消息体 - WxMaSubscribeMessage wxMaSubscribeMessage = new WxMaSubscribeMessage(); - wxMaSubscribeMessage.setToUser(openid); - wxMaSubscribeMessage.setTemplateId(templateId); - List msgDataList = getMsgData(); - wxMaSubscribeMessage.setData(msgDataList); - // 发送消息 - msgService.sendSubscribeMsg(wxMaSubscribeMessage); - return true; - } catch (Exception e) { - log.error("发送订阅消息失败:", e); - throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送订阅消息失败"); - } - } - - /** - * 提取消息数据 - */ - private static List getMsgData() { - List msgDataList = new ArrayList<>(); - String[][] dataItems = { - {"thing1", "新订单"}, - {"phrase2", "已支付"}, - {"amount3", "666"}, - {"thing10", "666666"}, - {"time8", "2019-12-18 12:12:12"} - }; - for (String[] item : dataItems) { - WxMaSubscribeMessage.MsgData msgData = new WxMaSubscribeMessage.MsgData(); - msgData.setName(item[0]); - msgData.setValue(item[1]); - msgDataList.add(msgData); - } - return msgDataList; - } - - /** - * 根据微信官方发送的请求获取信息 - */ - @SneakyThrows - public NotificationParser getNotificationParser(HttpServletRequest request) { - System.out.println("---------------------------获取信息-------------------------------"); - // 获取RSA配置 - NotificationParser notificationParser = new NotificationParser(wxPayConfig.getRSAConfig()); - // 构建请求 - StringBuilder bodyBuilder = new StringBuilder(); - BufferedReader reader = request.getReader(); - String line; - while ((line = reader.readLine()) != null) { - bodyBuilder.append(line); - } - String body = bodyBuilder.toString(); - String timestamp = request.getHeader("Wechatpay-Timestamp"); - String nonce = request.getHeader("Wechatpay-Nonce"); - String signature = request.getHeader("Wechatpay-Signature"); - String singType = request.getHeader("Wechatpay-Signature-Type"); - String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial"); - requestParam = new RequestParam.Builder() - .serialNumber(wechatPayCertificateSerialNumber) - .nonce(nonce) - .signature(signature) - .timestamp(timestamp) - .signType(singType) - .body(body) - .build(); - System.out.println(requestParam.toString()); - System.out.println("---------------------------信息获取完毕-------------------------------"); - return notificationParser; - } -} +// order.setOrderStatus("已退款"); +// ordersService.updateById(order); +// System.out.println("---------------------------微信退款回调(结束)-------------------------------"); +// return true; +// } +// +// @Override +// public boolean sendSubscribeMessage(String openid, String templateId) { +// try { +// // 获取小程序对象 +// WxMaService wxMaService = wxOpenConfig.getWxMaService(); +// // 获取消息接口 +// WxMaMsgService msgService = wxMaService.getMsgService(); +// // 构建订阅消息体 +// WxMaSubscribeMessage wxMaSubscribeMessage = new WxMaSubscribeMessage(); +// wxMaSubscribeMessage.setToUser(openid); +// wxMaSubscribeMessage.setTemplateId(templateId); +// List msgDataList = getMsgData(); +// wxMaSubscribeMessage.setData(msgDataList); +// // 发送消息 +// msgService.sendSubscribeMsg(wxMaSubscribeMessage); +// return true; +// } catch (Exception e) { +// log.error("发送订阅消息失败:", e); +// throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送订阅消息失败"); +// } +// } +// +// /** +// * 提取消息数据 +// */ +// private static List getMsgData() { +// List msgDataList = new ArrayList<>(); +// String[][] dataItems = { +// {"thing1", "新订单"}, +// {"phrase2", "已支付"}, +// {"amount3", "666"}, +// {"thing10", "666666"}, +// {"time8", "2019-12-18 12:12:12"} +// }; +// for (String[] item : dataItems) { +// WxMaSubscribeMessage.MsgData msgData = new WxMaSubscribeMessage.MsgData(); +// msgData.setName(item[0]); +// msgData.setValue(item[1]); +// msgDataList.add(msgData); +// } +// return msgDataList; +// } +// +// /** +// * 根据微信官方发送的请求获取信息 +// */ +// @SneakyThrows +// public NotificationParser getNotificationParser(HttpServletRequest request) { +// System.out.println("---------------------------获取信息-------------------------------"); +// // 获取RSA配置 +// NotificationParser notificationParser = new NotificationParser(wxPayConfig.getRSAConfig()); +// // 构建请求 +// StringBuilder bodyBuilder = new StringBuilder(); +// BufferedReader reader = request.getReader(); +// String line; +// while ((line = reader.readLine()) != null) { +// bodyBuilder.append(line); +// } +// String body = bodyBuilder.toString(); +// String timestamp = request.getHeader("Wechatpay-Timestamp"); +// String nonce = request.getHeader("Wechatpay-Nonce"); +// String signature = request.getHeader("Wechatpay-Signature"); +// String singType = request.getHeader("Wechatpay-Signature-Type"); +// String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial"); +// requestParam = new RequestParam.Builder() +// .serialNumber(wechatPayCertificateSerialNumber) +// .nonce(nonce) +// .signature(signature) +// .timestamp(timestamp) +// .signType(singType) +// .body(body) +// .build(); +// System.out.println(requestParam.toString()); +// System.out.println("---------------------------信息获取完毕-------------------------------"); +// return notificationParser; +// } +//} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a32ff44..96f5680 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -70,17 +70,19 @@ wx: #应用id(小程序id) appId: wx61b63e27bddf4ea2 #商户号 - merchantId: 1700326544 + #merchantId: 1700326544 + mchSerialNo: 1700326544 #商户API私钥 privateKeyPath: apiclient_key.pem #商户证书序列号 merchantSerialNumber: 6DC8953AB741D309920DA650B92F837BE38A2757 #商户APIv3密钥 apiV3Key: fbemuj4Xql7CYlQJAoTEPYxvPSNgYT2t -# 通知地址 + #通知地址 notifyUrl: https://winning-mouse-internally.ngrok-free.app -# notifyUrl: http://localhost:9092 - + #notifyUrl: http://localhost:9092 + #微信服务器地址 + domain: https://api.mch.weixin.qq.com knife4j: