|
@@ -183,8 +183,8 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
- [x] T053 **reconcile 扫描改全量未付**(`PosOrderOmgPaymentMapper.xml` + Service):`selectLeakOrderDdIds` 改 `EXISTS(任意 pay_status=0 行)` 去重 ddId(不再 MAX(id) 取一条);新增 `listUnpaidByDdId`(全量 pay_status=0,is_active 不限);`selectLatestPaidByDdId` 扩 `IN(1,3,4)`(修退款中/已退返 null);新增 `selectLatestRefundableByDdId`(pay_status IN(1,3,4))、`existsPaidByDdId`。
|
|
- [x] T053 **reconcile 扫描改全量未付**(`PosOrderOmgPaymentMapper.xml` + Service):`selectLeakOrderDdIds` 改 `EXISTS(任意 pay_status=0 行)` 去重 ddId(不再 MAX(id) 取一条);新增 `listUnpaidByDdId`(全量 pay_status=0,is_active 不限);`selectLatestPaidByDdId` 扩 `IN(1,3,4)`(修退款中/已退返 null);新增 `selectLatestRefundableByDdId`(pay_status IN(1,3,4))、`existsPaidByDdId`。
|
|
|
- [x] T054 **reconcileByQuery 两段式**(`OmgPayController.reconcileByQuery`):① 先 selectLatestPaidByDdId(pay_status=1 且 order.payStatus=0 → handlePaymentSuccess 自愈,L774 不能变死代码);② 否则 listUnpaidByDdId 遍历全量未付行 queryTrade:TradeStatus=1 applyPaidResult break / 0 no-op / 10200095 且 trade_no IS NULL markFail / 10200095 且 trade_no 非空 no-op。
|
|
- [x] T054 **reconcileByQuery 两段式**(`OmgPayController.reconcileByQuery`):① 先 selectLatestPaidByDdId(pay_status=1 且 order.payStatus=0 → handlePaymentSuccess 自愈,L774 不能变死代码);② 否则 listUnpaidByDdId 遍历全量未付行 queryTrade:TradeStatus=1 applyPaidResult break / 0 no-op / 10200095 且 trade_no IS NULL markFail / 10200095 且 trade_no 非空 no-op。
|
|
|
- [ ] T055 **三道节流闸(防 OMG 403)**(新建 queryTrade 节流组件 `ruoyi-admin/.../utils/omg/OmgQueryThrottle.java` + Redis):首查跳过 create_time<NOW()-INTERVAL 40 MINUTE;per-MerchantID 令牌桶 `omg:qt:token:{merchantId}`(1token/3s,burst1)acquire 失败 skip 本行本轮;per-ddId TTL(`omg:reconcile:dd:{ddId}` 180s / `omg:query:dd:{ddId}` 60s)。reconcileByQuery 与 /query 接入。
|
|
- [ ] T055 **三道节流闸(防 OMG 403)**(新建 queryTrade 节流组件 `ruoyi-admin/.../utils/omg/OmgQueryThrottle.java` + Redis):首查跳过 create_time<NOW()-INTERVAL 40 MINUTE;per-MerchantID 令牌桶 `omg:qt:token:{merchantId}`(1token/3s,burst1)acquire 失败 skip 本行本轮;per-ddId TTL(`omg:reconcile:dd:{ddId}` 180s / `omg:query:dd:{ddId}` 60s)。reconcileByQuery 与 /query 接入。
|
|
|
-- [ ] T056 **DDL 追加式 S 字段**(写 `updatesql/sql.md`,不直接执行):`pos_order_omg_payment` + `expire_date DATETIME NULL`(仅 paymentInfo 回调写,create 不猜)+ `is_active TINYINT NOT NULL DEFAULT 1` + 生成列 `active_dd_id=IF(is_active=1 AND pay_status=0,dd_id,NULL) VIRTUAL` + `UNIQUE uk_omg_active_dd(active_dd_id)`(需 MySQL 5.7.6+,**生产版本待确认**;不够则退化 `idx_omg_dd_active(dd_id,is_active,pay_status)`+app 锁+CAS);历史回填(每 ddId 最新 pay_status=0 置 is_active=1,其余 0;终态行 0;**绝不回填 expire_date**)。
|
|
|
|
|
-- [ ] T057 **实体 + mapper**(`PosOrderOmgPayment.java` + Mapper/XML):+ isActive/expireDate 字段(activeDdId 生成列只读可不映射);加 `selectActiveForReuse(ddId,freshMin)`(WHERE dd_id AND is_active=1 AND pay_status=0 AND trade_no IS NULL AND create_time>=NOW()-INTERVAL freshMin MINUTE ORDER BY id DESC LIMIT 1)、`markActiveHistorical(ddId)`(CAS UPDATE is_active=0 WHERE dd_id AND is_active=1 AND pay_status=0,不动 pay_status)、`markPaymentInfoIfOpen` 扩 expire_date 解析;**刻意不提供 updateMerchantTradeNo/updateTradeNo**(从能力杜绝换号丢钱)。
|
|
|
|
|
|
|
+- [x] T056 **DDL 追加式 S 字段**(写 `updatesql/sql.md`,不直接执行):`pos_order_omg_payment` + `expire_date DATETIME NULL`(仅 paymentInfo 回调写,create 不猜)+ `is_active TINYINT NOT NULL DEFAULT 1` + 生成列 `active_dd_id=IF(is_active=1 AND pay_status=0,dd_id,NULL) VIRTUAL` + `UNIQUE uk_omg_active_dd(active_dd_id)`(需 MySQL 5.7.6+,**生产版本待确认**;不够则退化 `idx_omg_dd_active(dd_id,is_active,pay_status)`+app 锁+CAS);历史回填(每 ddId 最新 pay_status=0 置 is_active=1,其余 0;终态行 0;**绝不回填 expire_date**)。
|
|
|
|
|
+- [x] T057 **实体 + mapper**(`PosOrderOmgPayment.java` + Mapper/XML):+ isActive/expireDate 字段(activeDdId 生成列只读可不映射);加 `selectActiveForReuse(ddId,freshMin)`(WHERE dd_id AND is_active=1 AND pay_status=0 AND trade_no IS NULL AND create_time>=NOW()-INTERVAL freshMin MINUTE ORDER BY id DESC LIMIT 1)、`markActiveHistorical(ddId)`(CAS UPDATE is_active=0 WHERE dd_id AND is_active=1 AND pay_status=0,不动 pay_status)、`markPaymentInfoIfOpen` 扩 expire_date 解析;**刻意不提供 updateMerchantTradeNo/updateTradeNo**(从能力杜绝换号丢钱)。
|
|
|
- [ ] T058 **create 复用预检**(`OmgPayController.create`,保留 @Transactional):Redisson `RLock lock:omg:create:{ddId}` tryLock(等 3s),**锁释放在 TransactionSynchronizationManager.registerSynchronization 的 afterCommit**(仿 WalletService.returnPoints,**禁 try/finally unlock**——否则 TX 提交前泄锁致重复活跃行);selectActiveForReuse 命中复用 MTN(form 用当前时刻重算 CheckMacValue);未命中同 TX:markActiveHistorical(旧 is_active→0)+ createPayment 新行(is_active=1, expire_date=NULL);DuplicateKeyException 重试 3 次(uk_omg_active_dd DB 兜底)。
|
|
- [ ] T058 **create 复用预检**(`OmgPayController.create`,保留 @Transactional):Redisson `RLock lock:omg:create:{ddId}` tryLock(等 3s),**锁释放在 TransactionSynchronizationManager.registerSynchronization 的 afterCommit**(仿 WalletService.returnPoints,**禁 try/finally unlock**——否则 TX 提交前泄锁致重复活跃行);selectActiveForReuse 命中复用 MTN(form 用当前时刻重算 CheckMacValue);未命中同 TX:markActiveHistorical(旧 is_active→0)+ createPayment 新行(is_active=1, expire_date=NULL);DuplicateKeyException 重试 3 次(uk_omg_active_dd DB 兜底)。
|
|
|
- [ ] T059 **读侧 7 处 getLatestByDdId 全迁移**:`getPaymentInfo`/`refundOrderOutcome`/L569 早退/`markRefunding` 并发/`confirmManualRefundOutcome`/`reconcileByQuery`(T054)/`OrderLifecycleService.buildContext`/`validateOmgReconcile`/`validateOmgRefund` → `selectLatestRefundableByDdId`(pay_status IN(1,3,4))或 `listPaidByDdId`;修"订单未支付"早退为 listPaidByDdId 为空才 FAILED。
|
|
- [ ] T059 **读侧 7 处 getLatestByDdId 全迁移**:`getPaymentInfo`/`refundOrderOutcome`/L569 早退/`markRefunding` 并发/`confirmManualRefundOutcome`/`reconcileByQuery`(T054)/`OrderLifecycleService.buildContext`/`validateOmgReconcile`/`validateOmgRefund` → `selectLatestRefundableByDdId`(pay_status IN(1,3,4))或 `listPaidByDdId`;修"订单未支付"早退为 listPaidByDdId 为空才 FAILED。
|
|
|
- [ ] T060 **退款遍历**(`refundOrderOutcome`):改 `listPaidByDdId` 遍历所有 pay_status=1 且 trade_no 非空行;每行先查 `pos_order_omg_refund` 是否 action=R 且 rtnCode=1 → 跳过;否则 `markRefundingIfPaid`(1→4 CAS)→ `DoAction(Action=R)` 逐笔退(每笔独立 tradeNo);ATM/超商无退款 API 走 manual pending;孤立已付(双付第二笔)写 `ipn_log(type=omg_orphan_paid)` + 运营告警。
|
|
- [ ] T060 **退款遍历**(`refundOrderOutcome`):改 `listPaidByDdId` 遍历所有 pay_status=1 且 trade_no 非空行;每行先查 `pos_order_omg_refund` 是否 action=R 且 rtnCode=1 → 跳过;否则 `markRefundingIfPaid`(1→4 CAS)→ `DoAction(Action=R)` 逐笔退(每笔独立 tradeNo);ATM/超商无退款 API 走 manual pending;孤立已付(双付第二笔)写 `ipn_log(type=omg_orphan_paid)` + 运营告警。
|