|
|
|
@@ -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<ShopOrderMapper, ShopOrder
|
|
|
|
|
private ShopExpressService shopExpressService;
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
@@ -321,13 +482,15 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|
|
|
|
Config wxPayConfig = getWxPayConfig(order);
|
|
|
|
|
NativePayService nativeService = new NativePayService.Builder().config(wxPayConfig).build();
|
|
|
|
|
|
|
|
|
|
// 订单金额(转换为分)
|
|
|
|
|
BigDecimal decimal = order.getTotalPrice();
|
|
|
|
|
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
|
|
|
|
|
Integer money = multiply.intValue();
|
|
|
|
|
// 订单金额(转换为分)- 优先使用 payPrice,避免与 JSAPI 金额字段不一致导致重入校验失败
|
|
|
|
|
BigDecimal payAmount = order.getPayPrice() != null ? order.getPayPrice() : order.getTotalPrice();
|
|
|
|
|
Integer money = toFen(payAmount);
|
|
|
|
|
if (money == null) {
|
|
|
|
|
throw new RuntimeException("订单金额为null");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 测试环境使用1分钱
|
|
|
|
|
if (active.equals("dev")) {
|
|
|
|
|
if ("dev".equals(active)) {
|
|
|
|
|
money = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -354,34 +517,87 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|
|
|
|
System.out.println("商户号(MchId): " + payment.getMchId());
|
|
|
|
|
System.out.println("应用ID(AppId): " + payment.getAppId());
|
|
|
|
|
|
|
|
|
|
// 设置回调地址
|
|
|
|
|
String notifyUrl = config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId();
|
|
|
|
|
if (active.equals("dev")) {
|
|
|
|
|
notifyUrl = "http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId();
|
|
|
|
|
String outTradeNo = order.getOrderNo();
|
|
|
|
|
WechatPrepaySnapshot snapshot = getPrepaySnapshot(payment, outTradeNo);
|
|
|
|
|
if (snapshot == null) {
|
|
|
|
|
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(snapshot.getDescription())) {
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
|
|
|
|
|
notifyUrl = payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString());
|
|
|
|
|
|
|
|
|
|
// 开发环境固定使用1分钱(与历史行为保持一致)
|
|
|
|
|
if ("dev".equals(active)) {
|
|
|
|
|
amount.setTotal(1);
|
|
|
|
|
request.setAmount(amount);
|
|
|
|
|
snapshot.setTotal(1);
|
|
|
|
|
}
|
|
|
|
|
request.setNotifyUrl(notifyUrl);
|
|
|
|
|
|
|
|
|
|
System.out.println("=== 发起Native支付请求 ===");
|
|
|
|
|
System.out.println("请求参数: " + request);
|
|
|
|
|
// 回调地址:优先用快照;若重入提示参数不一致,则尝试历史回调地址
|
|
|
|
|
Exception last = null;
|
|
|
|
|
String notifyUrlUsed = null;
|
|
|
|
|
for (String notifyUrl : buildNotifyUrlCandidates(order, payment, snapshot)) {
|
|
|
|
|
if (StrUtil.isBlank(notifyUrl)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
request.setNotifyUrl(notifyUrl);
|
|
|
|
|
try {
|
|
|
|
|
System.out.println("=== 发起Native支付请求 ===");
|
|
|
|
|
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("二维码URL: " + response.getCodeUrl());
|
|
|
|
|
System.out.println("=== Native支付响应成功 ===");
|
|
|
|
|
System.out.println("二维码URL: " + response.getCodeUrl());
|
|
|
|
|
|
|
|
|
|
// 构建返回数据
|
|
|
|
|
final HashMap<String, String> 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<String, String> 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<ShopOrderMapper, ShopOrder
|
|
|
|
|
JsapiServiceExtension service = getWxService(order);
|
|
|
|
|
System.out.println("微信支付服务构建完成");
|
|
|
|
|
|
|
|
|
|
// 订单金额
|
|
|
|
|
BigDecimal decimal = order.getPayPrice();
|
|
|
|
|
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
|
|
|
|
|
Integer money = multiply.intValue();
|
|
|
|
|
// 订单金额(分)
|
|
|
|
|
BigDecimal payAmount = order.getPayPrice() != null ? order.getPayPrice() : order.getTotalPrice();
|
|
|
|
|
Integer money = toFen(payAmount);
|
|
|
|
|
if (money == null) {
|
|
|
|
|
throw new RuntimeException("订单金额为null");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
System.out.println("=== 构建支付请求参数 ===");
|
|
|
|
|
|
|
|
|
@@ -419,7 +637,8 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|
|
|
|
request.setMchid(payment.getMchId());
|
|
|
|
|
|
|
|
|
|
// 微信支付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);
|
|
|
|
|
request.setDescription(description);
|
|
|
|
|
|
|
|
|
@@ -433,37 +652,99 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
|
|
|
|
System.out.println("设置用户OpenID: " + order.getOpenid());
|
|
|
|
|
payer.setOpenid(order.getOpenid());
|
|
|
|
|
request.setPayer(payer);
|
|
|
|
|
request.setNotifyUrl(config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
|
|
|
|
|
// 测试环境
|
|
|
|
|
if (active.equals("dev")) {
|
|
|
|
|
String outTradeNo = order.getOrderNo();
|
|
|
|
|
WechatPrepaySnapshot snapshot = getPrepaySnapshot(payment, outTradeNo);
|
|
|
|
|
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);
|
|
|
|
|
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())) {
|
|
|
|
|
request.setNotifyUrl(payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString()));
|
|
|
|
|
System.out.println("后台配置的回调地址 = " + request.getNotifyUrl());
|
|
|
|
|
|
|
|
|
|
Exception last = null;
|
|
|
|
|
String notifyUrlUsed = null;
|
|
|
|
|
for (String notifyUrl : buildNotifyUrlCandidates(order, payment, snapshot)) {
|
|
|
|
|
if (StrUtil.isBlank(notifyUrl)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
request.setNotifyUrl(notifyUrl);
|
|
|
|
|
try {
|
|
|
|
|
System.out.println("=== 发起微信支付请求 ===");
|
|
|
|
|
System.out.println("请求参数: " + request);
|
|
|
|
|
|
|
|
|
|
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
|
|
|
|
|
notifyUrlUsed = notifyUrl;
|
|
|
|
|
|
|
|
|
|
System.out.println("=== 微信支付响应成功 ===");
|
|
|
|
|
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<>();
|
|
|
|
|
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<String, String> 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
|
|
|
|
|