Low Level Design: Billing System

What Is a Billing System?

A billing system manages the full revenue lifecycle: creating subscription plans, tracking usage, generating invoices, triggering payment collection, and handling failures like dunning. It differs from a raw payment processor in that it owns the business logic around when and how much to charge. Billing systems must be accurate to the cent, auditable, and resilient to payment failures without losing customers unnecessarily.

Data Model


TABLE plans (
  id            UUID PRIMARY KEY,
  name          VARCHAR(128),
  price_cents   INT,
  currency      CHAR(3),
  interval      ENUM('MONTHLY', 'ANNUAL'),
  trial_days    INT DEFAULT 0
);

TABLE subscriptions (
  id            UUID PRIMARY KEY,
  user_id       BIGINT NOT NULL,
  plan_id       UUID REFERENCES plans(id),
  status        ENUM('TRIALING', 'ACTIVE', 'PAST_DUE', 'CANCELED', 'PAUSED'),
  current_period_start TIMESTAMP,
  current_period_end   TIMESTAMP,
  payment_method_id    UUID,
  created_at    TIMESTAMP DEFAULT NOW()
);

TABLE invoices (
  id            UUID PRIMARY KEY,
  subscription_id UUID REFERENCES subscriptions(id),
  amount_cents  INT,
  currency      CHAR(3),
  status        ENUM('DRAFT', 'OPEN', 'PAID', 'UNCOLLECTIBLE', 'VOID'),
  due_date      DATE,
  paid_at       TIMESTAMP,
  created_at    TIMESTAMP DEFAULT NOW()
);

TABLE invoice_line_items (
  id            UUID PRIMARY KEY,
  invoice_id    UUID REFERENCES invoices(id),
  description   TEXT,
  quantity      INT,
  unit_price    INT,
  total_cents   INT
);

TABLE dunning_attempts (
  id            UUID PRIMARY KEY,
  invoice_id    UUID REFERENCES invoices(id),
  attempt_number INT,
  attempted_at  TIMESTAMP,
  result        ENUM('SUCCESS', 'FAILED', 'PENDING')
);

Core Workflow

  1. Subscription creation: User selects a plan. A subscription record is created in TRIALING or ACTIVE state. The first invoice is generated in DRAFT.
  2. Invoice finalization: At the end of the billing period (or after trial), the draft invoice is finalized to OPEN and a payment attempt is triggered via the payment processing system.
  3. Payment success: Invoice moves to PAID. Subscription renews: current_period_start and current_period_end advance by one interval.
  4. Payment failure: Invoice stays OPEN. Subscription moves to PAST_DUE. Dunning begins.
  5. Dunning: Retry attempts are scheduled at increasing intervals (e.g., day 1, day 3, day 7). If all attempts fail, the subscription is canceled and the invoice marked UNCOLLECTIBLE.
  6. Cancellation: User or system cancels. Access is retained until current_period_end; no further invoices are generated.

Failure Handling

Billing jobs must be idempotent. A job that generates invoices must check whether an invoice for the current period already exists before creating one. Using the subscription ID and billing period as a composite unique key on invoices prevents duplicates even if the job runs twice.

Payment retries use the idempotency service: each dunning attempt generates a unique idempotency key so that network timeouts during retry do not cause double charges.

Failed invoice finalization is retried with exponential backoff. The job framework (e.g., Sidekiq, Celery) logs each attempt; alerts fire if an invoice stays in DRAFT beyond its due date.

Scalability Considerations

  • Billing scheduler: A distributed cron (e.g., using a leader-election lock in Redis or Postgres advisory locks) ensures only one instance runs billing jobs per time window, preventing fan-out duplicate charges.
  • Fanout at scale: For millions of subscriptions renewing on the same day, batch the invoice generation job into chunks and publish tasks to a queue (Kafka, SQS). Workers process in parallel.
  • Proration: Mid-cycle plan changes require calculating partial period credits and charges. Store proration line items explicitly on the next invoice rather than adjusting past invoices, preserving audit history.
  • Reporting: Aggregate revenue metrics (MRR, churn) are computed from a read replica or a data warehouse fed by a CDC (Change Data Capture) pipeline, never from the transactional database directly.
  • Multi-currency: Store all amounts in the smallest currency unit (cents, pence). Exchange rates are snapshotted at invoice creation time and stored on the invoice, not recomputed later.

Summary

A billing system layers business logic on top of a payment processor: it owns plans, subscriptions, and invoices, and orchestrates dunning when payments fail. Correctness hinges on idempotent job design, explicit state machines for subscriptions and invoices, and careful use of unique constraints to prevent duplicate billing. At scale, distributed job scheduling and queue-based fanout keep throughput high without sacrificing safety.

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What are the main components of a scalable billing system design?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A billing system consists of a usage metering pipeline (event ingestion and aggregation), a rating engine that applies pricing rules to metered usage, an invoicing service that generates line-item invoices at billing cycle boundaries, a payment collection layer that integrates with a payment processor, and a dunning workflow that handles failed payment retries. Supporting components include a product catalog, subscription state machine, and an immutable ledger.”
}
},
{
“@type”: “Question”,
“name”: “How do you handle subscription proration in a billing system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Proration is calculated by determining the fraction of the billing period remaining at the time of the plan change and applying that fraction to the price difference. For upgrades, the customer is charged for the prorated difference immediately. For downgrades, a credit is applied to the next invoice. The billing system records a proration line item with the effective date, old plan, new plan, and computed amount to maintain a clear audit trail.”
}
},
{
“@type”: “Question”,
“name”: “How does a billing system handle usage-based (metered) pricing at scale?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Usage events are ingested through a high-throughput event pipeline (Kafka or a similar message queue) and written to a time-series or columnar store for aggregation. A rating engine reads pre-aggregated usage at invoice time, applies tiered or per-unit pricing rules, and produces billable amounts. To support real-time usage dashboards, a streaming aggregator (Flink or Spark Streaming) computes running totals with at-least-once delivery and idempotent deduplication.”
}
},
{
“@type”: “Question”,
“name”: “What consistency and reliability guarantees does a billing system require?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Billing systems require exactly-once charge semantics to avoid double-billing, achieved through idempotency keys on every payment request. Invoice generation must be idempotent and versioned so reruns produce the same result. The ledger is append-only with double-entry bookkeeping to ensure credits equal debits. All state transitions (subscription created, invoice generated, payment collected) are recorded as immutable events enabling full audit trails and reconciliation.”
}
}
]
}

See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems

See also: Shopify Interview Guide

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

Scroll to Top