Engineering

Why a wallet balance should never be a column

A balance you update is a balance you have to trust. A balance you derive from postings is one you can prove. Here is what that difference looks like in production.

PQPanoQore engineering
, 7 minute read
More posts
Engineering

The column everyone starts with

The first version of almost every wallet is a table with a balance column. A credit adds to it; a debit subtracts. It works in the demo, and for a while in production.

Then two requests arrive in the same millisecond. Or a payout times out and is retried. Or someone fixes a customer complaint with a quick update in the database. The column is now a number, and nobody can say how it got there.

Postings first, balances second

In a double-entry ledger, money never appears or disappears. Every movement is an entry with at least two postings, and the debits equal the credits in every currency the entry touches. A balance is just the sum of an account’s postings.

Debit
Adaeze Stores wallet
LIA:WAL:NGN:0193
NGN 350,025.00
Credit
Bank settlement NGN 350,000.00
Fee income NGN 25.00
A payout with a fee. Three postings, one entry, balanced in NGN.

Because postings are only ever added, the balance at any moment in history can be recomputed and checked. A correction is a new entry that points at the old one, so the mistake and the fix are both on the record.

But isn’t summing slow?

Not if you don’t do it on every read. PanoQore keeps a running balance per account, updated in the same database transaction as the postings, with a version number for optimistic locking. The derived balance is the cache; the postings are the truth. A nightly job re-sums every account and alerts if any cached balance disagrees.

The derived balance is the cache. The postings are the truth.

What we’d tell our past selves

  • Use integer minor units as strings. Floats will round a kobo away eventually.
  • Put the idempotency key on the entry, not the HTTP request, and keep it long enough to survive a weekend.
  • Never let a balance go negative by accident. Make overdraft a limit you set, not a bug you find.
  • Give every account a code a human can read, like LIA:WAL:NGN:0193. Your operations team will thank you.

The concepts page walks through entries, holds and balances in the API.

Try it on your own books.