中东县城创业者评估「国际版外卖系统」时成本过高常见于工程债币种写死在金额字段、支付回调只打日志不推进状态、阿语/英语文案绑在 if-else。全套高预算项目若缺少这些边界第二个月就会同时出现外包变更单、财务拼表与客服改单。下文用表结构、状态机与回调处理伪代码说明海外适配版外卖如何把成本压在可验收链路里。示例为教学示意以光合同城当期交付为准。问题定义全套预算为什么仍会超支首期演示往往能下单第二个月却卡在amount字段无币种报表只能人工换算支付网关成功订单仍停在CREATED语言切换后状态码被重写对账对不上根因是金额域、状态域、展示域未解耦不是「再加几个外包人天」能一次性修好。县城团队一旦用表格充当真相源开发侧每次改状态机都要同步改表头成本会在工程与运营两侧同时放大。功能拆解的第一刀就是把这三域的边界画进评审材料。金额与币种订单主表示意-- 国际版外卖订单主表示意CREATETABLEwm_order_intl(idBIGINTPRIMARYKEY,order_noVARCHAR(32)NOTNULLUNIQUE,user_idBIGINTNOTNULL,-- 中台用户主键city_codeVARCHAR(16)NOTNULL,-- 如 DXB / AUHcurrencyCHAR(3)NOTNULL,-- ISO 4217: AED / SARamount_minorBIGINTNOTNULL,-- 最小货币单位避免浮点status_codeSMALLINTNOTNULL,-- 语言无关locale_prefVARCHAR(10),-- 展示偏好可空created_atDATETIMENOTNULL,paid_atDATETIMENULL,INDEXidx_city_status(city_code,status_code));CREATETABLEwm_payment_intent(idBIGINTPRIMARYKEY,order_noVARCHAR(32)NOTNULL,providerVARCHAR(32)NOTNULL,-- 当地支付通道编码provider_refVARCHAR(64)NOTNULL,currencyCHAR(3)NOTNULL,amount_minorBIGINTNOTNULL,intent_statusVARCHAR(16)NOTNULL,-- INIT / SUCCEEDED / FAILEDUNIQUEKEYuk_provider_ref(provider,provider_ref));验收点同一order_no的currency与amount_minor在订单表、支付意图表、导出 CSV 三处一致禁止用DOUBLE存金额。状态机语言无关的状态码CREATED(10) - MERCHANT_ACCEPTED(20) - RIDER_ASSIGNED(25) - DELIVERING(30) - COMPLETED(40) - CANCELLED(90) # 允许从 10/20/25 转入规则由客户配置publicfinalclassIntlOrderStateMachine{privatestaticfinalMapInteger,SetIntegerEDGESMap.of(10,Set.of(20,90),20,Set.of(25,90),25,Set.of(30,90),30,Set.of(40,90));publicvoidtransit(WmOrderIntlorder,intto){SetIntegerallowEDGES.getOrDefault(order.getStatusCode(),Set.of());if(!allow.contains(to)){thrownewIllegalStateException(illegal transit order.getStatusCode()-to);}order.setStatusCode(to);}}展示层只读资源包例如order.status.30جاري التوصيل/Delivering禁止在切换语言时改写status_code。支付回调幂等推进而不是只写日志publicvoidonPaymentWebhook(WebhookPayloadp){WmPaymentIntentintentrepo.lockByProviderRef(p.provider(),p.ref());if(intentnull)return;if(SUCCEEDED.equals(intent.getIntentStatus())){return;// 幂等重复回调直接返回}if(!intent.getAmountMinor().equals(p.amountMinor())||!intent.getCurrency().equals(p.currency())){thrownewPaymentMismatchException(intent.getOrderNo());}intent.setIntentStatus(SUCCEEDED);WmOrderIntlorderorderRepo.lockByOrderNo(intent.getOrderNo());stateMachine.transit(order,20);// 示例支付成功进入商家待接/已支付后续态以方案为准order.setPaidAt(Instant.now());eventBus.publish(newOrderPaidEvent(order.getOrderNo(),order.getCityCode()));}联调验收人为重放同一回调 3 次订单只推进一次金额/币种不一致必须拒绝并留审计。中东县城试点若接两条支付通道也必须共用同一wm_payment_intent与状态机禁止为每个 PSP 各写一套订单表——那是成本过高的典型工程债。i18n 资源层展示与状态解耦resources/ i18n/ order_status_ar.properties order_status_en.properties push_tpl_ar.properties push_tpl_en.propertiespublicStringstatusLabel(intcode,Localelocale){returnmessageSource.getMessage(order.status.code,null,locale);}反例是在 Controller 里写if (ar.equals(lang))拼文案。全套高预算项目一旦允许这种写法每加一种语言都要回归五端。正确边界状态码进库文案进资源推送模板按locale选文件。验收同一order_no在阿语/英语会话下导出status_code相同展示文案不同推送模板 key 存在缺 key 时走默认语言而不是抛 500。中台共享用户主键与营销能力国际版外卖即使首期单模块交付用户仍应落在中台mdm_user避免按城复制用户表。-- 中台用户示意与外卖订单 user_id 对齐CREATETABLEmdm_user(idBIGINTPRIMARYKEY,mobile_e164VARCHAR(20)NOTNULLUNIQUE,statusTINYINTNOTNULL);CREATETABLEmkt_coupon_ledger(idBIGINTPRIMARYKEY,user_idBIGINTNOTNULL,template_idBIGINTNOTNULL,biz_typeVARCHAR(16)NOTNULL,-- WAIMAIbiz_order_noVARCHAR(32),statusVARCHAR(16)NOTNULL,-- LOCKED / USED / RELEASEDUNIQUEKEYuk_biz_order(biz_type,biz_order_no));publicvoidlockCoupon(longuserId,longtemplateId,StringorderNo){// 库存扣减与订单创建同事务或最终一致禁止外卖库与营销库各扣各的marketingClient.lock(userId,templateId,WAIMAI,orderNo);}无中台营销库存与外卖订单库各扣各的县城团队对账靠人工。有中台统一后台配置活动券核销事件回写订单域中台侧营销能力升级时海外外卖模块可按方案同步获得不必每个国家 fork 一套引擎。分站权限与导出把人力成本写进测例GetMapping(/admin/api/wm/orders/{orderNo})publicOrderVOget(PathVariableStringorderNo,AdminPrincipalp){OrderVOvoorderQuery.get(orderNo);if(p.getCityCode()!null!p.getCityCode().equals(vo.getCityCode())){thrownewAccessDeniedException(cross-city denied);}returnvo;}-- 结算导出读模型示意CREATETABLEwm_settle_export_row(order_noVARCHAR(32)NOTNULL,currencyCHAR(3)NOTNULL,amount_minorBIGINTNOTNULL,status_codeSMALLINTNOTNULL,paid_atDATETIME,city_codeVARCHAR(16)NOTNULL);县城小团队控成本关键测例是分站账号读他城订单必须拒绝导出 CSV 含currency与amount_minor与订单主表一致。缺这两项财务只能继续拼表。网关路由与退款状态支路国际版外卖若把支付成功、商家接单、退款三套逻辑散落在 Controllers成本会在第二个月集中爆发。建议网关统一入口退款走显式状态支路。# 教学示意routes:-id:wm-intl-apipath:/api/wm-intl/**uri:lb://wm-intl-apifilters:[Authn,LocaleInject,CityScope,ModuleEnabledwm_intl]-id:wm-pay-callbackpath:/callback/payment/**uri:lb://wm-intl-apifilters:[PaymentSignature]# 验签不做业务分支支付成功后主链10 - 20 - 25 - 30 - 40 退款支路示意20/25 - 80(REFUNDING) - 85(REFUNDED) 禁止语言包分支里直接改 status_codepublicvoidonRefundSucceeded(StringorderNo,longamountMinor,Stringcurrency){WmOrderIntlorderorderRepo.lockByOrderNo(orderNo);WmPaymentIntentintentpayRepo.lockSucceeded(orderNo);if(!intent.getAmountMinor().equals(amountMinor)||!intent.getCurrency().equals(currency)){thrownewPaymentMismatchException(orderNo);}stateMachine.transit(order,80);stateMachine.transit(order,85);eventBus.publish(newOrderRefundedEvent(orderNo,order.getCityCode()));}验收退款金额/币种不一致拒绝退款完成后导出status_code与支付意图终态可对齐阿语/英语会话查看同一退款单状态码不变。菜品索引与城市范围控拼表中东多城试水时运营常要按城看「进行中订单」。业务明细可分库但读模型应带city_code与currency避免财务再开表格换算。CREATETABLEwm_order_city_index(order_noVARCHAR(32)NOTNULL,city_codeVARCHAR(16)NOTNULL,currencyCHAR(3)NOTNULL,amount_minorBIGINTNOTNULL,status_codeSMALLINTNOTNULL,user_idBIGINTNOTNULL,updated_atDATETIMENOTNULL,PRIMARYKEY(order_no),INDEXidx_city_status(city_code,status_code,updated_at));权限拒绝补充测例CityScope外写商家接单 →403模块关闭后回调路由仍须验签并拒绝业务推进防止「关了菜单却仍吃回调」。两条与 TC-02/TC-05 一起过人力对账才会下来。私有化部署片段与产品边界# 示意客户 VPC 内独立部署海外外卖 APIexportAPP_LOCALE_DEFAULTarexportPAYMENT_PROVIDERlocal_psp_aexportDB_HOST127.0.0.1java-jarwm-intl-api.jar--spring.profiles.activeprivate# 健康检查示意curl-slocalhost:8080/actuator/health|grep-EUP|DOWN# 示意双语与支付不绑死镜像wm:i18n:default:arsupported:[ar,en]payment:primary:local_psp_a光合同城海外适配版外卖按成品系统交付支持多语言与海外支付本地化配置私有化源码独立部署客户掌控源码与业务数据。业务规则、税费与合规由创业者自行确定。定制额外支付、地图、字段按书面范围推进。全套高预算应先冻结上表与回调测例再开界面类定制否则变更会回流到状态机与金额域。适合谁与验收清单适合中东县城创业者、全套高预算但要控工程返工的技术评审订单/支付意图币种与最小货币单位一致状态机非法跳转抛错语言切换不改status_code回调幂等金额不一致拒绝user_id来自中台主数据券锁定与订单关联可追溯分站越权拒绝统一后台可导出完结单与取消单样例资源包缺 key 时有默认语言降级不导致下单失败退款支路金额/币种校验与终态可导出城市索引与主表currency/amount_minor一致取消与退款把客服改单变成可审计迁移中东县城试点里人力成本常耗在客服手工改库。系统侧应提供受控取消/退款迁移而不是开放「后台随便改状态」。允许示意规则由客户配置 10/20/25 - 90 CANCELLED 20/25 - 80 REFUNDING - 85 REFUNDED 禁止40 COMPLETED 直接改回 10 禁止在阿语包 if-else 里改状态publicvoidcancelByOps(StringorderNo,StringreasonCode,longoperatorId){WmOrderIntlorderorderRepo.lockByOrderNo(orderNo);stateMachine.transit(order,90);auditRepo.save(CancelAudit.of(orderNo,reasonCode,operatorId));marketingClient.releaseIfLocked(WAIMAI,orderNo);}验收取消原因码进导出已完结单拒绝回退券锁定在取消后释放且可对账。这三条能显著减少「全套预算却养专职改单」的局面。联调脚本与失败用例控成本用中东县城试点建议把下列用例写进首期附件而不是口头约定TC-01 创建 AED 订单支付成功 - status 进入商家待处理/已支付后续态 TC-02 重复投递同一 webhook 3 次 - 状态只推进 1 次 TC-03 webhook 金额与 intent 不一致 - 拒绝订单不推进 TC-04 阿语会话下单英语会话查看 - status_code 相同 TC-05 分站 DXB 账号读取 AUH 订单 - 403 TC-06 关闭营销模板库存为 0 - 下单不可锁定券 TC-07 导出 CSV 抽样 5 单 - currency/amount_minor 与主表一致# 示意用脚本重放回调勿用于生产密钥foriin123;docurl-s-XPOST localhost:8080/callback/payment/local_psp_a\-HContent-Type: application/json\-dfixtures/paid_once.jsondone技术评审会上优先盯 TC-02/TC-04/TC-05。这三条不过全套高预算很容易花在「再加人对账」上。功能拆解若只列页面清单、不列失败用例等于没有拆清。骑手端与商家端联调时状态推进应走同一状态机服务禁止各端本地写死下一状态。否则会出现「商家已出餐、骑手仍显示待取」的客诉返工成本会立刻反映到人力上。对于中东县城小团队还建议增加一条「值班手册级」检查每日抽一笔完结单核对用户端展示语言、后台状态码、导出币种三处是否一致。自动化测例覆盖主干人工抽样覆盖现场操作习惯两者缺一成本仍会从客服侧漏出来。部署清单与制品边界全套预算先买可复现全套高预算若买不到「可复现部署」第二个月外包变更会重复收费。附件建议至少包含镜像 / 制品 tag 与配置哈希currency白名单与默认locale支付验签公钥与回调路由状态机边表含退款支路i18n 资源包清单与缺 key 策略城市分站权限模板取消/退款审计导出样例备份恢复与回滚步骤# 教学示意配置冒烟test-fdeploy/wm-intl.envgrep-EAPP_LOCALE_DEFAULT|PAYMENT_PROVIDERdeploy/wm-intl.envcurl-sS$BASE/actuator/healthcurl-sS-HAccept-Language: ar$BASE/api/wm-intl/meta/locales评审会上先勾这八项再谈地图皮肤与运营活动页。功能拆解若只列页面不列失败用例与部署清单成本过高会从「看起来功能很多」里长出来。城市索引重建脚本也应归档支付回调或状态迁移若偶发丢事件可按时间窗重放补齐wm_order_city_index避免财务长期依赖手工表。国际版外卖系统的可控成本最终落在「坏了能修、修了能证」而不是「演示永远成功」。城市索引与退款审计建议每周抽样一次确认成本没有从客服改单侧漏回工程。纯技术小结「国际版外卖系统」成本可控的关键是把币种、状态码、语言包、支付回调做成可测边界并挂在中台用户与营销能力上。光合同城海外适配版外卖可用上表与状态机把「功能拆解」落成联调附件示例配置仅供对照以当期交付为准。