Selaa lähdekoodia

fix: cancelOldDeviceOrders 增加创建时间保护,防止误取消购物流程中的订单

问题:
- 用户连续购物时,前一个 pre-create 的支付分订单(PayScore A)
  尚未被 ORC_RESULT 关联(DoorRecord 因 Fix2 未覆盖 Redis 而错配了旧 PayScore),
  下一个 pre-create 触发 cancelOldDeviceOrders 时将其取消
- 取消后 ORDER 回调 complete 失败(INVALID_REQUEST),订单扣费失败

修复:
- cancelOldDeviceOrders 新增 isRecentlyCreated() 时间保护
- 创建不到 MIN_CANCEL_AGE_MINUTES(5分钟) 的 CREATED 订单不取消
- outOrderNo 格式 PS{10位timestamp}{suffix},直接提取时间戳判断

Co-Authored-By: Claude <noreply@anthropic.com>
skyline 2 viikkoa sitten
vanhempi
säilyke
c2c0161a07

+ 34 - 0
haha-service/src/main/java/com/haha/service/payment/payscore/impl/PayScoreServiceImpl.java

@@ -1369,7 +1369,14 @@ 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;
@@ -1386,6 +1393,12 @@ public class PayScoreServiceImpl implements PayScoreService {
                             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);
                     }
@@ -1396,6 +1409,27 @@ public class PayScoreServiceImpl implements PayScoreService {
         }
     }
 
+    /**
+     * 从 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)的订单不应被取消