複式賬怎麼設計:借方、貸方和一張能對賬的憑證

從科目邊界、分錄平衡到多幣種、冪等與沖正,整理一套可以落到業務系統裡的複式賬設計方法。

「借方」和「貸方」容易被誤解成收入和支出的兩個按鈕。實際上,它們是分錄的兩個方向;方向的意義要結合科目類型、賬戶所有者和具體業務規則來確定。

這篇不試圖替代會計準則或企業的科目制度,而是從產品和工程角度整理一個可落地的賬本邊界:什麼事件可以入賬、分錄怎樣生成、怎樣保持可重算,以及出現未知結果時如何停下來。

借方和貸方不是正負號

對常見科目,可以先記住「增加方向」這張小表:

科目類型 借方增加 貸方增加 常見例子
資產 是 否 銀行存款、已確認的應收款
負債 否 是 商戶應付款、客戶預存義務
權益 否 是 所有者投入、留存結果
收入 否 是 符合確認條件的服務費收入
費用 是 否 通道費、營運費用

這張表描述的是通常的餘額方向,不是「錢從左邊流到右邊」的流程圖。平台收到客戶資金時,資產可能在借方增加,同時對客戶的負債在貸方增加;平台向商戶付款時,資產在貸方減少,商戶應付款在借方減少。

因此,業務人員不應只問「這筆錢是收入還是支出」,而應先問:誰控制這筆資金、誰對誰負有義務、這次事件改變了哪幾個賬戶。

先定義科目和賬戶

科目是賬本裡的分類,明細賬戶是某個主體在該分類下的記錄單位,餘額是分錄匯總的結果。科目、賬戶和餘額各有職責。

一個支付系統至少要區分平台自有資金、客戶資金、商戶應付款、通道清算、手續費收入、退款應付和待核對賬戶。具體名稱要隨業務主體和會計政策確定,下面的名稱只是系統建模示意。

我會給每個賬戶保留穩定身份和邊界:

  • accountId:不能因為展示名稱改變而變化。
  • ownerType / ownerId:說明是平台、客戶、商戶還是外部清算方。
  • accountType:說明資產、負債、收入、費用等類型。
  • currency:餘額的原幣種,不能依賴頁面當前選擇。
  • status:正常、凍結、關閉或待核對。
  • externalRef:外部賬戶或結算引用,允許為空但不能偽造。

賬戶本身不應直接接收任意頁面請求來修改餘額。業務服務產生事件,賬本服務根據已版本化的映射生成憑證,再把分錄寫入不可變的流水。餘額可以快取,但快取不是事實來源。

業務事件關聯憑證,憑證包含至少兩條分錄,每條分錄指向明細賬戶;餘額由已過賬分錄彙總
數據關係示意,窄屏可左右滑動。 查看原圖

表名只是工程示意:

記錄 主要欄位 關鍵約束
business_event 來源、業務事件引用、階段、業務時間、證據摘要 重複接收保留記錄;原始證據不可覆蓋
journal 憑證號、賬簿、事件引用、規則版本、過賬時間 同一應入賬業務階段只提交一次
entry 憑證號、行號、賬戶、借貸方向、金額、幣種 行號在憑證內唯一;正數金額精確存儲
ledger_account 主體、科目、幣種、正常餘額方向 主體與賬簿邊界明確,狀態變化留痕
balance_projection 賬戶、累計借貸、處理到的分錄序號 可從分錄重建;讀者知道投影截止點

在同一受控數據庫中,憑證、全部分錄、冪等記錄,以及用於資金決策的餘額或佔用變化應按事務一起提交。跨服務通知可用事務發件箱可靠傳遞,消費方繼續冪等;遠程通道無法加入本地數據庫事務,要通過狀態機、查詢和對賬收斂。

一張憑證怎樣保持平衡

憑證是一個業務階段在賬本中的一次確認。一個訂單可以產生收款、扣費、結算等多張憑證;每張應有唯一憑證號、事件類型、業務時間、記賬時間、來源、關聯訂單和一組分錄。每一條分錄至少包含賬戶、借貸方向、金額、幣種和精度。

本例選擇每幣種獨立平衡的業務賬本:同一憑證、賬簿、主體和幣種內,Σ借方金額 = Σ貸方金額;單幣種資產負債關係為「資產 = 負債 + 所有者權益」,本期收入減費用形成利潤並按政策結轉。借方正常賬戶的餘額 = 期初餘額 + 本期借方 − 本期貸方;貸方正常賬戶反過來計算。

簡化的入賬檢查可以寫成:

  1. 分錄數量至少為兩條,且借方和貸方都存在。
  2. 同一憑證按賬簿、主體與幣種分組,借方合計等於貸方合計;跨主體走各自的往來科目。
  3. 金額大於零,幣種有效,賬戶狀態允許記賬。
  4. 業務事件階段的冪等鍵、憑證號和憑證內分錄行號滿足各自唯一約束。
  5. 所有檢查通過後再一次性提交,失敗則整張憑證不生效。

例如收款 1,000 CNY 和收取 30 CNY 手續費,可以有不同的憑證拆法。關鍵不是把所有業務硬塞成兩行,而是讓每張憑證的業務含義清晰、金額在規定範圍內平衡,並能通過關聯引用還原完整過程。

借貸平衡檢查應在提交入口強制執行,不能只交給頁面或每天跑一次的報表。不要為了補齊差額隨意使用「其他收入」或「調整科目」。差額進入待核對流程,有憑據和權限後再生成調整憑證;單純在兩邊填相同金額不能證明業務正確。

多幣種、精度和匯率

多幣種會讓「借貸相等」多一層範圍問題。USD 100 和 EUR 92 不能直接相加,也不能只因為按某個匯率估值相同就說原幣賬平衡。

設計時至少要分開保存:

層次 需要保留什麼
交易層 原幣金額、幣種、訂單和外部引用
賬本層 每個幣種的分錄、賬戶和餘額
估值層 本位幣、匯率來源、時間、精度和捨入
費用層 費用幣種、承擔方和對應的原始事件

換匯可以有美元側和歐元側的關聯分錄,也可以通過橋接或清算科目表達在途狀態。兩種方式都要說明哪一側已經確認、哪一側仍未知,以及匯率和費用如何落位。

例如只說明業務賬戶內的兌換重分類,假設負債賬戶裡客戶有 100 USD,報價為換得 92 EUR,暫不計費用:美元憑證借記「客戶 USD 應付款」100 USD、貸記「USD 換匯橋接」100 USD;歐元憑證借記「EUR 換匯橋接」92 EUR、貸記「客戶 EUR 應付款」92 EUR。每幣種各自平衡,兩張憑證關聯同一兌換號。橋接科目代表平台承擔的幣種頭寸或在途關係,需要與真實頭寸、外部交割和費用繼續核對,不能當作憑空產生的資產。

法定會計或本位幣報表另按適用政策確認匯兌損益與捨入差額,不能直接用這個業務子賬例子替代財務總賬。

金額不要用二進制浮點累加。採用精確十進制或按幣種定義的最小單位,並把每次捨入記錄在可復現的規則裡。展示用的估值不應覆蓋原幣交易金額。

冪等、期間和沖正

賬本服務通常會收到重試、重複回調和亂序事件。冪等鍵應覆蓋賬簿、來源、穩定業務事件引用和入賬階段;部分請款或部分退款還需要獨立的子事件引用。消息投遞編號可能每次重試都變,不能拿它代替業務引用;規則版本作為歷史依據保存,不能換個版本就把同一事件重新入賬。

數據庫唯一約束與受控狀態遷移共同抵抗併發;「先查沒有、再插入」本身不夠。同鍵同載荷返回已提交結果,同鍵不同金額或幣種拒絕並記錄衝突。借貸同時重複一整張憑證仍然平衡,只有這些約束和對賬才能補上這個漏洞。

一條已經過賬的憑證不應被原地編輯。發現錯誤時,先保留原憑證,再按原業務規則生成沖正或調整憑證,並關聯原憑證號。部分退款、部分請款和手續費修正也應保留累計關係和上限。

記賬時間和業務發生時間可能不同。期間關閉後不能靜默改寫歷史;跨期修正應進入當前允許的期間,並留下原因、授權人和關聯憑證。查詢餘額時,明確是實時未結賬餘額、已過賬餘額還是某個截止時間的餘額。

超時或外部結果未知時保留待核對事件。已有充分證據的銀行到賬但歸屬不明,可以按已批准規則借記銀行、貸記暫收或待核對科目;連資金是否發生都不確定時,只保存事件和佔用狀態,不能憑空做一張最終資金憑證。已確認的早期分錄保留,之後用新證據推進到下一階段。

前提與驗收效果

做好複式賬前,至少要有這些前提:

  • 明確資金主體、賬戶所有權、幣種和本位幣範圍。
  • 規定哪些業務狀態可以生成憑證,授權、請款、清算、到賬和退款分別如何落賬。
  • 有可版本化的科目映射、費用規則、匯率和捨入規則。
  • 有穩定事件標識、冪等約束、權限和期間關閉機制。
  • 有外部對賬資料、異常處理人和可查詢的審計記錄。

從舊系統遷移還要有經核對的期初餘額:按主體、賬戶、幣種確定統一截止時間,盤點在途、佔用和未決交易,生成有來源的平衡期初憑證。不能把所有差額塞入「期初調整」來通過試算。先影子核對,再明確切換時刻與唯一寫入方,避免新舊賬重複記賬。

驗收應覆蓋:三張示例憑證重算得銀行 30 CNY、應付款 0;同一事件重放 100 次只入賬一次;可用 100 的資金池併發申請兩筆 60,最多一筆佔用成功;寫入中斷時憑證全成或全不成;關閉期間禁止無授權回寫;未知結果不會提前增加可用餘額。這裡列的是待實現賬本的驗收目標,本站沒有運行資金賬本服務。

達到效果後,團隊應該可以用同一套記錄回答四個問題:這筆錢從哪裡來、現在屬於誰、為什麼改變、怎樣證明沒有漏記或重複記。可執行的驗收包括:隨機抽一筆業務能還原事件到憑證;重放同一事件不產生新餘額變化;按幣種和賬戶重算結果與展示一致;沖正不刪除原記錄;未決狀態不會被報表誤當成已結算。

可以先讀為什麼複式賬比單邊記錄更適合業務系統,再用業務事件到最終入賬的流程圖檢查實現順序。

有具體場景,歡迎進一步交流。