CodeLabProsCodeLabPros — homeMenu

Payment systems

School fees, reconciled across many schools at once

A fee platform for schools that replaces the manual matching of payments to pupils, and the arguments about balances that follow from getting it wrong.

Our role
Architecture and payments
Built with
Next.js · PostgreSQL · Mobile money · Docker
Where it got to
Shipped
karo — reconciliation (illustration)Illustration
An illustration of the Karo reconciliation screen: incoming payments matched to pupils, duplicates flagged as already recorded, and two payments routed to a person because the guardian has more than one child enrolled
Every payment matched to a pupil, however it arrived
01

The idea

Let parents pay school fees from their phones, and give the school records it can trust, instead of a bursar reconciling a statement against a fee register by hand every week.

02

What it actually needed

The payment was never the hard part. What arrives is an amount, a phone number and a short reference, and somebody at the school was turning that into a sentence by hand: this is Amina's term-two fees, and her balance is now zero. At volume that work goes wrong quietly. A parent gets quoted a balance they have already paid, produces a receipt from their phone, and now two people are hunting through references and documents to establish who is right. The product is not collecting the money. It is making the record defensible so that conversation stops happening.

Live, as a parent paysA statement, uploadedBy hand, at the officeOne matching decisionTHE SAME CODE, ALWAYSMatched to a pupilWITH WHAT MATCHED IT, KEPTHanded to a personWHEN IT GENUINELY CANNOT TELL
If the file import and the live feed can disagree about who paid, the ledger becomes a matter of opinion. The first person to find out is a parent being told they still owe money.
03

What got built

Three ways to pay, one answer
Money arrives live as a parent pays, in a file when a bursar uploads a statement, and by hand when someone pays at the office. All three go through the same decision, so the ledger cannot depend on which door the money came through.
Paid once, however many times it arrives
A statement gets re-uploaded because nobody was sure the first one worked. A provider retries a message it thinks failed. The payment is recorded once regardless, and the parent is not billed twice for it.
Schools that cannot see each other
One platform, many schools, and no route by which one school's accounts can surface inside another's. It is enforced in two independent places, not one, so a single mistake is not enough to expose anything.
A queue for the payments nobody can place
Where a guardian has one child at the school, the match is obvious and the system makes it. Where they have three, it does not choose. The payment goes to a person with the context already assembled.
A balance you can defend
Every payment record carries where it came from and what matched it to a pupil, so when a parent disputes a figure the answer is on the screen instead of in somebody's drawer.
04

The decisions that shaped it

Refuse to guess between siblings

There were reasonable rules available: split it evenly, clear the oldest arrears, apply it to the most recent invoice. Each ships in an afternoon and each survives a design review. Each is also capable of crediting one child and leaving another in arrears, which nobody notices until a pupil is sent home from school. Undoing it means reversing money against a ledger and explaining it to a parent who is, correctly, furious, and who cannot take their business elsewhere because it is their child's school. The alternative costs somebody fifteen seconds of clicking the right name.

One decision, three doors

It would have been faster to let the live feed and the file import each do their own matching. If those two can ever disagree about who paid, the ledger becomes a matter of opinion, and the first person to discover that is a parent being told they still owe money they have already sent.

Enforce isolation twice

Once in the application and again underneath it. Doing it once is enough right up until the week somebody adds a report, an export or a route and forgets. Nobody finds out until a school sees a number belonging to a different school.

05

In production

Built and delivered. This is a client system, so what it does is described here and how it is wired is not.

06

What this is worth

A fee balance that can be defended. Every payment is matched to a pupil with the evidence attached, so a disputed figure is answered from the record instead of from memory.

In more detailfor whoever wants it

A parent pays school fees from their phone. What arrives at the other end is an amount, a phone number and a reference. The platform has to turn that into this is Amina's term-two fees, and her balance is now zero.

Doing that once is straightforward. Doing it for many schools at once, where each school's books must be invisible to every other, and where the money is real and the families are real, is the product.

Money arrives three different ways

Live, as the payment is made. In a file, when a bursar uploads a statement at the end of the week. And by hand, when someone pays at the office.

All three have to reach the same answer, so all three ask the same question of the same piece of code. One decision, three doors. If the file import and the live feed can disagree about who paid, then the ledger is a matter of opinion. The first person to notice will be a parent who has been told they still owe money.

The same discipline covers duplicates. A statement gets re-uploaded because the bursar was not sure the first one worked; a provider retries a message it thinks failed. The payment is recorded once regardless.

Where it deliberately stops

When a guardian has one child at the school, the match is unambiguous and the system makes it. When they have three, it does not choose. It routes to a person.

There were reasonable rules available: split evenly, oldest arrears first, most recent invoice. Any of them could have shipped in an afternoon and each is defensible in a design review.

Why none of them shippedA wrong guess credits one child and leaves another in arrears. Nobody notices until a pupil is sent home from school. Undoing it means reversing a payment against a ledger and explaining it to a parent who is, correctly, furious. They cannot take their business elsewhere, because it is their child's school. The alternative costs somebody fifteen seconds of clicking the right name.

Isolation between schools is treated the same way: enforced in the application and enforced again underneath it, so a single mistake in one layer is not enough to show one school another's books. Every payment record carries where it came from and what matched it, so any figure on a report can be traced back to the message that produced it.