What Is a Double-Entry Ledger API for Marketplaces?
A double-entry ledger API records marketplace money movement as balanced, immutable journal entries so you always know who is owed what and why.
一个多边市场 (marketplace) 的 orders(订单)表可能看起来非常健康,但其资金状况依然可能存在错误。订单显示“已支付”。卖家仪表盘显示了余额。然而,表中的任何数据都无法证明平台持有多少现金、欠每个卖家多少钱、支付金额中有多少属于佣金收入,或者现在需要缴纳多少增值税 (VAT)。复式记账总账 API 的存在,就是为了正确记录这些财务影响,每次处理一笔平衡的交易。
简而言之:复式记账总账 API 是一个可编程的会计系统。您的应用程序向其发送财务事件(例如已捕获的支付或已批准的打款),总账就会将该事件记录为包含多个借贷平衡明细的分录 (journal),并分配到指定的科目中。它并不负责资金转移,而是记录其他地方发生资金转移的真实财务状况。
这里的“复式记账总账”意味着什么
总账记录的是科目变动情况,而不是保存最新余额的表格。一个正常运转的总账包含三个相互关联的概念:
- 科目 (Account):具名的财务状况,例如应收支付机构款、应付卖家款、佣金收入或应付增值税。
- 交易 (Transaction/journal):包含多个平衡明细的单一原子财务事件。
- 分录 (Entry/posting):该交易中的单个借方或贷方明细。
将整个设计串联在一起的不变规则很简单:每笔交易和每种货币的借方总额必须等于贷方总额。总账 API 通常会在不平衡的写入操作到达存储层之前予以拒绝。Modern Treasury 的文档将总账交易定义为涉及两个或多个科目,且至少包含一个借方和一个贷方且总额相等的交易,同时将已过账的交易视为不可变 (immutable) 的。
复式记账并不意味着刚好只有两行。单次市场销售通常需要一个借方和多个贷方。这是人们最常误解的地方。
为什么市场需要总账,而不仅仅是支付表
多边市场同时维持着多层财务关系。买家进行支付。支付机构进行清算。平台可能会暂时控制这些资金。卖家赚取一部分。平台保留佣金。税款开始产生。可能会扣留一部分保证金。随后,打款操作将资金转出,而退款或拒付 (chargeback) 则会冲销其中的一部分。
payments 表可以告诉你订单的商业状态,但它无法可靠地告诉你:
- 存在多少应收支付机构款或现金
- 欠每个卖家多少钱
- 多少收入属于平台
- 应缴纳多少增值税,以及在哪个国家/地区缴纳
- 预留了多少资金来应对退款或纠纷
- 打款是否正确减少了对该卖家的负债
- 支付机构的清算金额是否与平台预期相符
Nordlet 的面向市场会计的双式记账总账 API 指南划定了清晰的界限:应用程序表描述的是产品状态,而总账记录的是该状态带来的财务影响。其中还有一个值得重复的观点,即支付机构报告是用于对账的输入数据,而不是完整的复式记账账簿。
一笔完整的市场销售示例
假设买家支付了 €100,卖家应得 €85,平台佣金为 €15。为了清晰起见,暂不考虑增值税和支付机构手续费。
| 科目 | 借方 | 贷方 | 原因 |
|---|---|---|---|
| 应收支付机构款 | €100 | 支付机构欠平台已捕获的支付款 | |
| 应付卖家款 | €85 | 平台欠卖家 | |
| 佣金收入 | €15 | 平台赚取的佣金 | |
| 合计 | €100 | €100 | 平衡 |
这里重要的分类是,欠卖家的 €85 是一项负债,而不是平台的收入。根据 IFRS 概念框架,负债是转移经济资源的现实义务。市场不应仅仅因为买家通过其平台付款,就将卖家的全部销售收入报告为自己的收入。无论您的身份是代理人、主要责任人 (principal) 还是视同供货商 (deemed supplier),其会计处理方式会有所不同,但总账的运作机制保持不变。
当卖家符合打款条件时,不会确认新的收入。此时您需要结清负债:
| 科目 | 借方 | 贷方 |
|---|---|---|
| 应付卖家款 | €85 | |
| 银行或打款清算 | €85 | |
| 合计 | €85 | €85 |
如果支付机构在清算前扣除 €3 的手续费,资金流动则会变成一个三行分录:€7 进入银行现金,€3 计入处理费用,€100 离开应收支付机构款。财务状况的每一次变动都有其来源和去向。
API 的工作原理:一步步解析
1. 应用程序检测到业务事件。 捕获支付、订单完成、批准打款、发出退款、开启拒付、结算落地。您的数据库仍然负责维护运营状态,而总账则记录相应的货币影响。
2. 发送结构化请求。 请求通常包含事件类型、金额和货币、订单或支付 ID、买家和卖家标识、佣金、税务数据、涉及的科目、生效日期和过账日期、幂等性键 (idempotency key),以及关联到支付机构和产品记录的元数据。Nordlet 的公共 API 示例展示了一个概念性的销售请求,其中包含卖家 ID、销售类型、金额、货币以及增值税国家/地区和税率。
3. API 进行验证。 它会检查借贷是否平衡、科目是否存在且可过账、是否支持该货币、金额是否使用了精确的辅币单位处理、幂等性键是否被重复用于不同的结果、会计期间是否开放、以及是否包含了所需的元数据。验证失败不会留下任何残缺的分录。
4. 原子化过账。 完整的交易作为一个整体写入。绝不能出现记录了借方却未能成功写入匹配贷方的情况。对于市场而言,部分拆分可能会产生虚假的卖家余额且没有对应的现金,或者在没有记录卖家债务的情况下记入佣金。
5. 衍生余额与报告。 过账后,您可以读取科目余额、卖家待结算和可用余额、试算平衡表 (trial balance)、总账活动、增值税汇总以及历史截止日期的数据。卖家余额应通过过账数据重新构建,而不是依赖可变的字段。
6. Webhook 通知您的产品。 诸如 sale_invoice.paid、已过账的分录、可用的打款、或对账异常等事件将被推送给您。请以幂等方式处理它们。Webhook 只是通知,而不是二次应用该财务事件的许可。API 约定中详细介绍了作用域、幂等性键、错误结构和 Webhook 签名。
卖家余额并非单一数字
大多数市场陷入困境的原因,是将卖家余额视为单一的数字。资金应当处于不同的状态中:
| 状态 | 含义 |
|---|---|
| 待定 (Pending) | 卖家可能应得,但未满足释放条件 |
| 可用 (Available) | 符合打款条件 |
| 预留 (Reserved) | 留存以应对退款、退货或纠纷 |
| 已打款 (Paid out) | 债务已通过转账结清 |
| 负数 (Negative) | 退款、拒付或费用超过了可用资金 |
支付机构的模型也反映了这一点。Stripe 的独立收费和转账流程允许平台向自己的账户收费,然后将部分资金转给连接账户,同时将费用、退款和拒付计入平台余额。Adyen 的市场模型则将用于在打款前保存资金的用户余额账户,与能够吸收退款或拒付产生的负余额的负债余额账户区分开来。这些都是构建总账的有益输入,但它们并不构成完整的会计模型。
打款与卖家收益发生在不同时间
卖家可能在周一获得收益,并在下周一收到款项。在这两个时间点之间,订单得到履行、扣留了保证金、生成了打款批次、支付机构扣除费用后完成清算、银行存款到账。总账必须保留每个阶段,而不是用最终的“已支付”覆盖订单。正是这一点,让您能够解释订单活动、卖家应得款、清算、打款发起和银行到账之间的时差。
退款与拒付:冲销,而非改写
退款和拒付是与原始支付关联的新事件。在不可变总账中,您可以通过冲销分录加上更正分录来进行纠正,以便审计时依然能看到原始记录。Modern Treasury 将已过账的交易视为不可变的,并支持幂等性键以防止重试时产生重复对象。
退款通常会减少现金或应收买家款、冲销应付卖家款、根据政策可能冲销部分佣金、调整税款,并可能消耗退款保证金。拒付会减少现金或应收支付机构款、可能重新开启卖家或平台欠款、记录争议费用和支付机构手续费,并通过保证金科目转移资金。
对账:将总账与真实资金连接
总账可能在内部保持平衡,但对外部世界的反映依然可能是错误的。对账是将内部记录与外部凭证进行比对:支付机构清算报告、银行对账单、打款文件、退款和争议记录。
可行的循环流程如下:
- 导入清算或银行批次。
- 将每一行与订单、支付、退款、费用、打款或争议进行匹配。
- 比对预期的总额、扣除额、保证金和净额。
- 将确认的变动过账到总账。
- 将未匹配的项目发送到指定的暂记账户 (suspense account)。
- 调查异常情况。
- 锁定会计期间。
Nordlet 的对账资料准确地描述了这一过程,即导入清算批次、匹配收费和退款明细、将未匹配的收款引导至暂记账户而不是盲目猜测发票。两项检查都至关重要且无法互相替代:复式记账验证询问每笔分录是否平衡,而对账询问其是否与支付机构、银行和业务事件相符。
决定成败的设计细节
幂等性。 将密钥绑定到稳定的事件,例如 payment_captured:pi_123。重试将返回原始结果,而不是产生第二个分录。
原子性。 一个事件的所有明细要么全部过账,要么全不过账。
不可变性与冲销。 已过账的历史记录无法通过常规更新进行修改。更正操作会创建关联的分录,同时保留原始金额、日期、分类、原因和操作人。
精准的货币计算。 绝不使用二进制浮点数。请使用整数辅币单位或精确的小数、显式货币,以及针对拆分的确定性舍路规则。如果没有这些规则,在将 €100 拆分给多个卖家和手续费时,一分钱的误差就会打破平衡。
生效日期与过账日期。 买家付款时间、卖家赚取时间、银行清算时间和分录过账时间是不同的日期。将它们混为一谈会导致期间报告和纳税时间不可靠。
多货币。 借方和贷方应在各自的货币范围内平衡。货币转换需要明确的汇率事件,并设有费用、舍入以及任何汇兑损益的科目。接受 currency(货币)字段并不能使总账自动变成外汇引擎。
总账 API 无法为您做的事
它无法决定您的科目表 (chart of accounts)。您仍然必须确定自己是主要责任人还是代理人、何时赚取佣金、哪一方承担支付机构手续费、退款如何影响佣金、以及如何计算和报告增值税。Nordlet 自身的指导原则是:首先绘制资金流向图、起草科目表、请会计师进行审核、在扩大规模之前,完整实现从订单到打款和对账的一个完整流程。
它无法解决税务问题。增值税取决于买家和卖家的所在地、产品类型、注册状态以及视同供货商规则;欧盟增值税指南列出了引擎推导出的内容以及需要人工标记的内容。Nordlet 的增值税覆盖范围主要集中在欧盟和欧洲经济区 (EEA),因此不延伸至美国销售税或拉丁美洲电子发票。它也无法解决运营上的模糊性,例如退货窗口是否关闭、是否应阻止打款、支付是否存在欺诈。这些都是您的决策,而总账负责记录它们的财务结果。
Nordlet 的定位
Nordlet 是专为平台和多边市场构建的会计 API,具备不可变的复式记账总账、打款、欧盟增值税处理、REST 端点、强类型 SDK,以及可通过单一表面访问的 Webhook。其独特之处在于将会计功能嵌入到您自己的产品中,而不是使用第三方结账服务。截至本文撰写时,该产品正处于早期访问阶段,并为设计合作伙伴提供沙盒环境,因此请将其视为该类产品的优秀范例,在做出承诺之前,请评估您自身业务的生产成熟度、正常运行时间和司法管辖区覆盖范围。
对于多边市场和平台开发者而言,有用的心智模型是支付流转与财务真相之间的拆分。您的支付机构负责转移资金,而复式记账总账 API 则负责记录谁欠谁什么、您赚了多少钱、您承担了哪些税款和保证金,以及外部世界是否与您的账簿相符。
FAQ
复式记账总账 API 与支付机构是同一回事吗?
不是。支付机构负责授权、捕获、清算资金,有时还负责打款。总账 API 则记录这些事件带来的会计后果。大多数多边市场两者都会使用,即使用 Stripe 或 Adyen 等支付机构负责资金流转,使用总账负责记账。
我可以直接将支付机构的仪表盘当作总账吗?
你可以尝试,但最终审计无法通过。支付机构的报告显示了费用、手续费、转账和清算情况,但通常不代表您完整的卖家债务、税务负债、非支付机构费用或会计调整。请将其视为对账的输入数据。
总账平衡是否意味着我的账目是正确的?
这只代表您的分录满足了借贷平衡规则。完全平衡的交易仍然可能被过账到错误的科目、错误的卖家,或采用错误的税务处理方式。平衡是必要的,但不是充分条件。对账才是捕获其余问题的手腕。
如果总账是不可变的,我该如何修正错误?
您需要发布一笔冲销分录,然后再发布一笔更正分录。原始分录将保持可见,这正是其核心意义所在。不可变性保护了审计追踪,它并不会将您的错误锁死。
会计 API 能自动让我合规吗?
不能。它强制执行会计结构并生成可用的记录。法律合规性仍然取决于您所在的司法管辖区、合同角色、税务处理、支付法规、KYC 和 AML 流程以及报告义务。