Skip to content
All projects

PublishedFeatured

Membership finance back office

The system of record for a vacation-ownership business: the contracts members sign, the interest that accrues on them, the annual fees they owe forever after, and the nightly close that moves all of it forward one day.

  • NestJS
  • TypeScript
  • PostgreSQL
  • TypeORM
  • BullMQ
  • Redis
  • Next.js
  • React
  • AWS Lambda
  • AWS S3
  • Docker

Overview

Selling a membership is the easy part. What follows is a loan that runs for years, an annual fee that never stops, and a collections process that has to be fair, explainable and auditable in several languages. This is the back office that carries all of it — used by finance, collections and admin staff, never by members directly.

A member is onboarded from the sales system with a payment plan and an amortisation schedule. From then on the system accrues interest, marks payments due, notices when someone is late, opens a case, sends the reminder, takes the payment, and applies relief when it is granted — deferment, reduced payments, a restructured contract.

The parts that were interesting to build

A nightly close that can be resumed. Everything that moves a day forward happens in one run: five phases, twenty-six steps, from reconciling yesterday's payments through interest accrual, due dates and late detection to the annual-fee instalments. A batch like this fails eventually — a bad row, a slow dependency — and re-running the whole thing is not an option when phase two has already written interest. Each phase therefore reads a run-type setting: all contracts, only these, everything except these, only the ones left out of date, or skip. Progress is checkpointed as it goes, so a failed run is resumed on the subset that still needs it rather than restarted.

Documents that render Thai correctly. Statements, reminder letters and invoices come from Word templates that the business edits itself. Rendering them is a two-stage pipeline: merge fields are resolved, then a container converts the document to PDF. The hard part was typography. Word keeps two font sizes on every run of text — one for Latin, one for complex scripts — and applies the second to Thai glyphs. A template that looks right in Word therefore renders Thai at the wrong size once converted, silently. The fix reaches into the document XML to normalise the two size properties and force a font that actually ships in the renderer, so what finance previews is what the member receives.

Money that never touches a float. Every amount is decimal arithmetic, end to end. Interest, instalments and allocations are computed with the same financial functions the business already used in its spreadsheets, so a figure produced by the system can be checked against the one an accountant works out by hand.

Shape of the system

Eleven deployables around one database: an API that carries the domain, a worker that runs the close, a thin gateway that validates onboarding payloads and queues them, a document renderer, a notification path, and a static staff application. Backend services never call each other over HTTP — they hand work to a queue, which is what lets the close run for an hour without holding anything open.

Scale

  • 11 deployable services · 126 entities · 188 migrations · 536 API endpoints
  • 46 queues and 46 processors · 8 scheduled jobs · a 26-step nightly close
  • 64 screens · documents generated in a dozen languages · 8 external integrations
Status
Published
Published
Sep 12, 2026
Stack
NestJS · TypeScript · PostgreSQL · TypeORM · BullMQ · Redis · Next.js · React · AWS Lambda · AWS S3 · Docker