平台结算规格:对账与争议处理
我见过太多 marketplace 平台在结算系统里悄悄流血,根因永远是同一个:规格把 payout 当成报表问题,而不是会计问题。如果你的书面规格在第一页没有强制要求复式记账 ledger,那文档后面写的东西全都是装饰。
内容整理说明
复查日期:2026-05-06。本文已重新纳入公开索引路径,作为 Spec-First 开发 Hub 的延伸阅读。我们补齐了专题路径、站内链接和可索引元数据,便于搜索引擎和读者理解它与核心主题的关系。
Ledger 才是规格,payouts 表不是
任何 payout 评审里,我开口的第一件事就是:把最初设计里的 payouts 表删掉,重新从一张复式记账 ledger 开始。一张带着 seller_id、amount、status 的 payouts 表,是工程师按屏幕上看到的东西建模、而不是按资金真实流动建模时才会得到的结果。每一美元进入平台,都必须落入某个账户,并从另一个账户离开,规格里必须白纸黑字写清楚。
我的规则是:规格禁止任何代码路径在没有配对借贷、且不在同一个事务内的情况下改动余额。orders receivable、seller payable、platform revenue、processor fees、sales tax liability、reserve、chargeback loss 各自是独立账户。当 payout 报表和 ledger 对不上时,ledger 赢。
Hold period、门槛与 payout 节奏
新卖家不能立刻拿到钱。这句话就该带着一个具体数字写进规格里。新卖家第一个结算周期里设置 7 到 14 天的 hold period,原因是 chargeback 集中发生在购买后的第一周。如果规格里没有指明这个 hold,在某个 sprint 里为了处理一张客服工单,就会有人"贴心地"把它删掉。
我视为不可妥协的节奏规则:
- 默认每周固定工作日 payout,这样 finance 可以预测资金流动。
- On-demand payout 必须由最低余额(底线 $10)和每卖家的每日上限一起门槛化。
- 对新卖家保留一定比例的 reserve,按仪表盘上可见的时间表释放。
- "available balance" 与 "pending balance" 必须显式定义——规格要指明哪些 status 才算数。
每日与支付处理方对账
每天都有一个 job 把平台 ledger 和 Stripe、Adyen 或者任何夹在我们和卡组织之间的那一方做对比。规格说的是两边不一致时会怎么做,而不是"假如"它们不一致。低于可配置容差的分币级舍入差异记录下来并自动关闭;更大的差异开一张对账工单,并阻塞该卖家的下一次 payout 运行。
"阻塞下一次 payout 运行"是 PM 会争论的部分。顶住。今天这一笔 0.43 美元的不明差额,正是你在 6 个月后发现 40,000 美元手续费计算 bug 的方式。
那笔 payout 后才被 chargeback 的 $100 订单
这是每一份 marketplace 规格都必须回答、而大多数都没有回答的场景。买家下了一笔 100 美元的订单。平台抽成 10 美元,支付处理费 3 美元,卖家的 payable 余额增加 87 美元。7 天后,卖家的周度 payout 执行,这 87 美元离开平台。订单发生后第 21 天,买家的银行就 100 美元全额发起了 chargeback。
规格必须指明谁来承担这笔损失,以及承担顺序。我的默认:
- 先尝试从卖家的 available balance 里追回。对 seller payable 账户写入一笔负向 ledger 条目。
- 如果 available balance 不足,冻结卖家账户,并创建一条 seller debt ledger 条目——未来的销售在触发任何新的 payout 之前,要先把这笔 debt 还清。
- 如果卖家不活跃、或者对这笔欠款提出争议,则升级到人工审核,并设定一个明确的 SLA(在我上一份规格里是 5 个工作日)。
- 如果 SLA 到期仍然追不回,就把余额移到平台自己持有的 chargeback loss 账户。这是做生意的成本,finance 要把它当成一行明细看见,而不是一团谜。
原始 100 美元订单上的 processor fee 不会回来——买家付款那一刻,平台就已经吃掉了 3 美元。规格必须把这笔手续费作为独立 debit 展示,否则你的利润率报表就是在悄悄撒谎。
Fee、tax、refund 都是 ledger 里的一等公民
一笔订单的每一个经济构成,在 capture 的那一刻都要有自己的 ledger 行:gross sale、platform commission、processor fee、sales tax collected、shipping、tip、促销抵扣。不在源头做任何 net。发生部分退款时,每一个构成都按比例、独立地反向冲销。
退款就是 naive 规格翻车的地方。规格必须区分处理三种口味:payout 前的退款(冲销 pending balance)、payout 后但卖家余额为正的退款(从 available 追回)、payout 后但余额不足的退款(走上面那套 chargeback 流程,只是没有争议环节)。假装"退款就是一笔负的订单"已经吃掉过我好几个完整的周末。
1099 数据在 payout 那一刻就要抓取
在美国,超过年度阈值的卖家要收到 1099-K。弄清楚"他们是谁、挣了多少钱"的最糟糕时间点,就是次年的 1 月。规格要求每一次 payout 事件都必须抓取一份不可变快照:legal name、TIN、地址、payout 金额、币种、tax year、jurisdiction。如果卖家 3 月份更新了 legal name,历史 payout 上保留的依然是 2 月份的名字。finance 和 IRS 都很在意这个区别,规格必须在 schema 层面强制执行。
Given/When/Then 形式的验收标准
- Given a seller with an available balance of $87 When a chargeback for $100 on a previously paid-out order is received Then the seller payable account is debited $87 And a seller debt entry of $13 is created And the seller's payout status is set to "frozen" And the next successful sale credits the debt account before payable - Given a daily reconciliation run When the processor balance and ledger balance differ by more than $1.00 Then payout jobs for the affected currency are paused And a P1 reconciliation ticket is opened with both balances attached And the pause persists until an operator resolves the ticket with a written reason - Given a new seller within their 14-day hold period When the seller requests an on-demand payout Then the request is rejected with reason "hold_period_active" And the response includes the release date and current held amount
多币种与不可变审计轨迹
如果平台做跨境销售,规格要明确 FX 快照时间点。我选 capture time,而不是 payout time,因为卖家预期拿到的 payout 金额不应该在资金还挂在 pending 期间浮动。这个选择是有代价的:capture 到 payout 之间的 FX 滑点由平台自己吸收。规格必须把这件事显式写出来,finance 也要把这笔成本计入平台手续费。
每一条 ledger 条目都是 append-only。更正是新增一条反向条目,绝不在原地更新。每一个会碰到钱的 operator 操作——解冻卖家、核销一笔 debt、手动触发一次 payout——都要产出一条审计记录,包含 operator 身份、原因、前后状态,以及超过某个金额阈值后必须有的二级审批人。审计师来的时候,"3 月 3 日那笔 4,200 美元的手动 payout 是谁批的"应该能用一条查询回答。如果规格没有强制要求这条轨迹,你交付的就是一个被当成功能卖出去的合规隐患。
补一张边界矩阵,少一次财务扯皮
平台结算规格最需要矩阵。订单、退款、拒付、冻结、税务快照都在影响同一组账本字段,散在段落里很容易漏。把边界写成表,finance、support 和工程才会看到同一件事。
Payout boundary matrix: - refund before payout: reduce pending balance, no seller debt - refund after payout with positive balance: debit available balance - refund after payout with zero balance: create seller debt - chargeback during hold: keep payout frozen until dispute closes - manual payout above $5,000: second approver required - FX adjustment: recorded as platform fee variance, never edits original ledger row
边界:这篇不是税务建议。规格只能保证系统留住 legal name、TIN、payout amount 和 audit trail;具体申报口径要让财务和法律签字。
状态机比散文更可靠
payout、hold、frozen、debt、disputed、released 这些状态要写成状态机。每条边都标出触发事件、owner、账本分录、通知和回滚限制。只写业务描述,工程和财务很容易在“冻结后还能不能人工打款”上理解不同。
最后要补一组测试数据:seller balance 表、ledger entries 表、payout batch 状态、chargeback 事件和人工调整记录。每个场景都断言金额、币种、owner、审计字段和回滚限制。钱的系统不能只靠流程图。
我还会把 support 后台也写进验收:订单状态、seller_balance 字段、ledger 表、payout_batch 状态、dispute owner、API 响应、审计日志和回滚按钮要显示同一组事实。客服看到的数据如果和财务表不一致,争议处理就会变成人工猜账。最后加一条测试:同一 chargeback 事件重复送达时,只生成一组账本分录。
上线前还要跑一次人工付款演练:创建一笔 manual_payout,检查审批状态、ledger_entry_id、operator_id、reason_code、二级 owner、通知事件和回滚限制。这个场景不常发生,但一旦发生通常金额很大,值得在规格里提前写死。
这条演练通过后再开放生产。
不要跳过,也不要事后补。
关键词:marketplace payout spec、double-entry ledger、reconciliation、chargeback handling、hold period、1099-K、audit trail
专题阅读路径
先读主题 Hub,再用下面的相邻文章和模板把这篇内容放进完整工作流。
继续阅读
编辑说明与免责声明
本文用于软件工程教学与实践参考,不构成法律、税务或投资建议。示例场景用于解释规格方法,不对应真实客户数据。
- 作者信息:Spec Coding 编辑部
- 编辑政策:编辑与事实核查政策
- 联系方式:联系页面