|
@@ -55,13 +55,13 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
|
|
|
|
|
**Goal**: C 端用户下单后调发起接口拿到 form 参数,前端 Form Post 跳转 OMG 收银台(新订单仅信用卡与 Apple Pay)
|
|
**Goal**: C 端用户下单后调发起接口拿到 form 参数,前端 Form Post 跳转 OMG 收银台(新订单仅信用卡与 Apple Pay)
|
|
|
|
|
|
|
|
-**Independent Test**: 对已开通 OMG 的门店下单 → 调 `/pay/omg/create` → 返回 `gatewayUrl` + 全参数(含 CheckMacValue) → 前端跳转 OMG 页出现支付方式;`pos_order_omg_payment` 落一行(pay_status=0)、`pos_order.pay_type="7"`
|
|
|
|
|
|
|
+**Independent Test**: 对已开通 OMG 的门店下单 → 调 `/pay/omg/create` → 返回 `gatewayUrl` + 全参数(含 CheckMacValue) → 前端跳转 OMG 页出现支付方式;`pos_order_omg_payment` 落一行(pay_status=0)、`pos_order.pay_type="2"`
|
|
|
|
|
|
|
|
- [x] T012 [P] [US1] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/StoreOmgCredentialDto.java`(storeId/merchantId/hashKey/hashIv/enabledPayments,`@Valid`)与 `ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/PosStoreOmgVo.java`(列表/详情 VO)
|
|
- [x] T012 [P] [US1] 创建 `ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/StoreOmgCredentialDto.java`(storeId/merchantId/hashKey/hashIv/enabledPayments,`@Valid`)与 `ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/PosStoreOmgVo.java`(列表/详情 VO)
|
|
|
- [x] T013 [US1] 实现 `ruoyi-system/.../service/IPosStoreOmgService.java` + `impl/PosStoreOmgServiceImpl.java`(纯 DB:`getOrCreateByStoreId`、`getEnabledConfig(storeId)` 返回已开通且启用的凭证或 null、`getEnabledByMerchantId(merchantId)` 回调按商店代号反查凭证),仿 PosStoreEzpayServiceImpl
|
|
- [x] T013 [US1] 实现 `ruoyi-system/.../service/IPosStoreOmgService.java` + `impl/PosStoreOmgServiceImpl.java`(纯 DB:`getOrCreateByStoreId`、`getEnabledConfig(storeId)` 返回已开通且启用的凭证或 null、`getEnabledByMerchantId(merchantId)` 回调按商店代号反查凭证),仿 PosStoreEzpayServiceImpl
|
|
|
- [x] T014 [US1] 实现 `ruoyi-system/.../service/IPosOrderOmgPaymentService.java` + `impl/PosOrderOmgPaymentServiceImpl.java`:`createPayment(ddId,merchantTradeNo,storeId,merchantId,amount,choosePayment)`、`getByTradeNo`、`getByMerchantTradeNo`、`getLatestByDdId`、`markSuccess(...payType,rtnCode,rtnMsg,authCode,payTime,callbackRaw)`(幂等:tradeNo 已 pay_status=1 返回 0)、`markFail`、`markRefunded`
|
|
- [x] T014 [US1] 实现 `ruoyi-system/.../service/IPosOrderOmgPaymentService.java` + `impl/PosOrderOmgPaymentServiceImpl.java`:`createPayment(ddId,merchantTradeNo,storeId,merchantId,amount,choosePayment)`、`getByTradeNo`、`getByMerchantTradeNo`、`getLatestByDdId`、`markSuccess(...payType,rtnCode,rtnMsg,authCode,payTime,callbackRaw)`(幂等:tradeNo 已 pay_status=1 返回 0)、`markFail`、`markRefunded`
|
|
|
-- [x] T015 [US1] 在 `ruoyi-system/.../domain/PosOrder.java` 附近或新建常量类定义 `PAY_TYPE_OMG="7"`(OMG 在线支付),不改表结构
|
|
|
|
|
-- [x] T016 [US1] 创建 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/OmgPayController.java` 实现 `POST /pay/omg/create`(`@Anonymous`+`@Auth`,Header token):校验订单存在/未支付/属当前用户/金额>0 → `getEnabledConfig(mdId)`(无则 `error("该门店暂不支持线上支付")`)→ `MerchantTradeNo="OMG"+清洗(ddId)` → 组参(MerchantID/MerchantTradeNo/MerchantTradeDate=yyyy/MM/dd HH:mm:ss/PaymentType=aio/TotalAmount=amount/ReturnURL/ChoosePayment=ALL/EncryptType=1/ItemName/InvoiceMark=N)→ `OmgPay.createAioForm` 生成 CheckMacValue 与 form 字段 → `createPayment` 落流水(pay_status=0) → 更新 `pos_order.pay_type="7"`/`pay_url=gatewayUrl` → 返回 form 字段(见 contracts/api.md B1)
|
|
|
|
|
|
|
+- [x] T015 [US1] 在 `ruoyi-system/.../domain/PosOrder.java` 附近或新建常量类定义 `PAY_TYPE_OMG="2"`(OMG 在线支付),不改表结构
|
|
|
|
|
+- [x] T016 [US1] 创建 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/OmgPayController.java` 实现 `POST /pay/omg/create`(`@Anonymous`+`@Auth`,Header token):校验订单存在/未支付/属当前用户/金额>0 → `getEnabledConfig(mdId)`(无则 `error("该门店暂不支持线上支付")`)→ `MerchantTradeNo="OMG"+清洗(ddId)` → 组参(MerchantID/MerchantTradeNo/MerchantTradeDate/PaymentType=aio/TotalAmount/ReturnURL/ChoosePayment=Credit/UnionPay=2/EncryptType=1/ItemName/InvoiceMark=N)→ 生成 CheckMacValue 与 form 字段 → 落流水(pay_status=0) → 更新 `pos_order.pay_type="2"`/`pay_url=gatewayUrl` → 返回 form 字段(见 contracts/api.md B1)
|
|
|
- [ ] T017 [US1] 前端 `E:\QtwCode\foodie\foodie-store\` 新增「OMG 支付」入口:下单后调 `/pay/omg/create`,用返回字段构建隐藏 form 自动 submit 到 `gatewayUrl`;支付相关文案用 `$t()` 四语 i18n(zh/tw/en/vi,key 加到对应对象层级,有意义命名) ⏸️ 推迟:属客户侧流程(客户下单→Form Post 跳 OMG),foodie-store 是商家端无此入口,客户 uni-app 不在当前工作区,待其就位再做
|
|
- [ ] T017 [US1] 前端 `E:\QtwCode\foodie\foodie-store\` 新增「OMG 支付」入口:下单后调 `/pay/omg/create`,用返回字段构建隐藏 form 自动 submit 到 `gatewayUrl`;支付相关文案用 `$t()` 四语 i18n(zh/tw/en/vi,key 加到对应对象层级,有意义命名) ⏸️ 推迟:属客户侧流程(客户下单→Form Post 跳 OMG),foodie-store 是商家端无此入口,客户 uni-app 不在当前工作区,待其就位再做
|
|
|
|
|
|
|
|
**Checkpoint**: 发起支付链路通,可跳转 OMG 测试收银台(此时回调未接,订单状态待 US2 更新)
|
|
**Checkpoint**: 发起支付链路通,可跳转 OMG 测试收银台(此时回调未接,订单状态待 US2 更新)
|
|
@@ -77,7 +77,7 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
- [x] T018 [US2] 在 `OmgPayController.java` 实现 `POST /pay/omg/notify`(`@Anonymous`):collectForm 收参 → 将脱敏回调内容写入 IpnLog(`type=omg`)→ 由 `MerchantID` 调 `getEnabledByMerchantId` → 无凭证记录并返回
|
|
- [x] T018 [US2] 在 `OmgPayController.java` 实现 `POST /pay/omg/notify`(`@Anonymous`):collectForm 收参 → 将脱敏回调内容写入 IpnLog(`type=omg`)→ 由 `MerchantID` 调 `getEnabledByMerchantId` → 无凭证记录并返回
|
|
|
- [x] T019 [US2] notify 验签:`OmgCheckMacValue.verify(全部回调参, hashKey, hashIv)`,与回传 `CheckMacValue` 不符 → 拒绝并记录(绝不变更订单),仍回 `1|OK`
|
|
- [x] T019 [US2] notify 验签:`OmgCheckMacValue.verify(全部回调参, hashKey, hashIv)`,与回传 `CheckMacValue` 不符 → 拒绝并记录(绝不变更订单),仍回 `1|OK`
|
|
|
- [x] T020 [US2] notify 幂等+金额+状态校验:按 `TradeNo` 查 `pos_order_omg_payment`(已 pay_status=1 → 直接返回 `1|OK`);`TradeAmt==订单 amount`(不符→拒绝核销、告警);由 `MerchantTradeNo` 反查流水与订单(不存在→记录待查返回);判 `RtnCode==1 且 SimulatePaid!=1`(模拟支付 `SimulatePaid=1` → 不发货、记录)
|
|
- [x] T020 [US2] notify 幂等+金额+状态校验:按 `TradeNo` 查 `pos_order_omg_payment`(已 pay_status=1 → 直接返回 `1|OK`);`TradeAmt==订单 amount`(不符→拒绝核销、告警);由 `MerchantTradeNo` 反查流水与订单(不存在→记录待查返回);判 `RtnCode==1 且 SimulatePaid!=1`(模拟支付 `SimulatePaid=1` → 不发货、记录)
|
|
|
-- [x] T021 [US2] notify 成功业务链路:`markSuccess`(pay_type=回覆 PaymentType 如 `Credit_CreditCard`、rtn_code、pay_time=PaymentDate、callback_raw)→ 更新 `pos_order`(state=0, payStatus=1) → `orderLogHelper.logSync` 记日志 → `PayPush`+`pushEventService.PublisherEvent` 推送用户/商家(参照 `PosOrderController.sendHdfkMessage` 货到付款同款 `OrderPushBodyDto.getJson`)→ **补全可接单骑手推送**:在 `OmgPayController` 内独立实现(复用 `PayPush.sendAcceptRiderPush` / 货到付款链路调用方式,**不复用 NewebPay 代码**,见 CLAUDE.md「sendAcceptRiderPush 迁移」)→ 回应**纯字符串 `1|OK`**(注意:非 JSON,与蓝新不同)
|
|
|
|
|
|
|
+- [x] T021 [US2] notify 成功业务链路:`markSuccess`(pay_type=回覆 PaymentType 如 `Credit_CreditCard`、rtn_code、pay_time=PaymentDate、callback_raw)→ 原子更新 `pos_order`(`state IN (0,1)`, `pay_status=0→1`) → `orderLogHelper.logSync` 记日志 → `PayPush`+`pushEventService.PublisherEvent` 推送用户/商家(参照 `PosOrderController.sendHdfkMessage` 货到付款同款 `OrderPushBodyDto.getJson`)→ **补全可接单骑手推送**:在 `OmgPayController` 内独立实现(复用 `PayPush.sendAcceptRiderPush` / 货到付款链路调用方式,**不复用 NewebPay 代码**,见 CLAUDE.md「sendAcceptRiderPush 迁移」)→ 回应**纯字符串 `1|OK`**(注意:非 JSON,与蓝新不同)
|
|
|
- [x] T022 [US2] notify 失败处理(`RtnCode!=1`):`markFail` 写流水(pay_status=2)、订单保持未支付、记 `rtn_code`+`rtn_msg`(不硬编码错误码分支,见 contracts §C3)、回 `1|OK`
|
|
- [x] T022 [US2] notify 失败处理(`RtnCode!=1`):`markFail` 写流水(pay_status=2)、订单保持未支付、记 `rtn_code`+`rtn_msg`(不硬编码错误码分支,见 contracts §C3)、回 `1|OK`
|
|
|
- [x] T023 [US2] 在 `OmgPayController.java` 实现 `GET|POST /pay/omg/return`(`@Anonymous`):仅 302 重定向前端结果页(带 ddId),**不改订单状态**(以 notify 为准)
|
|
- [x] T023 [US2] 在 `OmgPayController.java` 实现 `GET|POST /pay/omg/return`(`@Anonymous`):仅 302 重定向前端结果页(带 ddId),**不改订单状态**(以 notify 为准)
|
|
|
- [ ] T024 [US2] 前端 `E:\QtwCode\foodie\foodie-store\` 支付结果页:展示支付状态、轮询订单 payStatus(ReturnURL 不改状态,以轮询/回调后状态为准);文案四语 i18n ⏸️ 推迟:同 T017,支付结果页属客户侧
|
|
- [ ] T024 [US2] 前端 `E:\QtwCode\foodie\foodie-store\` 支付结果页:展示支付状态、轮询订单 payStatus(ReturnURL 不改状态,以轮询/回调后状态为准);文案四语 i18n ⏸️ 推迟:同 T017,支付结果页属客户侧
|
|
@@ -102,28 +102,26 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## Phase 6: User Story 3 - ATM / 超商 非即时支付 (Priority: P3)
|
|
|
|
|
|
|
+## Phase 6: User Story 3 - 非即时支付(已撤销)
|
|
|
|
|
|
|
|
-**Goal**: `ChoosePayment=ALL` 下消费者选 ATM/超商时,平台展示 OMG 虚帐/缴费码,消费者期限内付款后回调核销
|
|
|
|
|
|
|
+**Goal**: 不实现 ATM、超商或其他非即时支付;当前全是开发测试数据,没有历史交易兼容范围。
|
|
|
|
|
|
|
|
-**Independent Test**: 选 ATM/超商 → OMG 回调 `/pay/omg/paymentInfo` 带虚帐/缴费码 → 平台展示 + 期限 → 消费者付款 → `/pay/omg/notify`(RtnCode=1) → 订单核销(quickstart 场景 D)
|
|
|
|
|
|
|
+**Independent Test**: 新支付表单不包含 `PaymentInfoURL`、`ClientRedirectURL` 或延期缴费期限参数,且控制器不暴露延期支付回调/返回接口。
|
|
|
|
|
|
|
|
-- [x] T030 [US3] 在 `OmgPayController.java` 实现 `POST /pay/omg/paymentInfo`(`@Anonymous`):`OmgCheckMacValue.verify` → 解析 BankCode/vAccount/ExpireDate 或 PaymentNo/ExpireDate → 落 `pos_order_omg_payment.callback_raw`(并可用专用字段/`pay_url` 存取号码供前端读)→ 回 `1|OK`
|
|
|
|
|
-- [x] T031 [US3] 暴露取号信息给前端:`GET /pay/omg/paymentInfo/{orderid}`(或复用订单查询)返回虚帐/缴费码 + 期限;前端 `E:\QtwCode\foodie\foodie-store\` ATM/超商结果页展示 + 倒计时;文案四语 i18n
|
|
|
|
|
-- [x] T032 [US3] 延期付款最终核销:消费者实际付款后 OMG 回调 `/pay/omg/notify`(RtnCode=1)→ 复用 US2 的 `markSuccess` + 推送链路(pay_type=`ATM_*`/`CVS_*`)→ 订单已支付
|
|
|
|
|
|
|
+- [x] T030–T032 [US3] 原延期支付任务撤销;相关入口不进入新 `omgpay` 实现。
|
|
|
|
|
|
|
|
-**Checkpoint**: ATM/超商 全链路(取号→展示→付款→核销)
|
|
|
|
|
|
|
+**Checkpoint**: 当前范围只有信用卡和 Apple Pay。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## Phase 7: User Story 4 - 订单取消与退款 (Priority: P3)
|
|
## Phase 7: User Story 4 - 订单取消与退款 (Priority: P3)
|
|
|
|
|
|
|
|
-**Goal**: 订单取消时,信用卡订单调 OMG `DoAction` 退款并同步状态;ATM/超商记录待人工
|
|
|
|
|
|
|
+**Goal**: 订单取消时,信用卡/Apple Pay 订单调 OMG `DoAction` 退款并同步状态
|
|
|
|
|
|
|
|
**Independent Test**: 对一笔 OMG 已支付信用卡订单发起取消 → 调 `DoAction`(Action=R) → `pos_order_omg_refund` 记录、`pos_order_omg_payment.pay_status=3`、`pos_order.pay_status=2`;失败→记 `rtn_msg` 可重试(quickstart 场景 E)
|
|
**Independent Test**: 对一笔 OMG 已支付信用卡订单发起取消 → 调 `DoAction`(Action=R) → `pos_order_omg_refund` 记录、`pos_order_omg_payment.pay_status=3`、`pos_order.pay_status=2`;失败→记 `rtn_msg` 可重试(quickstart 场景 E)
|
|
|
|
|
|
|
|
- [x] T033 [US4] 实现 `ruoyi-system/.../service/IPosOrderOmgRefundService.java` + `impl`(`record(paymentId,ddId,tradeNo,action,amount,rtnCode,rtnMsg,callbackRaw)`、`listByPayment`、`listByDdId`)
|
|
- [x] T033 [US4] 实现 `ruoyi-system/.../service/IPosOrderOmgRefundService.java` + `impl`(`record(paymentId,ddId,tradeNo,action,amount,rtnCode,rtnMsg,callbackRaw)`、`listByPayment`、`listByDdId`)
|
|
|
-- [x] T034 [US4] 在 `OmgPayController.java` 实现退款(`POST /pay/omg/refund?orderid=` 或由取消链路调用):查 OMG 已支付订单 → 信用卡(pay_type=`Credit_CreditCard`) → `OmgPay.doAction(Action=R, TotalAmount=amount)` → 写 `pos_order_omg_refund` → `RtnCode==1` 则 `markRefunded`(pay_status=3) + `pos_order.pay_status=2`;`ATM_*/CVS_*/BarcodeATM_*` 无 API → 记录待人工处理
|
|
|
|
|
|
|
+- [x] T034 实现 `POST /pay/omg/refund`:查唯一有效的已支付尝试 → `DoAction(Action=R, TotalAmount=amount)` → 写退款流水;成功后同步支付尝试和订单退款状态。
|
|
|
- [x] T035 [US4](可选/增强)状态化 Action:下单时 `NeedExtraPaidInfo=Y` 取 `gwsr`,退款前查 `CreditDetail/QueryTrade/V2`(需盘合后台 `CreditCheckCode`) 决定 `N(已授權)/E(要關帳)/R(已關帳)`;MVP 可先统一 `R`,失败再分支
|
|
- [x] T035 [US4](可选/增强)状态化 Action:下单时 `NeedExtraPaidInfo=Y` 取 `gwsr`,退款前查 `CreditDetail/QueryTrade/V2`(需盘合后台 `CreditCheckCode`) 决定 `N(已授權)/E(要關帳)/R(已關帳)`;MVP 可先统一 `R`,失败再分支
|
|
|
- [x] T036 [US4] 接入订单取消链路:`PosOrderShOprateController`(商家取消) / `UserOrderController`(用户取消) 取消 OMG 已支付订单时触发退款;相关提示文案四语 i18n
|
|
- [x] T036 [US4] 接入订单取消链路:`PosOrderShOprateController`(商家取消) / `UserOrderController`(用户取消) 取消 OMG 已支付订单时触发退款;相关提示文案四语 i18n
|
|
|
|
|
|
|
@@ -136,7 +134,7 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
**Purpose**: 多 story 共享的收尾与护栏
|
|
**Purpose**: 多 story 共享的收尾与护栏
|
|
|
|
|
|
|
|
- [ ] T037 [P] 四语 i18n 统一校验:foodie-store 与 foodie-admin-vue 的 zh.js/tw.js/en.js/vi.js 中 OMG 支付相关 key 四文件齐全、命名有意义、位于正确对象层级(用 i18n-consistency-checker 或人工核)
|
|
- [ ] T037 [P] 四语 i18n 统一校验:foodie-store 与 foodie-admin-vue 的 zh.js/tw.js/en.js/vi.js 中 OMG 支付相关 key 四文件齐全、命名有意义、位于正确对象层级(用 i18n-consistency-checker 或人工核)
|
|
|
-- [x] T038 [P] 安全复核:`/pay/omg/notify`、`/pay/omg/paymentInfo`、`/pay/omg/return` 确为 `@Anonymous` 且仅这些匿名;HashKey/HashIV 不出现在任何前端响应/日志明文;CheckMacValue 验签为强制前置;金额校验与 `SimulatePaid` 守卫不可绕过
|
|
|
|
|
|
|
+- [x] T038 [P] 安全复核:`/pay/omg/notify`、`/pay/omg/result`、`/pay/omg/back` 为外部返回入口;HashKey/HashIV 不出现在前端响应或日志;CheckMacValue 验签、金额校验与 `SimulatePaid` 守卫不可绕过
|
|
|
- [x] T039 [P] **零蓝新依赖复核**:grep `com.ruoyi.app.utils.omg` 及 `OmgPayController`/`PosStoreOmg*`/`PosOrderOmg*` 全部 OMG 新建类,确认无任何 `newebpay` import(必须为 0,违反硬约束)
|
|
- [x] T039 [P] **零蓝新依赖复核**:grep `com.ruoyi.app.utils.omg` 及 `OmgPayController`/`PosStoreOmg*`/`PosOrderOmg*` 全部 OMG 新建类,确认无任何 `newebpay` import(必须为 0,违反硬约束)
|
|
|
- [ ] T040 运行 `specs/016-omg-payment/quickstart.md` 全部场景 A–E 端到端验证(stage + 测试卡 + 内网穿透),记录结果(退款场景 E 因 stage 不可用需正式小额/mock)
|
|
- [ ] T040 运行 `specs/016-omg-payment/quickstart.md` 全部场景 A–E 端到端验证(stage + 测试卡 + 内网穿透),记录结果(退款场景 E 因 stage 不可用需正式小额/mock)
|
|
|
- [x] T041 文档一致性:确认 spec.md/plan.md/tasks.md 描述一致;`updatesql/sql.md` 中 SQL 齐全(三建表 + OMG 菜单权限)
|
|
- [x] T041 文档一致性:确认 spec.md/plan.md/tasks.md 描述一致;`updatesql/sql.md` 中 SQL 齐全(三建表 + OMG 菜单权限)
|
|
@@ -155,7 +153,7 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
|
|
|
|
|
- [x] T042 [P] [US6] contracts/api.md:§B5 `POST /pay/omg/query` 由「可选/未实现」补全为正式契约(入参/鉴权/返回/TradeStatus 分支/幂等/自愈);新增 §B8 定时补单(@Scheduled+Redisson)说明
|
|
- [x] T042 [P] [US6] contracts/api.md:§B5 `POST /pay/omg/query` 由「可选/未实现」补全为正式契约(入参/鉴权/返回/TradeStatus 分支/幂等/自愈);新增 §B8 定时补单(@Scheduled+Redisson)说明
|
|
|
- [x] T043 [US6] 抽取共享补单核销逻辑(`OmgPayController`):`applyPaidResult(...)`(notify 成功分支改调此,消除重复)+ `public reconcileByQuery(ddId, source)`(queryTrade→金额校验→applyPaidResult/markFail,含幂等与跨事务中断自愈),供 /query 与定时任务复用
|
|
- [x] T043 [US6] 抽取共享补单核销逻辑(`OmgPayController`):`applyPaidResult(...)`(notify 成功分支改调此,消除重复)+ `public reconcileByQuery(ddId, source)`(queryTrade→金额校验→applyPaidResult/markFail,含幂等与跨事务中断自愈),供 /query 与定时任务复用
|
|
|
-- [x] T044 [US6] 方案A:`OmgPayController` 新增 `POST /pay/omg/query`(@Anonymous @Auth,校验登录+订单本人+payType=7+未支付未取消)→ 调 `reconcileByQuery` → 返回 `{payStatus:0/1/2, reconciled}`;补单写 `orderLogHelper` 日志
|
|
|
|
|
|
|
+- [x] T044 [US6] 方案A:`OmgPayController` 新增 `POST /pay/omg/query`(@Anonymous @Auth,校验登录+订单本人+payType=2+未支付未取消)→ 调 `reconcileByQuery` → 返回 `{payStatus:0/1/2, reconciled}`;补单写 `orderLogHelper` 日志
|
|
|
- [x] T045 [US6] 方案B 数据层:`PosOrderOmgPaymentMapper` 加 `selectLeakOrderDdIds(windowStart, graceCutoff, batchSize)`(每订单取最新一笔 `pay_status=0`,INNER JOIN pos_order 排除 state=4)+ XML;`IPosOrderOmgPaymentService` 加 `scanLeakOrders(...)`
|
|
- [x] T045 [US6] 方案B 数据层:`PosOrderOmgPaymentMapper` 加 `selectLeakOrderDdIds(windowStart, graceCutoff, batchSize)`(每订单取最新一笔 `pay_status=0`,INNER JOIN pos_order 排除 state=4)+ XML;`IPosOrderOmgPaymentService` 加 `scanLeakOrders(...)`
|
|
|
- [x] T046 [US6] 方案B 任务:新建 `ruoyi-admin/.../app/task/OmgReconcileTask.java`(@Scheduled fixedDelay 默认3分钟 + Redisson 分布式锁 `lock:omg:reconcile`,多实例单节点执行)→ 扫漏单逐笔调 `reconcileByQuery(ddId,"scheduled")`;`RuoYiApplication` 加 `@EnableScheduling`
|
|
- [x] T046 [US6] 方案B 任务:新建 `ruoyi-admin/.../app/task/OmgReconcileTask.java`(@Scheduled fixedDelay 默认3分钟 + Redisson 分布式锁 `lock:omg:reconcile`,多实例单节点执行)→ 扫漏单逐笔调 `reconcileByQuery(ddId,"scheduled")`;`RuoYiApplication` 加 `@EnableScheduling`
|
|
|
- [x] T047 [P] [US6] 配置:`application.yml` 加 `omg.reconcile.*`(fixed-delay-ms 默认180000 / window-hours 默认168=7天覆盖延期 ExpireDate / grace-minutes 默认2 / batch-size / lock-wait-seconds / lock-lease-seconds)
|
|
- [x] T047 [P] [US6] 配置:`application.yml` 加 `omg.reconcile.*`(fixed-delay-ms 默认180000 / window-hours 默认168=7天覆盖延期 ExpireDate / grace-minutes 默认2 / batch-size / lock-wait-seconds / lock-lease-seconds)
|
|
@@ -176,7 +174,7 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
|
|
|
|
|
**Prerequisites**: ✅ bugB 验签已修(`1a424c3` CASE_INSENSITIVE_ORDER,queryTrade 可靠);✅ parseKvResponse 尾随& 放宽(`7a8e248`);⏳ collation 3 表 CONVERT 待执行(sql.md 2026-08-12 节)。
|
|
**Prerequisites**: ✅ bugB 验签已修(`1a424c3` CASE_INSENSITIVE_ORDER,queryTrade 可靠);✅ parseKvResponse 尾随& 放宽(`7a8e248`);⏳ collation 3 表 CONVERT 待执行(sql.md 2026-08-12 节)。
|
|
|
|
|
|
|
|
-**Independent Test**: 同订单连点 create 9 次 → 只 1 条 is_active=1 活跃行(不堆积);ATM 取号后过期重新 create → 新行 + 旧行 is_active=0(虚帐仍可付,迟到 notify 落旧行 markSuccess);id=26 已付历史行迁移后 reconcile 补单核销;同门店多单未付 reconcile 不触发 OMG 403。
|
|
|
|
|
|
|
+**Independent Test**: 同订单重复 create 只保留并复用一条有效支付尝试;查询、回调和自动补偿均定位该尝试;同门店多订单查询不触发 OMG 限流。
|
|
|
|
|
|
|
|
- [x] T051 [P] **前置:parseKvResponse 重复键放宽**(`ruoyi-admin/.../utils/omg/OmgPay.java`):重复键 last-wins 不抛(用同一份 Map 重算 CheckMacValue 验签,避 kill-shot ⑥ 解析放宽与验签打架);仅整段无 `=` 或缺 CheckMacValue 才抛;`OmgPayTest` 补重复键用例。S 依赖 queryTrade 做换号/补单安全网,重复键仍抛会让安全网失效。
|
|
- [x] T051 [P] **前置:parseKvResponse 重复键放宽**(`ruoyi-admin/.../utils/omg/OmgPay.java`):重复键 last-wins 不抛(用同一份 Map 重算 CheckMacValue 验签,避 kill-shot ⑥ 解析放宽与验签打架);仅整段无 `=` 或缺 CheckMacValue 才抛;`OmgPayTest` 补重复键用例。S 依赖 queryTrade 做换号/补单安全网,重复键仍抛会让安全网失效。
|
|
|
- [x] T052 **collation 对齐执行确认**:执行 `updatesql/sql.md` 2026-08-12 节(pos_order_omg_payment/pos_order_omg_refund/pos_store_omg CONVERT 到 utf8mb4_unicode_ci);执行后 `selectLeakOrderDdIds` JOIN 不再报 Illegal mix of collations。
|
|
- [x] T052 **collation 对齐执行确认**:执行 `updatesql/sql.md` 2026-08-12 节(pos_order_omg_payment/pos_order_omg_refund/pos_store_omg CONVERT 到 utf8mb4_unicode_ci);执行后 `selectLeakOrderDdIds` JOIN 不再报 Illegal mix of collations。
|
|
@@ -189,11 +187,16 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
- [x] T059 **读侧 getLatestByDdId 迁移** ✅(OmgPayController 4 处 db043b4 + OrderLifecycleService 3 处 b9a0514;buildContext canReconcileOmg 重构为 hasOmgActivity):`getPaymentInfo`/`refundOrderOutcome`/L569 早退/`markRefunding` 并发/`confirmManualRefundOutcome`/`reconcileByQuery`(T054)/`OrderLifecycleService.buildContext`/`validateOmgReconcile`/`validateOmgRefund` → `selectLatestRefundableByDdId`(pay_status IN(1,3,4))或 `listPaidByDdId`;修"订单未支付"早退为 listPaidByDdId 为空才 FAILED。
|
|
- [x] T059 **读侧 getLatestByDdId 迁移** ✅(OmgPayController 4 处 db043b4 + OrderLifecycleService 3 处 b9a0514;buildContext canReconcileOmg 重构为 hasOmgActivity):`getPaymentInfo`/`refundOrderOutcome`/L569 早退/`markRefunding` 并发/`confirmManualRefundOutcome`/`reconcileByQuery`(T054)/`OrderLifecycleService.buildContext`/`validateOmgReconcile`/`validateOmgRefund` → `selectLatestRefundableByDdId`(pay_status IN(1,3,4))或 `listPaidByDdId`;修"订单未支付"早退为 listPaidByDdId 为空才 FAILED。
|
|
|
- [x] T060 **砍掉** ~~退款遍历~~(2026-08-12 用户拍板:双付不现实——用户不会对同一订单付两笔,实测 bugA 9 行也只 1 笔 paid。退款维持退单条已付行即可,无需遍历多笔)(`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)` + 运营告警。
|
|
- [x] T060 **砍掉** ~~退款遍历~~(2026-08-12 用户拍板:双付不现实——用户不会对同一订单付两笔,实测 bugA 9 行也只 1 笔 paid。退款维持退单条已付行即可,无需遍历多笔)(`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)` + 运营告警。
|
|
|
- [x] T061 **砍掉** ~~历史 9 行迁移~~(2026-08-12 用户:不管历史数据。新读路径对历史行仍兼容——可退款行优先)(orders 如 991786433092835 id=18~26):迁移前对每行 queryTrade 确认(已可靠);TradeStatus=1 的行无论本地 pay_status 一律保留并回填 pay_status=1(如 id=26 OMGFE0494FB7AD1474A8);仅 TradeStatus=10200095 或(0 且过 ExpireDate 且非信用卡)才允许清理;优先 is_active 软标记而非物理删除(lifecycle §9 Q10)。
|
|
- [x] T061 **砍掉** ~~历史 9 行迁移~~(2026-08-12 用户:不管历史数据。新读路径对历史行仍兼容——可退款行优先)(orders 如 991786433092835 id=18~26):迁移前对每行 queryTrade 确认(已可靠);TradeStatus=1 的行无论本地 pay_status 一律保留并回填 pay_status=1(如 id=26 OMGFE0494FB7AD1474A8);仅 TradeStatus=10200095 或(0 且过 ExpireDate 且非信用卡)才允许清理;优先 is_active 软标记而非物理删除(lifecycle §9 Q10)。
|
|
|
-- [ ] T062 **端到端验证**(stage):create 连点复用(单活跃 is_active=1)、reconcile 两段式补单(含 id=26)、过期延期支付轮换、两道节流闸不触发 403;抓 stage 真实 QueryTradeInfo 大响应(ATM/超商 NeedExtraPaidInfo=Y)fixture 补 OmgPayTest(重复键验证)。(T060 退款遍历/T061 历史迁移已砍,不纳入验证)
|
|
|
|
|
|
|
+- [ ] T062 **端到端验证**(stage):create 重复点击复用单一有效尝试、query/notify/自动补偿完成核销、两道节流闸不触发 403;历史数据与延期支付不纳入验证。
|
|
|
|
|
+- [x] T063 **统一 OMG 支付类型为 2**:`OmgPayController`、`OrderLifecycleService`、订单创建逻辑、实体/DTO 注释、016 规格与测试夹具统一使用 `payType=2`;`paymentMethod=2` 不再写成货到付款 `collectPayment=1`;增加常量一致性回归测试。不迁移历史数据,不处理其他 OMG 评审问题。
|
|
|
|
|
+- [x] T064 **修复堂食订单支付核销**:`OmgPayController.handlePaymentSuccess` 的订单 CAS 从仅允许 `state=0` 改为允许 `state IN (0,1)`,并继续要求 `pay_status=0`;回调和主动补单复用同一路径,配送中、已完成、已取消状态不核销;增加 CAS 条件回归测试。
|
|
|
|
|
+- [x] T065 取消后迟到付款由自动补偿扫描并复用信用卡退款服务,保持幂等且不触发履约。
|
|
|
|
|
+- [x] T066 支付尝试生命周期收敛为单一有效尝试;开发测试阶段不迁移历史数据、不兼容延期支付。
|
|
|
|
|
+- [ ] T067 **P1 修正 OMG 查询节流公平性(随 T066 设计)**:只有实际准备发送 QueryTradeInfo 时才同时获取 ddId 与 MerchantID 额度,任一额度失败不得留下无效 ddId 占位;扫描不得长期固定由相同首批订单抢占门店 token,应增加公平轮转/`next_query_at`;将 3 秒 MerchantID 限制标注为实测配置而非当前公开官方契约,并补多订单同门店无饥饿测试。
|
|
|
|
|
|
|
|
**Checkpoint**: 问题 A 收敛(连点单活跃 T058)、问题 B 闭环(reconcile 全量补单 T054 + 两道频率节流闸 T055)、追加式 S 落地(is_active 双轴 + mapper 杜绝换号 + 读侧迁移 T059)。**T060 退款遍历砍掉**(双付不现实,退款维持退单条已付行)、**T061 历史 9 行迁移砍掉**(不管历史数据)。T062 stage 端到端待测试环境。
|
|
**Checkpoint**: 问题 A 收敛(连点单活跃 T058)、问题 B 闭环(reconcile 全量补单 T054 + 两道频率节流闸 T055)、追加式 S 落地(is_active 双轴 + mapper 杜绝换号 + 读侧迁移 T059)。**T060 退款遍历砍掉**(双付不现实,退款维持退单条已付行)、**T061 历史 9 行迁移砍掉**(不管历史数据)。T062 stage 端到端待测试环境。
|
|
|
|
|
|
|
|
-**§9 待拍板(实现/stage 确认,不阻塞)**:freshMin(建议 3min,stage 实测同 MTN 重 POST)、生产 MySQL 版本(决定生成列唯一索引)、reuse 保留原 MerchantTradeDate、节流精确参数、AFTEE 窗口(现 168h 不够,建议 720h 或按 expire_date 动态)、/query 轮询 cadence(配合前端 30-60s)。
|
|
|
|
|
|
|
+**待 stage 确认(不阻塞)**:同 MTN 重复 POST、生产 MySQL 版本、节流精确参数、`/query` 轮询频率。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -209,28 +212,28 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## Phase 14: OMG ?????? App ???Priority: P1?
|
|
|
|
|
|
|
+## Phase 14: OMG 后端返回页与 App 唤醒(Priority: P1)
|
|
|
|
|
|
|
|
-- [x] T081 ?????? `OrderResultURL`?`ClientRedirectURL`?`ClientBackURL`?????? App Link ??? `omgpay` ????? HTTPS/path ?????
|
|
|
|
|
-- [x] T082 ?? Client POST ????????????? DTO?????????????? CheckMacValue???????????
|
|
|
|
|
-- [x] T083 ?? `/pay/omg/result`?`/pay/omg/payment-info-result`?`/pay/omg/back` ???? no-store/CSP HTML ????????????????
|
|
|
|
|
-- [x] T084 ??? Client POST ????????? `ipn_log` ???????????HashKey/HashIV ?????????
|
|
|
|
|
-- [x] T085 ?????????????????????????????HTML ?????????????
|
|
|
|
|
-- [ ] T086 ?? `OMG_APP_RETURN_URL` ??? iOS Universal Link / Android App Link ? App ????App ????? `/pay/omg/query`??? App ??? bundle/package ?????????????????
|
|
|
|
|
-- [ ] T087 ?? OMG ??????????? Maven ???????? stage ???????????????
|
|
|
|
|
|
|
+- [x] T081 创建表单加入 `OrderResultURL`、`ClientBackURL`,两个地址和 App Link 均通过 `omgpay` 配置并执行 HTTPS/path 安全校验。
|
|
|
|
|
+- [x] T082 新建 Client POST 验签服务,复用完整原始表单 DTO;未知额外字段与空值全部参与 CheckMacValue,验签页不写订单状态。
|
|
|
|
|
+- [x] T083 新建 `/pay/omg/result`、`/pay/omg/back` 控制器和 no-store/CSP HTML 渲染器,支持自动唤醒与手动按钮。
|
|
|
|
|
+- [x] T084 将支付结果 Client POST 的完整内容写入现有 `ipn_log` 并记录可复制调试日志,HashKey/HashIV 不进入日志或页面。
|
|
|
|
|
+- [x] T085 增加表单字段、配置安全、完整验签、额外字段篡改、两个路由、HTML 头和原始请求缓存测试源码。
|
|
|
|
|
+- [ ] T086 配置 `OMG_APP_RETURN_URL` 并完成 iOS Universal Link / Android App Link 的 App 端关联;App 打开后调用 `/pay/omg/query`。当前 App 工程与 bundle/package 信息不在本后端工作区,不猜测其值。
|
|
|
|
|
+- [ ] T087 全部 OMG 功能调整结束后统一运行 Maven 编译、定向测试和 stage 联调;本批按用户要求暂不执行。
|
|
|
|
|
|
|
|
-**Checkpoint**??????????????????? `OMG_APP_RETURN_URL`???????????????????
|
|
|
|
|
|
|
+**Checkpoint**:后端返回页已实现;部署前必须配置真实 `OMG_APP_RETURN_URL`,否则安全页只提示尚未配置,不会跳转。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## Phase 15: 支付方式收敛为信用卡与 Apple Pay(Priority: P0)
|
|
## Phase 15: 支付方式收敛为信用卡与 Apple Pay(Priority: P0)
|
|
|
|
|
|
|
|
- [x] T088 将新支付表单固定为 `ChoosePayment=Credit`、`UnionPay=2`,并保证两个字段都参加 CheckMacValue 计算。
|
|
- [x] T088 将新支付表单固定为 `ChoosePayment=Credit`、`UnionPay=2`,并保证两个字段都参加 CheckMacValue 计算。
|
|
|
-- [x] T089 从新支付表单移除 `ExpireDate`、`StoreExpireDate`、`BarcodeATMExpireDate`、`ClientRedirectURL` 及对应无用配置;保留历史延期支付回调路由处理变更前交易。
|
|
|
|
|
-- [x] T090 更新表单契约测试源码,精确断言只生成信用卡/Apple Pay 所需字段且不生成延期支付字段。
|
|
|
|
|
|
|
+- [x] T089 从新支付表单移除 `ExpireDate`、`StoreExpireDate`、`BarcodeATMExpireDate`、`ClientRedirectURL` 及对应无用配置;删除延期支付客户端返回路由,不保留历史兼容。
|
|
|
|
|
+- [x] T090 更新表单与返回路由测试源码,精确断言只生成信用卡/Apple Pay 所需字段且不暴露延期支付返回接口。
|
|
|
- [ ] T091 全部 OMG 功能调整结束后统一运行 Maven 编译、定向测试和 stage 收银台验证;本批按用户要求暂不执行。
|
|
- [ ] T091 全部 OMG 功能调整结束后统一运行 Maven 编译、定向测试和 stage 收银台验证;本批按用户要求暂不执行。
|
|
|
|
|
|
|
|
-**Checkpoint**:代码层已限制新订单只进入信用卡(含 Apple Pay)收银台,并隐藏银联;历史延期交易兼容入口未删除。
|
|
|
|
|
|
|
+**Checkpoint**:代码层已限制订单只进入信用卡(含 Apple Pay)收银台并隐藏银联;开发测试阶段不保留延期支付兼容入口。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -242,14 +245,14 @@ description: "Task list for OMG(歐買尬/FunPoint)AIO 支付接入"
|
|
|
- **Phase 3 US1**: 依赖 Phase 2
|
|
- **Phase 3 US1**: 依赖 Phase 2
|
|
|
- **Phase 4 US2**: 依赖 US1(回调处理依赖发起产生的流水与订单关联)
|
|
- **Phase 4 US2**: 依赖 US1(回调处理依赖发起产生的流水与订单关联)
|
|
|
- **Phase 5 US5(凭证管理)**: 依赖 Phase 2(凭证表/Mapper);**可与 US1/US2 并行**(US1 测试可临时手插凭证数据)
|
|
- **Phase 5 US5(凭证管理)**: 依赖 Phase 2(凭证表/Mapper);**可与 US1/US2 并行**(US1 测试可临时手插凭证数据)
|
|
|
-- **Phase 6 US3(ATM/超商)**: 依赖 US1+US2(复用发起+回调主链路,增量在取号回调与展示)
|
|
|
|
|
|
|
+- **Phase 6 US3(非即时支付)**: 已撤销,不进入当前交付范围
|
|
|
- **Phase 7 US4(退款)**: 依赖 US1+US2(依赖已支付流水与 trade_no)
|
|
- **Phase 7 US4(退款)**: 依赖 US1+US2(依赖已支付流水与 trade_no)
|
|
|
- **Phase 8 Polish**: 依赖所有欲交付的 story 完成
|
|
- **Phase 8 Polish**: 依赖所有欲交付的 story 完成
|
|
|
|
|
|
|
|
### User Story 独立性
|
|
### User Story 独立性
|
|
|
- **US1**: 依赖 Foundational,无跨 story 依赖
|
|
- **US1**: 依赖 Foundational,无跨 story 依赖
|
|
|
- **US2**: 依赖 US1(闭环下半段)
|
|
- **US2**: 依赖 US1(闭环下半段)
|
|
|
-- **US3**: 依赖 US1+US2,增量在 PaymentInfoURL 取号与展示
|
|
|
|
|
|
|
+- **US3**: 已撤销
|
|
|
- **US4**: 依赖 US1+US2
|
|
- **US4**: 依赖 US1+US2
|
|
|
- **US5**: 依赖 Foundational,可与 US1/US2 并行
|
|
- **US5**: 依赖 Foundational,可与 US1/US2 并行
|
|
|
|
|
|
|
@@ -288,9 +291,8 @@ Task: "OmgPay HTTP 客户端骨架 in ruoyi-admin/.../utils/omg/OmgPay.java"
|
|
|
### 增量交付
|
|
### 增量交付
|
|
|
1. MVP(US1+US2)→ 验证 → 可上测试环境
|
|
1. MVP(US1+US2)→ 验证 → 可上测试环境
|
|
|
2. + US5(凭证管理)→ 去除手插数据、运营自助
|
|
2. + US5(凭证管理)→ 去除手插数据、运营自助
|
|
|
-3. + US3(ATM/超商)→ 覆盖延期方式
|
|
|
|
|
-4. + US4(退款)→ 售后资金闭环
|
|
|
|
|
-5. Polish(i18n/安全/零蓝新复核/全场景验证)→ 可转正式
|
|
|
|
|
|
|
+3. + US4(退款)→ 售后资金闭环
|
|
|
|
|
+4. Polish(i18n/安全/零蓝新复核/全场景验证)→ 可转正式
|
|
|
|
|
|
|
|
### 并行策略
|
|
### 并行策略
|
|
|
- Foundational 完成后:开发者 A 做 US1+US2(主链路),开发者 B 做 US5(凭证管理前后端),互不阻塞
|
|
- Foundational 完成后:开发者 A 做 US1+US2(主链路),开发者 B 做 US5(凭证管理前后端),互不阻塞
|