Browse Source

docs: specify OMG payment creation rebuild

qmj 2 weeks ago
parent
commit
d223e8e32e
1 changed files with 324 additions and 0 deletions
  1. 324 0
      specs/020-omg-payment-rebuild/spec.md

+ 324 - 0
specs/020-omg-payment-rebuild/spec.md

@@ -0,0 +1,324 @@
+# Feature Specification: OMG AIO 支付重建——创建支付订单
+
+**Feature Branch**: `020-omg-payment-rebuild`
+
+**Created**: 2026-08-13
+
+**Status**: Draft — awaiting written specification review
+
+**Input**: 以 OMG 全方位金流 AIO 官方技术文件 V1.5.3(2026-07)为唯一外部事实来源,从零重建创建支付订单;现有 OMG 支付代码、旧支付流水和 `specs/016-omg-payment` 均不作为需求或设计依据。第一阶段只完成测试环境首次创建并进入 OMG 收银台,其余能力后续逐项重建。
+
+## 1. Scope and Trust Boundary
+
+### 1.1 In scope
+
+- 从订单所属门店读取其独立的 `MerchantID / HashKey / HashIV`。
+- 为合法、未支付的 OMG 订单创建一条全新的支付尝试。
+- 根据 OMG 官方 AIO 规则生成完整表单和 `CheckMacValue`。
+- 由客户端在当前页面以表单 POST 进入 OMG 测试收银台。
+- 防止同一业务订单同时产生多个未结束 OMG 支付尝试。
+- 停用旧 OMG Controller 及其补单、退款、定时任务和订单取消调用入口。
+
+### 1.2 Explicitly out of scope
+
+- `ReturnURL` 付款结果通知的接收、验签、核销和订单状态变更。
+- ATM、CVS、BarcodeATM 取号结果通知及缴费信息展示。
+- `OrderResultURL`、`PaymentInfoURL`、`ClientRedirectURL`、`ClientBackURL`。
+- OMG 订单查询、自动补单、人工补单。
+- 退款、取消交易、信用卡关账。
+- 分期、定期定额、记忆卡号、银联专用流程。
+- 正式环境开放和真实付款完成验收。
+- 旧 OMG 支付流水的数据迁移、兼容或清理。
+- 客户端页面代码;本阶段只定义客户端必须遵守的表单 POST 契约。
+
+### 1.3 Trust decisions
+
+- OMG 官方 AIO 技术文件 V1.5.3 是支付协议的唯一事实来源。
+- 旧 `OmgPayController`、旧 OMG 工具类、旧支付流水表和 `specs/016-omg-payment` 均不可信,不得复制其行为或用其解释官方文档。
+- 唯一获准复用的旧 OMG 能力是 `pos_store_omg` 及其现有门店凭证存储、录入、启停和查询实现。该表只保存每个门店独立的 `MerchantID / HashKey / HashIV` 等凭证信息。
+- 订单现有字段继续使用 `PosOrder.payType = "2"` 表示选择 OMG 支付。
+- 旧支付表及其数据不读取、不迁移;开发者会自行删除旧表。
+
+## 2. Confirmed Business Decisions
+
+- 每个门店使用独立的 `MerchantID / HashKey / HashIV`,不使用平台统一凭证。
+- 不传 `PlatformID`。
+- `ChoosePayment` 固定为 `ALL`;具体显示渠道以该门店在 OMG 后台实际开通的能力为准。
+- 客户端在当前页面提交表单,不使用 iframe,不打开新窗口。
+- 当前仅接 OMG 测试环境。
+- 新可信公开路径继续使用 `/pay/omg/*`;创建入口为 `POST /pay/omg/create`,后续可信回调仍预定为 `POST /pay/omg/notify`。
+- 旧 Controller 完全作废,不保留 `/legacy/*` 或任何其他旧 OMG 接口。
+- 所有新实现代码放在新的 `omgpay` 包目录;新代码不得引用旧支付 Controller、旧签名器、旧表单工具或旧支付流水服务。
+- 新支付尝试使用全新表 `pos_order_omg_attempt`。
+
+## User Scenarios & Testing
+
+### User Story 1 - 首次创建并进入 OMG 收银台 (Priority: P1)
+
+已登录用户为自己的单门店餐饮订单选择 OMG 后,调用创建接口并取得由服务端签名的表单。客户端在当前页面 POST 该表单,进入对应门店的 OMG 测试收银台,并看到 `ALL` 下该门店已开通的付款方式。
+
+**Why this priority**: 这是本阶段唯一交付的用户价值,也是后续回调、查询和退款的前置能力。
+
+**Independent Test**: 为测试门店配置有效测试凭证,创建一笔合法未支付订单,调用接口并提交响应表单,确认浏览器进入官方测试端点且收银台显示正确订单金额及可用渠道。
+
+**Acceptance Scenarios**:
+
+1. **Given** 用户拥有一笔 `payType="2"`、未取消、未付款、金额为正的单门店订单,且门店 OMG 凭证已启用,**When** 用户首次调用创建接口,**Then** 系统创建唯一支付尝试并返回可提交的 OMG 表单。
+2. **Given** 创建接口返回成功,**When** 客户端在当前页面向 `gatewayUrl` POST 全部 `formFields`,**Then** 浏览器进入 OMG 测试收银台,不通过 iframe 或新窗口加载。
+3. **Given** 门店在 OMG 后台开通多个付款渠道,**When** 收银台接收 `ChoosePayment=ALL`,**Then** 收银台按 OMG 与门店配置展示可用渠道。
+
+---
+
+### User Story 2 - 阻止重复创建有效付款入口 (Priority: P1)
+
+客户创建表单后没有立即付款,再次点击支付时,系统既不重复提交旧 `MerchantTradeNo`,也不贸然生成新的 `MerchantTradeNo`。
+
+**Why this priority**: ATM、CVS、BarcodeATM 可能在较长期限内仍可付款;多个有效入口可能造成重复付款。
+
+**Independent Test**: 对同一订单顺序或并发调用创建接口,数据库始终只有一条未结束尝试,后续请求得到 `PAYMENT_ATTEMPT_EXISTS`,且不会返回第二份可提交表单。
+
+**Acceptance Scenarios**:
+
+1. **Given** 某订单已有 `CREATED` 尝试,**When** 用户再次创建,**Then** 返回 `PAYMENT_ATTEMPT_EXISTS`,不生成新 `MerchantTradeNo`,也不重放旧表单。
+2. **Given** 同一订单同时发起多个创建请求,**When** 请求并发执行,**Then** 数据库约束和事务保证最多一条未结束尝试,其余请求得到一致的业务结果。
+3. **Given** 旧尝试状态尚不明确,**When** 系统尚未实现可信查询,**Then** 不以本地时间、新鲜窗口或用户重复点击为理由释放旧尝试。
+
+---
+
+### User Story 3 - 拒绝不合法或不安全的创建请求 (Priority: P1)
+
+系统只为订单本人、合法业务状态、合法金额且门店凭证可用的测试订单创建支付尝试,并确保门店密钥不出现在响应或日志中。
+
+**Why this priority**: 创建错误门店、错误金额或泄露密钥会形成直接资金风险。
+
+**Independent Test**: 分别使用无效 token、他人订单、终态订单、异常金额、错误支付类型、无凭证门店和非测试网关配置调用接口,均被拒绝且不写入尝试表。
+
+**Acceptance Scenarios**:
+
+1. **Given** 请求用户不是订单所有者,**When** 调用创建接口,**Then** 系统拒绝且不透露订单或门店支付详情。
+2. **Given** 订单已取消、已付款、金额不大于零、不是单门店订单或 `payType` 不是 `"2"`,**When** 调用创建接口,**Then** 系统返回国际化业务错误且不创建尝试。
+3. **Given** 门店没有已启用 OMG 凭证,**When** 调用创建接口,**Then** 系统拒绝且不创建尝试。
+4. **Given** 服务配置不是允许的 OMG 测试端点,**When** 调用创建接口,**Then** 系统拒绝生成表单。
+5. **Given** 创建成功或失败,**When** 检查 API 响应和应用日志,**Then** 所有响应和日志均不存在 `HashKey`、`HashIV`;完整 `CheckMacValue` 与签名表单只存在于订单本人获准取得的创建成功响应,不出现在错误响应或日志中。
+
+### Edge Cases
+
+- 业务订单号含有不适合 OMG `MerchantTradeNo` 的字符时,系统使用独立生成的英数字编号,不直接拼接或截断业务订单号。
+- 随机生成的 `MerchantTradeNo` 发生唯一索引冲突时,系统可在同一创建事务中重新生成;达到受控次数仍失败时整笔创建回滚。
+- 客户端取得表单但未提交、网络中断或关闭页面时,本地只能保持 `CREATED`,不得宣称 OMG 已建立或未付款。
+- 表单生成成功但尝试落库失败,或尝试落库事务最终回滚时,不得向客户端返回可提交表单。
+- 订单或凭证在并发过程中发生变化时,最终写入必须仍满足订单合法状态、凭证归属门店和单活跃尝试约束。
+- `TradeDesc`、`ItemName` 不接受客户端文本,必须由服务端生成,无 HTML 标签或未经允许的特殊符号,并满足官方长度限制。
+- `ReturnURL` 必须是服务端受控的 HTTPS URL,路径固定指向新的 `/pay/omg/notify`;客户端不得覆盖。
+- 第一阶段没有 `/pay/omg/notify` 处理器是预期行为;测试付款通知不会被旧回调接收或改变订单状态。
+
+## Requirements
+
+### Functional Requirements
+
+- **FR-001**: 系统 MUST 新建 `com.ruoyi.app.omgpay` 下的 Controller、请求/响应 DTO、创建服务、表单生成器、签名器和配置类型;这些新类 MUST NOT 引用旧 `OmgPayController`、旧 `OmgPay`、旧 `OmgCheckMacValue` 或旧 OMG 支付流水服务。
+- **FR-002**: 系统 MUST 新建 `com.ruoyi.system.omgpay` 下的支付尝试 Entity、Mapper 和 Service;新支付尝试 MUST 使用 `pos_order_omg_attempt`,不得读取或写入旧 OMG 支付流水表。
+- **FR-003**: 系统 MAY 复用现有 `pos_store_omg` 门店凭证查询实现,且这是唯一允许复用的旧 OMG 实现;新创建流程 MUST 按订单门店读取该门店已启用的 `MerchantID / HashKey / HashIV`。
+- **FR-004**: 系统 MUST 保持公开创建入口为 `POST /pay/omg/create`,使用 `@RequestHeader String token` 和显式 `@RequestBody` DTO;Controller 入参不得使用 Map,DTO 不使用 Bean Validation 注解。
+- **FR-005**: 创建请求 DTO MUST 只接收 `orderId`;金额、门店、用户、支付类型、说明文字、网关地址、回调地址和支付渠道均必须由服务端决定。
+- **FR-006**: 系统 MUST 校验登录用户为订单所有者,订单为单门店订单、未取消、未付款、未完成、金额为正且 `PosOrder.payType="2"`;任何校验失败 MUST NOT 创建支付尝试。
+- **FR-007**: 系统 MUST 使用订单的整数新台币金额作为 `TotalAmount`,不得接受或信任客户端金额。
+- **FR-008**: 系统 MUST 仅允许创建表单到 `https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5`;第一阶段不得配置或回退到正式环境。
+- **FR-009**: 系统 MUST 为每次新尝试生成全局唯一、不可复用、长度不超过 20 且只含 ASCII 英数字的 `MerchantTradeNo`;不得从业务订单号直接派生可冲突或超长的编号。
+- **FR-010**: 同一业务订单在任一时刻 MUST 最多存在一条未结束 OMG 尝试;顺序重复或并发创建 MUST 返回业务状态 `PAYMENT_ATTEMPT_EXISTS`,不得返回旧表单、重复提交旧编号或创建新编号。
+- **FR-011**: 在可信查询尚未实现前,系统 MUST NOT 基于固定分钟窗口、本地创建时间或用户再次点击自动结束 `CREATED` 尝试。
+- **FR-012**: 创建表单 MUST 发送以下字段和值:`MerchantID`、`MerchantTradeNo`、`MerchantTradeDate`、`PaymentType=aio`、`TotalAmount`、`TradeDesc`、`ItemName`、`ReturnURL`、`ChoosePayment=ALL`、`EncryptType=1`、`InvoiceMark=N`、`NeedExtraPaidInfo=Y`、`ExpireDate=1`、`StoreExpireDate=30`、`BarcodeATMExpireDate=1` 和 `CheckMacValue`。
+- **FR-013**: 创建表单 MUST NOT 发送 `PlatformID`、`PaymentInfoURL`、`OrderResultURL`、`ClientRedirectURL`、`ClientBackURL`、`Language`、分期、定期定额、记忆卡号或银联专用参数。
+- **FR-014**: `MerchantTradeDate` MUST 以 `Asia/Taipei` 时区格式化为 `yyyy/MM/dd HH:mm:ss`。
+- **FR-015**: `TradeDesc` 和 `ItemName` MUST 由服务端生成,禁止 HTML,符合 OMG 字符及长度限制;`ItemName` 不得超过中文 60 字或英数字 120 字的官方显示限制,字段总长度不得超过官方 `String(200)` 限制。
+- **FR-016**: `ReturnURL` MUST 是受控 HTTPS 地址并固定以 `/pay/omg/notify` 结尾;第一阶段只把它作为 OMG 必填值,不实现该路径的处理器。
+- **FR-017**: `CheckMacValue` MUST 严格按 OMG 官方规则生成:排除 `CheckMacValue` 本身,将其余全部实际发送字段按官方字母顺序排序,以 `&` 串接,前置 `HashKey=...&`、后置 `&HashIV=...`,执行符合官方 .NET 表的 URL 编码并转小写,使用 SHA-256,最后输出大写十六进制。
+- **FR-018**: 除 `CheckMacValue` 自身外,创建请求实际发送的全部字段 MUST 参加签名,包括 `NeedExtraPaidInfo=Y` 和三个期限字段;不得挑选所谓核心字段计算。
+- **FR-019**: 后续回调阶段 MUST 遵守同一完整字段原则:除 `CheckMacValue` 外,OMG 实际返回的全部字段均参加验签;启用 `NeedExtraPaidInfo=Y` 后,全部额外回传字段及空值字段也必须进入验签集合。本阶段只固化该约束,不实现回调。
+- **FR-020**: 创建成功响应 MUST 使用明确对象 `{status, gatewayUrl, formFields}`;`status` 固定为 `CREATED`,`gatewayUrl` 为测试 AioCheckOut 端点,`formFields` 含实际需要 POST 的全部字段但不含 `gatewayUrl`。
+- **FR-021**: 客户端 MUST 在当前页面以 `application/x-www-form-urlencoded` 表单 POST 全部 `formFields` 到 `gatewayUrl`;不得使用 iframe,不得另开新窗口,不得把响应转换为 GET 查询链接。
+- **FR-022**: 系统 MUST NOT 在数据库保存 `HashKey`、`HashIV`、完整 `CheckMacValue` 或整份签名表单;支付尝试只保存本地可确认事实和创建快照。
+- **FR-023**: 系统 MUST NOT 在任何响应或日志中输出登录 token、`HashKey`、`HashIV`。完整 `CheckMacValue` 和签名表单只允许出现在通过归属及业务校验的创建成功响应中,不得出现在错误响应或日志中。日志可记录本地支付尝试 ID、脱敏后的 `MerchantTradeNo`、业务订单号和阶段结果。
+- **FR-024**: 业务校验错误 MUST 使用项目国际化机制,不得硬编码单一语言错误;新增错误 key 必须同步 `vi/zh/tw/en` 支持来源。
+- **FR-025**: 旧 `OmgPayController` MUST 取消 Spring Controller 身份且所有旧接口不可访问;不得保留 legacy 路径。
+- **FR-026**: 旧 OMG 定时补单任务、订单取消链路中的旧 OMG 退款调用、旧管理端 OMG 补单/退款接口和其他对旧 Controller 的运行时调用 MUST 一并停用或移除;不得影响非 OMG 订单取消及其他支付通道。
+- **FR-027**: 在开发者删除旧 OMG 支付及退款表后,任何可达运行链路 MUST NOT 再查询或写入这些旧表;旧表对应的 Mapper/Service 即使暂时保留源码,也不得被新流程或当前有效业务入口调用。`pos_store_omg` 凭证表不属于该禁用范围。
+- **FR-028**: 所有新建表、索引或约束 SQL MUST 只追加到 `updatesql/sql.md`,实现过程不得执行数据库变更。
+- **FR-029**: 第一阶段 MUST 保持测试环境边界,验收不得把旧回调或旧查询结果当成新流程成功证据。
+- **FR-030**: 新 `omgpay` 代码 MUST 包含标准且必要的注释:公开类型说明职责和安全边界;协议字段、检查码编码、事务与数据库唯一约束等非显然逻辑说明“为什么”;不为显然的赋值、访问器或框架样板添加重复注释。注释不得包含真实凭证、完整签名原文或可用测试秘密。
+- **FR-031**: 新创建流程 MUST 使用项目日志框架输出足够的结构化排障上下文。创建开始记录业务订单号和请求用户标识;校验通过后记录门店 ID;落库成功记录支付尝试 ID、业务订单号、门店 ID、金额、状态和脱敏 `MerchantTradeNo`;业务拒绝记录稳定业务错误码及已有的安全上下文;非预期异常记录相同安全上下文并保留服务端异常堆栈。不得以拼接整份 DTO、凭证对象或表单对象的方式记录日志。
+
+### API Contract
+
+#### Request
+
+```http
+POST /pay/omg/create
+Content-Type: application/json
+token: <login-token>
+
+{
+  "orderId": "991786433092835"
+}
+```
+
+#### Success data
+
+```json
+{
+  "status": "CREATED",
+  "gatewayUrl": "https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5",
+  "formFields": {
+    "MerchantID": "1000031",
+    "MerchantTradeNo": "OMGR8K3P7W2M9C4X6A1",
+    "MerchantTradeDate": "2026/08/13 15:30:23",
+    "PaymentType": "aio",
+    "TotalAmount": "100",
+    "TradeDesc": "Food order 991786433092835",
+    "ItemName": "Order 991786433092835",
+    "ReturnURL": "https://example.test/pay/omg/notify",
+    "ChoosePayment": "ALL",
+    "EncryptType": "1",
+    "InvoiceMark": "N",
+    "NeedExtraPaidInfo": "Y",
+    "ExpireDate": "1",
+    "StoreExpireDate": "30",
+    "BarcodeATMExpireDate": "1",
+    "CheckMacValue": "<64 uppercase hexadecimal characters>"
+  }
+}
+```
+
+`HashKey` 和 `HashIV` 永远不属于响应。外层继续使用项目现有 `AjaxResult` 成功/失败封装;上例只定义 `data` 契约。
+
+#### Existing-attempt failure
+
+- 外层返回项目业务失败响应。
+- 机器可识别业务状态为 `PAYMENT_ATTEMPT_EXISTS`。
+- 不返回旧 `formFields` 或新的 `MerchantTradeNo`。
+
+### Key Entities
+
+#### `OmgPaymentAttempt` / `pos_order_omg_attempt`
+
+表示本次重建产生的一次不可覆盖的 OMG 支付尝试。第一阶段字段如下:
+
+| Field | Meaning | Constraint |
+|---|---|---|
+| `id` | 本地支付尝试主键 | 自增主键 |
+| `dd_id` | `pos_order.dd_id` 业务订单号 | 非空 |
+| `merchant_trade_no` | 本次 OMG 特店交易编号 | 非空、全局唯一、≤20 位英数字 |
+| `store_id` | 创建时订单门店 | 非空 |
+| `merchant_id` | 创建时门店 MerchantID 快照 | 非空、≤10 位 |
+| `amount` | 创建时订单整数 TWD 金额快照 | 非空、>0 |
+| `attempt_status` | 本地事实状态 | `0=CREATED` |
+| `active_dd_id` | 单活跃约束生成列 | `attempt_status=0` 时为 `dd_id`,否则为 `NULL` |
+| `create_time` | 本地创建时间 | 非空 |
+| `update_time` | 本地更新时间 | 非空 |
+
+约束:
+
+- `UNIQUE (merchant_trade_no)`;
+- `UNIQUE (active_dd_id)`,利用 MySQL 唯一索引允许多个 `NULL` 的语义,为后续终态释放活跃键;
+- `CREATED` 只表示本地已生成并持久化表单所需事实,不能解释为 OMG 已接收、已建立订单或未付款;
+- 后续回调/查询规格负责定义可信的后续状态;本阶段不得预先猜测完整状态机。
+
+#### Existing `PosStoreOmg` / `pos_store_omg`
+
+可信门店凭证来源。本阶段不修改其表结构、录入流程或启停流程。新创建服务只读取与订单 `storeId` 对应、已启用的 `MerchantID / HashKey / HashIV`。
+
+## 3. Error Handling and Security
+
+- 创建流程必须在返回表单前完成所有订单、凭证、环境和并发校验。
+- 尝试记录与订单合法性必须处于可证明的一致事务边界;事务失败不得泄出可提交表单。
+- 数据库唯一约束是并发安全的最终防线;应用锁只能作为降低冲突的辅助机制,不能取代唯一约束。
+- `PAYMENT_ATTEMPT_EXISTS` 是安全终止,不是系统异常,也不能自动删除或覆盖已有行。
+- 外部网关地址和 `ReturnURL` 必须由服务端配置并严格校验,不接受客户端 URL,防止开放重定向或向非 OMG 主机泄露签名表单。
+- `HashKey / HashIV` 只在服务端签名过程中短暂使用;不得复制到新支付尝试表。
+- API 错误不返回堆栈、SQL、密钥、签名原文或内部类名。
+- 旧 Controller 下线后,`/pay/omg/create` 只能由新 `omgpay` Controller 映射;`/pay/omg/notify` 在下一阶段前必须没有旧处理器。
+- 日志级别必须有一致语义:正常阶段事件使用 `INFO`,可预期的业务拒绝使用 `WARN`,非预期且需要调查的异常使用 `ERROR` 并携带异常对象;单元测试可依赖稳定业务错误码,不依赖自然语言日志文本。
+- 日志中的 `MerchantTradeNo` 只显示足以关联记录的首尾片段;如果应用已有 trace/request ID,则沿用现有上下文,不自行生成新的支付追踪体系。
+
+## 4. Verification Requirements
+
+### 4.1 Automated tests
+
+- 使用 OMG 官方 AioCheckOut 示例参数和官方期望 `CheckMacValue` 验证完整 SHA-256 签名链路;不得用旧实现测试或其他金流向量代替官方依据。
+- 验证字段排序、HashKey/HashIV 包夹、.NET URL 编码替换、转小写、SHA-256 和大写十六进制各步骤。
+- 验证实际发送字段集合与签名输入集合完全一致;删除、增加或修改任一非 `CheckMacValue` 字段都会改变签名。
+- 验证空值字段在未来回调验签集合中不会被静默删除;本阶段至少通过签名器单元测试锁定“保留空值”的通用能力。
+- 验证 `MerchantTradeNo` 仅含英数字、长度不超过 20、重复冲突不会覆盖旧行。
+- 验证台北时区与 `yyyy/MM/dd HH:mm:ss` 格式。
+- 验证固定字段和值、禁止字段不出现在表单、金额只取订单、文字字段安全和长度限制。
+- 验证 token、订单归属、单门店、业务状态、`payType="2"`、金额和门店凭证校验。
+- 验证非测试网关和不合法 `ReturnURL` 被拒绝。
+- 验证顺序重复和真实并发创建最多产生一条 `CREATED` 尝试。
+- 验证所有响应和日志均不包含登录 token、`HashKey` 或 `HashIV`;完整 `CheckMacValue` 与签名表单只出现在创建成功响应,不出现在错误响应或日志中。
+- 验证创建开始、校验拒绝、创建成功、重复尝试和非预期异常路径均输出规定的排障上下文与正确日志级别,同时不记录 token、凭证对象、DTO 或表单对象。
+- 通过定向代码审查验证新公开类型、签名编码、事务和唯一约束处理具有必要注释,且没有对显然代码的噪声注释。
+- 验证旧 Controller 不再注册,旧定时任务不再调度,旧补单/退款接口和取消链路调用不可达。
+- 验证删除旧 OMG 支付/退款表后,当前有效运行链路不包含对这些旧表的 Mapper/Service 调用;门店凭证 `pos_store_omg` 查询保持可用。
+
+所有新增生产方法必须遵循测试先行:先运行定向测试并观察其因缺少新行为而失败,再写最小实现使其通过。
+
+### 4.2 Module verification
+
+- 临时使用 `C:\Users\qmj\.jdks\graalvm-jdk-21.0.7` 设置当前命令的 `JAVA_HOME` 和 `PATH`。
+- 运行新 `omgpay` 测试及受旧链路下线影响的订单回归测试。
+- 运行 `ruoyi-admin` 及其依赖模块的 JDK 21 Maven 构建。
+- 检查 `git diff`,确认未改动旧凭证能力、未重写无关文件、未引入编码或换行噪音。
+
+### 4.3 OMG stage acceptance
+
+- 使用门店测试凭证调用 `POST /pay/omg/create`。
+- 在当前页面将全部 `formFields` POST 到返回的测试 `gatewayUrl`。
+- 确认进入 OMG 测试收银台,金额、商品说明和 `ALL` 可用渠道显示正确。
+- 确认不使用 iframe 或新窗口。
+- 本阶段不以付款、回调、查询、取号或退款结果作为完成标准。
+
+## Success Criteria
+
+### Measurable Outcomes
+
+- **SC-001**: 合法首次创建请求 100% 返回固定测试端点和完整签名表单,并可进入 OMG 测试收银台。
+- **SC-002**: 官方 AioCheckOut 签名向量自动化测试与官方 `CheckMacValue` 完全一致。
+- **SC-003**: 实际发送的每一个非 `CheckMacValue` 字段均由测试证明参与签名,额外字段与空值字段不会被签名器丢弃。
+- **SC-004**: 对同一订单进行顺序或并发重复创建时,数据库未结束尝试数始终不超过 1,第二个 `MerchantTradeNo` 产生率为 0。
+- **SC-005**: 未授权、非法状态、非法金额、错误支付类型、无凭证和非测试环境请求的尝试写入数为 0。
+- **SC-006**: 新 OMG 创建链路中对旧支付 Controller、旧工具类和旧支付流水服务的引用数为 0;对旧支付表的 SQL 访问数为 0。
+- **SC-007**: 旧 OMG Controller、旧补单/退款入口和旧定时任务的可达运行入口数为 0;`/pay/omg/create` 只映射到新 Controller。
+- **SC-008**: API 响应及应用日志中的登录 token、`HashKey`、`HashIV` 泄露数为 0;错误响应和应用日志中的完整 `CheckMacValue` 或完整签名表单泄露数为 0。
+- **SC-009**: 受影响的自动化测试和 JDK 21 模块构建均以退出码 0 完成。
+- **SC-010**: 创建开始、创建成功、业务拒绝和非预期异常均可通过业务订单号及安全的支付尝试上下文定位;敏感信息日志泄露数为 0,非预期异常堆栈保留率为 100%。
+- **SC-011**: 新 `omgpay` 的公开类型、签名编码、事务和数据库并发边界均有必要注释,显然样板代码上的重复注释数为 0。
+
+## Assumptions
+
+- `pos_order.dd_id` 是客户端使用的业务订单号,`PosOrder.mdId` 能唯一定位单门店订单的门店。
+- `PosOrder.amount` 是当前订单应支付的整数新台币金额。
+- `pos_store_omg` 的既有启用凭证查询能按 `storeId` 返回正确门店凭证。
+- 测试环境使用 OMG 官方公开的 AIO stage 端点;正式环境切换将在可信回调及后续流程完成后单独设计和批准。
+- 新表 SQL 由开发者手动执行;代码实现不会连接数据库执行 DDL。
+- 第一阶段测试人员接受:一旦某订单已有 `CREATED` 尝试,在可信查询阶段完成前,需要人工准备新业务订单才能再次测试创建;系统不会自动清理或换号。
+
+## Official Sources
+
+- [OMG AIO 技术文件首页(V1.5.3)](https://developers.omg.com.tw/payment/aio/)
+- [介接流程](https://developers.omg.com.tw/payment/aio/preparation/workflow.html)
+- [前置准备事项](https://developers.omg.com.tw/payment/aio/preparation/test-setting.html)
+- [产生订单](https://developers.omg.com.tw/payment/aio/api/01_order.html)
+- [ATM、CVS、BarcodeATM 取号结果通知](https://developers.omg.com.tw/payment/aio/api/02_ATM_CSV_notify.html)
+- [付款结果通知](https://developers.omg.com.tw/payment/aio/api/03_payment_notify.html)
+- [查询订单](https://developers.omg.com.tw/payment/aio/api/04_order_query.html)
+- [退款/取消交易](https://developers.omg.com.tw/payment/aio/api/05_refund.html)
+- [信用卡定期定额](https://developers.omg.com.tw/payment/aio/api/06_recurring.html)
+- [检查码机制与自检表](https://developers.omg.com.tw/payment/aio/api/07_appendices.html)
+- [关键字一览](https://developers.omg.com.tw/payment/aio/reference/01_keywords.html)
+- [交易讯息代码](https://developers.omg.com.tw/payment/aio/reference/02_response-codes.html)
+- [付款方式一览](https://developers.omg.com.tw/payment/aio/reference/03_payment-methods.html)
+- [回覆付款方式一览](https://developers.omg.com.tw/payment/aio/reference/04_payment-response-types.html)
+- [URL Encode 转换表](https://developers.omg.com.tw/payment/aio/reference/05_url-encode-table.html)
+- [产生检查码范例程序](https://developers.omg.com.tw/payment/aio/reference/06_check-value-sample.html)
+- [定期定额范例](https://developers.omg.com.tw/payment/aio/reference/07_periodical-payment-sample.html)
+- [版本与更新纪录](https://developers.omg.com.tw/payment/aio/reference/08_changelog.html)