JR
All writing
Fintech

Money is an integer

Floats, rounding, and the reconciliation that finds out nine months later. How to build a ledger that a finance team will actually trust.

Published
18 Sep 2026
Length
9 min read
Written by
Sodiarc JR

The first rule of building anything that touches money is the one most often broken in the first week: store money as an integer number of the smallest unit, never as a decimal or a float. Paise, not rupees. 125000, not 1250.00.

This is not pedantry, and the reason is not the famous floating-point example. It is that a system storing 1250.00 has already accepted that 1250.005 is representable, and once a half-paisa can exist, somebody has to decide what happens to it. That decision will be made in several places, differently, by people who never discussed it — and the differences will surface nine months later as a reconciliation that is off by an amount too small to investigate and too persistent to ignore.

Integers do not have this problem because the question never arises. Rounding has to happen explicitly, at a named moment, in one place you can point at.

A ledger is not a balance column

The second structural decision matters more, and it is the one that separates a system a finance team trusts from one they shadow in Excel.

The naive design stores a balance and updates it. UPDATE wallets SET balance = balance - 12000. It is obvious, it is fast, and it destroys information on every write. When the balance is wrong — and it will be wrong, once — there is no way to find out why, because the history of how it got there was overwritten each time.

The durable design stores entries and derives the balance. Every movement is an immutable row: what moved, how much, when, why, and what it referenced. The balance is the sum. It is never stored as a source of truth, only ever cached with a way to rebuild it.

This has a property that sounds abstract and turns out to be the entire value: any balance, at any past moment, is recomputable. “What was this driver’s balance on the 14th, before the debit?” is a query, not an investigation. Every dispute becomes answerable, and answerable quickly, which is most of what finance actually wants from software.

Every entry needs a reason and a reference

An entry that says “−₹120” is nearly useless. An entry that says “−₹120, DAILY_RENT, assignment 4471, 18 Sep” can be explained to the person it was taken from. The reason code is what lets you answer “what did we collect in rent versus deposits versus penalties” without anybody parsing free text.

The transaction boundary is the whole game

Debiting a wallet is three things: read the balance, check it is sufficient, write the entry. If those three are not inside one database transaction holding a lock on the row, two simultaneous debits can both read the same balance, both decide it is sufficient, and both write.

This is not a rare race. It is what happens the first time a scheduled job and a user action touch the same wallet in the same second, which at a few hundred accounts is a weekly event rather than a theoretical one.

The defence is to put the money movement in a database function that does all three inside a transaction, and to let nothing else write to the ledger. On the daily-wallet system we built for an EV rental operator, that is an enforced boundary: the orchestration layer calls a Postgres function that debits inside a transaction and returns the new balance. No workflow, and no application code, computes a balance itself. The rule is short enough to state and check — money moves in one function, or it does not move.

Idempotency, or how to avoid charging twice

Any job that moves money will, one day, run twice. A deploy restarts a worker mid-batch. An operator triggers a re-run after an outage. A webhook is redelivered.

The defence is an idempotency key derived from the thing being done rather than from the attempt — driver + date + DAILY_RENT — with a unique index on it in the ledger. The second write fails against the index and the operation is a no-op.

The freedom this buys is worth more than the errors it prevents. A system where re-running is safe is a system you can operate calmly: after an incident you re-run the day and inspect the result, rather than reasoning at 2am about which of four hundred accounts were half-processed.

Six questions for any system holding customer money
  • Is money stored as an integer of the smallest unit?
  • Can you reconstruct any account’s balance as at any past date?
  • Does every entry carry a reason code and a reference to what caused it?
  • Is there exactly one code path that writes to the ledger?
  • If tonight’s job runs twice, does anyone get charged twice?
  • When does the gateway’s view get compared against yours, and who sees the difference?

Reconcile nightly, and show the difference

That last question is the one most often left until it is urgent. Your ledger is your opinion. The payment gateway has its own, and the bank has a third. They will disagree — settlement timing, a webhook that never arrived, a refund processed out of band.

The disagreement is not the problem. Not knowing about it is. A nightly job that pulls the gateway’s settled transactions and diffs them against the ledger turns a month-end mystery into a task somebody picks up over coffee. On the wallet system above it runs at 02:00 and files anything unmatched as a finance task before the office opens.

And resist the urge to reconcile discrepancies into a single comforting number. Show both figures and the gap. The gap is information — usually it is money that has moved in the world and not yet in your system, which is exactly the thing you built the job to find.

Why regulation pushes in the same direction

Two Indian requirements land on the same design, which is convenient. RBI card-on-file rules mean you store provider tokens and never card data — so the ledger references a payment method it does not itself hold. The DPDP Act’s erasure obligations mean personal data needs one home that everything else points at, rather than copies scattered through transaction rows.

Both are easier in a normalised, entry-based ledger and painful in a denormalised one where the customer’s name and phone number were copied into every transaction for convenience. As is often the case, the compliant design and the correct design are the same design — which is a good reason to make the decision once, at the beginning, on the day it costs nothing.

The offer

Find out where the money is going.

A two-week operations leak audit. We map where money, time and proof go missing between your systems, and come back with numbers: what is leaking, where, and what it takes to close it. Applies against the build if you continue.