# 新 OMG 支付 App 对接文档 **适用对象**:用户端 App(uni-app)前端开发人员 **接口版本**:`specs/020-omg-payment-rebuild` 新实现 **当前环境**:OMG 测试环境 **支付方式目标**:App 只提供信用卡、Apple Pay,OMG 直接进入所选渠道 **更新时间**:2026-08-18 > 本文只描述当前新 OMG 实现。`specs/016-omg-payment` 中的旧 Controller、旧流水、旧退款和旧前端文档已经退役,不得作为接入依据。 ## 1. 接入结论 OMG 是网页托管式收银台,App 不需要接入 OMG SDK。前端只需要完成以下流程: 1. 创建订单时把 OMG 支付方式设置为 `payType = "2"`。 2. 在 App 自有界面让用户选择信用卡或 Apple Pay,分别使用 `paymentMethod = "CREDIT"` 或 `"APPLE_PAY"`。 3. 调用 `POST /pay/omg/create` 获取所选渠道的 `gatewayUrl` 和完整 `formFields`。 4. 在当前 App WebView 页面中,把所有 `formFields` 原样以 HTML Form POST 到 `gatewayUrl`。 5. 用户付款后,OMG 将浏览器 POST 到后端 `/pay/omg/result`;后端验证返回资料后,优先通过 uni-app Bridge 打开支付结果页,Bridge 不可用时再通过 App Scheme 兜底。 6. App 结果页取得 `ddId`,调用 `POST /pay/omg/query` 确认最终支付状态。 7. 只有 query 返回 `status = "PAID"` 才能展示支付成功。 App 需要新增“信用卡 / Apple Pay”选择,但不接入原生 Apple Pay SDK。后端只接受两个稳定枚举并生成单渠道签名表单,因此 OMG 页面不会再展示超商快付、AFTEE 或其他渠道选择项。用户可见文案必须接入 App 现有 i18n。 当前测试环境不支持退款。`POST /pay/omg/refund` 只会返回“测试环境不支持退款”,不会发起真实退款,也不会修改订单。 ## 2. 整体流程 ```text [App 创建订单,payType="2"] | v [App 选择 CREDIT / APPLE_PAY] | | POST /pay/omg/create | Header: token | Body: {"orderId":"...","paymentMethod":"..."} v [后端返回 gatewayUrl + formFields] | | 当前 WebView 原样 Form POST v [OMG 测试环境:直接进入所选付款流程] | +-------------------------------+ | | | 服务端付款通知 | 浏览器付款结果返回 | POST /pay/omg/notify | POST /pay/omg/result | 前端不调用 | 前端不直接调用 v v [后端验签并更新付款事实] [后端安全桥接页打开 App 结果路由] | | iOS: 父 uni-app 页面 redirectTo | 其他环境: uni.webView.redirectTo | 失败时: App Scheme 兜底 v [App 支付结果页] | | POST /pay/omg/query v [PAID 才展示支付成功] ``` 必须注意: - `create.status = "CREATED"` 只表示后端已创建本地支付尝试和签名表单,不表示 OMG 已收单,更不表示已付款。 - App Scheme 成功打开只表示浏览器返回资料通过后端校验,不表示服务端付款通知已经处理完成。 - 订单最终支付结果必须以 `/pay/omg/query` 返回的 `data.status` 为准。 - 当前新回调只更新支付事实,不推进订单、配送状态,也不发送 App 推送;App 不得依赖推送判断付款结果。 ## 3. 公共约定 ### 3.1 Base URL 以下接口路径都拼接在 App 当前使用的后端 Base URL 后,例如: ```text https://foodieapi.waimai-paotui.com/pay/omg/create ``` ### 3.2 登录鉴权 App 主动调用的 create、query 和 refund 接口都必须携带登录 token: ```http token: <用户登录 token> ``` token 只发送给本项目后端,绝对不能放入 OMG Form,也不能发送给 `gatewayUrl`。 ### 3.3 AjaxResult 外层结构 成功响应: ```json { "code": 200, "msg": "操作成功", "data": {} } ``` 业务失败响应: ```json { "code": 500, "msg": "已国际化的错误提示", "data": { "status": "稳定的业务状态码" } } ``` 处理规则: - 先判断外层 `code`。 - `code === 200` 时再读取 `data`。 - `code !== 200` 时直接向用户展示后端返回的 `msg`;`msg` 已按项目语言国际化。 - 需要按场景分支时使用 `data.status`,不要匹配 `msg` 文本。 ### 3.4 订单号字段 create/retry 请求使用: ```json {"orderId":"业务订单号","paymentMethod":"CREDIT 或 APPLE_PAY"} ``` query/refund 请求仍使用 `{"orderId":"业务订单号"}`。 后端 App Scheme 回跳参数使用: ```text ?ddId=<业务订单号> ``` App 收到 `ddId` 后,把它作为 `orderId` 调用 query。不要把 create/query 的 JSON 字段写成 `ddId`、`orderid` 或其他名称。 ## 4. 创建支付 ### 4.1 接口 ```http POST /pay/omg/create Content-Type: application/json token: <用户登录 token> { "orderId": "991786433092835", "paymentMethod": "CREDIT" } ``` 前置条件: - 订单属于当前登录用户。 - 必须是单门店父订单;多门店子订单不支持。 - 订单 `payType` 必须为字符串 `"2"`。 - 订单未付款、金额大于 0,且当前订单状态允许付款。 - 订单门店已启用有效 OMG 凭证。 - 同一订单当前不能存在另一个未结束的 OMG 支付尝试。 create/retry 只能传 `orderId` 与 `paymentMethod`;`paymentMethod` 仅允许 `CREDIT/APPLE_PAY`。query/refund 仍只传 `orderId`。金额、商户号、回调地址、网关地址、OMG 原始付款参数和签名都由后端决定。 ### 4.2 成功响应 当前测试环境返回示例: ```json { "code": 200, "msg": "操作成功", "data": { "status": "CREATED", "gatewayUrl": "https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5", "formFields": { "MerchantID": "1000031", "MerchantTradeNo": "OMGR8K3P7W2M9C4X6A1B", "MerchantTradeDate": "2026/08/14 10:30:00", "PaymentType": "aio", "TotalAmount": "100", "TradeDesc": "Foodie order 991786433092835", "ItemName": "Order 991786433092835", "ReturnURL": "https://foodieapi.waimai-paotui.com/pay/omg/notify", "OrderResultURL": "https://foodieapi.waimai-paotui.com/pay/omg/result", "ChoosePayment": "Credit", "UnionPay": "2", "EncryptType": "1", "InvoiceMark": "N", "NeedExtraPaidInfo": "Y", "CheckMacValue": "<64 位十六进制签名>" } } } ``` 上例为信用卡响应。Apple Pay 响应的 `ChoosePayment` 为 `ApplePay`,且不包含 `UnionPay` 或 `IgnorePayment`。后端不会返回 `ChoosePayment=ALL`。 字段说明: | 字段 | 类型 | App 用法 | |---|---|---| | `status` | string | 固定为 `CREATED`;不能当作支付成功 | | `gatewayUrl` | string | HTML Form 的 `action`,本身不是 Form 字段 | | `formFields` | object | 所有键值都必须原样创建为隐藏 input 并 POST | | `MerchantTradeNo` | string | 后端生成的 OMG 交易编号;前端只透传,不作为业务订单号 | | `TotalAmount` | string | 后端根据订单金额生成;前端不得修改 | | `ChoosePayment` | string | 后端按 `paymentMethod` 生成 `Credit` 或 `ApplePay`;前端不得修改 | | `UnionPay` | string | 仅信用卡表单存在且固定为 `2`,用于隐藏银联;Apple Pay 表单不包含该字段 | | `ReturnURL` | string | OMG 服务端付款通知地址;前端不调用 | | `OrderResultURL` | string | OMG 浏览器付款结果地址;前端不直接调用 | | `CheckMacValue` | string | 表单签名;修改任意已签名字段都会导致 OMG 拒绝 | 后端当前实际返回的字段集合以 `formFields` 为准。前端不要维护字段白名单,不要自行新增、删除、改名、格式化或重新计算字段。 ### 4.3 Form POST 要求 必须遵守: - 使用 `POST`,不能把字段拼成 GET URL。 - `Content-Type` 由浏览器 Form 提交为 `application/x-www-form-urlencoded`。 - 在当前完整 WebView 页面中跳转,不使用 iframe,不使用 `window.open` 新窗口。 - `gatewayUrl` 只作为 Form `action`,不能创建名为 `gatewayUrl` 的 input。 - `formFields` 中的每个键值都创建为隐藏 input,并保持值完全不变。 - 不把 `formFields`、`CheckMacValue` 或 token 写入业务日志、埋点、崩溃上报或远程调试日志。 浏览器侧核心代码: ```js function submitOmgForm(gatewayUrl, formFields) { if (!gatewayUrl || !formFields || typeof formFields !== 'object') { throw new Error('OMG payment payload is invalid'); } const form = document.createElement('form'); form.method = 'POST'; form.action = gatewayUrl; form.acceptCharset = 'UTF-8'; Object.entries(formFields).forEach(([name, value]) => { const input = document.createElement('input'); input.type = 'hidden'; input.name = name; input.value = value == null ? '' : String(value); form.appendChild(input); }); document.body.appendChild(form); form.submit(); } ``` ### 4.4 uni-app 页面传值建议 建议通过页面 `eventChannel`、内存状态或原生桥接把 create 响应交给收银台页面,再由收银台页面的 renderjs 创建并提交 Form。 不要把整个 `formFields` 拼入页面 URL query。这样会把 `CheckMacValue` 和完整支付表单暴露在浏览记录、路由日志或错误上报中。 发起页示例: ```js async function startOmgPayment(orderId, paymentMethod) { if (this.omgSubmitting) return; this.omgSubmitting = true; try { const response = await request({ url: '/pay/omg/create', method: 'POST', header: { token: uni.getStorageSync('token') }, data: { orderId, paymentMethod } }); if (response.code !== 200) { uni.showToast({ title: response.msg, icon: 'none' }); return; } const payload = response.data; uni.navigateTo({ url: '/pages/pay/omgCheckout/omgCheckout', success: ({ eventChannel }) => { eventChannel.emit('omgCheckoutPayload', payload); } }); } finally { this.omgSubmitting = false; } } ``` 收银台页面只需要接收: ```js { status: 'CREATED', gatewayUrl: 'https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5', formFields: { /* 后端原样返回的全部字段 */ } } ``` 然后在该页面的 WebView/renderjs 环境调用: ```js submitOmgForm(payload.gatewayUrl, payload.formFields); ``` 具体页面路径和请求封装按 App 现有结构调整,Form POST 规则不能改变。 ### 4.5 创建错误状态 | `data.status` | 含义 | App 处理建议 | |---|---|---| | `AUTH_REQUIRED` | token 无法取得登录用户 | 返回登录页 | | `ORDER_REQUIRED` | `orderId` 缺失或格式不合法 | 提示后返回订单页 | | `ORDER_NOT_AVAILABLE` | 订单不存在或不属于当前用户 | 展示后端 `msg` | | `MULTI_STORE_ORDER_NOT_SUPPORTED` | 当前是多门店子订单 | 展示后端 `msg`,不可继续支付 | | `ORDER_STATE_NOT_PAYABLE` | 当前订单状态不可付款 | 刷新订单数据 | | `ORDER_ALREADY_PAID` | 订单已经付款 | 进入订单结果页并刷新状态 | | `ORDER_AMOUNT_INVALID` | 订单金额异常 | 展示后端 `msg` | | `PAYMENT_TYPE_INVALID` | 订单 `payType` 不是 `"2"` | 检查创建订单时的支付方式 | | `PAYMENT_METHOD_INVALID` | `paymentMethod` 缺失或不是 `CREDIT/APPLE_PAY` | 停止支付并检查 App 渠道映射 | | `STORE_CREDENTIAL_UNAVAILABLE` | 门店未启用有效 OMG 凭证 | 展示后端 `msg` | | `PAYMENT_ATTEMPT_EXISTS` | 已有未结束的支付尝试 | 用户确认重新支付时调用 `/pay/omg/retry`,不要循环调用 create | | `PAYMENT_CONFIGURATION_INVALID` | 后端测试网关或回调配置不合法 | 展示后端 `msg`,通知后端排查 | | `PAYMENT_CREATION_FAILED` | 本次创建失败 | 展示后端 `msg`,稍后重试 | 同一订单创建成功后,重复点击不会返回旧表单,而是返回 `PAYMENT_ATTEMPT_EXISTS`。因此支付按钮必须防重复点击;如果用户只是等待支付结果,调用 query;如果用户已经退出支付页并明确再次支付,调用 retry。不要循环调用 create,也不要缓存并重新提交旧表单。 ## 5. 浏览器结果返回与 App Scheme ### 5.1 `/pay/omg/result` ```http POST /pay/omg/result Content-Type: application/x-www-form-urlencoded ``` 该接口由 OMG 收银台调用,不是 App API。后端会验证返回表单的签名、商户号、OMG 交易编号和金额,然后返回一个禁止缓存的安全桥接页面。 桥接页面的目标路由固定为: ```text com.twanmsdyh.app://pages/OrderList/paySuccess/paySuccess?ddId= ``` 在 uni-app App 的 `` 中,页面先加载后端自托管的 Bridge SDK。iOS App-Plus 环境由支付子 WebView 的父 uni-app 页面执行 `uni.redirectTo`,避免旧运行时调用不存在的 `UniPlusBridge`;Android 等其他 App 环境继续使用 `uni.webView.redirectTo`。上述方式不可用或没有完成页面交接时,页面才使用 `com.twanmsdyh.app` Scheme 兜底。 页面同时保留“返回 App”按钮,自动跳转失败时用户可手动点击。App 端现有支付 WebView 页面不需要为本次 iOS 修复新增接口或页面,但仍必须保留 Scheme 注册作为兜底。 ### 5.2 App 必须完成的配置 App 必须注册以下 Scheme: ```text com.twanmsdyh.app ``` 并把以下路由映射到现有支付结果页: ```text pages/OrderList/paySuccess/paySuccess ``` 结果页读取参数: ```text ddId=<业务订单号> ``` 收到 Scheme 后的正确处理: 1. 读取 `ddId`。 2. 从 App 本地安全存储读取当前登录 token。 3. 调用 `/pay/omg/query`,请求体使用 `{ "orderId": ddId }`。 4. 根据 query 的 `data.status` 展示结果。 Scheme 参数可以被外部伪造。禁止因为进入支付结果路由或拿到 `ddId` 就直接展示付款成功。 ## 6. 查询支付结果 ### 6.1 接口 ```http POST /pay/omg/query Content-Type: application/json token: <用户登录 token> { "orderId": "991786433092835" } ``` query 不是简单读取本地状态。后端会使用当前支付尝试的凭证快照查询 OMG 测试网关、验证完整响应,并在付款回调丢失时补偿订单付款状态。因此 App 不应高频调用。 ### 6.2 成功响应 已付款示例: ```json { "code": 200, "msg": "操作成功", "data": { "status": "PAID", "tradeStatus": "1", "merchantTradeNo": "OMGR8K3P7W2M9C4X6A1B", "tradeNo": "26081400000000000001", "amount": 100, "paymentDate": "2026/08/14 10:32:10", "tradeDate": "2026/08/14 10:30:00", "paymentType": "Credit_CreditCard", "handlingCharge": "0", "paymentTypeChargeFee": "3" } } ``` 未付款时,`tradeNo`、`paymentDate`、`paymentType` 等字段可能为 `null` 或空字符串。前端应以 `status` 为主,不要依赖某个详情字段是否为空判断成功。 字段说明: | 字段 | 类型 | 说明 | |---|---|---| | `status` | string | 前端判断结果的标准化状态 | | `tradeStatus` | string | OMG 原始交易状态,仅用于排查或辅助展示 | | `merchantTradeNo` | string | 后端生成的 OMG 特店交易编号 | | `tradeNo` | string/null | OMG 金流交易编号;始终按字符串处理 | | `amount` | number | 订单金额,整数 TWD | | `paymentDate` | string/null | OMG 付款时间,台北时区 | | `tradeDate` | string/null | OMG 建单时间,台北时区 | | `paymentType` | string/null | OMG 尚未确认付款渠道时为空;完成渠道选择/付款并在 query 或回调中回传后才有值。信用卡与 Apple Pay 可能同样回传 `Credit_CreditCard`,App 不用它做二级支付方式选择 | | `handlingCharge` | string/null | 手续费相关原始值 | | `paymentTypeChargeFee` | string/null | 付款方式手续费原始值 | ### 6.3 `status` 处理 | `status` | 含义 | App 行为 | |---|---|---| | `PAID` | 已确认付款 | 展示支付成功,刷新订单 | | `UNPAID` | OMG 当前仍未付款 | 保持“结果确认中”,按受控频率继续查询 | | `FAILED` | 当前支付尝试已失败 | 展示支付未完成,停止本轮轮询 | | `UNKNOWN` | OMG 返回了当前系统未归一化的状态 | 不得当作成功;提示确认中并受控重试 | OMG 原始 `tradeStatus` 常见值: | `tradeStatus` | 标准化结果 | |---|---| | `1` | `PAID` | | `0` | `UNPAID` | | `10200095` | `FAILED` | | 其他 | `UNKNOWN` | ### 6.4 轮询建议 App 从 Scheme 返回后立即查询一次。如果结果是 `UNPAID` 或 `UNKNOWN`: - 每 3 秒查询一次。 - 最多再查询 10 次,总计约 30 秒。 - App 页面进入后台或销毁时停止轮询。 - 任意一次返回 `PAID` 或 `FAILED` 时立即停止。 - query 返回业务错误时停止自动轮询并展示后端 `msg`。 - 达到上限仍未终态时提示“支付结果确认中,请稍后在订单列表查看”,不要展示支付失败。 参考代码: ```js async function confirmOmgPayment(orderId) { const maxAttempts = 10; for (let attempt = 0; attempt < maxAttempts; attempt += 1) { const response = await request({ url: '/pay/omg/query', method: 'POST', header: { token: uni.getStorageSync('token') }, data: { orderId } }); if (response.code !== 200) { uni.showToast({ title: response.msg, icon: 'none' }); return; } const status = response.data.status; if (status === 'PAID') { showPaymentSuccess(); return; } if (status === 'FAILED') { showPaymentIncomplete(); return; } await new Promise(resolve => setTimeout(resolve, 3000)); } showPaymentConfirming(); } ``` 实际代码需要结合页面生命周期取消定时器或异步任务,避免离开页面后继续请求。 ### 6.5 查询错误状态 | `data.status` | 含义 | App 处理建议 | |---|---|---| | `AUTH_REQUIRED` | 登录信息不可用 | 返回登录页 | | `ORDER_REQUIRED` | `orderId` 缺失或不合法 | 停止查询并返回订单页 | | `ORDER_NOT_AVAILABLE` | 订单不存在或不属于当前用户 | 展示后端 `msg` | | `PAYMENT_QUERY_NOT_AVAILABLE` | 当前没有可查询的 OMG 支付尝试 | 刷新订单后展示后端 `msg` | | `PAYMENT_QUERY_FAILED` | OMG 查询失败、响应非法或身份校验失败 | 展示后端 `msg`,稍后人工重试 | ## 6.6 退出支付页后重新支付 App 返回业务页面后原支付 WebView 会销毁,本流程不要求保存或恢复 WebView。旧 `gatewayUrl + formFields` 也不能当作“继续付款网址”缓存重放:其中的 `MerchantTradeNo` 只能建单一次,重新提交会被 OMG 以重复订单编号拒绝。 用户点击“重新支付”时调用: ```http POST /pay/omg/retry Content-Type: application/json token: <用户登录 token> { "orderId": "991786433092835", "paymentMethod": "APPLE_PAY" } ``` 后端会先使用旧尝试的凭证快照向 OMG 查询真实状态,然后按以下规则处理: | OMG 查询结果 | 后端处理 | |---|---| | `PAID` | 不创建新支付,返回 `ORDER_ALREADY_PAID` | | `FAILED` | 旧尝试已经结束,生成新的 `MerchantTradeNo` 和新表单 | | 本地无进行中的尝试(旧尝试已被自动补偿关闭为终态 FAILED/SUPERSEDED,`active` 指针已释放) | 不再查询旧尝试,直接生成新的 `MerchantTradeNo` 和新表单(等价首次 create;订单已付会被 create 的校验以 `ORDER_ALREADY_PAID` 拒绝) | | `UNPAID` 且 `paymentType` 为空 | 原子地把旧尝试改为 `SUPERSEDED`,生成新的 `MerchantTradeNo` 和新表单 | | `UNPAID` 且 `paymentType` 非空 | 为避免覆盖可能正在授权的交易,返回 `PAYMENT_RETRY_NOT_AVAILABLE` | | `UNKNOWN`、查询失败或并发状态已改变 | 不修改旧尝试,返回对应错误 | retry 成功响应与 create 完全相同。App 收到后创建新的支付 WebView,将新的全部 `formFields` 以 Form POST 提交到新的 `gatewayUrl`: ```json { "code": 200, "msg": "操作成功", "data": { "status": "CREATED", "gatewayUrl": "https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5", "formFields": { "MerchantTradeNo": "OMGR8K3P7W2M9C4X6A1B" } } } ``` `paymentType` 是 OMG 查询结果,不是 App 传参;`paymentMethod` 才是 App 本次选择并提交给 create/retry 的受控枚举。用户重新支付时可以重新选择信用卡或 Apple Pay。 App 推荐处理: ```js async function retryOmgPayment(orderId, paymentMethod) { const response = await request({ url: '/pay/omg/retry', method: 'POST', header: { token: uni.getStorageSync('token') }, data: { orderId, paymentMethod } }); if (response.code === 200) { openNewOmgWebView(response.data.gatewayUrl, response.data.formFields); return; } if (response.data?.status === 'ORDER_ALREADY_PAID') { await confirmOmgPayment(orderId); return; } uni.showToast({ title: response.msg, icon: 'none' }); } ``` retry 可能返回 query、create 的既有错误,也可能返回: | `data.status` | App 处理建议 | |---|---| | `PAYMENT_RETRY_NOT_AVAILABLE` | 不自动循环;刷新订单并调用 query 确认状态 | | `PAYMENT_RETRY_FAILED` | 提示稍后重试,不复用旧表单 | ## 7. 后端专用接口 ### 7.1 付款通知 `/pay/omg/notify` ```http POST /pay/omg/notify Content-Type: application/x-www-form-urlencoded ``` 这是 OMG 到后端的 server-to-server 回调: - App 不调用。 - 不携带用户 token。 - 后端负责验签、幂等处理和更新付款事实。 - App 不需要解析其 `1|OK` 或 `0|ERROR` 响应。 ### 7.2 浏览器结果 `/pay/omg/result` 这是 OMG 收银台到后端的浏览器返回接口: - App 不主动调用。 - 后端只验证返回资料并生成 App 路由桥接页;iOS 优先由父 uni-app 页面执行路由,其他环境使用官方 Bridge,失败时才使用 App Scheme。 - 该接口本身不修改支付状态。 ## 8. 退款接口:当前不可用 ### 8.1 接口形式 ```http POST /pay/omg/refund Content-Type: application/json token: <用户登录 token> { "orderId": "991786433092835" } ``` ### 8.2 当前固定响应 ```json { "code": 500, "msg": "OMG 测试环境不支持退款,请在正式环境启用后操作", "data": { "status": "PAYMENT_REFUND_UNAVAILABLE_IN_TEST_ENVIRONMENT" } } ``` ### 8.3 必须理解的行为 当前接口只是测试阶段的安全拒绝边界: - 不调用 OMG 正式环境退款接口。 - 不读取或修改订单、支付、退款状态。 - 不创建退款记录。 - 不写入付款通知日志。 - 不支持由 App 传退款金额、OMG 交易号、网关地址或退款动作。 因此当前 App 不应把 `/pay/omg/refund` 接入订单取消流程,也不能向用户展示“退款成功”。正式环境退款需要后端另行实现并确认契约后,前端才能接入。 ## 9. 安全与易错点 1. 创建支付请求字段是 `orderId`,不是 `ddId`;`ddId` 只出现在 App Scheme 参数中。 2. 订单 OMG 支付类型是字符串 `"2"`;LINE Pay 是 `"3"`,不要混用。 3. `CREATED` 不是支付成功。 4. App Scheme 到达不是支付成功。 5. 只有 query 的 `status === "PAID"` 才能展示支付成功。 6. 不修改、过滤或重新计算 `formFields`;尤其不能修改 `TotalAmount`、回调地址和 `CheckMacValue`。 7. 不把 Form 改成 GET,不使用 iframe,不使用新窗口。 8. 不把 token 发送给 OMG,也不把 token 放入页面 URL。 9. 不把完整 `formFields` 或 `CheckMacValue` 放入 URL、日志、埋点或错误上报。 10. `tradeNo`、`merchantTradeNo` 始终按字符串处理,不转换为 JavaScript Number。 11. query 会访问 OMG 网关,不做高频轮询。 12. `PAYMENT_ATTEMPT_EXISTS` 不能靠重复调用 create 解决,应进入 query 确认流程。 13. App 只传 `CREDIT/APPLE_PAY`;不得传 `ChoosePayment`、`UnionPay`、`IgnorePayment` 等 OMG 原始字段,也不得通过 CSS 或 DOM 修改第三方支付页面。 14. 当前没有真实退款能力。 ## 10. App 验收清单 ### 10.1 创建与收银台 - [ ] 创建订单时 OMG 使用 `payType = "2"`。 - [ ] App 只显示信用卡与 Apple Pay,并把选择映射为 `CREDIT/APPLE_PAY`;用户可见文案使用现有 i18n。 - [ ] create 请求 Header 包含 token,JSON 只传 `orderId` 与 `paymentMethod`。 - [ ] 支付按钮有防重复点击处理。 - [ ] create 成功后,当前完整 WebView 使用 Form POST 打开 `gatewayUrl`。 - [ ] 所有 `formFields` 原样提交,`gatewayUrl` 不作为 input。 - [ ] 没有 iframe、GET 跳转或新窗口。 - [ ] `CREDIT` 直接进入信用卡流程,表单为 `ChoosePayment=Credit + UnionPay=2`,不显示其他渠道。 - [ ] `APPLE_PAY` 在门店已开通且设备支持时直接进入 Apple Pay 流程,表单为 `ChoosePayment=ApplePay`,不显示其他渠道。 - [ ] 两种表单都不包含 `ChoosePayment=ALL` 或 `IgnorePayment`,页面不出现超商快付、AFTEE。 ### 10.2 App 返回与状态确认 - [ ] App 已注册 `com.twanmsdyh.app` Scheme。 - [ ] Scheme 路由可打开 `pages/OrderList/paySuccess/paySuccess`。 - [ ] iOS 从 OMG 信用卡付款完成后可由支付 WebView 自动进入 `pages/OrderList/paySuccess/paySuccess`。 - [ ] 结果页能读取 `ddId`。 - [ ] 结果页把 `ddId` 作为 `orderId` 调用 query。 - [ ] `PAID` 才展示成功。 - [ ] `UNPAID`/`UNKNOWN` 受控轮询,不误判为失败或成功。 - [ ] `FAILED` 停止本轮轮询并展示未完成。 - [ ] 轮询超时展示“确认中”,并引导用户稍后查看订单。 - [ ] 页面离开或进入后台时停止轮询。 ### 10.3 错误与安全 - [ ] 业务错误直接显示后端国际化 `msg`。 - [ ] 需要分支判断时使用 `data.status`,不匹配中文文案。 - [ ] `PAYMENT_ATTEMPT_EXISTS` 不会触发 create 重试循环。 - [ ] token、完整 Form 和 `CheckMacValue` 不出现在日志、URL、埋点或上报中。 - [ ] 当前订单取消流程没有调用 OMG refund 并宣称退款成功。 - [ ] 所有 App 新增用户可见文字已加入 zh、tw、en、vi 四种语言。 ## 11. 接口速查 | 用途 | Method | 路径 | token | 调用方 | |---|---|---|---|---| | 创建支付 | POST | `/pay/omg/create` | 必须 | App | | 查询结果 | POST | `/pay/omg/query` | 必须 | App | | 重新支付 | POST | `/pay/omg/retry` | 必须 | App | | 退款安全拒绝 | POST | `/pay/omg/refund` | 必须 | 当前 App 不调用 | | 付款结果通知 | POST | `/pay/omg/notify` | 不需要 | OMG 服务器 | | 浏览器结果返回 | POST | `/pay/omg/result` | 不需要 | OMG 收银台 | ## 12. 当前后端配置依赖 后端当前配置为: ```yaml omgpay: return-url: https://foodieapi.waimai-paotui.com/pay/omg/notify order-result-url: https://foodieapi.waimai-paotui.com/pay/omg/result app-return-url: com.twanmsdyh.app://pages/OrderList/paySuccess/paySuccess ``` App 接入人员只需要确保 Scheme 和结果页路由与 `app-return-url` 一致。`return-url` 和 `order-result-url` 是后端与 OMG 的配置,前端不得覆盖。