国际版外卖系统里,税规则一变就改下单主流程,是最伤回归成本的做法:改税率字段要重测库存、运力、支付状态机。更合理的拆法是------下单主流程只关心商品、库存、运力、支付结果;税作为可插拔计算与字段回写,各国税制支持按需定制对接,不默认写死某一国。

下文用管道与适配器示意解耦方式。税务结论由客户在当地确定;系统提供可配置项与导出,不替代财务判断。结算核对走字段与导出,商务结算规则由客户确定。
结论:税是插件,不是主干
主流程状态机保持稳定:created → paying → paid → ...。税计算发生在报价阶段,结果以快照字段落单,避免日后改税规则重算历史单。
text
cart
-> quote_items # 商品与运费
-> tax_provider.calc # 可替换实现
-> payable
-> payment_bridge
-> order_hub (状态机不含税规则分支)
把税公式写进支付回调,是最常见的耦合:通道一换,税也跟着坏;税一改,支付回归全量重跑。
报价管道:税适配器可替换
python
class TaxProvider:
def calc(self, base_amount: int, region: str) -> dict:
raise NotImplementedError
class NullTax(TaxProvider):
def calc(self, base_amount, region):
return {"tax": 0, "tax_code": "none", "lines": []}
class CustomRegionTax(TaxProvider):
"""按需定制对接的示意实现,不代表某国默认税制。"""
def calc(self, base_amount, region):
rate_bp = lookup_rate_bp(region) # 基点,来自配置/对接
tax = base_amount * rate_bp // 10000
return {"tax": tax, "tax_code": region, "lines": [{"bp": rate_bp}]}
def price_pipeline(cart, tax_provider: TaxProvider):
base = quote_items(cart)
tax = tax_provider.calc(base, region=cart.region)
payable = base + tax["tax"]
return {"base": base, "tax": tax, "payable": payable}
下单时把税快照写入订单,而不是只存「当前税率配置 ID」指望以后重算。
sql
CREATE TABLE order_price_snapshot (
order_id VARCHAR(32) PRIMARY KEY,
base_amount INT NOT NULL,
tax_amount INT NOT NULL,
tax_code VARCHAR(32) NOT NULL,
tax_payload JSON NOT NULL,
payable INT NOT NULL
);
导出对账时以快照为准。税务报表若需要额外字段,通过定制对接扩展 tax_payload,不要去改支付状态机。
测试金字塔:主流程不依赖某国税
- 主流程回归 :使用
NullTax或固定 Mock,不启真实税对接 - 税适配器单测 :只测
calc输入输出与边界 - 支付适配器 :另一条线,金额取
payable,不内嵌税公式
python
def test_checkout_without_tax_rules():
pipe = price_pipeline(sample_cart(), NullTax())
assert pipe["tax"]["tax"] == 0
order = checkout(pipe)
assert order.status == "paying"
def test_tax_adapter_isolated():
tax = CustomRegionTax().calc(10000, region="region_x")
assert "tax" in tax and "tax_code" in tax
支付对接各国不同,同样走适配器;与税插件并行迭代,互不阻塞。语言包也是展示层,不要和税计算焊死。
和结算核对的关系
税字段解决的是「下单时算清应付」。商家侧结算、平台侧核对是客户商务规则问题:系统提供可配置核对字段与导出项,商务结算规则由客户确定,系统侧不抽成客户平台订单。不要把「某国自动算清所有结算」写成产品默认能力。
验收清单
- 切换
NullTax与定制税适配器,支付状态机用例集合不变 - 历史订单税金额不被新规则重算
- 支付通道更换不要求修改税适配器代码路径(仅改支付桥)
- 导出含 base / tax / payable 快照字段
- 文档写明:税制结论由客户当地确定,系统不替代财务判断
成品怎么接住
光合同城国际版外卖成品将报价、税字段、支付适配分层:不默认某一国税制;税务相关字段与对接支持按需定制开发。结算与核对字段由客户侧规则确定。私有化交付后,可用「切换税适配器不影响支付状态机」做验收。
多市场并行时的发布策略
市场甲改税适配器,不应触发市场乙的支付全量回归。做法是:主流程镜像共用,税与支付都以配置或独立包加载。回归矩阵按「主流程加 Mock 插件」与「插件单测」分开维护。
报价页展示税明细时,用快照字段渲染,不要现场再调外部税接口------外部超时不应挡住查看历史订单。下单当刻算清,历史单只读快照,是解耦后的直接收益。
若当地法规要求额外申报字段,扩展税载荷与导出列,不改订单状态枚举。状态机保持稳定,合规字段按需生长。支付通道更换只动支付桥,税插件保持不变。
对账导出同时给出商品金额、税金额、应付金额三列,财务才不用反推。若某市场暂不启用税插件,使用空实现,主流程仍可回归。空实现不是永久方案,只是把定制排期从主干解耦。
税额展示、客服话术与插件版本
客服解释账单时,应能在后台看到税码与税金额快照,而不是按现行规则重算。现行规则只影响新单。若某市场暂不征税,空实现返回零税,页面可隐藏税行;订单快照仍保留税字段为零,方便导出列稳定。
税插件的配置变更也要有版本与生效时间,和新单绑定,逻辑与运力快照相同。市场甲改税适配器,不应触发市场乙支付全量回归。对账导出同时给出商品金额、税金额、应付金额三列。支付通道更换只动支付桥;税务按需定制对接,不默认某一国,不替代客户财务判断。
主流程回归集怎么维护
主流程回归集固定:加购、报价、支付中、支付成功、取消。全部在空税或固定 Mock 下跑。税适配器变更只跑插件单测加一条「下单后快照税字段存在」。支付桥变更不碰税用例。矩阵越干净,多市场越敢并行。把「禁止用新税规则重算历史单」写进代码评审清单。对账导出固定商品金额、税金额、应付金额三列。
纯技术小结
- 主流程状态机不含税规则分支
- 税以快照落单,禁止用新规则重算历史
- 主流程回归与税适配器单测分开
- 支付与税都是可替换适配器,按需定制对接
适合谁:多市场试水、税规则可能变化但仍要稳住下单支付的团队。不适合:把某国税公式写进支付回调的硬编码方案。
把上述做法写进下一次变更复查:只认证据,不认感觉。复查记录与配置版本号、发布单号交叉引用,方便半年后追溯。若人手不足,先保住可回滚与可审计,再追求体验细节。
对外沟通时用同一套术语:进度码、配置版本、审计流水、导出抽查。术语统一后,研发、运营、财务才不会各说各话。本篇清单可以作为术语对照的附件一起存档。
新品类上线时,先确认报价管道仍走税插件接口,而不是在品类代码里写死税额。品类只提供商品金额与类目信息,税插件决定是否计税。边界清晰后,开新品类不必重训支付状态机。
把本段做法与前文验收清单交叉引用,形成可执行闭环:改完必验,验完留证,证与版本号同存。对外说明时强调:系统提供核对与权限能力,商务规则由客户确定;海外相关篇目中支付税务支持按需定制对接。