# Contracts: OMG(歐買尬/FunPoint)AIO 支付接入 **Feature**: specs/016-omg-payment/spec.md **Date**: 2026-07-29 > **约束**:OMG 全部独立实现,零 `newebpay` 依赖(见 research.md §0)。签名统一用 `CheckMacValue`(SHA256, EncryptType=1),无 AES、无解密。 --- ## A. OMG 外部接口(我方 → 盘合) ### A1. 幕前下单 — `Cashier/AioCheckOut/V5`(US1) - **URL**:测试 `https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5`;正式 `https://payment.funpoint.com.tw/Cashier/AioCheckOut/V5` - **Method/CT**:`POST`,`application/x-www-form-urlencoded` - **方式**:后端组参 + 计算 CheckMacValue 返回 form 字段,**前端 Form Post** 跳转 OMG 收银台(不可 iframe;iOS 不可另开窗) - **必填请求参数**:`MerchantID`、`MerchantTradeNo`(=OMG+ddId)、`MerchantTradeDate`(yyyy/MM/dd HH:mm:ss)、`PaymentType=aio`、`TotalAmount`(整数元)、`ReturnURL`、`ChoosePayment=ALL`、`CheckMacValue`、`EncryptType=1` - **常用可选**:`TradeDesc`、`ItemName`(# 分隔)、`OrderResultURL`、`PaymentInfoURL`、`ClientRedirectURL`、`NeedExtraPaidInfo`(建议 Y,供退款查 gwsr)、`CustomField1–4`、`Language` - **固定**:`InvoiceMark=N`(发票走 ezPay);`ReturnURL ≠ OrderResultURL`;`MerchantTradeNo` 全平台唯一 ### A2. 付款结果回调 — ReturnURL(盘合 → 我方,US2) - 盘合**服务端 POST** 我方 `/pay/omg/notify`(CT `text/html`) - **参数**:`MerchantID`、`MerchantTradeNo`、`StoreID`、`RtnCode`(**1=成功**,其余勿发货)、`RtnMsg`、`TradeNo`(OMG 交易号,须存并与 MerchantTradeNo 关联)、`TradeAmt`、`PaymentDate`、`PaymentType`、`TradeDate`、`SimulatePaid`(**1=模拟,勿发货**)、`CustomField1–4`、`CheckMacValue` - **我方响应**:纯字符串 `1|OK`(首字符 1=成功);未收到 5–15 分重试,当天最多 4 次 - **核销校验顺序**:CheckMacValue 验签 → `RtnCode==1` → `SimulatePaid!=1` → `TradeAmt==订单 amount` → `trade_no` 幂等 ### A3. ATM/超商 取号回调 — PaymentInfoURL(US3) - 盘合服务端 POST 我方 `/pay/omg/paymentInfo`,返回虚帐/缴费码(`BankCode`/`vAccount`/`ExpireDate` 或 `PaymentNo`/`ExpireDate` 等,详见文档 02 页),带 `CheckMacValue` 需验签 - 我方落库展示并回 `1|OK` ### A4. 订单查询 — `Cashier/QueryTradeInfo/V5`(凭证验证 / 补单) - **URL**:测试 `https://payment-stage.funpoint.com.tw/Cashier/QueryTradeInfo/V5`;正式 `https://payment.funpoint.com.tw/Cashier/QueryTradeInfo/V5` - **请求**:`MerchantID`、`MerchantTradeNo`、`TimeStamp`(3 分钟内)、`PlatformID?`、`CheckMacValue` - **响应**(text/html,k=v):`TradeStatus`(**0 未付/1 已付/10200095 失败**)、`TradeNo`、`TradeAmt`、`PaymentDate`、`PaymentType`、`TradeDate`、`CheckMacValue`… - **用途**:录入凭证探测金钥 + 回调漏收时补单 ### A5. 信用卡退款/取消 — `CreditDetail/DoAction`(US4) - **URL**:正式 `https://payment.funpoint.com.tw/CreditDetail/DoAction`(**stage 不可用**) - **请求**:`MerchantID`、`MerchantTradeNo`、`TradeNo`、`Action`(**C** 關帳/**R** 退刷/**E** 取消/**N** 放棄)、`TotalAmount`、`CheckMacValue`、`PlatformID?` - **响应**(k=v):`RtnCode`(**1=成功**)、`RtnMsg` - **规则**:`已關帳→R`、`已授權→N`、`要關帳→E 再 N 或 R`;分期必须全额;ATM/CVS/BarcodeATM **不适用**(走人工);自动关帳开启时避开 20:15–20:30 --- ## B. 平台内部接口(暴露给前端/管理端) ### B1. 发起 OMG 支付 — `POST /pay/omg/create`(US1,@Anonymous @Auth,Header token) - 入参:`orderid`(= ddId) - 逻辑:校验订单归属/金额/未支付 → 查门店启用凭证 → 生成 `MerchantTradeNo` → 组参 + CheckMacValue → 落 `pos_order_omg_payment`(pay_status=0) + 更新 `pos_order.pay_type="7"`/`pay_url` - 返回:form 字段 `{ gatewayUrl, MerchantID, MerchantTradeNo, MerchantTradeDate, PaymentType=aio, TotalAmount, ReturnURL, ChoosePayment=ALL, EncryptType=1, ItemName, CheckMacValue, ... }`,前端构建隐藏 form 自动 submit 到 `gatewayUrl` ### B2. OMG 支付结果回调 — `POST /pay/omg/notify`(US2,@Anonymous) - 处理 A2:collectForm → IpnLog → 凭证(MerchantID) → 验签 CheckMacValue → 幂等(trade_no) → 金额校验 → RtnCode==1 && SimulatePaid!=1 → markSuccess → 更新订单(state=0, payStatus=1) + 订单日志 + 推送用户/商家/骑手 + sendAcceptRiderPush → 回纯串 `1|OK` ### B3. 支付完成返回页 — `GET|POST /pay/omg/return`(US2,@Anonymous) - 仅引导回前端结果页(带 ddId),**不改订单状态**(以 notify 为准) ### B4. ATM/超商 取号回调 — `POST /pay/omg/paymentInfo`(US3,@Anonymous) - 处理 A3:验签 → 落虚帐/缴费码 + 期限 → 回 `1|OK` ### B5. 订单查询/补单 — `POST /pay/omg/query`(US2 补单,可选) - 入参 `orderid` → 调 A4 → 据 TradeStatus 补单/查状态 ### B6. 订单取消退款(US4) - 触发点:订单取消链路(`PosOrderShOprate` 商家取消 / `UserOrderController` 用户取消)内,对 OMG 已支付订单调 A5 DoAction;或 `POST /pay/omg/refund?orderid=`(落点 tasks 定) - 写 `pos_order_omg_refund`;成功置 `pos_order_omg_payment.pay_status=3`、`pos_order.pay_status=2` ### B7. 门店 OMG 凭证管理 — `/system/storeOmg/*`(US5,@PreAuthorize) - `GET /list`(分页筛选)、`GET /{storeId}`、`PUT /apply/{storeId}`、`PUT /saveCredentials`(录入+QueryTradeInfo 探测验证)、`PUT /toggleEnable/{storeId}`、`PUT /reset/{storeId}`、`PUT /enabledPayments/{storeId}` - 镜像 011 `PosStoreNewebpayController` 模式,但**独立 Controller/Service/实体/表** --- ## C. 代码表与枚举(OMG reference 02–05,2026-07-29 补读) ### C1. ChoosePayment(请求参数,US1) 本期固定 `ALL`(聚合全部)。可细分值:`Credit`(信用卡/銀聯/Apple Pay)、`ATM`(FIRST/CHINATRUST/UBOT/KGI)、`CVS`(CVS/FAMILY/IBON/HILIFE)、`BarcodeATM`(CHINATRUST)、`AFTEE`。 ⚠️ **Apple Pay 不是独立 ChoosePayment**,归在 `Credit` 下(付款页内选);回覆 PaymentType 同为 `Credit_CreditCard`,**仅凭 PaymentType 无法区分信用卡与 Apple Pay**(本期不区分)。 ### C2. 回覆 PaymentType(回调 `PaymentType`,落 `pos_order_omg_payment.pay_type`) | PaymentType | 名称 | 即时/延期 | |---|---|---| | `Credit_CreditCard` | 信用卡(含銀聯/Apple Pay) | 即时 | | `BarcodeATM_CHINATRUST` | 超商快付代碼繳費 | 即时 | | `ATM_FIRST` / `ATM_CHINATRUST` / `ATM_UBOT` / `ATM_KGI` | 各银行 ATM | 延期(取虚帐→期限内付款) | | `CVS_CVS` / `CVS_FAMILY` / `CVS_IBON` / `CVS_HILIFE` | 超商代碼繳費 | 延期 | | `AFTEE_AFTEE` | AFTEE 先享後付 | 延期 | 延期方式(ATM/CVS/AFTEE)走 `PaymentInfoURL` 取号 → US3。 ### C3. RtnCode(回调 / 退款响应) `1` = 成功;其余均为失败。完整代码表文档为**图片**且持续新增,须到「歐買尬廠商後台 → 系統開發管理 → 交易狀態代碼查詢」查全。 **实现策略**:`RtnCode==1` 视成功(核销 / 退款成功);非 1 一律失败,记录 `rtn_code`+`rtn_msg` 供诊断,**不硬编码错误码分支**。 ### C4. URLEncode(CheckMacValue 计算,须完全照此 · reference 05) .NET 风格 URLEncode。**Java 实现**:`URLEncoder.encode(s, UTF_8)`(空格已→`+`)后,照 PHP 范例做替换以对齐 .NET,再转小写、SHA256、hex 大写: - 不编码(字面):`-` `_` `.` `!` `*` `(` `)`;`~`→`%7e`(两侧同)。 - 空格→`+`(.NET/Java 表单编码一致;标准 URLEncode 是 `%20`,勿混用)。 - 其余特殊符 `@ # $ % ^ & = + ; ? / \ > < [ ] { } : ' " , |` 均为 `%XX`;中文按 UTF-8 `%XX`。 - 替换集(防御性,照 PHP 范例):`%2d→-`、`%5f→_`、`%2e→.`、`%21→!`、`%2a→*`、`%28→(`、`%29→)`(Java `URLEncoder` 本就不编 `-_.`,`!*()` 需替换回)。 ## 签名约定(所有请求/回调通用) `CheckMacValue`(EncryptType=1, SHA256): 去 CheckMacValue → 参数按 key 字母序 → `k=v&...` → 包夹 `HashKey={k}&..&HashIV={iv}` → .NET 风格 URL 编码(`-_.!*()` 不编码) → 转小写 → SHA256 → hex 大写。校验同理重算比对。实现:`OmgCheckMacValue`(独立,不复用 `NewebPayEncryptUtil`)。