EliconStart a project →
← ALL WORK

CASE 10

8 weeks

Multi-Tenant Billing Portal for a SaaS Startup

Billing stopped being an existential risk.

  • Next.js
  • Postgres
  • Stripe
  • Redis
Sector
B2B SaaS
Stage
Series A, ~600 accounts
Before
Homegrown billing, manual invoices
Engagement
Rescue + rebuild, 8 weeks
Migration
Zero billing downtime

A Series A SaaS had a homegrown billing system quietly losing money — missed dunning, hand-written invoices, proration computed three different ways. We replaced the engine with a Stripe-backed one, piped real usage metering into it, and gave customers self-service, without a single day of billing downtime.

THE PROBLEM

What was going wrong.

Homegrown billing was breaking in subtle, expensive ways: missed dunning, manual invoices, proration mistakes, and no self-service for customers.

Failed payments were retried by a cron job that had silently stopped firing after a deploy months earlier. Nobody noticed, because nobody had a dashboard for it, and involuntary churn was being read as ordinary churn.

Mid-cycle plan changes were prorated by a function written twice — once in the API and once in the admin panel — which disagreed. Finance reconciled the difference by hand and by feel.

Every enterprise invoice was assembled manually in a spreadsheet, so month-end consumed several days of the founder's time and produced numbers that never quite matched the ledger.

CONSTRAINTS WE WORKED UNDER

  • ·Billing cannot go down. Money had to keep moving throughout the migration.
  • ·Existing subscriptions, trial states, discounts and grandfathered plans had to survive intact — some accounts were on terms that no longer existed.
  • ·Revenue reporting had to reconcile with the general ledger from day one; finance would not accept a system that needed adjustment entries.
  • ·The team is four engineers, none of whom wanted to own billing internals ever again.

WHAT WE DID

The short version.

  1. 01

    Replaced the custom billing with a Stripe-backed engine — subscriptions, usage metering, proration handled correctly.

  2. 02

    Customer self-service portal: update card, download invoices, upgrade/downgrade plans without emailing support.

  3. 03

    Usage metering piped into Stripe from their event stream so invoices are always right.

  4. 04

    Dunning and retry schedules tuned against their historical churn data.

ARCHITECTURE

How it's built.

01

Billing engine

Stripe as the pricing, proration and invoicing authority, with a thin local mapping between tenants, plans and Stripe objects. Exactly one implementation of proration now exists, and we did not write it.

02

Usage metering

Their existing event stream aggregated into billable units, deduplicated by idempotency key and pushed to Stripe on a schedule with a reconciliation pass. Late events land in the correct period rather than being silently dropped.

03

Webhook processing

Signed webhook handling into a queue with idempotent processors and a dead-letter path. Every state change is derived from Stripe's events rather than assumed after an API call — the failure mode that caused most of the original system's drift.

04

Self-service portal

Card updates, invoice history, plan changes with a preview of the exact proration before confirming, and seat management. Support tickets that used to be billing questions largely stopped existing.

05

Migration harness

A dual-write and shadow-compare period: the new engine computed invoices alongside the old system, differences were reported daily, and cutover only happened once the diff stayed empty.

KEY DECISIONS

What we chose, and what it cost.

Stripe as the source of truth, not a payment processor behind our own logic

WHY

Every homegrown billing system converges on reimplementing proration, tax and dunning badly. The strategic move was deleting that surface area, not maintaining it better.

TRADE-OFF

Pricing experiments now live within Stripe's model, and vendor lock-in is real. The team judged that acceptable versus owning invoice arithmetic with four engineers.

Shadow-compare before cutover

WHY

The only trustworthy proof that new billing is correct is agreement with old billing on real accounts across a full cycle.

TRADE-OFF

Added two weeks and required dual-write plumbing that was thrown away afterwards. It surfaced four genuine discrepancies before a customer ever saw one.

Preserved grandfathered plans exactly rather than migrating customers

WHY

Quietly re-pricing legacy accounts during a technical migration is how a billing project becomes a churn event.

TRADE-OFF

More plan objects to carry, including some that will never be sold again. Cheap insurance.

THE HARD PARTS

What nearly broke it.

The cron that had stopped

We found the dead dunning job during the audit, not the build. Restarting retries recovered a meaningful slice of accounts that had been written off as churned — the fastest payback in the engagement, delivered in week one.

Usage events arriving late

Their pipeline could deliver events hours late, which under naive aggregation meant under-billing. Period-aware aggregation with a reconciliation window fixed it, with a deliberate rule that late events never retroactively increase a closed invoice — they roll forward.

Four discrepancies in shadow mode

Three were the old system being wrong, one was our mapping. Finding them against real accounts before cutover is exactly why the shadow period existed, and it turned finance from sceptics into advocates.

HOW IT RAN

The timeline.

  1. 01

    Billing audit

    Week 1

    Read the existing implementation end to end, catalogued every plan, discount and grandfathered term, and documented what was actually broken versus merely ugly.

  2. 02

    Engine & migration harness

    Weeks 2–4

    Stripe object model, tenant mapping, webhook processing, and the dual-write shadow-compare harness.

  3. 03

    Metering & portal

    Weeks 5–6

    Usage aggregation with reconciliation, self-service portal, and dunning schedules tuned against their historical recovery data.

  4. 04

    Shadow run & cutover

    Weeks 7–8

    A full cycle in shadow, discrepancy resolution, then cutover with the old system left readable but inert.

OUTCOME

What changed.

0

Manual invoices after cutover

0

Minutes of billing downtime during migration

4

Real discrepancies caught before any customer saw them

Days → 0

Founder time spent on month-end

Zero manual invoices. Revenue reporting matches the ledger for the first time. The founder can reason about churn instead of worrying about billing bugs.

Recovered involuntary churn was the immediate financial story: a dunning process that actually runs turns a category of loss back into revenue.

The strategic outcome was smaller surface area. Billing went from the scariest part of the codebase to a mapping layer a new engineer can read in an afternoon.

STACK

What it's made of.

APPLICATION

  • Next.js
  • TypeScript
  • Postgres
  • Redis

BILLING

  • Stripe Billing
  • Usage metering
  • Idempotent webhooks
  • Dunning schedules

MIGRATION

  • Dual-write harness
  • Shadow compare
  • Daily diff reporting

OPS

  • Queue with dead-letter
  • Reconciliation jobs
  • Revenue dashboards

HANDOVER

What they kept.

  • Billing architecture document plus a written map of every legacy plan and why it still exists.
  • Reconciliation runbook and the daily diff tooling, kept live after cutover.
  • Finance-facing walkthrough connecting the revenue dashboard to the ledger line by line.