From 9b31b3ce5710ee1f2bc9565e79b7425237df9579 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com>
Date: Sat, 7 Feb 2026 14:01:04 +0800
Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E4=BC=98=E5=8C=96=E5=BE=AE?=
=?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E5=9B=9E=E8=B0=83=E5=85=BC=E5=AE=B9=E6=80=A7=E6=94=AF?=
=?UTF-8?q?=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复信用风险关系查询中的公司ID条件判断逻辑
- 将关键词搜索从main_body_name改为match_name以提高匹配准确性
- 在安全配置中添加/api/system/wx-pay/**路径到公共访问白名单
- 添加RoundingMode导入和WechatPrepaySnapshot数据类用于支付快照管理
- 实现微信支付预下单快照机制以解决请求重入参数不一致问题
- 添加多种回调地址候选方案包括新默认地址和历史兼容地址
- 实现支付金额转换为分的统一方法toFen,使用四舍五入避免精度问题
- 添加Redis缓存存储支付快照,TTL设置为30分钟
- 实现notifyUrl动态切换重试机制,支持多个回调地址备选
- 创建WxPayNotifyAliasController提供旧版回调地址兼容性支持
- 修复JSAPI和Native支付中的金额计算逻辑,优先使用payPrice字段
- 添加支付金额空值检查防止运行时异常
- 优化支付描述字段截断处理逻辑,改进默认值设置
---
.../common/core/security/SecurityConfig.java | 1 +
.../mapper/xml/CreditRiskRelationMapper.xml | 8 +-
.../WxPayNotifyAliasController.java | 47 +++
.../service/impl/ShopOrderServiceImpl.java | 397 +++++++++++++++---
4 files changed, 391 insertions(+), 62 deletions(-)
create mode 100644 src/main/java/com/gxwebsoft/shop/controller/WxPayNotifyAliasController.java
diff --git a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java
index 9864eaa..8cd95a0 100644
--- a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java
+++ b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java
@@ -67,6 +67,7 @@ public class SecurityConfig {
"/api/shop/wx-login/**",
"/api/shop/wx-native-pay/**",
"/api/shop/wx-pay/**",
+ "/api/system/wx-pay/**",
"/api/bszx/bszx-pay/notify/**",
"/api/wxWorkQrConnect",
"/WW_verify_QMv7HoblYU6z63bb.txt",
diff --git a/src/main/java/com/gxwebsoft/credit/mapper/xml/CreditRiskRelationMapper.xml b/src/main/java/com/gxwebsoft/credit/mapper/xml/CreditRiskRelationMapper.xml
index 9683d91..b425443 100644
--- a/src/main/java/com/gxwebsoft/credit/mapper/xml/CreditRiskRelationMapper.xml
+++ b/src/main/java/com/gxwebsoft/credit/mapper/xml/CreditRiskRelationMapper.xml
@@ -12,9 +12,9 @@
AND a.id = #{param.id}
-
- AND a.company_id = #{param.companyId}
-
+
+ AND a.company_id = #{param.companyId}
+
AND a.main_body_name LIKE CONCAT('%', #{param.mainBodyName}, '%')
@@ -63,7 +63,7 @@
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR b.name = #{param.keywords}
- OR b.main_body_name LIKE CONCAT('%', #{param.keywords}, '%')
+ OR b.match_name LIKE CONCAT('%', #{param.keywords}, '%')
)
diff --git a/src/main/java/com/gxwebsoft/shop/controller/WxPayNotifyAliasController.java b/src/main/java/com/gxwebsoft/shop/controller/WxPayNotifyAliasController.java
new file mode 100644
index 0000000..6a27350
--- /dev/null
+++ b/src/main/java/com/gxwebsoft/shop/controller/WxPayNotifyAliasController.java
@@ -0,0 +1,47 @@
+package com.gxwebsoft.shop.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * 微信支付回调别名入口(兼容历史 notify_url)
+ *
+ * 说明:
+ * - 旧代码曾使用 /api/system/wx-pay/notify/{tenantId} 或 /api/shop/wx-pay/notify/{tenantId}
+ * - 微信支付“请求重入”要求 notify_url 等参数与首次一致,因此需要保留旧回调地址可用
+ */
+@Tag(name = "微信支付回调(别名)")
+@RestController
+public class WxPayNotifyAliasController {
+
+ @Resource
+ private ShopOrderController shopOrderController;
+
+ @Operation(summary = "微信支付回调别名(兼容旧回调地址)")
+ @PostMapping({"/api/system/wx-pay/notify/{tenantId}", "/api/shop/wx-pay/notify/{tenantId}"})
+ public String wxNotifyAlias(@RequestHeader Map header,
+ @RequestBody String body,
+ @PathVariable("tenantId") Integer tenantId) {
+ // ShopOrderController.wxNotify 读取的是小写 header key,这里做一次兼容转换
+ Map lower = new HashMap<>();
+ if (header != null) {
+ header.forEach((k, v) -> {
+ if (k != null) {
+ lower.put(k.toLowerCase(Locale.ROOT), v);
+ }
+ });
+ }
+ return shopOrderController.wxNotify(lower, body, tenantId);
+ }
+}
+
diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java
index 3c0e4d4..24322b7 100644
--- a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java
+++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java
@@ -30,12 +30,14 @@ import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
// Native支付的类将使用完全限定名避免冲突
import com.wechat.pay.java.service.payments.model.Transaction;
+import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
@@ -86,6 +88,165 @@ public class ShopOrderServiceImpl extends ServiceImpl buildNotifyUrlCandidates(ShopOrder order, Payment payment, WechatPrepaySnapshot snapshot) {
+ LinkedHashSet urls = new LinkedHashSet<>();
+ if (snapshot != null && StrUtil.isNotBlank(snapshot.getNotifyUrl())) {
+ urls.add(trimTrailingSlashes(snapshot.getNotifyUrl()));
+ }
+ if (payment != null && StrUtil.isNotBlank(payment.getNotifyUrl())) {
+ urls.add(ensureTenantSuffix(payment.getNotifyUrl(), order.getTenantId()));
+ }
+
+ // 新默认回调(本项目已实现)
+ String shopOrderNotify = defaultShopOrderNotifyUrl(order.getTenantId());
+ if (StrUtil.isNotBlank(shopOrderNotify)) {
+ urls.add(shopOrderNotify);
+ }
+
+ // 兼容历史回调地址(用于已创建订单的“重新支付”重入校验)
+ String legacySystem = legacySystemWxPayNotifyUrl(order.getTenantId());
+ if (StrUtil.isNotBlank(legacySystem)) {
+ urls.add(legacySystem);
+ }
+
+ if ("dev".equals(active)) {
+ String devNotify = devShopOrderNotifyUrl(order.getTenantId());
+ if (StrUtil.isNotBlank(devNotify)) {
+ urls.add(devNotify);
+ }
+ String devLegacy = legacyShopWxPayNotifyUrl(order.getTenantId());
+ if (StrUtil.isNotBlank(devLegacy)) {
+ urls.add(devLegacy);
+ }
+ }
+
+ return new ArrayList<>(urls);
+ }
@Override
@@ -321,13 +482,15 @@ public class ShopOrderServiceImpl extends ServiceImpl orderInfo = new HashMap<>();
- orderInfo.put("provider", "wxpay");
- orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL
- orderInfo.put("orderNo", order.getOrderNo());
- orderInfo.put("payType", WechatPayType.NATIVE);
- orderInfo.put("wechatPayType", WechatPayType.NATIVE);
+ snapshot.setNotifyUrl(notifyUrlUsed);
+ snapshot.setTotal(amount.getTotal());
+ snapshot.setDescription(request.getDescription());
+ savePrepaySnapshot(payment, snapshot);
- return orderInfo;
+ final HashMap orderInfo = new HashMap<>();
+ orderInfo.put("provider", "wxpay");
+ orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL
+ orderInfo.put("orderNo", order.getOrderNo());
+ orderInfo.put("payType", WechatPayType.NATIVE);
+ orderInfo.put("wechatPayType", WechatPayType.NATIVE);
+
+ return orderInfo;
+ } catch (Exception e) {
+ last = e;
+ if (!isIdempotencyParamMismatch(e)) {
+ throw e;
+ }
+ log.warn("Native预下单重入参数不一致,尝试切换notifyUrl重试 - outTradeNo={}, notifyUrl={}", outTradeNo, notifyUrl, e);
+ }
+ }
+ if (last != null) {
+ throw last;
+ }
+ throw new RuntimeException("创建Native支付订单失败:notifyUrl为空");
}
/**
@@ -399,10 +615,12 @@ public class ShopOrderServiceImpl extends ServiceImpl orderInfo = new HashMap<>();
+ orderInfo.put("provider", "wxpay");
+ orderInfo.put("timeStamp", response.getTimeStamp());
+ orderInfo.put("nonceStr", response.getNonceStr());
+ orderInfo.put("package", response.getPackageVal());
+ orderInfo.put("signType", "RSA");
+ orderInfo.put("paySign", response.getPaySign());
+ orderInfo.put("orderNo", order.getOrderNo());
+ orderInfo.put("payType", WechatPayType.JSAPI);
+ orderInfo.put("wechatPayType", WechatPayType.JSAPI);
+ return orderInfo;
+ } catch (Exception e) {
+ last = e;
+ if (!isIdempotencyParamMismatch(e)) {
+ throw e;
+ }
+ log.warn("JSAPI预下单重入参数不一致,尝试切换notifyUrl重试 - outTradeNo={}, notifyUrl={}", outTradeNo, notifyUrl, e);
+ }
}
- System.out.println("=== 发起微信支付请求 ===");
- System.out.println("请求参数: " + request);
-
- PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
-
- System.out.println("=== 微信支付响应成功 ===");
- System.out.println("预支付ID: " + response.getPackageVal());
-
- final HashMap orderInfo = new HashMap<>();
- orderInfo.put("provider", "wxpay");
- orderInfo.put("timeStamp", response.getTimeStamp());
- orderInfo.put("nonceStr", response.getNonceStr());
- orderInfo.put("package", response.getPackageVal());
- orderInfo.put("signType", "RSA");
- orderInfo.put("paySign", response.getPaySign());
- orderInfo.put("orderNo", order.getOrderNo());
- orderInfo.put("payType", WechatPayType.JSAPI);
- orderInfo.put("wechatPayType", WechatPayType.JSAPI);
- return orderInfo;
+ if (last != null) {
+ throw last;
+ }
+ throw new RuntimeException("创建JSAPI支付订单失败:notifyUrl为空");
}
@Override