ssam//commerce
Writing
2026-09-28Case studyBackendPostgreSQL

Anatomy of an installment platform

Ledger-first balances, idempotent reminders, and deliberately boring staff tooling — a walkthrough of a retail-credit operations architecture.

This is a walkthrough of the architecture. The client stays anonymous; the engineering is described as built.

Ledger first, balances derived

The obvious data model stores a balance on each account and updates it as payments arrive. It is also the model that quietly corrupts. One failed write, one manual "fix", and the number everyone trusts is wrong in a way nobody can prove.

Instead, the platform keeps an append-only ledger of charges and payments. A customer's balance is always derived from the ledger — never stored as editable truth.

-- balances are a query, not a column
SELECT account_id, SUM(amount_cents) AS balance
FROM ledger_entries
GROUP BY account_id;

Corrections are new ledger entries that reference what they correct — not edits to history. When accounting asks "how did we get to this number?", the answer is a query, not an investigation.

Schedules are plans, not promises

An installment schedule is generated at account creation: dates, amounts, status. But the schedule is a plan. What actually happened lives in the ledger and in payment events.

Keeping those separate means the awkward realities — early payments, partial payments, restructured plans — become normal operations instead of schema surgery.

Reminders: late is acceptable, duplicate is not

The reminder pipeline has one hard invariant: a reminder may be late, but it must never fire twice for the same due date.

  • Each reminder carries an idempotency key: account + due_date + reminder_type.
  • Delivery goes through a queue with retries.
  • Every send is logged against the account — who was contacted, when, about what.

The audit log matters more than it sounds. In collections, "the system texted them five times" is the kind of bug that costs trust with exactly the customers who are hardest to win back.

Staff tooling stays boring

The back office is server-rendered tables and explicit forms. No optimistic UI, no drag-and-drop rearranging of someone's debt.

Boring is the requirement: a staff member at the end of a long day should be able to adjust a schedule, record a payment, and leave a note — with every action producing a clear, attributable record.

The deployment mirrors the values

The whole platform ships as one isolated stack: web, jobs, Postgres, proxy, backups. Nightly dumps run in their own container, and restores are rehearsed.

A platform that guards other people's money should be legible end to end — the boring kind of system where nothing surprising is happening on purpose.

// Engage

Have a system to build, improve, or rescue?

Tell me what you're working on. I read everything myself and reply to serious inquiries.