# 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**: 完成测试环境创建支付订单、最终付款结果回调及用户触发的当前支付查询补偿;不实现取号、定时/批量补单、退款、推送、关账或正式环境。 ## 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` 的普通 `