Feature Branch: 020-omg-payment-rebuild
Created: 2026-08-13
Status: 创建支付已实现;付款结果回调设计已批准并进入实现
Input: 以 OMG 全方位金流 AIO 官方技术文件 V1.5.3(2026-07)为唯一外部事实来源,从零重建创建支付订单和 ReturnURL 付款结果回调;现有 OMG 支付代码、旧支付流水和 specs/016-omg-payment 均不作为需求或设计依据。查询、补单、退款与通用支付推送后续逐项重建;2026-08-28 起仅追加外送订单支付成功后的骑手开放推送。
MerchantID / HashKey / HashIV。CheckMacValue。POST /pay/omg/notify 接收 OMG 最终付款结果,按创建尝试的凭证快照验签并幂等更新支付事实。POST /pay/omg/query 只查询订单当前有效支付尝试;可信已付款结果复用回调状态机补偿丢失回调。POST /pay/omg/refund 的测试阶段拒绝契约:明确提示测试环境不支持退款,且不调用正式网关、不改本地状态。ipn_log,并保存完整、可重放的 form-urlencoded 回传内容。payStatus 更新为已付款,不推进订单或配送状态;外送订单支付成功后可触发骑手开放推送,其他通用支付推送仍不在本阶段实现。pages/OrderList/buy/omgCheckout 通过当前原生 WebView 的 loaded 与固定结果 URL 接管返回流程。PaymentInfoURL、ClientRedirectURL、ClientBackURL。OmgPayController、旧 OMG 工具类、旧支付流水表和 specs/016-omg-payment 均不可信,不得复制其行为或用其解释官方文档。pos_store_omg 及其现有门店凭证存储、录入、启停和查询实现。该表只保存每个门店独立的 MerchantID / HashKey / HashIV 等凭证信息。PosOrder.payType = "2" 表示选择 OMG 支付。MerchantID / HashKey / HashIV,不使用平台统一凭证。PlatformID。CREDIT 与 APPLE_PAY 两个受控选项;服务端分别映射为 ChoosePayment=Credit + UnionPay=2 与 ChoosePayment=ApplePay,不再进入 OMG 的 ALL 付款方式选择页。/pay/omg/*;创建入口为 POST /pay/omg/create,可信回调为 POST /pay/omg/notify。/legacy/* 或任何其他旧 OMG 接口。omgpay 包目录;新代码不得引用旧支付 Controller、旧签名器、旧表单工具或旧支付流水服务。pos_order_omg_attempt。HashKey / HashIV 保存为凭证快照;回调不读取可能已被覆盖的新凭证。ipn_log 是可信的通用 IPN 流水表,但旧 OMG 写入逻辑不可信;新回调只复用其现有表结构和通用插入 Service。已登录用户为自己的单门店餐饮订单选择 OMG 后,先在 App 选择信用卡或 Apple Pay,再调用创建接口取得对应渠道的服务端签名表单。客户端在当前页面 POST 该表单,直接进入所选 OMG 测试付款流程,不展示包含超商快付或 AFTEE 的 OMG ALL 付款方式选择页。
Why this priority: 这是进入 OMG 收银台的基础,也是本期最终付款回调及后续查询、退款的前置能力。
Independent Test: 为测试门店配置有效测试凭证,创建一笔合法未支付订单,调用接口并提交响应表单,确认浏览器进入官方测试端点且收银台显示正确订单金额及可用渠道。
Acceptance Scenarios:
payType="2"、未取消、未付款、金额为正的单门店订单,且门店 OMG 凭证已启用,When 用户首次调用创建接口,Then 系统创建唯一支付尝试并返回可提交的 OMG 表单。gatewayUrl POST 全部 formFields,Then 浏览器进入 OMG 测试收银台,不通过 iframe 或新窗口加载。ChoosePayment=Credit 与 UnionPay=2,页面不得显示超商快付、AFTEE、Apple Pay 或银联选择项。ChoosePayment=ApplePay,直接进入 Apple Pay 流程,不显示信用卡、超商快付或 AFTEE 选择项。paymentMethod 缺失或不属于 CREDIT/APPLE_PAY,When 调用 create 或 retry,Then 服务端返回稳定错误 PAYMENT_METHOD_INVALID,且不创建或替换支付尝试。客户创建表单后没有立即付款,再次点击支付时,系统既不重复提交旧 MerchantTradeNo,也不贸然生成新的 MerchantTradeNo。
Why this priority: ATM、CVS、BarcodeATM 可能在较长期限内仍可付款;多个有效入口可能造成重复付款。
Independent Test: 对同一订单顺序或并发调用创建接口,数据库始终只有一条未结束尝试,后续请求得到 PAYMENT_ATTEMPT_EXISTS,且不会返回第二份可提交表单。
Acceptance Scenarios:
CREATED 尝试,When 用户再次创建,Then 返回 PAYMENT_ATTEMPT_EXISTS,不生成新 MerchantTradeNo,也不重放旧表单。系统只为订单本人、合法业务状态、合法金额且门店凭证可用的测试订单创建支付尝试,并确保门店密钥不出现在响应或日志中。
Why this priority: 创建错误门店、错误金额或泄露密钥会形成直接资金风险。
Independent Test: 分别使用无效 token、他人订单、终态订单、异常金额、错误支付类型、非法 paymentMethod、无凭证门店和非测试网关配置调用接口,均被拒绝且不写入尝试表。
Acceptance Scenarios:
payType 不是 "2" 或 paymentMethod 非法,When 调用创建接口,Then 系统返回国际化业务错误且不创建尝试。HashKey、HashIV;完整 CheckMacValue 与签名表单只存在于订单本人获准取得的创建成功响应,不出现在错误响应或日志中。OMG 向 ReturnURL 发送最终付款结果时,系统保存本次 HTTP 回传原文,使用创建支付时的门店凭证快照验证全部实际字段,并幂等同步支付尝试与订单付款状态。
Why this priority: 创建支付后必须依靠可信 Server POST 确认资金事实;客户端跳转、旧回调和本地推测都不能证明付款结果。
Independent Test: 对同一 MerchantTradeNo 分别提交合法成功、模拟成功、合法失败、重复、失败后成功、成功后失败、金额不符、商户不符和验签失败的 form-urlencoded 请求,确认支付尝试状态、订单 payStatus、ipn_log 流水和纯文本响应符合契约。
Acceptance Scenarios:
MerchantID、MerchantTradeNo、TradeAmt 均匹配创建快照,When RtnCode=1,Then 支付尝试标记 PAID,订单只把 payStatus 改为 1,并返回精确的 1|OK。SimulatePaid=1,When 当前系统仍处于测试阶段,Then 仍按已付款处理,同时在尝试记录和日志中保留模拟付款标记。RtnCode!=1,When 处理最终失败结果,Then 尝试标记 FAILED 并保存原始 RtnCode/RtnMsg,订单保持未付款,活动尝试被释放,返回 1|OK。FAILED 升级为 PAID;已 PAID 的尝试不得被后续失败通知降级。PAID、订单仍更新为已付款,并记录严重异常日志;本阶段不自动退款。1|OK。0|ERROR 以允许 OMG 重试。ipn_log;日志表故障不得阻断真实支付处理。iOS 用户在 OMG 收银台完成或结束支付流程后,OMG 加载后端 /pay/omg/result。专用页面 pages/OrderList/buy/omgCheckout 监听当前页面原生 WebView 的加载地址,识别固定结果 URL 后只触发一次 App 业务交接;后端现有结果页、按钮和 Scheme 继续作为兼容兜底。
Why this priority: 远程结果页在同 App WKWebView 中调用 Bridge、父 WebView 或 Scheme 已有真机失败证据;如果 App 逻辑层不能接管,iOS 用户会停留在返回页。
Independent Test: 在 iPhone 中完成一笔 OMG stage 支付,确认当前页面 WebView 加载 /pay/omg/result 后仅出现一次 result_page_detected;中间页、取消页和其他 URL 不触发;Android 和外部浏览器的现有返回页按钮及 Scheme 不受影响。
Acceptance Scenarios:
omgCheckout 已经提交 OMG Form 并绑定当前页面 WebView 的 loaded,When 当前 URL 精确进入 /pay/omg/result,Then App 逻辑层只触发一次 handleOmgReturnDetected({ orderId, currentUrl })。loaded 触发,Then App 不产生 OMG_RETURNED 交接信号。10200095 时同步失败信息;任何查询不得把已付款降级。MerchantTradeNo。测试环境调用退款时必须在任何支付/订单读取、网关 HTTP 或状态写入前失败关闭,返回稳定的环境不支持状态。
业务订单号含有不适合 OMG MerchantTradeNo 的字符时,系统使用独立生成的英数字编号,不直接拼接或截断业务订单号。
随机生成的 MerchantTradeNo 发生唯一索引冲突时,系统可在同一创建事务中重新生成;达到受控次数仍失败时整笔创建回滚。
客户端取得表单但未提交、网络中断或关闭页面时,本地只能保持 CREATED,不得宣称 OMG 已建立或未付款。
表单生成成功但尝试落库失败,或尝试落库事务最终回滚时,不得向客户端返回可提交表单。
订单或凭证在并发过程中发生变化时,最终写入必须仍满足订单合法状态、凭证归属门店和单活跃尝试约束。
TradeDesc、ItemName 不接受客户端文本,必须由服务端生成,无 HTML 标签或未经允许的特殊符号,并满足官方长度限制。
ReturnURL 必须是服务端受控的 HTTPS URL,路径固定指向新的 /pay/omg/notify;客户端不得覆盖。
表单中出现重复参数名、无法解码的 percent encoding 或超出受控大小时,按非法请求处理,避免参数污染或资源滥用。
OMG 新增未列明的回传字段时,只要请求合法,字段也必须被 DTO 边界完整捕获并参加验签;不能依赖固定字段白名单计算检查码。
同一订单存在另一笔活动尝试时,一笔成功将关闭其他活动尝试;以后若另一历史交易也收到合法成功通知,仍记录其支付事实并输出严重异常日志,不吞掉第二笔资金事实。
CREATED 尝试,已付款订单选择其首条 PAID 尝试。MerchantID / HashKey / HashIV 快照向 OMG stage QueryTradeInfo/V5 发送表单请求。CheckMacValue,并核对商户号、交易号与金额。TradeStatus=1 MUST 复用回调的锁与幂等状态机更新尝试和订单为已付款;0 MUST 不修改;10200095 MUST 同步失败;已付款 MUST 不可逆。ipn_log;HashKey、HashIV、CheckMacValue 和完整网关响应 MUST NOT 返回客户端或写入应用日志。POST /pay/omg/refund MUST 需要 token,并以显式 JSON DTO 只接收 orderId;不得接收客户端提供的金额、交易号、Action、凭证或地址。PAYMENT_REFUND_UNAVAILABLE_IN_TEST_ENVIRONMENT,MUST NOT 调用 OMG 正式 CreditDetail/DoAction、查询或写入任何订单/支付/退款状态。POST /pay/omg/retry,使用 token 和只含 orderId、paymentMethod 的显式 JSON DTO;paymentMethod 仅允许 CREDIT/APPLE_PAY。客户端不得提交旧 MerchantTradeNo、OMG 原始支付参数、金额、凭证、网关地址或旧表单。UNKNOWN 时不得修改尝试或创建新表单。PAID 时 retry MUST 返回 ORDER_ALREADY_PAID;确认 FAILED 时 MAY 创建新尝试;确认 UNPAID 时仅当 paymentType 为空才 MAY 替换旧尝试。MerchantTradeNo 与当前活动尝试,把该行原子更新为 SUPERSEDED 后再生成新交易号和新表单;任一条件变化 MUST 返回 PAYMENT_RETRY_NOT_AVAILABLE。CREATED 尝试;迟到的旧尝试可信成功回调仍 MUST 能升级为 PAID 并关闭更新的活动尝试。MerchantTradeNo 的新表单。CheckMacValue,并始终核对 MerchantID、MerchantTradeNo、TradeAmt 与 TradeStatus;缺少任一核心字段时 MUST fail closed。除 10200047 外,TradeAmt MUST 与本地尝试金额一致。StoreID、TradeNo、PaymentDate、PaymentType、HandlingCharge、PaymentTypeChargeFee、TradeDate、ItemName 与 CustomField1..4 不得仅因字段不存在而使 UNPAID、10200095 或 10200047 查询失败;实际存在的字段仍 MUST 参与验签。TradeStatus=1 MUST 继续要求可信结算所需的 TradeNo、PaymentDate、PaymentType、PaymentTypeChargeFee 与 TradeDate,不得因兼容稀疏未付款响应而放宽已付款事实校验。CheckMacValue、HashKey、HashIV 或登录 token。TradeStatus=10200047 且 TradeAmt=0 时,系统 MUST 将其视为网关不存在该交易并同步旧尝试失败,使 retry 创建新表单;该状态返回非零金额或任一身份、签名校验失败时 MUST fail closed。pages/OrderList/buy/omgCheckout MUST 在 App-Plus iOS 获取并监听当前 this.$scope.$getAppWebview();该页面通过 renderjs 在当前 WebView 提交 Form,不得按 <web-view> 子组件使用 children()[0]。https://foodieapi.waimai-paotui.com/pay/omg/result 及其 query/hash 形式;只包含 omg、result 或其他域名的地址 MUST NOT 触发交接。handleOmgReturnDetected({ orderId, currentUrl });页面卸载时 MUST 移除原生 loaded 监听并清理引用。/pay/omg/result MUST 继续返回现有安全 HTML;繁体提示、“返回 App”按钮和 App Scheme MAY 作为 Android、外部浏览器或监听失效时的兼容兜底保留,但 iOS 主流程 MUST NOT 依赖 Bridge、parent.evalJS 或 Scheme 成功。FR-060: 结果页 URL 到达只表示 OMG 浏览器流程返回,MUST NOT 作为付款成功事实;App 后续查询、提示和路由由前端业务实现。
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 与 paymentMethod;paymentMethod 仅允许 CREDIT/APPLE_PAY。金额、门店、用户、订单支付类型、说明文字、网关地址、回调地址及原始 OMG 参数均必须由服务端决定。
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、OrderResultURL、EncryptType=1、InvoiceMark=N、NeedExtraPaidInfo=Y 和 CheckMacValue。CREDIT 额外固定发送 ChoosePayment=Credit、UnionPay=2;APPLE_PAY 额外固定发送 ChoosePayment=ApplePay 且不发送 UnionPay。
FR-013: 创建表单 MUST NOT 发送 ChoosePayment=ALL、IgnorePayment、PlatformID、PaymentInfoURL、ClientRedirectURL、ClientBackURL、Language、ATM/CVS/BarcodeATM 期限字段、分期、定期定额或记忆卡号参数;APPLE_PAY 也 MUST NOT 发送仅适用于信用卡的 UnionPay。
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 结尾;该路径 MUST 由新的 omgpay Controller 处理,不得被旧 Controller 接收。
FR-017: CheckMacValue MUST 严格按 OMG 官方规则生成:排除 CheckMacValue 本身,将其余全部实际发送字段按官方字母顺序排序,以 & 串接,前置 HashKey=...&、后置 &HashIV=...,执行符合官方 .NET 表的 URL 编码并转小写,使用 SHA-256,最后输出大写十六进制。
FR-018: 除 CheckMacValue 自身外,创建请求实际发送的全部字段 MUST 参加签名,包括 OrderResultURL、所选渠道对应的 ChoosePayment、信用卡渠道的 UnionPay=2 和 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 保存创建时实际使用的 HashKey / HashIV 快照,确保门店凭证被覆盖或停用后仍可验证在途交易;不得保存完整创建表单。完整回调内容保存到现有 ipn_log,不新增该表字段。
FR-023: 创建接口和创建日志 MUST NOT 输出登录 token、HashKey、HashIV。回调入口按已批准的排障策略 MUST 在应用日志和 ipn_log.ipn_log 直接记录完整回传内容,包括完整 MerchantTradeNo、TradeNo、额外参数、空值和 CheckMacValue;任何日志均不得输出数据库中的 HashKey / HashIV。
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 支付及退款表 pos_order_omg_payment、pos_order_omg_refund MUST 通过 updatesql/sql.md 登记删除,任何可达运行链路和源码 MUST NOT 再查询或写入这些旧表。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、凭证对象或表单对象的方式记录日志。
FR-032: POST /pay/omg/notify MUST 是无需登录 token 的第三方 form-urlencoded 接口,Controller MUST 使用明确 DTO 作为唯一业务入参,不得使用 Map 或 HttpServletRequest 作为 Controller 入参。DTO 边界 MUST 保留全部实际参数、空值、原始可重放表单内容和请求 IP。
FR-033: 每次回调 MUST 先以独立事务向现有 ipn_log 新增一行:type="omg"、ip 为请求来源、cretim 为接收时间、ipn_log 为完整可重放 form-urlencoded 内容。该写入失败只记录应用错误日志,不得阻断后续验签与付款处理。
FR-034: 回调 MUST 先按 MerchantTradeNo 读取并锁定新支付尝试,再使用该尝试的 HashKey / HashIV 快照验签;不得只凭请求 MerchantID 选择密钥,也不得使用门店当前可能已变更的凭证。
FR-035: 验签成功后 MUST 同时校验请求 MerchantID、MerchantTradeNo、TradeAmt 与尝试快照完全一致。交易号不存在、字段缺失/格式非法、商户或金额不符、验签失败及业务事务异常 MUST NOT 修改订单或尝试,并返回纯文本 0|ERROR。
FR-036: 当 RtnCode=1 时,无论 SimulatePaid 为 0 或 1,系统 MUST 把尝试标记 PAID 并保存网关交易事实;订单仅把 pay_status 从未付款改为已付款,不得修改 state、delivery_status 或触发接单、出餐、完成、退款。若该订单为可配送的外送订单,MUST 在事务提交后触发可接单骑手推送。
FR-037: 当验签通过且 RtnCode!=1 时,系统 MUST 把非 PAID 尝试标记 FAILED,原样保存 RtnCode/RtnMsg 并释放活动尝试;订单保持未付款,下一次创建支付生成新的 MerchantTradeNo。
FR-038: 回调状态 MUST 成功优先且不可逆:CREATED/FAILED -> PAID,CREATED -> FAILED,PAID 不得降级。重复通知不得重复改变订单;正确处理或已处理的通知均返回精确 1|OK。
FR-039: 合法成功通知到达时,即使订单已取消也 MUST 记录付款事实并把订单 pay_status 更新为 1;系统 MUST 输出异常日志,但本阶段不得自动退款。
FR-040: 一笔尝试成功后 MUST 关闭同订单其他仍活动的尝试。若历史尝试后来也收到合法成功通知,仍 MUST 记录第二笔付款事实并输出严重异常日志,不得因本地单活跃约束丢弃真实资金通知。
FR-041: 支付尝试 MUST 保存 trade_no、rtn_code、rtn_msg、payment_type、payment_date、trade_date、payment_type_charge_fee、simulate_paid 和 last_notify_time;完整逐次回调原文只保存于 ipn_log。trade_no 非空时全局唯一,防止同一 OMG 交易被绑定到多个本地尝试。
FR-042: 旧 com.ruoyi.app.utils.omg 工具包及其测试 MUST 删除。可信门店凭证录入保留原表与管理流程,但联网验证 MUST 迁入新的 com.ruoyi.app.omgpay,并复用 rebuild 的签名、严格响应解析和 stage 查询边界。
FR-043: specs/016-omg-payment 和历史建表 SQL MUST 作为变更记录保留,但不得再作为运行实现依据;旧表删除只能追加新迁移记录,不改写或移除历史 SQL。
POST /pay/omg/create
Content-Type: application/json
token: <login-token>
{
"orderId": "991786433092835",
"paymentMethod": "CREDIT"
}
{
"status": "CREATED",
"gatewayUrl": "https://payment-stage.funpoint.com.tw/Cashier/AioCheckOut/V5",
"formFields": {
"MerchantID": "1000031",
"MerchantTradeNo": "OMGR8K3P7W2M9C4X6A1B",
"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",
"OrderResultURL": "https://example.test/pay/omg/result",
"ChoosePayment": "Credit",
"UnionPay": "2",
"EncryptType": "1",
"InvoiceMark": "N",
"NeedExtraPaidInfo": "Y",
"CheckMacValue": "<64 uppercase hexadecimal characters>"
}
}
HashKey 和 HashIV 永远不属于响应。上例是 paymentMethod=CREDIT 的响应;paymentMethod=APPLE_PAY 时 ChoosePayment=ApplePay,且不包含 UnionPay 或 IgnorePayment。外层继续使用项目现有 AjaxResult 成功/失败封装;上例只定义 data 契约。
PAYMENT_ATTEMPT_EXISTS。formFields 或新的 MerchantTradeNo。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 |
hash_key_snapshot |
创建时 HashKey 快照 | 非空,不得输出到日志或 API |
hash_iv_snapshot |
创建时 HashIV 快照 | 非空,不得输出到日志或 API |
attempt_status |
本地事实状态 | 0=CREATED, 1=PAID, 2=FAILED, 3=SUPERSEDED |
active_dd_id |
单活跃约束生成列 | attempt_status=0 时为 dd_id,否则为 NULL |
trade_no |
OMG 金流交易编号 | 可空;非空时全局唯一 |
rtn_code / rtn_msg |
OMG 原始结果码及说明 | 可空,不维护固定错误码枚举 |
payment_type |
OMG 回覆付款方式 | 可空 |
payment_date / trade_date |
OMG 付款及建单时间 | 可空,Asia/Taipei 语义 |
payment_type_charge_fee |
OMG 回传手续费 | 可空 |
simulate_paid |
模拟付款标记 | 可空;1 仍按已付款处理 |
last_notify_time |
最近一次合法通知处理时间 | 可空 |
create_time |
本地创建时间 | 非空 |
update_time |
本地更新时间 | 非空 |
约束:
UNIQUE (merchant_trade_no);UNIQUE (active_dd_id),利用 MySQL 唯一索引允许多个 NULL 的语义,为后续终态释放活跃键;CREATED 只表示本地已生成并持久化表单所需事实,不能解释为 OMG 已接收或已建立订单;PAID 是不可逆资金事实;FAILED 可被后续合法成功通知升级为 PAID;SUPERSEDED 表示同订单已有其他尝试成功,并非 OMG 返回的支付失败;ipn_log 承载。PosStoreOmg / pos_store_omg可信门店凭证来源。不修改其表结构、录入流程或启停流程。创建服务读取与订单 storeId 对应、已启用的 MerchantID / HashKey / HashIV,并把密钥写入本次尝试快照;回调不再依赖门店当前行。
IpnLog / ipn_log每个回调 HTTP 请求新增一行,不新增字段:ip 保存来源地址,cretim 保存接收时间,type 固定为 omg,ipn_log 保存完整原始 form-urlencoded 请求体。该流水使用独立事务,后续业务回滚不删除已经接收的通知记录。
PAYMENT_ATTEMPT_EXISTS 是安全终止,不是系统异常,也不能自动删除或覆盖已有行。ReturnURL 必须由服务端配置并严格校验,不接受客户端 URL,防止开放重定向或向非 OMG 主机泄露签名表单。HashKey / HashIV 只在服务端签名过程中短暂使用;不得复制到新支付尝试表。/pay/omg/create 只能由新 omgpay Controller 映射;/pay/omg/notify 在下一阶段前必须没有旧处理器。INFO,可预期的业务拒绝使用 WARN,非预期且需要调查的异常使用 ERROR 并携带异常对象;单元测试可依赖稳定业务错误码,不依赖自然语言日志文本。MerchantTradeNo 只显示足以关联记录的首尾片段;如果应用已有 trace/request ID,则沿用现有上下文,不自行生成新的支付追踪体系。CheckMacValue 验证完整 SHA-256 签名链路;不得用旧实现测试或其他金流向量代替官方依据。CheckMacValue 字段都会改变签名。CheckMacValue 被排除。ipn_log 在验签失败和业务事务回滚时仍独立保留,且日志写入失败不阻断付款处理。MerchantTradeNo 仅含英数字、长度不超过 20、重复冲突不会覆盖旧行。yyyy/MM/dd HH:mm:ss 格式。payType="2"、金额和门店凭证校验。ReturnURL 被拒绝。CREATED 尝试。HashKey 或 HashIV;完整 CheckMacValue 与签名表单只出现在创建成功响应,不出现在错误响应或日志中。pos_store_omg 查询保持可用。所有新增生产方法必须遵循测试先行:先运行定向测试并观察其因缺少新行为而失败,再写最小实现使其通过。
C:\Users\qmj\.jdks\graalvm-jdk-21.0.7 设置当前命令的 JAVA_HOME 和 PATH。omgpay 测试及受旧链路下线影响的订单回归测试。ruoyi-admin 及其依赖模块的 JDK 21 Maven 构建。git diff,确认未改动旧凭证能力、未重写无关文件、未引入编码或换行噪音。POST /pay/omg/create。formFields POST 到返回的测试 gatewayUrl。CREDIT 与 APPLE_PAY 创建并提交表单,确认直接进入所选 OMG 测试付款流程;不得出现超商快付或 AFTEE 选择项。payStatus=1 的业务订单。type=0、state=0、deliveryStatus=0、afterSaleStatus=0 且未分配骑手时,事务提交后通知附近可接单骑手。state 或 deliveryStatus,不得提前通知商家备餐;商家通知由骑手实际接单后触发。CheckMacValue 完全一致。CheckMacValue 字段均由测试证明参与签名,额外字段与空值字段不会被签名器丢弃。MerchantTradeNo 产生率为 0。paymentMethod、无凭证和非测试环境请求的尝试写入数为 0。/pay/omg/create 只映射到新 Controller。HashKey、HashIV 泄露数为 0;错误响应和应用日志中的完整 CheckMacValue 或完整签名表单泄露数为 0。omgpay 的公开类型、签名编码、事务和数据库并发边界均有必要注释,显然样板代码上的重复注释数为 0。com.ruoyi.app.utils.omg 类定义、导入和调用数为 0;旧支付/退款表没有运行 SQL,测试源码只允许用类名或表名做退役断言。UNPAID + paymentType 为空 重试 100% 使用新 MerchantTradeNo;同一旧尝试并发重试时新活动尝试数不超过 1,旧表单重放次数为 0。/pay/omg/result 最多产生一次 result_page_detected;中间页、取消页和其他 URL 的误触发次数为 0,Android 与外部浏览器的现有返回页兜底保持可用。pos_order.dd_id 是客户端使用的业务订单号,PosOrder.mdId 能唯一定位单门店订单的门店。PosOrder.amount 是当前订单应支付的整数新台币金额。pos_store_omg 的既有启用凭证查询能按 storeId 返回正确门店凭证。