# Feature Specification: OMG 支付接入（订单在线支付）

**Feature Branch**: `（待创建，暂未建分支/未提交）`

**Created**: 2026-07-29

**Status**: Draft —— 关键决策已定（D1–D6，见「已定决策」）

**Input**: 接入台湾 OMG（歐買尬 / 金流引擎 FunPoint）AIO 幕前支付（`https://developers.omg.com.tw/payment/aio/api/01_order.html`）用于订单在线支付。替代已就绪未启用的蓝新 NewebPay（011，不再启用）；OMG 独立实现、不复用蓝新代码，仅复用平台共享订单/推送链路。

---

## 集成背景（已确认，来自 OMG AIO 文档）

- **网关端点**：
  - 测试：`https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5`
  - 生产：`https://payment.funpoint.com.tw/Cashier/AioCheckOut/V5`
  - `POST`，`application/x-www-form-urlencoded`
- **签名模型（关键差异）**：OMG 用 **`CheckMacValue`（SHA256，`EncryptType=1`）** 单字段签名；这与蓝新 NewebPay 的 `TradeInfo`(AES) + `TradeSha`(SHA256) 双层模型**不同**。结论：**需独立的 OMG 签名工具，不能复用 `NewebPayEncryptUtil`**。具体算法（参数排序、URI 编码、HashKey/HashIV 包夹、SHA256 大写）在文档附錄 `07_appendices` / 范例 `06_check-value-sample`，留待 plan/research 落地。
- **本项目启用的支付方式**：仅信用卡和 Apple Pay。OMG 官方将 Apple Pay 包含在 `ChoosePayment=Credit` 内；同时传 `UnionPay=2` 隐藏银联，不使用 `ALL`。
  - ATM、CVS、BarcodeATM、AFTEE 和 LINE Pay 均不属于新支付订单范围。
- **幕前流程**：商户组参 + 计算 `CheckMacValue` → POST 到 `AioCheckOut/V5` → 消费者在 OMG 收银台支付 → OMG **服务端 POST 回 `ReturnURL`**（商户须回 `1|OK`）→ 可选 `OrderResultURL` 客户端跳转（仅即时支付方式支持）。
- **非即时支付已移出范围**：开发测试阶段没有需要兼容的历史订单；不提供 `PaymentInfoURL`、`ClientRedirectURL` 或延期支付返回接口。
- **退款**：`05_refund` API；**订单查询**：`04_order_query`；**付款结果通知**：`03_payment_notify`。
- **发票**：`InvoiceMark` 必须 `N` —— 电子发票仍走本项目 ezPay 链路（010/014），与支付解耦。
- **约束**：不可用 iframe；iOS 下不可另开新窗口；`MerchantTradeNo` 全平台唯一；`ReturnURL` 与 `OrderResultURL` 不可相同。

## 与本项目现有支付的关系

- 本功能把 **OMG 启用为订单在线支付**。蓝新 NewebPay（011）代码就绪**未启用**；VNPay/ZaloPay 已废弃（见 `project-deprecated-code` 记忆）。
- **复用现有链路**：订单状态流转入口（`PosOrderShOprate`/`PosOrderQsOprate`/`UserOrderController`/`PosOrderController`）、`PayPush`（iOS uni 云函数 msduser/msdrider/msdstore + `PushEvent`→`push_message`）、以及 `sendAcceptRiderPush`（迁移契机，见 CLAUDE.md「当前在用的订单操作入口」）。
- **凭证门店级**：每门店独立 OMG 凭证（MerchantID/HashKey/HashIV），存于门店维度配置表 `pos_store_omg`。
- **不复用蓝新代码（用户硬约束）**：OMG 全部代码（工具类/客户端/实体/Service/Controller）与表（凭证/流水/退款）独立新建、零 `newebpay` 依赖，不复用蓝新的 `pos_store_newebpay` / `pos_order_payment`；仅复用平台共享基础设施（订单状态机、推送、订单日志）。

---

## 已定决策（D1–D6，2026-07-29 确认）

- **D1 — 支付方式范围（2026-08-13 调整）**：仅信用卡和 Apple Pay；新订单固定 `ChoosePayment=Credit`、`UnionPay=2`，不开放 ATM、CVS、BarcodeATM、AFTEE 或银联。
- **D2 — 与 NewebPay(011) 的关系**：**OMG 替代蓝新金流**，作为订单在线支付启用；NewebPay 不再启用。
- **D3 — 退款/取消**：**本期范围**，订单取消走 OMG 退款 API（`05_refund`）。
- **D4 — LINE Pay**：**本期不做**（文档未列为独立 ChoosePayment，后续再议）。
- **D5 — 覆盖订单范围**：**本期仅餐饮订单**；旅游·机票订单（015）后续衔接，不在本期。
- **D6 — 凭证粒度**：**门店级**（每门店独立 MerchantID / HashKey / HashIV，启用开关，同 011 模式）。

> 说明：OMG 文档可正常获取（与 015 的 iFlight 不同），无硬阻塞。决策已定，可直接进 `/speckit-plan`。

---

## User Scenarios & Testing *(mandatory)*

### User Story 1 - 消费者在线支付订单（Priority: P1）

消费者下单后选择在线支付，选定方式（如信用卡/Apple Pay），跳转 OMG 收银台完成付款；付款成功后订单变为已支付并进入履约。

**Why this priority**：在线支付是订单交易闭环的核心，没有它订单无法在线收款。

**Independent Test**：从一笔待支付订单出发，能跳转 OMG、用测试卡完成支付，订单状态变为已支付。

**Acceptance Scenarios**:

1. **Given** 一笔待支付订单，**When** 消费者发起支付并选择支付方式，**Then** 跳转 OMG 收银台且金额/订单号正确。
2. **Given** 消费者在 OMG 完成付款，**When** OMG 回调平台，**Then** 订单核销为已支付，消费者看到支付成功。
3. **Given** 消费者取消或关闭支付页未付款，**When** 超时，**Then** 订单保持未支付并可重试，不误判为已付。

---

### User Story 2 - 支付回调核销与履约触发（Priority: P2）

OMG 服务端回调（`ReturnURL`）到达后，验签 + 幂等更新订单，并触发后续履约（状态流转、商家/骑手推送）。

**Why this priority**：支付成功必须可靠地驱动订单履约，否则「付了钱却没人接单」。

**Independent Test**：模拟 OMG 回调（合法/伪造/重复），合法回调能更新订单并触发推送，伪造被拒、重复不重复发货。

**Acceptance Scenarios**:

1. **Given** OMG 合法回调到达，**When** 平台验签通过，**Then** 订单更新为已支付并回 `1|OK`，履约链路（含 `sendAcceptRiderPush` 推送）被触发。
2. **Given** 同一订单的重复回调，**When** 再次到达，**Then** 幂等处理，不重复更新/不重复推送。
3. **Given** 伪造或验签失败的回调，**When** 到达，**Then** 拒绝并记录，订单状态不变。

---

### User Story 3 - 非即时支付（已撤销）

当前仍在开发测试阶段，不保留 ATM、CVS、BarcodeATM、AFTEE 的创建、取号、返回或兼容处理。

**Why removed**：最终支付范围已经收敛为信用卡与 Apple Pay，且不存在生产历史订单。

**Independent Test**：新建表单不含延期支付参数，应用也不暴露延期支付返回接口。

**Acceptance Scenarios**:

1. **Given** 新支付订单，**When** 平台生成 OMG 表单，**Then** 表单不得包含 `PaymentInfoURL`、`ClientRedirectURL` 或延期缴费期限参数。
2. **Given** 请求旧延期支付返回路径，**When** 访问应用，**Then** 不得命中 OMG Controller。

---

### User Story 4 - 订单取消与退款（Priority: P3）

订单取消（用户取消/商家取消/超时退款）时，通过 OMG 退款 API（`05_refund`）原路退款，并同步状态。

**Why this priority**：售后资金闭环，依赖 D3 是否本期。

**Independent Test**：对一笔已支付订单发起取消，能调用 OMG 退款并同步为已退款。

**Acceptance Scenarios**:

1. **Given** 一笔已支付订单可退款，**When** 发起取消，**Then** 调 OMG 退款 API 并将订单标记为退款中/已退款。
2. **Given** 退款失败，**When** OMG 返回失败，**Then** 记录原因并可重试，不误标已退款。
3. **Given** 订单已先取消且 OMG 成功付款回调或主动补单随后到达，**When** 系统记录真实付款事实，**Then** 不触发履约，并必须将该笔付款纳入自动退款或人工退款待办，且由持久化兜底机制保证进程中断后仍可继续处理。

---

### User Story 5 - 商家/平台配置 OMG 凭证（Priority: P2）

商家或平台在后台维护 OMG 凭证（MerchantID/HashKey/HashIV），支持测试/生产切换与启用开关。

**Why this priority**：支付前提是凭证可用，属 P2 基础设施。

**Independent Test**：在后台录入门店 OMG 凭证，能用于发起支付与验签。

**Acceptance Scenarios**:

1. **Given** 已授权运营，**When** 录入门店 OMG 凭证并启用，**Then** 该门店订单可发起 OMG 支付。
2. **Given** 未配置或未启用凭证的门店，**When** 尝试在线支付，**Then** 不展示/不可用在线支付（可降级货到付款）。

---

### Edge Cases

- 回调签名验证失败（伪造/篡改）—— 必须拒绝并记录。
- 重复回调（网络重试）—— 按 `MerchantTradeNo` 幂等，不重复发货/推送。
- 消费者支付页关闭/超时未付 —— 订单不误判，可重试或按规则失效。
- 金额不一致（下单价 vs 支付价，如改了单/用了券）—— 以平台实际应收金额为准。
- 回调与「用户取消」并发 —— 先保留已付款资金事实且不触发履约；已取消订单的迟到付款必须进入可恢复的退款闭环，不能只写异常日志。
- 支付尝试重复创建 —— 同一业务订单只允许一个当前有效 OMG 支付入口；重复点击必须复用或查询现有尝试，不能产生多个同时可付款的 `MerchantTradeNo`。
- 支付尝试轮换 —— 只有 OMG 查询明确失败才能关闭原有效入口并创建新 `MerchantTradeNo`；查询超时、限流或结果不确定时必须保留原尝试。
- 退款失败 / 部分退款 / 跨日退款。
- 多门店凭证：A 门店凭证不可用于 B 门店订单。
- 测试凭证误用到生产、或反之。

## Requirements *(mandatory)*

### Functional Requirements

- **FR-001**：系统 MUST 支持消费者对未支付订单发起 OMG 幕前在线支付，跳转 OMG 收银台（金额、订单号、商品描述正确）。
- **FR-002**：系统 MUST 只向新支付订单提供信用卡和 Apple Pay，固定传 `ChoosePayment=Credit` 与 `UnionPay=2`；不得传 `ALL`，不得展示 ATM、CVS、BarcodeATM、AFTEE、银联或 LINE Pay。
- **FR-003**：系统 MUST 使用 OMG `CheckMacValue`（SHA256，`EncryptType=1`）对请求签名、对回调验签；签名工具**独立于**现有 NewebPay 工具。
- **FR-004**：系统 MUST 在 OMG `ReturnURL` 回调时验签 + 按 `MerchantTradeNo` 幂等更新订单为已支付，并回 `1|OK`；订单核销的原子条件允许待接单 `state=0` 和堂食已自动接单 `state=1`，同时要求 `pay_status=0`，不得核销配送中、已完成或已取消订单。
- **FR-005**：系统 MUST 在支付成功后触发现有订单履约链路（状态流转 + iOS 推送 + `sendAcceptRiderPush`）。
- **FR-006**：系统 MUST NOT 生成 ATM/超商取号或延期缴费参数，也 MUST NOT 暴露延期支付专用返回接口。
- **FR-007**：系统 MUST 支持订单取消时经 OMG 退款 API（`05_refund`）退款并同步状态（本期范围，D3）；若取消先于 OMG 成功回调或主动补单到达，系统 MUST 记录真实付款、不触发履约，并通过持久化扫描自动退款，不能依赖单次回调线程完成退款。
- **FR-008**：系统 MUST 支持门店级 OMG 凭证配置（MerchantID/HashKey/HashIV，启用开关，测试/生产切换）。
- **FR-009**：本期功能覆盖范围**仅餐饮订单**的在线支付；旅游·机票订单（015）后续衔接，不在本期（D5）。
- **FR-010**：系统 MUST 持久化支付流水（`MerchantTradeNo`、金额、方式、时间、回调原始报文、退款记录），并将脱敏后的后台回调内容写入共享 `ipn_log`，以 `type=omg` 区分支付渠道，支持排查与对账。
- **FR-011**：系统 MUST 在 `InvoiceMark=N` 下与 ezPay 电子发票链路解耦，支付不干涉开票。
- **FR-012**：OMG **替代蓝新 NewebPay(011)** 作为订单在线支付启用，NewebPay 不再启用（D2）。
- **FR-013**：系统 MUST NOT 复用或依赖任何蓝新 NewebPay 代码/表；OMG 凭证、流水、退款表与工具类/Controller 全部独立新建，仅可使用平台共享基础设施（订单状态机、推送、订单日志）。
- **FR-014**：系统 MUST 保证同一业务订单同时只有一个可向消费者展示并完成付款的 OMG 支付入口；重复点击必须复用该有效尝试，不得另建第二个可付款入口。
- **FR-015**：系统 MUST NOT 保留 ATM、CVS、BarcodeATM、AFTEE 的历史订单兼容分支；当前测试数据可直接清理。
- **FR-016**：系统 MUST 以公平、可恢复的方式调度 OMG `queryTrade`。未实际发出查询时不得消耗该订单的查询间隔；限流不得使固定排序后的后续订单长期饥饿。查询频率参数须标明其来源是官方契约或环境实测，不能把查询 `TimeStamp` 的 3 分钟有效期解释为支付尝试有效期。

- **FR-017**：系统 MUST 自动补偿仍为 `CREATED` 的当前有效支付尝试。调度器按 `next_query_time` 取有限批次，并在真实查询前用条件更新预留该行；查询确认已付款或明确失败时必须复用主动查询的验签与同一状态机，未付款或暂时异常不得关闭支付尝试。

- **FR-018**：系统 MUST 将新订单的 `OrderResultURL` 指向 `/pay/omg/result`，由后端返回禁止缓存的轻量 HTML。中转页 MUST 从匿名只读地址 `/pay/omg/bridge.js` 加载后端自托管的 `uni.webview.1.5.8.js`，在 `UniAppJSBridgeReady` 后确认处于 uni-app App 环境；iOS App-Plus 环境 MUST 优先让支付子 WebView 的父 uni-app 页面执行 `uni.redirectTo`，其他 App 环境调用 `uni.webView.redirectTo`，目标均为 `/pages/OrderList/paySuccess/paySuccess?ddId={订单ddId}`。Bridge 不可用、环境不是 App 或调用后未发生页面交接时，MUST 回退到 `com.twanmsdyh.app://pages/OrderList/paySuccess/paySuccess?ddId={订单ddId}`，手动按钮也 MUST 保留同一 Scheme 兜底。不得生成 `ClientBackURL`。`OrderResultURL` 的全部实际回传字段（包括未知额外字段与空值）必须参加 CheckMacValue 验签；入口仅负责导航，不得直接改变付款状态。App 页面打开后必须调用 `/pay/omg/query` 确认最终状态。
- **FR-019**：支付结果中转页 MUST 为 Bridge 就绪/环境/跳转、Scheme 兜底、自动唤起、手动点击、页面可见性/焦点变化和 JavaScript 异常输出统一前缀的浏览器诊断日志，并在自动尝试后输出分阶段观察结果；日志载荷 MUST 序列化为 JSON，避免 HBuilderX 只显示 `[object Object]`。日志只能包含浏览器环境、页面生命周期、固定 App 路由、目标 Scheme 的协议/主机/路径及脱敏订单号，不得包含 token、CheckMacValue、HashKey、HashIV 或 OMG 原始支付表单。App 端必须同步记录生命周期、启动参数、`newintent`、当前路由和支付 WebView 事件；Bridge 内部路由成功不要求出现 `newintent`。本期日志仅输出到本地调试控制台，不新增远程上报接口。

### Key Entities *(include if feature involves data)*

- **门店 OMG 凭证（StoreOmgCredential）**：门店关联、MerchantID、HashKey、HashIV、环境（测试/生产）、启用开关。
- **支付请求（OmgPaymentRequest）**：`MerchantTradeNo`（幂等键）、订单、金额、`ChoosePayment`、ReturnURL/NotifyURL、生成的 `CheckMacValue`。
- **支付流水（OrderPayment）**：`MerchantTradeNo`、关联订单、金额、支付方式、状态（待付/已付/失败/退款）、OMG 交易号、回调原始报文、时间。
- **退款记录（OmgRefund）**：关联支付流水、退款金额、退款状态、OMG 回应、时间。

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**：消费者从下单到完成在线支付（即时方式）端到端时长满足体验预期（如主流在数分钟内）。
- **SC-002**：支付回调核销成功率达标；重复回调 **100% 幂等**，不重复发货/推送。
- **SC-003**：支付成功后订单履约（状态流转 + 推送）**100% 被触发**，无「已付款未接单」。
- **SC-004**：伪造/验签失败的回调 **100% 被拒绝**，资金安全零失误。
- **SC-005**：退款（若本期）在目标时长内完成且订单状态与资金同步。

## Assumptions

- 平台已/将向 OMG 申请商店号与 HashKey/HashIV（测试 + 生产）。
- 复用现有订单状态机、`PayPush` 推送链路、用户体系，不为此功能重建。
- OMG AIO `CheckMacValue` 算法与 ECPay 同源（业界标准），具体实现留待 plan/research。
- 电子发票仍走 ezPay（010/014），与 OMG 支付解耦（`InvoiceMark=N`）。
- 测试先用 `payment-stage.funpoint.com.tw` 测试端点与测试凭证，生产环境与凭证后续切换。
- 凭证签名等敏感操作在服务端完成，HashKey/HashIV 不下发前端。
- 在线支付与现有「货到付款」并存，由门店配置决定是否提供在线支付。
