Marketplace Seller Subledgers: Revenue vs Payables
A practitioner's guide to separating customer cash, platform commission, seller payables, reserves, and payouts with balanced journal entries and a working order-to-payout data model.
在多边市场(marketplace)会计中,最常见的错误是将买家支付的总金额直接作为平台收入。例如,客户支付了 100 欧元,这 100 欧元进入了支付处理商账户,而有人将整个金额都记入了销售收入。账目在一段时间内看似平了,收入数据也很漂亮,直到审计师询问卖家的负债记在哪里。结果发现根本没有,因为卖家的 90 欧元从头到尾都没有被记录为应付款项。
解决方案是结构性的,而非表面修饰。一个多边市场需要一个分类账模型,从第一笔分录开始就将五个不同的资金池严格区分开:流转中的客户资金、平台佣金收入、应付卖家款项、费用以及准备金。而打款(payout)是第六个事件,它只是用于结清应付款项,本身既不是收入事件,也不是费用事件。本指南将展示如何通过平衡的会计分录和正常运转的“订单到打款”数据模型,使这些资金池保持清晰独立。
从“主要责任人与代理人”的定性开始
在涉及任何账户结构之前,平台必须先确定自己到底在销售什么。根据 ASC 606 准则,这一点需要针对每项具体的商品或服务进行评估,其结论决定了收入是以总额法还是净额法确认。平台可以在某一交易流中充当主要责任人(principal),而在另一交易流中充当代理人(agent)。
对于典型的多边市场卖家商品销售,平台通常扮演代理人角色。它负责安排将卖家的产品送达买家,不承担库存风险,其对价是收取佣金。德勤(Deloitte)关于 ASC 606 下主要责任人与代理人的指南将测试框架建立在控制权之上:履行承诺的责任、库存风险以及定价自主权是判断指标,而不是用来凌驾于底层控制权评估之上的核对清单。
之所以必须将这一结论纳入分类账的设计中(而不仅仅写在备忘录里),是因为该结论会驱动后续的每一笔分录。如果平台是代理人,那么卖家的份额在赚取的瞬间就成了负债。如果平台针对履约或质保服务充当主要责任人,那么这部分则属于平台收入。请将该定性结论作为数据存储下来:
principal_agent_role(主客体角色)specified_good_or_service(指定的商品或服务)commission_rule_id(佣金规则 ID)control_assessment_version(控制权评估版本)effective_from/effective_to(生效起止时间)
两年后,复核人员在查看净佣金明细行时,应该能够一目了然地看出为什么没有采用总额法。正如 BillingPlatform 在其关于 ASC 606 主要责任人与代理人的文章中所指出,“多边市场”这个标签本身并不能解决问题,控制权分析才可以。
在编写任何日记账规则之前,先定义卖家余额
“卖家余额”并不是单一的数字。一个卖家可能拥有健康的累计生命周期余额,但却没有任何可用于打款的可用资金,因为这些钱仍处于待结算状态或被扣留作准备金。如果将这些状态混为一谈,就会错误报告卖家可以提取的金额以及平台仍欠下的债务。
至少需要追踪以下状态:
- 待定(Pending): 已赚取或已收取,但尚不符合打款条件
- 可用(Available): 根据平台规则符合打款条件
- 保留/准备金(Reserved): 预扣以应对预期退款、争议或拒付
- 已打款(Paid out): 已转账或提交结算
- 负债/欠款(Negative): 卖家欠平台的金额,可从未来收益中扣回
- 暂记(Suspense): 已收到资金,但卖家身份或账务处理方式尚未确定
支付服务商已经对这种区分进行了建模。Stripe 将每个关联账户划分为待定余额和可用余额,并针对某些负债卖家余额保留了平台级的准备金余额。如果支付处理商进行了这种区分,分类账也必须如此,否则对账将永远无法平账。
会计科目表与至关重要的维度
一套可行的初始结构应当将清算账户、负债账户和收入账户隔离开,并在每一笔重要的过账中携带卖家维度。
| 用途 | 账户或维度 | 会计处理 |
|---|---|---|
| 等待结算的处理商资金 | 支付处理商清算账户 | 清算资产 |
| 已存入银行的处理商打款 | 银行存款 | 资产 |
| 已赚取但未付的卖家金额 | 应付卖家款项 | 负债 |
| 卖家级明细 | seller_id 维度 |
负债明细,而非收入 |
| 平台佣金 | 佣金收入 | 收入 |
| 增值税或销售税 | 应交税费 | 负债 |
| 处理商手续费 | 支付处理费 | 费用 |
| 卖家准备金 | 准备金负债 | 视合同而定 |
| 退款 | 冲减收入或退款科目 | 冲销原始处理 |
| 拒付(Chargebacks) | 损失、应收款或向卖家追索 | 取决于由谁承担损失 |
| 等待银行确认的打款 | 卖家打款清算账户 | 清算账户 |
| 未分配金额 | 暂记账户 | 临时异常处理 |
每一笔重要分录都应携带卖家、订单、打款批次、支付处理商、币种、法律实体、税务处理、事件类型、外部参考编号以及任何冲销关联。缺少了这些,卖家子分类账就无法证明单个余额的真实性,总分类账也无法证明总数的准确性。
你可以将子分类账实现为每个卖家一个负债账户、一个带有 seller_id 维度的汇总应付款账户,或者拆分为应付款、准备金和打款清算账户。一旦卖家数量达到几十家以上,“汇总加维度”的方法具有更好的可扩展性。请与你的会计师共同选择该模型,因为资金保障规则和当地的支付法规可能会影响资金的呈报方式。关于过账背后的底层机制,我们关于多边市场会计的双分录分类账 API一文详细介绍了该模型所依赖的分类账原语。
记录销售分录,将卖家分项与平台分项独立列示
以代理人销售为例:客户支付 100 欧元,平台佣金为 10 欧元,卖家赚取 90 欧元。在赚取佣金和卖家权益的时点:
| 账户 | 借方 | 贷方 |
|---|---|---|
| 支付处理商清算账户 | 100 欧元 | |
| 应付卖家款项 - 卖家 A | 90 欧元 | |
| 佣金收入 | 10 欧元 |
至关重要的控制点在于:90 欧元贷记负债,而不是收入。平台确认的收入是 10 欧元。这就是 ASC 606 准则中关于代理人的多边市场示例所达到的会计结果,即平台确认其佣金而非卖家的全部销售金额。
如果税款是单独收取的,请添加应交税费行,并根据税务定性相应调减卖家或平台的金额。切勿为了图省事而将税款并入佣金或应付卖家款项中。
将支付处理商手续费排除在收入和应付款计算之外
有三样东西经常被混为一谈:平台佣金、处理商手续费和打款金额。它们是完全不同的概念,并且会影响不同的账户。
客户支付 100 欧元,佣金为 10 欧元,支付处理商扣留 3 欧元。
- 佣金收入保持 10 欧元不变(如果合同如此规定)。
- 3 欧元的处理商手续费属于费用。
- 应付卖家款项为 90 欧元,除非卖家合同规定由卖家承担处理费。只有在这种情况下,应付款才会降至 87 欧元,同时向卖家收取 3 欧元的匹配费用。
如果仅仅过账最终到达银行的净额 97 欧元,就会一步到位地抹杀总销售额、佣金和手续费。我们的支付对账参考指南也强调了这一点:拆解结算明细,将处理商手续费作为费用处理,绝不能让净存款额替代收入。
将资金可用性、准备金和打款建模为独立的事件
资金在完成扣款(capture)和最终结算之间会多次变换状态。每一次状态转换都应拥有自己的分录,而且准备金绝不应该是对收入的莫名扣减。
当可用资金转入准备金时:
| 账户 | 借方 | 贷方 |
|---|---|---|
| 应付卖家款项 - 卖家 A | 15 欧元 | |
| 卖家准备金负债 | 15 欧元 |
当准备金释放时,做相反的分录冲销。卖家的总债务并未改变,改变的只是其资金状态。Stripe 的文档中记录了增加和释放准备金的交易,以及针对长期负债余额的追索转账,这也是为什么这些业务需要可识别的账户,而不是单一的网购抵扣分录。
打款则采取独立的双步处理法。在获批时:
| 账户 | 借方 | 贷方 |
|---|---|---|
| 应付卖家款项 - 卖家 A | 90 欧元 | |
| 卖家打款清算账户 | 90 欧元 |
在确认银行结算完成时:
| 账户 | 借方 | 贷方 |
|---|---|---|
| 卖家打款清算账户 | 90 欧元 | |
| 银行存款 | 90 欧元 |
如果打款失败,需将清算账户冲回应付卖家款项。打款是清偿负债,而不是费用。如果将其作为费用处理,就会与已经记录了应付款的销售额产生重复计算。Stripe 将打款状态(pending、in_transit、paid、failed、canceled)以及预计到账日期与发起日期进行了区分,这些日期不应该折叠融合成单一的时间戳。
订单到打款的数据模型
子分类账基于事件运行,而不是基于应用程序当前的订单状态。每个影响卖家的事件都包含相同的信封结构:
seller_id、event_id、order_idevent_type(事件类型:sale、refund、chargeback、reserve_hold、reserve_release、payout、adjustment)effective_date、posting_date(生效日期、过账日期)currency、direction、gross、commission、fees、reserve(币种、方向、总额、佣金、费用、准备金)refund_ref/dispute_ref(退款参考/争议参考)payout_batch_id(打款批次 ID)reversal_of(冲销自)source_status、idempotency_key(源状态、幂等键)
通过这些事件,你可以计算出期末余额,而不是去存储一个可变的字段:
卖家期末余额
= 期初余额
+ 卖家贷项
− 卖家借项
− 打款额
± 准备金变动
± 调整额
每个外部事件都需要一个持久的幂等键,以确保重试和重复的 Webhook 不会导致卖家被重复贷记或重复打款。行之有效的模式包括:
- 卖家日记账事件:
seller_id:order_id:event_type:version - 打款批次:
processor:payout_id - 处理商事件:
processor:event_type:processor_event_id
Stripe 自身的 Webhook 指南警告称重复事件不可避免,因此重复检测是必不可少的。已过账的分录保持不可变。更正操作必须通过冲销分录加上更正分录来完成,并且正是你自己的事件信封承载着 reversal_of 关联链接和原因。分类账本身只保存过账结果,而不保存该链接:Nordlet 在其API 参考文档中公开了针对日记账交易的创建、获取和列表查询功能,不支持更新或删除,因此更正操作永远是一笔新的交易,而不是对旧交易的修改。
退款和拒付必须建立关联,绝不能悬空
在下周打款中体现的退款,绝不能变成附加在该打款上的一笔崭新的负销售额。请将其关联到原始交易,并保留原始的卖家分摊比例。根据合同条款:
- 如果合同规定佣金可退,则冲销佣金
- 如果由卖家承担退款,则借记应付卖家款项
- 如果由平台承担退款,则借记平台损失账户
- 在争议审理期间增加准备金,在争议解决时予以释放或核销
打款后的拒付是负债余额产生的原因。卖家的钱已经花光了,因此平台要么从未来的收益中扣回,要么自行吸收损失。该模型必须允许出现负的卖家余额,并明确定义追索规则。Adyen 的多边市场报告显示,退款和拒付会为每个账户持有人产生单独的借方行,这正是应当追求的颗粒度。
按打款批次对账,而不是按月对账
在过账之前拆解每一笔处理商结算,并且对每一笔打款进行对账,而不是将整月数据汇总:
总扣款额
− 退款
− 处理商手续费
− 拒付
± 准备金与调整额
= 净打款额
然后,验证每个卖家和币种的四个平衡点是否一致:卖家子分类账、处理商的卖家余额、打款报告以及银行清算账户。当它们不一致时,应对差异进行分类(时间差、缺失事件、重复、归属错误、币种错误、打款失败、拒付错记给其他方)。用抵消净额的方式掩盖错误,只会让错误叠加并一直延续到下一次结账。
汇总整个月的账目会让时间差相互抵消,从而掩盖真正的断层。通过打款 ID 和交易参考编号(而不是金额和日期)进行按打款批次匹配,能够及时发现这些问题。
常见的故障模式
| 故障模式 | 危害所在 | 控制措施 |
|---|---|---|
| 客户支付总额被记录为收入 | 虚增收入,掩盖卖家负债 | 在赚取时点的事件中分别记录应付款和佣金 |
| 仅入账净存款额 | 丢失总额、手续费、退款、准备金 | 拆解每一笔结算 |
| 打款被记为费用 | 错误表述负债的清偿 | 打款时借记应付卖家款项 |
| 只有一个卖家余额字段 | 混淆了待定、可用、准备金和已付状态 | 显式追踪各种余额状态 |
| 在打款时计算佣金 | 收入确认跟随打款调度走 | 在赚取时点确认佣金 |
| 准备金被当作收入削减 | 模糊了时间和风险 | 分别追踪扣留、释放和核销 |
| 退款应用到最近一次打款 | 破坏了订单归属关系 | 将退款关联到原始交易 |
| 修改已过账的分录 | 破坏审计追踪线索 | 冲销并重新过账 |
| 重复触发 Webhook | 重复贷记和打款 | 幂等键、重复检测 |
| 仅按金额和日期匹配 | 不同的项目相互碰撞冲突 | 按打款 ID、交易 ID、卖家、币种匹配 |
我会优先做什么
在触碰会计科目表之前,先画出资金流向图。写下从扣款到准备金释放的每一个事件,并标明哪些事件产生收入、哪些产生负债、哪些仅仅移动状态。
接着,构建一个端到端的完整流程并证明其平衡性:正常销售、部分退款、打款后拒付、准备金的扣留与释放,以及打款失败。使用已知的预期余额运行这些流程。如果应付卖家款项控制账户等于子分类账的总和、处理商清算账户与结算报告相符、银行存款与打款 ID 相符,那么你就拥有了一个能运转的核心骨架。
只有在第一个月顺利结账且暂记账户余额为零之后,才能开启期间锁定功能。过早锁定只会把错误冻结在账里。
Nordlet 开箱即用地沿用这些准则来过账结算:平台的佣金计入佣金收入,卖家的份额计入应付卖家款项,处理商手续费计入支付处理费,任何尚未归属的金额计入暂记账户。分类账期间将一直保持开放,直到你显式将其锁定,因此只要暂记账户清零,第一个月就可以结账。功能页面列出了包含的具体特性。
FAQ
买家的付款在任何情况下都是平台的收入吗?
仅针对平台作为主要责任人的特定商品或服务。对于代理人多边市场销售的卖家商品,收入是佣金,卖家的份额是负债。在同一个订单中,平台既可以充当履约服务的代理人,又可以充当底层商品的主要责任人,因此评估标准是针对特定的商品或服务,而不是针对整家公司。
准备金应该冲减收入吗?
不应该。准备金是时间和风险的分配,而不是收入调整。当你扣留资金时,应将资金从应付卖家款项转入准备金负债,释放时做相反冲销。卖家的资金目前是否被扣留为准备金,完全不影响收入和佣金的确认。
如何防止 Webhook 重试导致的重复打款?
为每个外部事件附加一个幂等键,例如将打款批次设为 processor:payout_id,并在写入时检测重复事件。提供商明确警告过可能会重复投递 Webhook,因此分类账必须拒绝第二次尝试,而不是再次过账。
卖家余额可以为负吗?
可以,模型必须允许这种情形。在卖家已经获得打款之后才落地的拒付,会导致平台面临来自卖家的应收款或者自己承担损失。压制负余额只会隐藏未记录的债务。
为什么要按打款逐笔对账,而不是按月汇总对账?
因为当汇总一个月的数据时,时间和差错会相互抵消,导致总账能够对平,但个别交易却是错误的。按打款批次进行对账,根据打款和交易 ID 进行匹配,能够在结账前浮现出缺失的事件、重复项以及归属错误的拒付。