|
@@ -911,8 +911,6 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
if (result.isSuccess()) {
|
|
if (result.isSuccess()) {
|
|
|
trackPreAuthOrder(userId, outOrderNo);
|
|
trackPreAuthOrder(userId, outOrderNo);
|
|
|
storePreAuthToRedis(deviceId, userId, result);
|
|
storePreAuthToRedis(deviceId, userId, result);
|
|
|
- // 取消同设备旧的追踪订单(已被本次新订单替代)
|
|
|
|
|
- cancelOldDeviceOrders(userId, deviceId, outOrderNo);
|
|
|
|
|
|
|
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
Map<String, Object> data = new HashMap<>();
|
|
|
data.put("success", true);
|
|
data.put("success", true);
|
|
@@ -1213,14 +1211,12 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
*
|
|
*
|
|
|
* 分级策略(按优先级从高到低):
|
|
* 分级策略(按优先级从高到低):
|
|
|
* 1. 自动补扣:有金额(>0) 且 创建超过 1 分钟的订单 → 直接扣款,不损失资金
|
|
* 1. 自动补扣:有金额(>0) 且 创建超过 1 分钟的订单 → 直接扣款,不损失资金
|
|
|
- * 2. 取消未确认:CREATED 状态(用户从未授权) → 安全取消
|
|
|
|
|
- * 3. 取消超时孤儿:DOING/USER_PAYING + 金额为空 + 超过 30 分钟 → 异常单清理
|
|
|
|
|
|
|
+ * 2. 取消超时孤儿:DOING/USER_PAYING + 金额为空 + 超过 30 分钟 → 异常单清理
|
|
|
*
|
|
*
|
|
|
- * 注意:tracking SET 的清理已移至以下独立流程:
|
|
|
|
|
- * - 正常消费:applyPreAuthPayScore 创建订单时自动移除
|
|
|
|
|
- * - 无消费:handleNoConsume → cancelPayScoreForNoConsume 取消+移除
|
|
|
|
|
- * - 同设备旧订单:cancelOldDeviceOrders 在 preCreate 成功后清理
|
|
|
|
|
- * - 过期兜底:Redis tracking SET 24h TTL 自动过期
|
|
|
|
|
|
|
+ * 注意:CREATED 状态订单在以下流程中自然完结,不在此处批量取消:
|
|
|
|
|
+ * - 正常消费:ORC_RESULT(创建) → ORDER回调(complete) → USER_PAID(DONE)
|
|
|
|
|
+ * - 无消费:handleNoConsume → cancelPayScoreForNoConsume 针对当前 activity 取消
|
|
|
|
|
+ * - tracking SET 过期:Redis 24h TTL 自动清理
|
|
|
*
|
|
*
|
|
|
* @return 清理后用户剩余的未完结支付分订单数量
|
|
* @return 清理后用户剩余的未完结支付分订单数量
|
|
|
*/
|
|
*/
|
|
@@ -1253,26 +1249,7 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
log.error("[支付分服务] 自动补扣查询失败 - userId: {}", userId, e);
|
|
log.error("[支付分服务] 自动补扣查询失败 - userId: {}", userId, e);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 取消 CREATED 状态的孤儿订单(用户从未授权使用,不会损失资金)
|
|
|
|
|
- try {
|
|
|
|
|
- List<Order> inProgressOrders = orderService.lambdaQuery()
|
|
|
|
|
- .eq(Order::getUserId, userId)
|
|
|
|
|
- .eq(Order::getPayChannel, PaymentChannel.WECHAT_PAYSCORE.getCode())
|
|
|
|
|
- .eq(Order::getPayScoreState, PayScoreState.CREATED.getCode())
|
|
|
|
|
- .isNotNull(Order::getPayScoreOrderId)
|
|
|
|
|
- .list();
|
|
|
|
|
-
|
|
|
|
|
- if (inProgressOrders != null && !inProgressOrders.isEmpty()) {
|
|
|
|
|
- log.info("[支付分服务] 清理用户 {} 的 {} 笔CREATED状态订单", userId, inProgressOrders.size());
|
|
|
|
|
- for (Order order : inProgressOrders) {
|
|
|
|
|
- cancelPayScoreOrderByOutOrderNo(order.getPayScoreOrderId(), order.getId());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("[支付分服务] 清理CREATED订单失败 - userId: {}", userId, e);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 3. 取消超时孤儿:DOING/USER_PAYING + 金额为空 + 超过 30 分钟
|
|
|
|
|
|
|
+ // 2. 取消超时孤儿:DOING/USER_PAYING + 金额为空 + 超过 30 分钟
|
|
|
// 仅清理回调签名失败等异常造成的遗留订单,正常等待 ORDER 回调的不受影响
|
|
// 仅清理回调签名失败等异常造成的遗留订单,正常等待 ORDER 回调的不受影响
|
|
|
try {
|
|
try {
|
|
|
java.time.LocalDateTime staleThreshold = java.time.LocalDateTime.now().minusMinutes(30);
|
|
java.time.LocalDateTime staleThreshold = java.time.LocalDateTime.now().minusMinutes(30);
|
|
@@ -1408,70 +1385,6 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 取消同设备旧的预创建订单(已被本次新订单替代)
|
|
|
|
|
- * 必须检查关联的本地订单状态,避免误取消已确认/正在扣款的订单
|
|
|
|
|
- *
|
|
|
|
|
- * 安全规则:
|
|
|
|
|
- * 1. 微信侧已确认(DOING/DONE/USER_PAYING) → 不取消
|
|
|
|
|
- * 2. 创建不到 MIN_CANCEL_AGE_MINUTES 分钟(可能正在购物流程中)→ 不取消
|
|
|
|
|
- * 3. 本地有 DoorRecord 引用该 payScoreOrderId 且尚未关联订单 → 不取消(正在使用中)
|
|
|
|
|
- */
|
|
|
|
|
- private static final int MIN_CANCEL_AGE_MINUTES = 5;
|
|
|
|
|
-
|
|
|
|
|
- private void cancelOldDeviceOrders(Long userId, String deviceId, String currentOutOrderNo) {
|
|
|
|
|
- if (stringRedisTemplate == null) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- try {
|
|
|
|
|
- String safeDeviceId = deviceId.replaceAll("[^A-Za-z0-9]", "");
|
|
|
|
|
- String suffix = safeDeviceId.length() > 10 ? safeDeviceId.substring(safeDeviceId.length() - 10) : safeDeviceId;
|
|
|
|
|
- String trackingKey = REDIS_TRACKING_KEY + userId;
|
|
|
|
|
- java.util.Set<String> tracked = stringRedisTemplate.opsForSet().members(trackingKey);
|
|
|
|
|
- if (tracked != null) {
|
|
|
|
|
- for (String outOrderNo : tracked) {
|
|
|
|
|
- if (outOrderNo != null && outOrderNo.endsWith(suffix) && !outOrderNo.equals(currentOutOrderNo)) {
|
|
|
|
|
- if (!isPayScoreOrderSafeToCancel(outOrderNo)) {
|
|
|
|
|
- log.info("[支付分服务] 跳过取消同设备已确认的支付分订单 - outOrderNo: {}, deviceId: {}", outOrderNo, deviceId);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- // 订单太新,可能在购物流程中(等待开门/ORC_RESULT),不取消
|
|
|
|
|
- if (isRecentlyCreated(outOrderNo)) {
|
|
|
|
|
- log.info("[支付分服务] 跳过取消同设备刚创建的支付分订单 - outOrderNo: {}, deviceId: {}",
|
|
|
|
|
- outOrderNo, deviceId);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- cancelPayScoreOrderByOutOrderNo(outOrderNo, null);
|
|
|
|
|
- stringRedisTemplate.opsForSet().remove(trackingKey, outOrderNo);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.warn("[支付分服务] 清理设备旧追踪失败 - deviceId: {}", deviceId, e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 从 outOrderNo 中提取创建时间戳,判断是否在保护窗口内
|
|
|
|
|
- * outOrderNo 格式: PS{10位timestamp}{deviceSuffix},如 PS1786332436B150534
|
|
|
|
|
- */
|
|
|
|
|
- private boolean isRecentlyCreated(String outOrderNo) {
|
|
|
|
|
- try {
|
|
|
|
|
- if (outOrderNo == null || outOrderNo.length() < 12 || !outOrderNo.startsWith("PS")) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- // 提取 PS 后面的 10 位数字时间戳
|
|
|
|
|
- String tsStr = outOrderNo.substring(2, 12);
|
|
|
|
|
- long createTimestamp = Long.parseLong(tsStr);
|
|
|
|
|
- long nowSeconds = System.currentTimeMillis() / 1000;
|
|
|
|
|
- long ageMinutes = (nowSeconds - createTimestamp) / 60;
|
|
|
|
|
- return ageMinutes < MIN_CANCEL_AGE_MINUTES;
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.warn("[支付分服务] 解析 outOrderNo 时间戳失败 - outOrderNo: {}", outOrderNo, e);
|
|
|
|
|
- return false; // 解析失败保守不跳过,允许取消
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 检查支付分订单是否可以安全取消
|
|
* 检查支付分订单是否可以安全取消
|
|
|
* 已确认(DOING)或正在扣款(USER_PAYING)或已完成(DONE)的订单不应被取消
|
|
* 已确认(DOING)或正在扣款(USER_PAYING)或已完成(DONE)的订单不应被取消
|