订单文档中不应存结算账期字段,只存不可变的settlementsnapshot快照,含commissionrate、settlementcycletype、settlementstartdate;账期规则独立存于merchantsettlementpolicies集合,支持平台默认→类目级→商家级三级覆盖;结算任务须幂等,依赖settlementbatchid与settlementstatus控制,禁止实时事务分账,退款须按原始快照逆向冲正。

结算账期字段该不该存在订单文档里
不能。订单文档里只存 settlementSnapshot,也就是创建订单时固化下来的抽成比例、账期类型、结算周期起始日等快照数据。一旦订单生成,这些值就不可变——哪怕商家后续把平台抽佣从 15% 改成 10%,已下单的订单仍按原快照执行。否则退款、对账、重算都会乱套。
常见错误是把 platformCommissionRate 直接挂在 Orders 文档顶层,结果运营一调配置,历史订单全“自动更新”,导致分账金额错位、对账不平。
- 订单文档中只保留不可变快照,字段如:
commissionRate、settlementCycleType("DAILY"/"WEEKLY"/"MONTHLY")、settlementStartDate - 账期规则本身存在独立集合
merchantSettlementPolicies,供商家后台修改,但不反向影响已有订单 - 结算任务跑批时,按
settlementStartDate+settlementCycleType算出应结算时间窗,再聚合匹配的订单
抽成比例怎么支持多级覆盖
硬编码 15% 或只配一个全局值,上线两周就会被运营追着改。必须支持三层优先级:平台默认 → 类目级 → 商家级。MongoDB 用嵌套文档 + 查询时 $lookup 或应用层合并更可控。
推荐结构:在 merchantSettlementPolicies 集合中,每个商家文档含:
{"merchantId": ObjectId("..."), "defaultCommissionRate": 0.15, "categoryRules": [{"categoryId": ObjectId("..."), "rate": 0.12}], "overrideRules": [{"orderTag": "VIP", "rate": 0.08}]}
-
defaultCommissionRate是兜底值,所有未匹配规则的订单都用它 -
categoryRules按商品类目动态降佣,比如生鲜类目抽 8%,数码类目抽 18% -
overrideRules可扩展为营销活动、大客户协议等场景,靠orderTag字段关联 - 订单创建时查这个文档,按优先级顺序取第一个匹配的
rate,写入自己的commissionRate快照字段
结算任务如何避免重复入账
结算任务不是“执行一次就完事”,而是每天定时触发,且必须幂等。核心是靠两个字段控制:settlementBatchId 和 settlementStatus("PENDING"/"PROCESSED"/"FAILED")。
典型错误是只靠时间范围查询订单,没加状态过滤,导致同一批订单被反复结算。
- 每次跑批先生成唯一
settlementBatchId(如"20260907-DAILY-001") - 聚合查询条件必须包含:
{ settlementStatus: "PENDING", settlementBatchId: null, settlementStartDate: { $lte: ISODate("2026-09-07") } } - 更新时用
findAndModify或事务确保只锁定未处理过的订单,并原子写入settlementBatchId和settlementStatus: "PROCESSED" - 失败后重试,只查
settlementStatus: "FAILED"的,不碰已成功的
为什么不能用 MongoDB 原生事务做实时分账
订单支付成功那一刻,你不需要、也不应该立刻扣平台佣金、加商家余额、记流水三件事情一起在事务里做完。这会严重拖慢支付链路,而且和退款逻辑耦合太紧。
MongoDB 事务适合强一致性场景,比如库存扣减+订单生成;但分账是最终一致性场景,必须解耦。
- 支付回调只做一件事:把订单状态设为
"PAID",并写一条paymentEvents日志 - 结算任务异步读
paymentEvents,再查订单快照,生成分账明细,最后批量更新商家余额和平台收入账户 - 退款走逆向流程:查原始快照,按原比例反向冲正,不依赖任何“当前配置”
- 这样设计,即使结算服务宕机三天,补跑也不会丢数据、不会错金额
settlementSnapshot 的完整性校验。上线前务必写个脚本,扫描最近 1000 条已支付订单,确认每条都含 commissionRate、settlementCycleType、settlementStartDate 三个字段且非空——漏一个,整批结算就可能跳过或错算。











