# Tasks: OMG AIO 创建支付订单与付款结果回调重建

**Input**: [spec.md](spec.md), [plan.md](plan.md), [research.md](research.md), [data-model.md](data-model.md), [contracts/api.md](contracts/api.md), [quickstart.md](quickstart.md)

**Tests**: 本功能强制 TDD。每个生产任务先写失败测试并实际观察失败，再写最小实现。

**Scope**: 完成测试环境创建支付订单、最终付款结果回调及用户触发的当前支付查询补偿；不实现取号、定时/批量补单、退款、通用支付推送、关账或正式环境。2026-08-28 增量仅追加外送订单支付成功后的骑手开放推送。

## Phase 1: 新持久化基础

- [x] T001 [US1] 在 `ruoyi-system/src/test/java/com/ruoyi/system/omgpay/service/OmgPaymentAttemptServiceTest.java` 编写新尝试字段校验、`CREATED` 插入和生成列不由 Java 写入的失败测试
- [x] T002 [US2] 在 `ruoyi-system/src/test/java/com/ruoyi/system/omgpay/mapper/OmgPaymentAttemptMapperContractTest.java` 编写 `FOR UPDATE`、新表名、唯一键及无旧表 SQL 的失败合约测试
- [x] T003 [US1] 在 `updatesql/sql.md` 仅追加 `pos_order_omg_attempt` DDL，不执行数据库变更
- [x] T004 [US1] 在 `ruoyi-system/src/main/java/com/ruoyi/system/omgpay/domain/` 新建 `OmgPaymentAttempt` 和 `OmgPaymentOrderSnapshot`
- [x] T005 [US1] 在 `ruoyi-system/src/main/java/com/ruoyi/system/omgpay/mapper/OmgPaymentAttemptMapper.java` 与 `ruoyi-system/src/main/resources/mapper/omgpay/OmgPaymentAttemptMapper.xml` 实现锁定订单、活跃尝试、交易号查询和显式 INSERT
- [x] T006 [US1] 在 `ruoyi-system/src/main/java/com/ruoyi/system/omgpay/service/` 实现 `IOmgPaymentAttemptService` 与校验严格的 `OmgPaymentAttemptServiceImpl`
- [x] T007 [US1] 运行 `OmgPaymentAttemptServiceTest,OmgPaymentAttemptMapperContractTest` 并确认通过

**Checkpoint**: 新表 DDL 已登记，system 模块可锁定订单并只写新尝试表。

## Phase 2: 官方检查码与表单

- [x] T008 [US1] 在 `OmgCheckMacSignerTest` 写入官方期望值 `AA5842FDA7E55ACEB7118D6353E9822CA6D6FF09A0D1FC129A879DD5CAF93266`、额外字段、空值字段、输入不可变和非法字段失败测试
- [x] T009 [US1] 在 `com.ruoyi.app.omgpay.OmgCheckMacSigner` 实现排序、HashKey/HashIV 包夹、官方 .NET URL encode、小写、SHA-256 和大写输出
- [x] T010 [US1] 运行 `OmgCheckMacSignerTest`，确认官方向量和字段完整性全部通过
- [x] T011 [US1] 在 `OmgMerchantTradeNoGeneratorTest` 编写 20 位大写英数字与样本唯一性失败测试
- [x] T012 [US1] 在 `OmgPaymentFormFactoryTest` 编写 Taipei 时间、stage URL、15 字段、固定字段、全部字段签名及不安全 ReturnURL 失败测试
- [x] T013 [US1] 实现 `OmgPaymentProperties`、`OmgMerchantTradeNoGenerator`、`OmgPaymentForm` 与 `OmgPaymentFormFactory`
- [x] T014 [US1] 在 `application.yml` 新增独立 `omgpay.return-url`，新代码不读取旧 `omg.*` 创建/回调配置
- [x] T015 [US1] 运行生成器和表单测试并确认通过

**Checkpoint**: 可从服务端事实生成一份严格 stage-only、全字段签名的表单。

## Phase 3: 创建编排与重复防护

- [x] T016 [US3] 在 `OmgPaymentCreateServiceTest` 编写订单不存在/越权、非单店、非法状态、已支付、金额、payType、门店和凭证失败矩阵
- [x] T017 [US2] 在同一测试编写已有活跃尝试时返回 `PAYMENT_ATTEMPT_EXISTS` 且不调用生成器/表单/凭证的失败测试
- [x] T018 [US1] 在同一测试编写成功调用顺序、响应白名单和安全内部元数据失败测试
- [x] T019 [US2] 在同一测试编写 active 唯一键冲突、MerchantTradeNo 冲突三次受控重试和未知重复键失败测试
- [x] T020 [US1] 实现 `OmgPaymentErrorCode`、`OmgPaymentBusinessException`、`OmgPaymentCreateOutcome` 和响应 DTO
- [x] T021 [US1] 实现 `@Transactional OmgPaymentCreateService#create`，保证锁订单和活跃查询发生在 MerchantTradeNo 生成前
- [x] T022 [US2] 实现重复键分类：发现活跃行立即终止，仅交易号碰撞允许最多三次重新生成
- [x] T023 [US1] 运行 `OmgPaymentCreateServiceTest` 并检查新包对旧 OMG 类/Service 零引用

**Checkpoint**: 首次请求创建，重复请求稳定拒绝；没有任何网关 HTTP 或旧支付表访问。

## Phase 4: 公共 API、i18n 与排障日志

- [x] T024 [US3] 在 `OmgPaymentControllerTest` 编写 `/pay/omg/create`、token header、显式 DTO、无 Map 入参和请求字段白名单失败测试
- [x] T025 [US3] 编写 Controller 业务错误、成功、未知异常的响应契约失败测试
- [x] T026 [US3] 编写 INFO/WARN/ERROR、成功安全上下文、异常堆栈及敏感值不入日志的失败测试
- [x] T027 [US3] 在 default、zh_CN、zh_TW、en_US、vi 五个 i18n 文件加入完全一致的 `omg.pay.*` key 集合
- [x] T028 [US3] 实现 `OmgPaymentTokenUserResolver`、请求/错误 DTO 与新 `OmgPaymentController`
- [x] T029 [US3] 实现创建开始、成功、业务拒绝和非预期异常日志，不记录 token、凭证、完整检查码或表单
- [x] T030 [US1] 运行 `OmgPaymentControllerTest` 和 i18n key 对齐检查

**Checkpoint**: 新 Controller 独占 `/pay/omg/create`，响应与日志满足安全契约。

## Phase 5: 旧 OMG 运行时退役

- [x] T031 [US3] 保存当前脏文件定向 diff，确认需要保留的 LINE/订单状态改动
- [x] T032 [US3] 在 `OmgLegacyRetirementTest` 编写旧 Controller、旧任务、旧支付实体必须不存在及新 Controller 只有 create 路由的失败测试
- [x] T033 [US3] 删除旧 `OmgPayController`、专属 DTO、`OmgReconcileTask`、`OmgQueryThrottle` 和旧 Controller 测试
- [x] T034 [US3] 删除旧支付/退款 Entity、Mapper、XML、Service、ServiceImpl 和对应旧测试；保留 `pos_store_omg` 全部可信实现
- [x] T035 [US3] 从 `UserOrderController` 与 `PosOrderShOprateController` 取消链路移除旧 OMG 退款调用，不改变其他取消行为
- [x] T036 [US3] 从 `PosOrderController` 移除旧 OMG 补单、退款、人工确认端点及注入/辅助方法
- [x] T037 [US3] 从 `OrderLifecycleService` 移除旧表依赖和旧 OMG 补单/退款方法，保留 `payType="2"` 的支付前进门禁
- [x] T038 [US3] 定点更新 `OrderLifecycleServiceTest`，移除旧表测试/构造参数并保留已有 LINE 和订单生命周期测试
- [x] T039 [US3] 运行 `OmgLegacyRetirementTest,OrderLifecycleServiceTest`，并确认 main Java/XML 对旧支付/退款表及服务零匹配
- [x] T040 [US3] 将 `application.yml` 的旧 `omg` 配置收敛为凭证验证所需 `base-url`，移除旧回调/查询/补单/退款配置并保留新 `omgpay.return-url`
- [x] T041 [US3] 复核三个原脏文件的无关改动仍在，且没有全文件换行或格式化

**Checkpoint**: 旧表删除后没有可达运行链路访问它们；旧回调、查询、补单和退款接口全部不可达。

## Phase 6: 综合验证与手动验收准备

- [x] T042 运行全部新 OMG 测试以及 `OrderLifecycleServiceTest,LinePayCancellationRaceTest`
- [x] T043 使用 JDK 21 运行 `mvn -pl ruoyi-admin -am -DskipTests package`
- [x] T044 检查禁止的 production URL、旧 OMG import、旧表 SQL、敏感日志与实际表单字段集合
- [x] T045 审查新公开类型、检查码、事务行锁和唯一键处理的必要注释，移除显然代码噪声注释
- [x] T046 运行 `git diff --check`、`git status --short`、`git diff --stat` 并确认无无关文件
- [ ] T047 开发者手动执行 DDL 后，按 `quickstart.md` 验证首次创建、当前页 POST、重复、真实并发、负例和日志

## Phase 7: 回调数据与凭证快照

- [x] T048 [US4] 更新 `pos_order_omg_attempt` DDL：新增 HashKey/HashIV 快照、`PAID/FAILED/SUPERSEDED` 状态、网关结果字段和 `trade_no` 唯一键；只写 `updatesql/sql.md`，不执行 SQL
- [x] T049 [US4] 先编写 Entity/Mapper/Service 合约测试源码，覆盖创建密钥快照、交易号锁定、成功不可逆、失败释放、其他活动尝试关闭与订单仅更新 `pay_status`
- [x] T050 [US4] 扩展 `OmgPaymentAttempt`、Mapper XML 和 `IOmgPaymentAttemptService`，实现带当前状态条件的原子更新
- [x] T051 [US4] 修改创建服务，把本次表单使用的 HashKey/HashIV 传入 `createCreated` 并持久化快照

## Phase 8: 原始表单边界与 IPN 流水

- [x] T052 [US4] 先编写原始 form-urlencoded 解析测试源码，覆盖未知字段、空值、重复字段、非法编码、大小限制和可重放原文
- [x] T053 [US4] 实现 `OmgNotifyRequest` 与专用参数解析/ArgumentResolver；Controller 业务入参保持一个 DTO，不使用 Map 或 HttpServletRequest
- [x] T054 [US4] 先编写 IPN 独立事务测试源码，覆盖每次请求一行、`type=omg`、完整原文以及日志写入失败不阻断处理
- [x] T055 [US4] 在 `com.ruoyi.system.omgpay` 新建 `OmgIpnAuditService`，以 `REQUIRES_NEW` 复用 `IIpnLogService` 写入现有 `ipn_log`

## Phase 9: 验签、状态机与公开回调

- [x] T056 [US4] 先编写回调服务测试源码，覆盖全部实际字段验签、快照密钥、商户/金额匹配、成功、模拟成功、失败、失败后成功、成功后失败、重复、取消后成功和第二笔迟到成功
- [x] T057 [US4] 实现 `OmgPaymentNotifyService`，在一个业务事务内锁定尝试、验签、校验并更新尝试与订单；不触发订单/配送状态、推送或退款副作用
- [x] T058 [US4] 先编写 Controller 契约测试源码，覆盖匿名 form POST、纯文本 `1|OK`/`0|ERROR`、完整请求日志和无旧回调引用
- [x] T059 [US4] 在新 `OmgPaymentController` 增加 `POST /pay/omg/notify`，先独立记录 IPN，再执行业务处理；所有请求完整记录，数据库密钥不入日志
- [x] T060 [US4] 静态审计 Controller 无 Map/HttpServletRequest 入参、回调只用新尝试表和可信 `ipn_log`/`pos_store_omg` 创建来源、无旧 OMG 业务引用

## Phase 10: 回调文档与延后验证

- [x] T061 更新 `quickstart.md` 的回调重放示例、成功/失败/重复/模拟/取消后付款与 IPN 查询步骤
- [x] T062 运行 `git diff --check`、定向 `rg` 和 staged diff 审计；本阶段不运行 Maven、编译或测试
- [x] T063 在后续 OMG 查询、补单、退款等功能全部调整完成后，统一运行 JDK 21 定向测试、模块构建和完整回归

## Phase 11: 当前有效支付查询与丢失回调补偿

- [x] T064 [US5] 扩展 Mapper/Service，以订单归属读取唯一 `CREATED` 或已付款订单的首条 `PAID` 尝试，不接受客户端指定 `MerchantTradeNo`
- [x] T065 [US5] 编写查询响应严格解析、全部实际字段验签、额外/空值字段篡改、当前尝试选择与 Controller 契约测试源码
- [x] T066 [US5] 新建 `OmgPaymentQueryGateway`，只 POST OMG stage `QueryTradeInfo/V5`，使用尝试的 MerchantID/HashKey/HashIV 快照签名并设置连接/读取超时
- [x] T067 [US5] 实现 `POST /pay/omg/query`：核验订单归属、交易号、商户号、金额和完整响应签名，只返回字段白名单
- [x] T068 [US5] 将可信 `TradeStatus=1` 复用回调同一幂等事务补偿为已付款；`0` 不修改；`10200095` 同步失败；任何结果都不得降级已付款事实
- [x] T069 [US5] 增加查询 i18n、必要安全日志、API/规格说明；不写 `ipn_log`，因为它不是网关主动 IPN
- [x] T070 所有 OMG 功能调整完成后，统一执行本阶段测试源码、JDK 21 模块构建和完整回归

## Phase 12: 测试环境退款安全拒绝接口

- [x] T071 [US6] 先编写 Refund Service 与 Controller 测试源码，覆盖 token、显式 `orderId` DTO、稳定状态、异常映射和无网关/无状态依赖
- [x] T072 [US6] 新建 `OmgRefundPaymentRequest` 与 `OmgPaymentRefundService`，在身份/订单号格式校验后直接返回测试环境不支持
- [x] T073 [US6] 在新 Controller 增加 `POST /pay/omg/refund`，返回 `PAYMENT_REFUND_UNAVAILABLE_IN_TEST_ENVIRONMENT`，不接受金额、交易号或 Action
- [x] T074 [US6] 增加 default、zh_CN、zh_TW、en_US、vi 一致 i18n 和脱敏拒绝日志
- [x] T075 [US6] 更新规格与 API 契约，明确不调用正式 OMG 地址、不读取或修改订单/支付/退款状态、不新增 SQL
- [x] T076 所有 OMG 功能调整完成后，统一执行退款测试源码、JDK 21 模块构建和完整回归

## Phase 13: 旧实现与旧表最终清理

- [x] T077 扩展 `OmgLegacyRetirementTest`，先确认旧 `com.ruoyi.app.utils.omg` 三个类仍存在而失败
- [x] T078 为门店凭证联网探测编写签名、完整响应验签和额外字段篡改测试，再在 `com.ruoyi.app.omgpay` 实现 `OmgStoreCredentialVerifier`
- [x] T079 将 `PosStoreOmgController` 切换到新 verifier，删除旧工具类、旧工具测试和 `omg.base-url`；保留 `pos_store_omg` 全部凭证持久化与管理能力
- [x] T080 在 `updatesql/sql.md` 追加删除 `pos_order_omg_refund`、`pos_order_omg_payment` 的 SQL，不执行数据库变更
- [x] T081 静态检查源码旧包/旧表引用为零，并用 JDK 21 运行完整 Maven 回归

## Phase 14: App 销毁 WebView 后重新支付

- [x] T082 [US7] 先编写 retry Service、Controller、精确活动尝试替换与并发状态变化测试，并观察当前实现因缺少接口和行为而失败
- [x] T083 [US7] 扩展 Attempt Mapper/Service，以 `id + CREATED` 条件原子更新单条尝试为 `SUPERSEDED`；复用现有表结构，不新增或执行 SQL
- [x] T084 [US7] 在创建服务实现订单锁、订单再次校验、已验证 `MerchantTradeNo` 精确比较、旧尝试关闭与新表单创建的同事务替换
- [x] T085 [US7] 实现 `OmgPaymentRetryService` 与 `POST /pay/omg/retry`，按 `PAID/FAILED/UNPAID/UNKNOWN` 安全分流并增加五语言错误状态
- [x] T086 [US7] 更新 API 契约与 App 接入文档，明确不保存/恢复 WebView、不重放旧表单，App 仍只有 OMG/LINE Pay 顶层选择
- [x] T087 使用 JDK 21 运行 retry、OMG 回归、模块构建与最终 diff/暂存范围检查

## Phase 15: OMG 官方支付方式选择页渠道过滤

- [x] T088 [US1] 更新表单工厂测试源码，断言 `ChoosePayment=ALL`、`IgnorePayment=ATM#CVS#BarcodeATM` 且不发送 `UnionPay`，并检查两个固定字段进入签名集合
- [x] T089 [US1] 修改 `OmgPaymentFormFactory`，由服务端固定生成并签名上述渠道参数，不改变创建、retry 请求或响应结构
- [x] T090 [US1] 更新规格、契约、快速验收和 App 接入文档，明确 App 继续直接提交 OMG 表单、不增加二级选择页面，AFTEE 由 OMG 商户侧关闭
- [ ] T091 [US1] 在全部 OMG 功能调整完成后的统一验证阶段，使用 JDK 21 运行表单定向测试、OMG 回归和模块构建，并检查签名字段集合
- [ ] T092 [US1] 在 OMG stage 手动确认信用卡可用、Apple Pay 在适用环境可用、ATM/CVS/BarcodeATM 不显示，并确认 OMG 商户侧已关闭 AFTEE

## Phase 16: retry 查询稀疏响应兼容

- [x] T093 [US7] 为已签名但省略非核心字段的 `TradeStatus=0/10200095` 响应编写回归测试，并观察固定字段校验导致测试失败
- [x] T094 [US7] 将查询校验拆分为所有状态必需的核心身份字段与仅 `PAID` 必需的结算字段；所有实际字段继续参与验签
- [x] T095 [US7] 增加缺少核心字段、签名错误和已付款缺少结算字段的 fail-closed 测试，并确保诊断日志不记录字段值或完整响应
- [x] T096 [US7] 按 OMG stage 实际响应增加 `10200047 + TradeAmt=0` 回归测试，并验证非零金额时 fail closed
- [x] T097 [US7] 将签名有效且身份匹配的 `10200047 + TradeAmt=0` 同步为失败，释放旧尝试并复用 retry 新表单流程
- [x] T098 使用 JDK 21 运行查询定向测试、全部 OMG 回归、模块构建与最终 diff/暂存范围检查

## Phase 17: iOS WebView 返回交接

- [ ] T099 [US8] 在用户端 App `pages/OrderList/buy/omgCheckout.vue` 确认 renderjs Form 替换当前页面 WebView，并记录旧版本停留结果页的失败基线
- [ ] T100 [US8] 在 `omgCheckout.vue` 的普通 `<script>` 中仅对 App-Plus iOS 监听当前 `this.$scope.$getAppWebview()` 的 `loaded`，不得读取 `children()[0]`
- [ ] T101 [US8] 精确识别 `https://foodieapi.waimai-paotui.com/pay/omg/result` 及其 query/hash 形式，只触发一次 `handleOmgReturnDetected({ orderId, currentUrl })`，页面卸载时移除监听
- [ ] T102 [US8] 使用 JDK 21 运行 `OmgPaymentClientReturnServiceTest`，确认后端现有 HTML、繁体提示、“返回 App”按钮与 Scheme 兜底保持不变
- [ ] T103 [US8] 使用 iPhone 真机验证 `result_page_detected` 单次触发，并用中间页/取消页、Android 和外部浏览器完成负例及兼容验收

## Phase 18: App 信用卡与 Apple Pay 双入口

- [x] T104 [US1] 更新现有规格、研究结论、实施计划、API 契约、快速验收与 App 交接文档，以本阶段设计取代 `ALL + IgnorePayment` 方案
- [x] T105 [US1] 在同一批次补充 create/retry DTO、Controller、Service 与表单工厂测试源码，覆盖 `CREDIT/APPLE_PAY`、非法枚举无副作用、动态字段集合和完整签名输入
- [x] T106 [US1] 为 create/retry 增加受控 `paymentMethod`，统一映射 `CREDIT -> Credit + UnionPay=2`、`APPLE_PAY -> ApplePay`，新增五语言 `PAYMENT_METHOD_INVALID`，禁止客户端 OMG 原始参数
- [ ] T107 [US1] 在用户端 App 增加信用卡/Apple Pay i18n 选择，并让首次支付与重新支付分别提交本次选择；当前工作区未包含 App 源码，需提供用户端仓库后实施
- [ ] T108 [US1] 全部 OMG 计划功能调整完成后，使用 JDK 21 统一运行渠道定向测试、OMG 回归和模块构建；核对最终 diff、暂存范围及无 SQL 变更
- [ ] T109 [US1] 在 OMG stage 分别验收信用卡和 Apple Pay 直接流程，确认不出现超商快付、AFTEE、银联或其他渠道选择项

## Phase 19: 外送订单支付后开放骑手

- [x] T110 更新 OMG 规格，允许首次成功结算在不推进订单/配送状态的前提下触发外送骑手开放推送
- [x] T111 在 `OmgPaymentNotifyService` 首次更新订单支付状态后加载业务订单，并复用配送推送服务按统一条件开放给骑手
- [x] T112 补充 OMG 首次成功结算触发骑手开放推送测试，并复用统一开放条件拦截未支付或非待接单状态

## Dependencies & Execution Order

- Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 → Phase 6 → Phase 7 → Phase 8 → Phase 9 → Phase 10 → Phase 11 → Phase 12 → Phase 13 → Phase 14 → Phase 15 → Phase 16 → Phase 17 → Phase 18。
- T008 与 T011/T012 可独立写测试，但实施时顺序执行以维持清晰 TDD 证据。
- T031 必须早于任何脏文件修改；T041 必须在旧代码退役完成后执行。
- 手动 stage 验收依赖开发者执行 DDL，自动测试和构建不依赖数据库变更。

## Completion Definition

- `POST /pay/omg/create` 首次返回可提交的 stage 表单。
- `POST /pay/omg/notify` 对所有实际字段验签，并幂等同步成功/失败支付事实。
- 每次回调完整保存到现有 `ipn_log`；不新增该表字段。
- 成功仅修改订单 `payStatus`，不推进业务状态或触发退款；外送订单首次支付成功可触发骑手开放推送，其他通用支付推送不触发。
- 同订单重复/并发创建最多一条 `CREATED`，后续请求为 `PAYMENT_ATTEMPT_EXISTS`。
- 官方检查码向量一致，实际发送字段无遗漏。
- App 只提交 `CREDIT/APPLE_PAY`；新支付表单分别固定为 `ChoosePayment=Credit + UnionPay=2` 或 `ChoosePayment=ApplePay`，不发送 `ChoosePayment=ALL` 或 `IgnorePayment`。
- 新代码全在 `omgpay` 包，不引用旧 OMG 支付实现。
- 旧 Controller、回调、查询、补单、退款、任务及旧表运行时引用为零。
- `pos_store_omg` 凭证表、持久化和管理能力保留；联网探测只使用新 `omgpay` 实现。
- 日志足以定位问题且无敏感信息；非预期异常保留堆栈。
- JDK 21 定向测试与构建通过，最终 diff 无无关改动。
- iPhone 中 `omgCheckout` 监听当前页面 WebView，精确结果 URL 单次触发交接；后端现有结果页、按钮和 Scheme 兜底保持兼容。
