Context
The money-handling core of the production platform I run for an online school, extracted into a standalone, framework-free TypeScript library on plain PostgreSQL. The production code is private; this repository is the part worth reviewing, with every lock and constraint visible as plain SQL.
Billing: exactly-once charges under concurrency
Staff mark, re-mark, clear and cancel lessons at the same time. The student must be charged exactly once, and the ledger must always agree with the attendance journal.
- Append-only ledger. A trigger rejects
UPDATEandDELETE; corrections are reversal rows. - One lock order everywhere. Writes for a student take
FOR NO KEY UPDATEon the student row, thenFOR SHAREon the lesson. Cancellations takeFOR UPDATEon the lesson, so "is this lesson cancelled?" cannot change between the check and the write. - Why
NO KEY UPDATE. A cancellation writing a reversal needs aKEY SHARElock on the student via foreign-key checks.FOR UPDATEwould block it and deadlock;FOR NO KEY UPDATEallows it.
Payments: a webhook that trusts nothing
Rate limit → constant-time secret check → provider IP allowlist → re-fetch the payment from the provider's API and validate it at runtime → match owner, amount and currency against our own payment intent → credit idempotently with ON CONFLICT DO NOTHING on the provider's payment id. Ten identical deliveries credit once.
Auth: enumeration-safe login
Unknown emails still cost one bcrypt compare, so response time does not reveal who has an account. Login limits run per (IP, email) and per email across IPs, checked and counted atomically under a per-email advisory lock.
How it's tested
- Deterministic interleavings. A test pauses one operation right after it takes its locks, starts a competing one and asserts it waits — one test per lock, plus the deadlock case.
- Bursts. 20 simultaneous marks of one lesson, 10 identical webhook deliveries, 50 parallel login guesses: the ledger and the limits come out exact.
- Randomized ledger invariants. Seeded runs of 200 random operations, five at a time in parallel, checking eight invariants after every batch.
- Mutation check. A script removes each defence in turn — either lock, the cancelled-lesson guard, the idempotency key, the dummy bcrypt compare and others — and the suite has to fail for all 12.
A bug the randomized test caught
The first run failed on "a cancelled lesson nets to zero for every student". A student was charged, the lesson was cancelled (charge reversed), then the attendance mark was cleared — reversing the charge a second time. The student was credited for a lesson they were never charged for.
The fix: clearing refuses a cancelled lesson, and the check runs under the lesson's share lock so a cancellation committing at the same moment cannot slip between check and write. Both the plain case and the race are now tests.