|
|
@@ -50,6 +50,10 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
private static final String GETMETHOD = "GET";
|
|
|
private static final String REDIS_PREPAY_KEY_PREFIX = "merchant:wechat:prepay:order:";
|
|
|
private static final long REDIS_PREPAY_EXPIRE_SECONDS = 15 * 60;
|
|
|
+ /** 防抖 key 前缀:同一订单在有效期内仅允许一次创建预支付 */
|
|
|
+ private static final String REDIS_PREPAY_DEBOUNCE_KEY_PREFIX = "merchant:wechat:prepay:debounce:order:";
|
|
|
+ /** 防抖有效期(秒),期内重复调用返回请勿重复提交 */
|
|
|
+ private static final long REDIS_PREPAY_DEBOUNCE_SECONDS = 5;
|
|
|
|
|
|
@Value("${payment.wechatPay.host:https://api.mch.weixin.qq.com}")
|
|
|
private String wechatPayApiHost;
|
|
|
@@ -121,6 +125,14 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 防抖:同一订单在有效期内只允许一次创建预支付,防止重复调用
|
|
|
+ String debounceKey = REDIS_PREPAY_DEBOUNCE_KEY_PREFIX + orderId;
|
|
|
+ Boolean debounceSet = stringRedisTemplate.opsForValue().setIfAbsent(debounceKey, "1", REDIS_PREPAY_DEBOUNCE_SECONDS, TimeUnit.SECONDS);
|
|
|
+ if (Boolean.FALSE.equals(debounceSet)) {
|
|
|
+ log.warn("商户微信预支付防抖拦截,orderId={}", orderId);
|
|
|
+ return R.fail("请勿重复提交,请稍后再试");
|
|
|
+ }
|
|
|
+
|
|
|
int deleted = merchantPaymentOrderService.logicDeleteByOrderIdAndPayType(orderId, PaymentEnum.WECHAT_PAY.getType());
|
|
|
if (deleted > 0) {
|
|
|
log.info("未命中缓存,已逻辑删除该订单下微信支付单 {} 条,orderId={}", deleted, orderId);
|
|
|
@@ -199,19 +211,24 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
|
|
|
@Override
|
|
|
public R<Object> queryPayStatus(Integer storeId, String outTradeNo) {
|
|
|
+ log.info("queryPayStatus 开始 storeId={} outTradeNo={}", storeId, outTradeNo);
|
|
|
if (storeId == null || StringUtils.isBlank(outTradeNo)) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=参数为空", storeId, outTradeNo);
|
|
|
return R.fail("门店ID和商户订单号不能为空");
|
|
|
}
|
|
|
StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
if (config == null) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=未配置支付参数", storeId, outTradeNo);
|
|
|
return R.fail("该门店未配置支付参数");
|
|
|
}
|
|
|
MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
|
|
|
if (paymentOrder == null) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=支付单不存在", storeId, outTradeNo);
|
|
|
return R.fail("支付单不存在");
|
|
|
}
|
|
|
UserReservationOrder order = userReservationOrderService.getById(paymentOrder.getOrderId());
|
|
|
if (order == null) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=预订订单不存在 orderId={}", storeId, outTradeNo, paymentOrder.getOrderId());
|
|
|
return R.fail("预订订单不存在");
|
|
|
}
|
|
|
try {
|
|
|
@@ -223,8 +240,10 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
req.mchid = config.getWechatMchId();
|
|
|
WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse response = searchOrderRun(config, privateKey, wechatPayPublicKey, req);
|
|
|
if (response == null) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=微信返回为空", storeId, outTradeNo);
|
|
|
return R.fail("查询失败");
|
|
|
}
|
|
|
+ log.info("queryPayStatus 微信查单结果 storeId={} outTradeNo={} tradeState={} tradeStateDesc={}", storeId, outTradeNo, response.tradeState, response.tradeStateDesc);
|
|
|
if ("SUCCESS".equals(response.tradeState)) {
|
|
|
Date now = new Date();
|
|
|
paymentOrder.setPayStatus(1);
|
|
|
@@ -255,20 +274,26 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
}
|
|
|
}
|
|
|
reservationOrderPaymentTimeoutService.cancelReservationOrderPaymentTimeout(order.getOrderSn());
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=success tradeState=SUCCESS orderId={}", storeId, outTradeNo, order.getId());
|
|
|
return R.success("支付成功");
|
|
|
}
|
|
|
if ("CLOSED".equals(response.tradeState)) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=交易已关闭", storeId, outTradeNo);
|
|
|
return R.fail("交易已关闭");
|
|
|
}
|
|
|
if ("NOTPAY".equals(response.tradeState) || "USERPAYING".equals(response.tradeState)) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=等待用户付款 tradeState={}", storeId, outTradeNo, response.tradeState);
|
|
|
return R.fail("等待用户付款");
|
|
|
}
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=其他状态 tradeState={} tradeStateDesc={}", storeId, outTradeNo, response.tradeState, response.tradeStateDesc);
|
|
|
return R.fail("订单状态:" + (response.tradeStateDesc != null ? response.tradeStateDesc : response.tradeState));
|
|
|
} catch (WXPayUtility.ApiException e) {
|
|
|
- log.error("查询商户微信订单状态异常,outTradeNo={}", outTradeNo, e);
|
|
|
+ log.error("queryPayStatus 异常 storeId={} outTradeNo={} ApiException statusCode={} body={}", storeId, outTradeNo, e.getStatusCode(), e.getBody(), e);
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=ApiException", storeId, outTradeNo);
|
|
|
return R.fail("查询异常:" + e.getMessage());
|
|
|
} catch (Exception e) {
|
|
|
- log.error("商户微信查询支付状态异常,storeId={}", storeId, e);
|
|
|
+ log.error("queryPayStatus 异常 storeId={} outTradeNo={}", storeId, outTradeNo, e);
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=Exception", storeId, outTradeNo);
|
|
|
return R.fail("查询失败:" + e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
@@ -311,9 +336,37 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
request.amount.total = order.getDepositAmount().multiply(new BigDecimal(100)).longValue();
|
|
|
request.amount.currency = "CNY";
|
|
|
|
|
|
- WeChatPaymentStrategyImpl.Refund response = refundRun(config, privateKey, wechatPayPublicKey, request);
|
|
|
+ WeChatPaymentStrategyImpl.Refund response = null;
|
|
|
+ WXPayUtility.ApiException lastApiException = null;
|
|
|
+ int maxAttempts = 3;
|
|
|
+ for (int attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
|
+ try {
|
|
|
+ response = refundRun(config, privateKey, wechatPayPublicKey, request);
|
|
|
+ break;
|
|
|
+ } catch (WXPayUtility.ApiException e) {
|
|
|
+ lastApiException = e;
|
|
|
+ if (e.getStatusCode() == 429 && attempt < maxAttempts) {
|
|
|
+ long delayMs = 2000L * attempt;
|
|
|
+ log.warn("微信退款 429 限频,{}ms 后重试,第 {}/{} 次,outTradeNo={}", delayMs, attempt, maxAttempts, outTradeNo);
|
|
|
+ try {
|
|
|
+ Thread.sleep(delayMs);
|
|
|
+ } catch (InterruptedException ie) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ return R.fail("操作过于频繁,请稍后再试");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (e.getStatusCode() == 429) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
if (response == null) {
|
|
|
- return R.fail("退款失败");
|
|
|
+ String msg = lastApiException != null && lastApiException.getStatusCode() == 429
|
|
|
+ ? (StringUtils.isNotBlank(lastApiException.getErrorMessage()) ? lastApiException.getErrorMessage() : "操作过于频繁,请稍后再试")
|
|
|
+ : "退款失败";
|
|
|
+ return R.fail(msg);
|
|
|
}
|
|
|
String status = response.status != null ? response.status.name() : "";
|
|
|
if (!"SUCCESS".equals(status) && !"PROCESSING".equals(status)) {
|
|
|
@@ -324,7 +377,6 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
BigDecimal refundAmountDecimal = new BigDecimal(refundAmount);
|
|
|
paymentOrder.setPayStatus(3);
|
|
|
paymentOrder.setUpdatedTime(now);
|
|
|
- merchantPaymentOrderService.updateById(paymentOrder);
|
|
|
|
|
|
order.setOrderStatus(7);
|
|
|
order.setPaymentStatus(2);
|
|
|
@@ -350,12 +402,15 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
record.setCreatedUserId(order.getUserId());
|
|
|
record.setUpdatedUserId(order.getUserId());
|
|
|
record.setDeleteFlag(0);
|
|
|
- refundRecordAsyncService.saveRefundRecordAsync(record);
|
|
|
+ refundRecordAsyncService.completeRefundAsync(paymentOrder, record);
|
|
|
|
|
|
log.info("商户预订订单微信退款成功,outTradeNo={}", outTradeNo);
|
|
|
return R.data("退款成功");
|
|
|
} catch (WXPayUtility.ApiException e) {
|
|
|
log.error("商户预订订单微信退款异常,outTradeNo={}", outTradeNo, e);
|
|
|
+ if (e.getStatusCode() == 429) {
|
|
|
+ return R.fail(StringUtils.isNotBlank(e.getErrorMessage()) ? e.getErrorMessage() : "操作过于频繁,请稍后再试");
|
|
|
+ }
|
|
|
return R.fail("退款失败:" + e.getMessage());
|
|
|
} catch (Exception e) {
|
|
|
log.error("商户微信退款异常,storeId={}", storeId, e);
|