feat(payment): 优化微信支付功能并添加回调兼容性支持
- 修复信用风险关系查询中的公司ID条件判断逻辑 - 将关键词搜索从main_body_name改为match_name以提高匹配准确性 - 在安全配置中添加/api/system/wx-pay/**路径到公共访问白名单 - 添加RoundingMode导入和WechatPrepaySnapshot数据类用于支付快照管理 - 实现微信支付预下单快照机制以解决请求重入参数不一致问题 - 添加多种回调地址候选方案包括新默认地址和历史兼容地址 - 实现支付金额转换为分的统一方法toFen,使用四舍五入避免精度问题 - 添加Redis缓存存储支付快照,TTL设置为30分钟 - 实现notifyUrl动态切换重试机制,支持多个回调地址备选 - 创建WxPayNotifyAliasController提供旧版回调地址兼容性支持 - 修复JSAPI和Native支付中的金额计算逻辑,优先使用payPrice字段 - 添加支付金额空值检查防止运行时异常 - 优化支付描述字段截断处理逻辑,改进默认值设置
This commit is contained in:
@@ -67,6 +67,7 @@ public class SecurityConfig {
|
|||||||
"/api/shop/wx-login/**",
|
"/api/shop/wx-login/**",
|
||||||
"/api/shop/wx-native-pay/**",
|
"/api/shop/wx-native-pay/**",
|
||||||
"/api/shop/wx-pay/**",
|
"/api/shop/wx-pay/**",
|
||||||
|
"/api/system/wx-pay/**",
|
||||||
"/api/bszx/bszx-pay/notify/**",
|
"/api/bszx/bszx-pay/notify/**",
|
||||||
"/api/wxWorkQrConnect",
|
"/api/wxWorkQrConnect",
|
||||||
"/WW_verify_QMv7HoblYU6z63bb.txt",
|
"/WW_verify_QMv7HoblYU6z63bb.txt",
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<if test="param.keywords != null">
|
<if test="param.keywords != null">
|
||||||
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR b.name = #{param.keywords}
|
OR b.name = #{param.keywords}
|
||||||
OR b.main_body_name LIKE CONCAT('%', #{param.keywords}, '%')
|
OR b.match_name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
)
|
)
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
|
|||||||
@@ -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<String, String> header,
|
||||||
|
@RequestBody String body,
|
||||||
|
@PathVariable("tenantId") Integer tenantId) {
|
||||||
|
// ShopOrderController.wxNotify 读取的是小写 header key,这里做一次兼容转换
|
||||||
|
Map<String, String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -30,12 +30,14 @@ import com.wechat.pay.java.service.payments.jsapi.model.*;
|
|||||||
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||||
// Native支付的类将使用完全限定名避免冲突
|
// Native支付的类将使用完全限定名避免冲突
|
||||||
import com.wechat.pay.java.service.payments.model.Transaction;
|
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||||
|
import lombok.Data;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -86,6 +88,165 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|||||||
private ShopExpressService shopExpressService;
|
private ShopExpressService shopExpressService;
|
||||||
|
|
||||||
private static final long USER_ORDER_STATS_CACHE_SECONDS = 60L;
|
private static final long USER_ORDER_STATS_CACHE_SECONDS = 60L;
|
||||||
|
private static final long WECHAT_PREPAY_SNAPSHOT_TTL_MINUTES = 30L;
|
||||||
|
private static final String WECHAT_PREPAY_SNAPSHOT_KEY_PREFIX = "wxpay:prepay:snapshot:";
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class WechatPrepaySnapshot {
|
||||||
|
private String outTradeNo;
|
||||||
|
private String appid;
|
||||||
|
private String mchid;
|
||||||
|
private String openid;
|
||||||
|
private Integer total; // 分
|
||||||
|
private String description;
|
||||||
|
private String notifyUrl;
|
||||||
|
private String attach;
|
||||||
|
private String wechatPayType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String trimTrailingSlashes(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String u = url.trim();
|
||||||
|
while (u.endsWith("/")) {
|
||||||
|
u = u.substring(0, u.length() - 1);
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String ensureTenantSuffix(String baseUrl, Integer tenantId) {
|
||||||
|
if (StrUtil.isBlank(baseUrl) || tenantId == null) {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
String base = trimTrailingSlashes(baseUrl);
|
||||||
|
String suffix = "/" + tenantId;
|
||||||
|
if (base.endsWith(suffix)) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
return base + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prepaySnapshotKey(Payment payment, String outTradeNo) {
|
||||||
|
return WECHAT_PREPAY_SNAPSHOT_KEY_PREFIX + payment.getMchId() + ":" + outTradeNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WechatPrepaySnapshot getPrepaySnapshot(Payment payment, String outTradeNo) {
|
||||||
|
try {
|
||||||
|
return redisUtil.get(prepaySnapshotKey(payment, outTradeNo), WechatPrepaySnapshot.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 缓存不可用时不影响支付主流程
|
||||||
|
log.warn("读取微信预下单快照失败 - outTradeNo={}, mchId={}", outTradeNo, payment != null ? payment.getMchId() : null, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePrepaySnapshot(Payment payment, WechatPrepaySnapshot snapshot) {
|
||||||
|
if (payment == null || snapshot == null || StrUtil.isBlank(snapshot.getOutTradeNo())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
redisUtil.set(prepaySnapshotKey(payment, snapshot.getOutTradeNo()), snapshot, WECHAT_PREPAY_SNAPSHOT_TTL_MINUTES, TimeUnit.MINUTES);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("保存微信预下单快照失败 - outTradeNo={}, mchId={}", snapshot.getOutTradeNo(), payment.getMchId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isIdempotencyParamMismatch(Throwable t) {
|
||||||
|
Throwable cur = t;
|
||||||
|
while (cur != null) {
|
||||||
|
if (cur instanceof ServiceException) {
|
||||||
|
ServiceException se = (ServiceException) cur;
|
||||||
|
String code = se.getErrorCode();
|
||||||
|
String msg = se.getErrorMessage();
|
||||||
|
String body = se.getResponseBody();
|
||||||
|
if ("INVALID_REQUEST".equals(code) && ((msg != null && msg.contains("请求重入")) || (body != null && body.contains("请求重入")))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String m = cur.getMessage();
|
||||||
|
if (m != null && m.contains("INVALID_REQUEST") && m.contains("请求重入")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cur = cur.getCause();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer toFen(BigDecimal amountYuan) {
|
||||||
|
if (amountYuan == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 微信支付金额字段使用整数分,这里按两位小数四舍五入再转分
|
||||||
|
BigDecimal fen = amountYuan.setScale(2, RoundingMode.HALF_UP).movePointRight(2);
|
||||||
|
return fen.intValueExact();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultShopOrderNotifyUrl(Integer tenantId) {
|
||||||
|
String base = trimTrailingSlashes(config.getServerUrl());
|
||||||
|
if (StrUtil.isBlank(base) || tenantId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return base + "/shop/shop-order/notify/" + tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String legacySystemWxPayNotifyUrl(Integer tenantId) {
|
||||||
|
String base = trimTrailingSlashes(config.getServerUrl());
|
||||||
|
if (StrUtil.isBlank(base) || tenantId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return base + "/system/wx-pay/notify/" + tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String legacyShopWxPayNotifyUrl(Integer tenantId) {
|
||||||
|
// 旧代码曾使用 /api/shop/wx-pay/notify/{tenantId}
|
||||||
|
if (tenantId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return "http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String devShopOrderNotifyUrl(Integer tenantId) {
|
||||||
|
if (tenantId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return "http://jimei-api.natapp1.cc/api/shop/shop-order/notify/" + tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> buildNotifyUrlCandidates(ShopOrder order, Payment payment, WechatPrepaySnapshot snapshot) {
|
||||||
|
LinkedHashSet<String> 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
|
@Override
|
||||||
@@ -321,13 +482,15 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|||||||
Config wxPayConfig = getWxPayConfig(order);
|
Config wxPayConfig = getWxPayConfig(order);
|
||||||
NativePayService nativeService = new NativePayService.Builder().config(wxPayConfig).build();
|
NativePayService nativeService = new NativePayService.Builder().config(wxPayConfig).build();
|
||||||
|
|
||||||
// 订单金额(转换为分)
|
// 订单金额(转换为分)- 优先使用 payPrice,避免与 JSAPI 金额字段不一致导致重入校验失败
|
||||||
BigDecimal decimal = order.getTotalPrice();
|
BigDecimal payAmount = order.getPayPrice() != null ? order.getPayPrice() : order.getTotalPrice();
|
||||||
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
|
Integer money = toFen(payAmount);
|
||||||
Integer money = multiply.intValue();
|
if (money == null) {
|
||||||
|
throw new RuntimeException("订单金额为null");
|
||||||
|
}
|
||||||
|
|
||||||
// 测试环境使用1分钱
|
// 测试环境使用1分钱
|
||||||
if (active.equals("dev")) {
|
if ("dev".equals(active)) {
|
||||||
money = 1;
|
money = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,26 +517,67 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|||||||
System.out.println("商户号(MchId): " + payment.getMchId());
|
System.out.println("商户号(MchId): " + payment.getMchId());
|
||||||
System.out.println("应用ID(AppId): " + payment.getAppId());
|
System.out.println("应用ID(AppId): " + payment.getAppId());
|
||||||
|
|
||||||
// 设置回调地址
|
String outTradeNo = order.getOrderNo();
|
||||||
String notifyUrl = config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId();
|
WechatPrepaySnapshot snapshot = getPrepaySnapshot(payment, outTradeNo);
|
||||||
if (active.equals("dev")) {
|
if (snapshot == null) {
|
||||||
notifyUrl = "http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId();
|
snapshot = new WechatPrepaySnapshot();
|
||||||
|
snapshot.setOutTradeNo(outTradeNo);
|
||||||
|
snapshot.setWechatPayType(WechatPayType.NATIVE);
|
||||||
|
snapshot.setAppid(payment.getAppId());
|
||||||
|
snapshot.setMchid(payment.getMchId());
|
||||||
|
snapshot.setAttach(order.getTenantId().toString());
|
||||||
|
snapshot.setTotal(money);
|
||||||
|
snapshot.setDescription(description);
|
||||||
|
// notifyUrl 在下面根据重试结果回填
|
||||||
|
} else {
|
||||||
|
// 使用快照中的关键字段,确保重入时参数一致
|
||||||
|
if (snapshot.getTotal() != null) {
|
||||||
|
amount.setTotal(snapshot.getTotal());
|
||||||
}
|
}
|
||||||
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
|
if (StrUtil.isNotBlank(snapshot.getDescription())) {
|
||||||
notifyUrl = payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString());
|
request.setDescription(snapshot.getDescription());
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getAttach())) {
|
||||||
|
request.setAttach(snapshot.getAttach());
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getAppid())) {
|
||||||
|
request.setAppid(snapshot.getAppid());
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getMchid())) {
|
||||||
|
request.setMchid(snapshot.getMchid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开发环境固定使用1分钱(与历史行为保持一致)
|
||||||
|
if ("dev".equals(active)) {
|
||||||
|
amount.setTotal(1);
|
||||||
|
request.setAmount(amount);
|
||||||
|
snapshot.setTotal(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回调地址:优先用快照;若重入提示参数不一致,则尝试历史回调地址
|
||||||
|
Exception last = null;
|
||||||
|
String notifyUrlUsed = null;
|
||||||
|
for (String notifyUrl : buildNotifyUrlCandidates(order, payment, snapshot)) {
|
||||||
|
if (StrUtil.isBlank(notifyUrl)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
request.setNotifyUrl(notifyUrl);
|
request.setNotifyUrl(notifyUrl);
|
||||||
|
try {
|
||||||
System.out.println("=== 发起Native支付请求 ===");
|
System.out.println("=== 发起Native支付请求 ===");
|
||||||
System.out.println("请求参数: " + request);
|
System.out.println("请求参数: " + request);
|
||||||
|
|
||||||
// 调用Native支付API
|
|
||||||
com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = nativeService.prepay(request);
|
com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = nativeService.prepay(request);
|
||||||
|
notifyUrlUsed = notifyUrl;
|
||||||
|
|
||||||
System.out.println("=== Native支付响应成功 ===");
|
System.out.println("=== Native支付响应成功 ===");
|
||||||
System.out.println("二维码URL: " + response.getCodeUrl());
|
System.out.println("二维码URL: " + response.getCodeUrl());
|
||||||
|
|
||||||
// 构建返回数据
|
snapshot.setNotifyUrl(notifyUrlUsed);
|
||||||
|
snapshot.setTotal(amount.getTotal());
|
||||||
|
snapshot.setDescription(request.getDescription());
|
||||||
|
savePrepaySnapshot(payment, snapshot);
|
||||||
|
|
||||||
final HashMap<String, String> orderInfo = new HashMap<>();
|
final HashMap<String, String> orderInfo = new HashMap<>();
|
||||||
orderInfo.put("provider", "wxpay");
|
orderInfo.put("provider", "wxpay");
|
||||||
orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL
|
orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL
|
||||||
@@ -382,6 +586,18 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|||||||
orderInfo.put("wechatPayType", WechatPayType.NATIVE);
|
orderInfo.put("wechatPayType", WechatPayType.NATIVE);
|
||||||
|
|
||||||
return orderInfo;
|
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<ShopOrderMapper, ShopOrder
|
|||||||
JsapiServiceExtension service = getWxService(order);
|
JsapiServiceExtension service = getWxService(order);
|
||||||
System.out.println("微信支付服务构建完成");
|
System.out.println("微信支付服务构建完成");
|
||||||
|
|
||||||
// 订单金额
|
// 订单金额(分)
|
||||||
BigDecimal decimal = order.getPayPrice();
|
BigDecimal payAmount = order.getPayPrice() != null ? order.getPayPrice() : order.getTotalPrice();
|
||||||
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
|
Integer money = toFen(payAmount);
|
||||||
Integer money = multiply.intValue();
|
if (money == null) {
|
||||||
|
throw new RuntimeException("订单金额为null");
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("=== 构建支付请求参数 ===");
|
System.out.println("=== 构建支付请求参数 ===");
|
||||||
|
|
||||||
@@ -419,7 +637,8 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|||||||
request.setMchid(payment.getMchId());
|
request.setMchid(payment.getMchId());
|
||||||
|
|
||||||
// 微信支付description字段限制127字节,需要截断处理
|
// 微信支付description字段限制127字节,需要截断处理
|
||||||
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
|
String rawDescription = StrUtil.isNotBlank(order.getComments()) ? order.getComments() : "订单支付";
|
||||||
|
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(rawDescription);
|
||||||
System.out.println("设置描述: " + description);
|
System.out.println("设置描述: " + description);
|
||||||
request.setDescription(description);
|
request.setDescription(description);
|
||||||
|
|
||||||
@@ -433,26 +652,76 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|||||||
System.out.println("设置用户OpenID: " + order.getOpenid());
|
System.out.println("设置用户OpenID: " + order.getOpenid());
|
||||||
payer.setOpenid(order.getOpenid());
|
payer.setOpenid(order.getOpenid());
|
||||||
request.setPayer(payer);
|
request.setPayer(payer);
|
||||||
request.setNotifyUrl(config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
|
String outTradeNo = order.getOrderNo();
|
||||||
// 测试环境
|
WechatPrepaySnapshot snapshot = getPrepaySnapshot(payment, outTradeNo);
|
||||||
if (active.equals("dev")) {
|
if (snapshot == null) {
|
||||||
|
snapshot = new WechatPrepaySnapshot();
|
||||||
|
snapshot.setOutTradeNo(outTradeNo);
|
||||||
|
snapshot.setWechatPayType(WechatPayType.JSAPI);
|
||||||
|
snapshot.setAppid(payment.getAppId());
|
||||||
|
snapshot.setMchid(payment.getMchId());
|
||||||
|
snapshot.setOpenid(order.getOpenid());
|
||||||
|
snapshot.setAttach(order.getTenantId().toString());
|
||||||
|
snapshot.setTotal(amount.getTotal());
|
||||||
|
snapshot.setDescription(request.getDescription());
|
||||||
|
// notifyUrl 在下面根据重试结果回填
|
||||||
|
} else {
|
||||||
|
// 使用快照中的关键字段,确保重入时参数一致
|
||||||
|
if (snapshot.getTotal() != null) {
|
||||||
|
amount.setTotal(snapshot.getTotal());
|
||||||
|
request.setAmount(amount);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getDescription())) {
|
||||||
|
request.setDescription(snapshot.getDescription());
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getAttach())) {
|
||||||
|
request.setAttach(snapshot.getAttach());
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getOpenid())) {
|
||||||
|
payer.setOpenid(snapshot.getOpenid());
|
||||||
|
request.setPayer(payer);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getAppid())) {
|
||||||
|
request.setAppid(snapshot.getAppid());
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(snapshot.getMchid())) {
|
||||||
|
request.setMchid(snapshot.getMchid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试环境使用1分钱(快照优先)
|
||||||
|
if ("dev".equals(active)) {
|
||||||
amount.setTotal(1);
|
amount.setTotal(1);
|
||||||
request.setAmount(amount);
|
request.setAmount(amount);
|
||||||
request.setNotifyUrl("http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
|
snapshot.setTotal(1);
|
||||||
}
|
}
|
||||||
// 后台配置的回调地址
|
|
||||||
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
|
Exception last = null;
|
||||||
request.setNotifyUrl(payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString()));
|
String notifyUrlUsed = null;
|
||||||
System.out.println("后台配置的回调地址 = " + request.getNotifyUrl());
|
for (String notifyUrl : buildNotifyUrlCandidates(order, payment, snapshot)) {
|
||||||
|
if (StrUtil.isBlank(notifyUrl)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
request.setNotifyUrl(notifyUrl);
|
||||||
|
try {
|
||||||
System.out.println("=== 发起微信支付请求 ===");
|
System.out.println("=== 发起微信支付请求 ===");
|
||||||
System.out.println("请求参数: " + request);
|
System.out.println("请求参数: " + request);
|
||||||
|
|
||||||
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
|
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
|
||||||
|
notifyUrlUsed = notifyUrl;
|
||||||
|
|
||||||
System.out.println("=== 微信支付响应成功 ===");
|
System.out.println("=== 微信支付响应成功 ===");
|
||||||
System.out.println("预支付ID: " + response.getPackageVal());
|
System.out.println("预支付ID: " + response.getPackageVal());
|
||||||
|
|
||||||
|
snapshot.setNotifyUrl(notifyUrlUsed);
|
||||||
|
snapshot.setTotal(request.getAmount().getTotal());
|
||||||
|
snapshot.setDescription(request.getDescription());
|
||||||
|
snapshot.setOpenid(request.getPayer() != null ? request.getPayer().getOpenid() : null);
|
||||||
|
snapshot.setAppid(request.getAppid());
|
||||||
|
snapshot.setMchid(request.getMchid());
|
||||||
|
snapshot.setAttach(request.getAttach());
|
||||||
|
savePrepaySnapshot(payment, snapshot);
|
||||||
|
|
||||||
final HashMap<String, String> orderInfo = new HashMap<>();
|
final HashMap<String, String> orderInfo = new HashMap<>();
|
||||||
orderInfo.put("provider", "wxpay");
|
orderInfo.put("provider", "wxpay");
|
||||||
orderInfo.put("timeStamp", response.getTimeStamp());
|
orderInfo.put("timeStamp", response.getTimeStamp());
|
||||||
@@ -464,6 +733,18 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|||||||
orderInfo.put("payType", WechatPayType.JSAPI);
|
orderInfo.put("payType", WechatPayType.JSAPI);
|
||||||
orderInfo.put("wechatPayType", WechatPayType.JSAPI);
|
orderInfo.put("wechatPayType", WechatPayType.JSAPI);
|
||||||
return orderInfo;
|
return orderInfo;
|
||||||
|
} catch (Exception e) {
|
||||||
|
last = e;
|
||||||
|
if (!isIdempotencyParamMismatch(e)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.warn("JSAPI预下单重入参数不一致,尝试切换notifyUrl重试 - outTradeNo={}, notifyUrl={}", outTradeNo, notifyUrl, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (last != null) {
|
||||||
|
throw last;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("创建JSAPI支付订单失败:notifyUrl为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user