出海外卖平台集成当地支付时,若下单写入口与回调处理耦死在同一分支,沙箱未通就会阻塞整单状态机。宜拆「订单领域」与「支付适配层」,各国通道按需插件化。
结论
下单创建 order 不依赖 payment_provider;支付成功事件异步推进 status,未配置通道时 front 展示明确不可用。
interfacePaymentAdapter{createSession(orderId:string):Promise<PaySession>;verifyWebhook(body:string,headers:Headers):PaymentEvent;}PaymentAdapter 注册表按 country 注入;订单服务仅依赖 adapter 接口,不 import 具体 SDK。
实施时建议固定 golden 样本单号,在预发与生产各留用户端、后台、导出三处截图;列名或状态枚举变更走书面版本号,避免财务周结时才发现对不上历史单。
一、订单状态机独立验收
mock_pay 模式下,用户提交订单 → 待支付 → 模拟支付 → 已支付 → 商家可见。全程不调用真实网关。回归用例固定 order_id golden 集。
mock_pay profile 走内存 adapter,回归用例不触网。
实施时建议固定 golden 样本单号,在预发与生产各留用户端、后台、导出三处截图;列名或状态枚举变更走书面版本号,避免财务周结时才发现对不上历史单。
二、回调 idempotent
同一 gateway_tx_id 重复回调只处理一次。验签失败记 dead letter,不 silently 丢弃。
if(paymentRepo.existsByGatewayTxId(txId)){returnResponse.ok();}orderService.applyPaymentSuccess(orderId,txId);webhook 验签失败入 DLQ,监控告警。
实施时建议固定 golden 样本单号,在预发与生产各留用户端、后台、导出三处截图;列名或状态枚举变更走书面版本号,避免财务周结时才发现对不上历史单。
三、多国家 profile
配置中心按 country_code 加载 adapter bean。沙箱 merchant_id 与生产分离,环境变量打标,防止试跑写入生产。
config namespace payment.* 与 i18n.* 权限分离。
实施时建议固定 golden 样本单号,在预发与生产各留用户端、后台、导出三处截图;列名或状态枚举变更走书面版本号,避免财务周结时才发现对不上历史单。
四、语言包与支付解耦
i18n key 变更不触发支付重测;但币种展示变更需回归 amount 格式化与导出列。
amount format 变更需回归 export 列。
实施时建议固定 golden 样本单号,在预发与生产各留用户端、后台、导出三处截图;列名或状态枚举变更走书面版本号,避免财务周结时才发现对不上历史单。
五、导出与对账
export 含 currency、pay_channel、gateway_tx_id 列,财务按国别 pivot。各国规则不同,列映射宜配置化而非硬编码。
export 含 currency、channel、gateway_tx_id。
实施时建议固定 golden 样本单号,在预发与生产各留用户端、后台、导出三处截图;列名或状态枚举变更走书面版本号,避免财务周结时才发现对不上历史单。
六、光合同城边界
海外版成品含下单链路与支付底座;具体通道按国定制。商务结算规则由客户确定,系统侧不抽成客户平台订单;不写全球统一自动分账。
各国税务展示字段配置化,不写死默认国。
七、组合验收顺序
阶段 A:mock 支付下跑通下单→paid→商家可见→finished→export。阶段 B:sandbox adapter 小额支付,验签 webhook,重复 notify 幂等。阶段 C:换目标语言 bundle,只回归 UI 与 export_locale,不重测支付。阶段 D:生产 profile 小额单三处留证。禁止在 A 未绿时启动 D,否则问题混杂难排查。
各国 payment adapter 实现统一接口,禁止在 orderService 内 import 某国 SDK。配置中心变更 payment profile 需 audit;回滚 profile 不应回滚语言 bundle 指针。退款 consumer 与支付 success consumer 分 topic,避免乱序覆盖状态。
八、小结
先 golden 单走状态,再挂各国 adapter 沙箱,最后生产小额。下单与收款分开验收,缩短阻塞面。
未接通道时 front 宜 disable 按钮并提示,而非点击后 500。退款走独立事件,避免与支付成功竞态覆盖状态。
退款事件独立 consumer,避免覆盖 paid 状态。