The problem
Every new product needed billing, and every new product was implementing billing from scratch. Webhook handling, trial logic, plan upgrades, proration, dunning emails — the same code, slightly different bugs, in twenty places. The cost wasn’t the first build; it was the maintenance tax across the portfolio.
What we built
Billing API is a Fastify service backed by Postgres that owns customers, plans, subscriptions, invoices, and webhook reconciliation. Every product calls a small set of endpoints — create checkout, get entitlements, cancel subscription — and never touches Stripe directly. The service handles retries, idempotency, plan changes, and downstream lifecycle email through Brevo. Jest tests cover the state machine.
The automation angle
The flagship automation is the dunning flow: failed payment triggers a sequence of escalating reminders with grace periods, and reaches a hard cancel only after the configured policy is exhausted. Win-back happens automatically when a cancelled customer renews. The sequence is product-specific without each product having to understand it — the policy lives once, in the API.
How it’s used
- Every paid product in the Dainty Trading portfolio.
- Internal admin tools for support, refunds, and entitlement overrides.
- Reporting rolling up MRR, churn, LTV across the whole portfolio in one place.
What it taught us
That billing is a state machine, and pretending otherwise costs you sleep. Modeling the lifecycle explicitly — with named states, named transitions, and a strict rule that webhook events can only mutate state through known transitions — turned a class of intermittent bugs into impossibilities. We carry that pattern into every client engagement that touches money.