Skip to main content
INS // Insights

Custom Subscription Billing Engine We Shipped

Updated July 2026 · 5 min read

Stripe Checkout gets you to first revenue. It does not survive every enterprise contract, hybrid usage meter, multi-entity catalog, or finance team that needs ledger-grade invoice truth. When product and finance start arguing over “what Stripe thinks we billed,” you need a custom subscription billing engine — not another Zapier glue layer on top of webhooks.

Rutagon builds billing systems as first-class software: domain models, state machines, idempotent workers, and integrations that finance can audit. Below is how we ship them for commercial SaaS teams.

Buyer Pain: Billing Becomes the Product Bottleneck

Signals you have outgrown vanilla SaaS billing:

  • Sales sells terms the catalog cannot express (custom seats, phased discounts, mid-cycle add-ons)
  • Finance reconciles Stripe exports in spreadsheets every month
  • Dunning emails fire, but entitlement state drifts from payment state
  • Tax and entity logic is “someone knows” tribal knowledge
  • Usage metering lives in a separate pipeline that does not agree with invoices

The cost is churn, disputed invoices, and engineering time spent firefighting instead of shipping features. Related multi-tenant data patterns show up in our multi-tenant database architecture for SaaS work.

Custom Subscription Billing Engine: Domain First

We treat the payment processor as a rail — not the source of truth. The engine owns:

  • Catalog — products, plans, price versions, entitlements
  • Subscriptions — lifecycle, pauses, trials, cancels
  • Invoices — line items, proration, credits, tax hooks
  • Payments — attempts, retries, dunning states
  • Entitlements — what the product unlocks because billing says so
Catalog / Price versions
        │
        ▼
Subscription state machine
        │
        ├── Invoice generator (proration + credits)
        ├── Tax adapter (Avalara/TaxJar/etc.)
        └── Payment adapter (Stripe/Adyen/...)
                │
                ▼
        Entitlement projector → product APIs

Our full-stack development capability ships the app; data analytics often follows for revenue recognition views finance trusts.

Proration Without Spreadsheet Math

Mid-cycle changes are where naive systems die. We model invoice lines as time-bounded:

from dataclasses import dataclass
from datetime import datetime, timezone
from decimal import Decimal

@dataclass
class PeriodCharge:
    price_id: str
    unit_amount: Decimal
    quantity: int
    start: datetime
    end: datetime

def prorate(charge: PeriodCharge, change_at: datetime) -> tuple[Decimal, Decimal]:
    """Return (unused_credit, remaining_debit) for a mid-period change."""
    total = (charge.end - charge.start).total_seconds()
    used = (change_at - charge.start).total_seconds()
    if total <= 0:
        return Decimal("0"), Decimal("0")
    amount = charge.unit_amount * charge.quantity
    unused = amount * Decimal(str((total - used) / total))
    return unused.quantize(Decimal("0.01")), (amount - unused).quantize(Decimal("0.01"))

Money uses Decimal. Timestamps are UTC. Price versions are immutable — you never edit a live price row; you create a new version and attach it at a cutover.

Dunning and Entitlements Must Share State

A classic failure: Stripe retries succeed, but the app still shows “past due.” We project a single billing status into the product:

Billing state Product entitlement
trialing Full (trial flag)
active Full
past_due Grace window / limited
unpaid Locked
canceled Locked + export path

Workers are idempotent on invoice_id + attempt. Webhooks are verified and deduped. See also patterns from our AI agent back office automation work when AP/collections workflows sit beside billing.

Tax, Credits, and Enterprise Edges

Custom engines earn their keep on edges:

  • Multi-entity invoicing (different legal sellers)
  • Prepaid credits and drawdowns
  • Minimum commits with overage
  • Purchase-order required invoices (no auto-charge)
  • Manual invoice approval gates for enterprise

We wire tax as an adapter with a recorded request/response for audit — not a silent rounding function in the UI.

Production Lessons

Lesson 1 — Never dual-write “Stripe is truth” and “DB is truth.” Pick the engine as truth; processor is settlement.

Lesson 2 — Price immutability saves lawsuits. Historical invoices must reprint the same math forever.

Lesson 3 — Observability is a billing feature. Trace subscription_id across invoice create → payment → entitlement. Related: distributed tracing implementation guide patterns once that article is live in your cluster.

Lesson 4 — Start with the catalog pain, not a rewrite fantasy. Sometimes the right MVP is a thin engine for enterprise plans while self-serve stays on Stripe Billing — with one entitlement projector.

For adjacent custom builds, see legacy codebase modernization when billing is tangled in a monolith.

Outcomes

Teams that ship this pattern typically get:

  • Finance-reconcilable invoices without monthly heroics
  • Sales flexibility without engineering rewriting Stripe Coupons by hand
  • Entitlements that match cash state
  • A clear path to usage and hybrid models later

Ready to ship a custom subscription billing engine that finance and product both trust? Talk to Rutagon — contact@rutagon.com or 907-841-8407.

Start a Conversation →

Dunning and Tax Hooks

Failed payments enter a dunning state machine with retry cadence and customer email templates owned by finance, not engineering. Tax calculation is delegated to a provider API behind an interface so jurisdiction changes do not require a billing rewrite. Monthly close exports a reconciliation file finance can match to the bank without screenshot archaeology.

Frequently Asked Questions

When do we need a custom subscription billing engine?

When catalog terms, proration rules, multi-entity invoicing, or entitlement coupling outgrow what your processor’s hosted billing can express cleanly — and finance is reconciling in spreadsheets.

Do you replace Stripe entirely?

Not always. Many builds keep Stripe (or another processor) for payment rails while the custom engine owns catalog, invoices, and entitlements. Replacement is a later decision, not a day-one religion.

How do you handle proration and mid-cycle changes?

Time-bounded line items with immutable price versions and Decimal math. Changes create credit/debit lines rather than mutating historical invoices.

What about tax and compliance?

Tax is an adapter with stored request/response evidence. We design for auditability; your counsel and tax advisor remain the compliance owners for jurisdictions you sell into.

How long does an MVP take?

A focused MVP covering catalog, subscriptions, invoices, one processor, and entitlement projection often lands in a multi-week delivery once plan rules are decided. Enterprise approval gates and multi-entity catalogs extend scope.