Building payments for a world that pays by cheque
Half the payments content online assumes cards and instant settlement; my customers send a cheque, and the data model has to fit how money actually moves here.
Almost every payments tutorial you’ll read opens with a card form and ends with a webhook that fires the instant the money clears. I built a B2B system where not a single payment works that way. My customers pay by cheque, or a bank transfer that lands days later, and a finance admin sits down and records it by hand.
That one fact quietly invalidates most of what the internet will teach you about modeling money. No gateway, no instant settlement event, no clean one-to-one between an invoice and a payment. Just a person, a cheque book, and a reconciliation on its own schedule. Build for the Stripe demo and you’ll ship a beautiful system that can’t represent a single real transaction your client makes.
What “one invoice, one payment” gets wrong
The seductive shape is an invoice with a paid boolean and an amount. It’s clean, it’s what every example shows, and it falls apart on contact with the first real cheque.
Here’s the reality the model has to hold:
- One invoice gets paid in pieces. A customer settles half this month, half next month. The invoice isn’t paid or unpaid; it’s partially paid, and the balance is a running figure, not a flag. A payment is its own record pointing at an invoice, and the invoice’s status is derived from the sum of its payments, never stored as a single truth that can drift.
- One payment covers many invoices, and one invoice gets many payments. A finance team writes a single cheque against three outstanding invoices, or sends two cheques toward one big one. The honest model is many-to-many, an allocation table that says this much of this payment went to that invoice. The day I tried to skip that and pretend it was one-to-one, I was already losing money to invoices that looked unpaid because their cheque had been split.
- Every payment carries the paperwork. Bank name, cheque or reference number, the date it was actually deposited (not the date someone typed it in). Where settlement is manual and slow, that reference number is the only thread connecting your record to the bank’s. Drop it and a dispute next quarter has nothing to pull on.
A payment you can’t undo isn’t a real record
The other thing the card-first model never teaches you: money gets recorded wrong, and you have to be able to say so without deleting the truth.
An admin fat-fingers an amount, records a cheque that later bounces, or applies a payment to the wrong customer. In a gateway world the processor reverses it for you. Here, the reversal is also a human action, so it has to be first-class in the model. You don’t delete the payment. You void it, with a stated reason and the name of whoever did it, and keep the original row sitting right there in the history.
If your payments table only knows how to add money, it isn’t a ledger, it’s a wishlist. A real money system has to record being wrong as gracefully as it records being right.
That single rule, void-don’t-delete, is the difference between a finance admin who trusts the system and one who keeps a parallel spreadsheet because yours can’t explain last Tuesday.
Model the market you actually have
None of this is exotic. It’s just unfashionable, because the loud examples all come from markets where a card clears in two seconds and the gateway hands you a tidy event. Plenty of the world doesn’t run that way, and pretending otherwise ships a payments feature the people paying you can’t use.
So before I write a line of schema, I ask one boring question: how does money actually move between these two parties? Not how Stripe says it moves. How the cheque gets written, who records it, when it clears, what happens when it bounces. The answer is the spec. A partial cheque against two invoices, captured by hand with a reference number, voidable with a reason, is not an edge case in my market. It’s the main case. Build for that, and the card flow becomes the easy thing you add later, if you ever need it at all.