Designing a Double-Entry Wallet Ledger for Fintech Startups
Every payments or lending startup needs a ledger. A simple balance column is not enough: it invites race conditions, makes audit trails impossible, and breaks under reconciliation. This note covers the foundational double-entry model and how to implement it.
Debit/credit model
Every transaction is recorded as paired debits and credits. The sum of debits equals the sum of credits for each transaction. Balances are derived by aggregating the journal; they are not updated in place.
-- Journal entries (immutable)
INSERT INTO journal (tx_id, account_id, type, amount_cents, currency)
VALUES
('tx_1', 'wallet_A', 'DR', 10000, 'USD'),
('tx_1', 'wallet_B', 'CR', 10000, 'USD');Balance calculation strategy
Balance = sum(credits) − sum(debits) per account and currency. Compute from the journal or maintain materialized views/caches that are updated only by appending to the journal and recomputing. Never update balance rows in place.
Preventing negative balances
Enforce at transaction time: before appending a debit, check that available balance (balance − holds) is sufficient. Use a single serializable transaction or optimistic locking so concurrent debits cannot overdraw.
Reconciliation principles
The journal is the source of truth. External statements (banks, gateways) are matched to journal entries by reference id, amount, and date. Mismatches go to an exception queue. Reconciliation runs in batches with clear cut-off times.
Why not a simple balance column
A single balance column forces in-place updates. Under concurrency you get lost updates or race conditions. There is no audit trail. You cannot reconcile to external records. Double-entry gives you consistency, auditability, and a path to scale.
Book Architecture Strategy Call
Schedule a call →