By Umair Malik ·Principal Software Engineer at Techfoot
Umair leads engineering on Billspree—billing and AR for recurring-service businesses across utilities, submetering, telecom, and subscriptions.
TL;DR — An invoice records what a customer was charged. What they owe, the receivable, changes with every payment, credit and late fee, and the invoice itself must never change. So where should a billing system keep the receivable? Ruling out the obvious designs one at a time (amounts kept open on each invoice and payment, or a single balance per account) leads to an append-only log of everything that changes what is owed, with a derived balance. By the end it is clear it was never a log: it is the customer subledger, which accountants have kept for centuries.
What Are Receivables?
When a business delivers a service it has not been paid for yet, the customer owes it money. That obligation, and the business’s right to be paid, is a receivable. An invoice is what usually creates one: it says what the customer was charged for the services on it, how much those services added to what they owe, and nothing more. It is not a total.
From then on, other documents change what is owed. Some add to it, some take from it, and every one of them is a separate piece of paper:
InvoiceCharges for services deliveredowed ↑
Credit noteTakes back part of a chargeowed ↓
Debit noteAdds a charge that was missedowed ↑
Late feeCharged when a bill is paid lateowed ↑
Payment receiptMoney receivedowed ↓
RefundMoney paid back to the customerowed ↑
Returned paymentA payment that bounced or was reversedowed ↑
Write-offA debt given up as uncollectableowed ↓
Common documents that change a receivable. Each one is issued once and never edited.
So what a customer owes at any moment is not on any single document. It is what all of them add up to.
The Receivables Ecosystem
Here is how those documents meet in practice. The example uses only a few of them, and a real account sees more: refunds, returned payments, write-offs, and whatever else a business needs. A handful is enough to show the problem.
On March 1, a customer gets an invoice for $100. Over the next two weeks, four perfectly ordinary things happen. The billing team finds the customer was overcharged and issues a $20 credit note. The customer pays $50. A charge that should have been on the invoice turns up missing, so a $5 debit note follows. And when the rest is still unpaid after the grace period, a $3 late fee is added.
Mar 1InvoiceMarch's bill· INV-101+$100
Mar 6Credit noteUsage overcharged· CN-1−$20
Mar 9Payment receiptBank transfer· RCPT-1−$50
Mar 12Debit noteCharge missed on the invoice· DN-1+$5
Mar 16Late feeUnpaid after the grace period· LF-1+$3
March: the invoice, then four more documents, each referring back to INV-101. None of them changes the invoice.
The customer now owes $38. The invoice still says $100. Neither number is wrong: the invoice says what was charged, the $38 is what is owed after everything that followed.
Each of these documents points back to INV-101, and none of them changes it. That holds for every document here: once issued, a document never changes. If one turns out to be wrong, another document corrects it.
And it doesn’t stop at March. On April 1 a second invoice goes out for $100, and on April 4 the customer pays $150 in one go. That payment clears the $38 still owed on the first invoice and the $100 on the second, and leaves $12 that belongs to no invoice at all: the customer’s money, waiting for a bill that hasn’t been made yet.
Apr 1InvoiceApril's bill· INV-102+$100
Apr 4Payment receiptPays both bills, with $12 to spare· RCPT-2−$150
April: a new bill, and one payment that covers both bills and more.
That is the ecosystem a billing system has to keep up with: bills that never change, and a stream of credits, debits, fees and payments that change what is owed, some tied to one invoice, some to several, some to none. Where should the system keep track of what the customer owes?
The Obvious Designs
There are two obvious ways to do it, and both are reasonable.
Track what is open on each document. Give every invoice an outstanding amount that goes down as credits and payments arrive. Give every payment and credit an “unapplied” amount for whatever has not been used yet, and keep a record of which payment went to which invoice. After April 4, both invoices show nothing outstanding and the payment shows $12 unapplied. This is the idea behind what accountants call open-item accounting: every invoice, payment and credit stays open until it has been fully matched, and the customer’s total is the sum of what is still open.
Track one balance per account. Skip the matching. Each account has a single balance: invoices add to it, payments and credits take from it, and April’s $150 simply takes it from $138 to $12 below zero. This is balance-forward: previous balance, plus new charges, minus payments. It is simpler, and a payment never has to name an invoice. What it cannot say is which invoice is still unpaid.
Open items
A stored amount on each document
INV-101outstanding$0
INV-102outstanding$0
April paymentunapplied$12
Sum of open itemscalculated−$12
Balance-forward
One stored balance per account
Previous balance$38
New charges (INV-102)$100
Payments−$150
Balancestored−$12
The same customer on April 4, stored both ways. Both say the customer is $12 in credit. Only the first says where that comes from.
Both solve the same problem, what is owed. Open items simply keep more detail: which invoices make up the total. A system can also keep both, open items for matching plus a balance on the account as a quick total, which is really just the open items added up and saved. And both share one design choice. Both store the result of what happened, a number per document or per account, and overwrite it every time money moves.
The Mutable Balance
Every one of those stored numbers works like a whiteboard. When something changes, you wipe the old figure and write the new one. The board always shows the right number for today.
Customer balance
$100$80$30$35
$38
Correct today. No record of how it got here.
A running total works like a whiteboard: always current, and every earlier number has been wiped.
The trouble starts the first time someone asks about anything other than today.
The customer calls: “I paid fifty dollars. Why do I still owe thirty-eight?” The board says $38. It can’t say why.
They push back on the late fee: “Was I even behind when you added it?” That depends on what they owed on March 16, and that number was wiped long ago.
The owner asks: “How much were customers owing us at the end of March?” Every outstanding amount and every balance has been rewritten since. Unless someone copied them down on the 31st, that figure is gone.
Open items make it worse, not better: there are simply more boards. When the $150 arrives, the first invoice goes from 38 to 0, the second from 100 to 0, the payment’s unapplied amount from 150 to 12, and later from 12 to 0. When a payment applied to the wrong invoice is moved, the old application is overwritten and the mistake disappears.
And there is a quieter failure. Two writers update the same number at once, a payment posting while the late-fee job runs, and one write silently overwrites the other: the classic lost update. Nothing errors. The number is just wrong, with no trail to show how.
Where the number lived was never the problem. Keeping only the result was.
An Append-Only Log
The next idea is a log. Something happens, so log it: one row per event, in order, with a running balance alongside. It is the right instinct. It is also what a neighbourhood shop that lets regulars buy on credit does: a notebook with a page per customer, every purchase and every payment on a new line, nothing ever rubbed out. Ask why you owe what you owe, and the shopkeeper turns the notebook round and points down the page.
A log is the right shape. Turning it into something you can bill from takes a few decisions about what goes in it, what it may never do, and what it should not store.
Here is our customer’s account kept that way:
Customer account
Date
What happened
Amount
Balance
Mar 1
Invoice INV-101
+100
100
Mar 6
Credit note: usage overcharged
−20
80
Mar 9
Payment received
−50
30
Mar 12
Debit note: missed charge
+5
35
Mar 16
Late fee
+3
38
Apr 1
Invoice INV-102
+100
138
Apr 4
Payment received
−150
−12
↳ applied to INV-101
38
↳ applied to INV-102
100
↳ held as credit
12
Every change on its own line, nothing rubbed out. The indented lines record how the April payment was split: entries with no document of their own.
One line for every event that changes what the customer owes, in the order it happened, each one pointing to the invoice, note or receipt behind it. Nobody writes the total down. It is simply what the lines add up to.
Two rules hold it together. The log is append-only: entries are added, never updated or deleted. And the balance is derived, never stored: it is computed from the entries whenever it is needed, so it can never drift away from them. If that sounds like event sourcing, it is.
One part of the instinct needs care: the running balance. It is tempting to store it on each row, and it looks harmless until an entry arrives out of order. Suppose a bank transfer comes in on March 30 but is only recorded on April 2. It belongs before every row dated after March 30, so every running balance after it is now wrong. Fixing them means rewriting rows, which is exactly what an append-only log must never do. So the running balance is computed when the log is read. If it is stored for speed, it is a cache that can always be rebuilt from the entries, never the record itself.
The date that decides where an entry sits, the day it counts rather than the day it was written, is its posting date. It can reach back into a month that is still open, but never into one that has been closed, or the month-end figure already reported would change.
Now every question from the whiteboard has an answer on the page. Why $38? Read the lines: $100 billed, $20 credited, $50 paid, $5 added, $3 late fee. Was the late fee fair? Just before it, the customer owed $35, so yes. What were they owing at the end of March? Add the lines up to March 31: $38. The leftover $12 from April? It is the total after the last line, waiting to come off the next bill. And when a mistake is corrected, it is corrected with a new line, so the mistake and the fix both stay visible.
And the two families from earlier become views. Record each application of a payment or credit as an entry too: “April 4, $38 of the payment applied to INV-101”, “April 4, $100 applied to INV-102”. Now the amount still outstanding on an invoice, the amount still unapplied on a payment, and the balance of the account are all sums over the same entries, never stored. Open items and balance-forward stop being competing designs. They are two ways of reading one log.
It Was Never a Log
Step back and look at what the log has become. It has a job nothing else in the system does: it is the one place that records what every customer owes, and why. It has rules of its own: append-only, posting dates, closed months that never reopen, a reference to evidence on every line. And everything else in billing reads from it: the arrears on the next bill, the check before a late fee, the customer’s statement, the month-end figure. It is no longer a table inside the billing code. It is a module in its own right.
And it is not really a log. An application log is there for debugging. It can be sampled, rotated, truncated or lost, and nothing breaks. This record is the opposite. If a line goes missing, a customer is billed wrongly. If a line changes, the books no longer prove anything. It is not a trace of what the system did. It is the record of what is owed.
Accountants have kept exactly this record, a page per customer, for about five hundred years. They call it the customer subledger. Nobody needs to invent it. Every design above was a step on the way back to it, and that, more than anything, is its birth: not designed from scratch, but arrived at, one ruled-out shortcut at a time.
One Customer, Several Subledgers
Once the subledger is a module, it can be configured like one, and the most useful setting is how many a customer has. Suppose the same customer rents an apartment and, separately, a storage unit, each with its own bill. In one subledger the two run together. When the customer pays $40 “for the storage”, nobody can say afterwards which one was paid, and a late fee meant for the apartment can end up charged on money owed for the storage.
Apartment subledger
Date
What happened
Amount
Balance
Mar 1
Invoice
+60
60
Storage subledger
Date
What happened
Amount
Balance
Mar 1
Invoice
+40
40
Mar 9
Payment
−40
0
Customer total (60 + 0, computed)$60
One subledger per service. The customer's total is the two added together.
Give each service its own subledger and the problem goes away. Each keeps its own entries, its own arrears, its own due dates and its own late fees. The customer can still see one total, but that total isn’t stored either: it is the subledgers added together.
Whether a customer has one subledger or one per service is a setting, not a rebuild. One business needs each service to stand alone; another bills everything together and only cares about the total. The rules stay the same either way. Only how the entries are divided changes.
Design Principles
Each of these is forced by an example above.
Documents are immutable
An issued invoice, credit note, debit note or receipt never changes. Corrections are new documents that reference the one they correct.
The receivable is a separate record
What is charged lives on documents. What is owed lives in its own record, because it changes when the documents cannot.
Entries are append-only
Every event that changes what is owed is a new entry pointing to the document or allocation behind it: invoices, notes, payments, late fees, refunds, reversals, and the allocation of payments and credit. Nothing is updated or deleted; an error is fixed by a new entry.
Entries carry a posting date
The date an entry counts, which can be earlier than when it was written while a month is still open, but never inside a month that has been closed. Closed periods stay as reported, and any past balance can be reproduced.
Subledgers are configurable
A customer can have one subledger, or one per service. The rules are the same either way, and the customer’s total is computed across them, never stored.
Every amount is computed from the entries
The account balance, what is still open on each invoice, what is still unapplied on each payment, and the running balance on each line are all sums over entries, for today or any past date. Any of them can be cached for speed, but the entries are the record.