Digital Product Engineering5.7 Payments & Billing Systems
VOL. V · CH. 5.7 · BACKEND SYSTEMS

Payments & Billing Systems

The single category of bug in a product where "mostly correct" is not an acceptable outcome.

DivisionFinance / Backend
DifficultyAdvanced
Prerequisites5.6
Related2.20 5.14
2 min read · 346 words

5.7.1Definition

A payments system handles the movement of money into (and sometimes out of) a product — one-time charges, recurring subscriptions, refunds, and payouts. Nearly all products integrate a third-party payment processor (Stripe, PayPal, Flutterwave, local Mobile Money rails) rather than handling raw card or bank data directly, due to the compliance burden described below.

5.7.2Why It Exists

Handling raw payment credentials directly brings a product into scope for PCI-DSS compliance — a substantial regulatory and security burden. Payment processors exist to absorb that compliance surface, exposing a simpler API (often just a token representing a card) so the product's own backend never touches raw card numbers.

5.7.3Core Billing Models

ModelPatternComplexity driver
One-time chargeSingle payment, single eventLow — mostly a single API call
SubscriptionRecurring charge on a cycleProration, upgrades/downgrades, failed-payment retries (dunning)
Usage-basedCharge scales with metered usageAccurate usage tracking and invoice reconciliation
Marketplace payoutsPlatform splits payment between itself and sellersMulti-party settlement, tax/reporting obligations

5.7.4Common Mistakes

  • Storing raw card numbers instead of relying entirely on the processor's tokenization — a serious compliance and security failure.
  • Trusting client-side "payment succeeded" callbacks as the source of truth, instead of confirming the charge via the processor's signed webhook (5.6) — a client-side callback can be spoofed or simply never fire.
  • No handling for failed recurring payments, silently losing subscription revenue instead of running a dunning (retry) process.
  • Double-charging on retried requests with no idempotency key on the charge creation call.

5.7.5Best Practices

  • Treat the payment processor's server-to-server webhook, not the client redirect, as the single source of truth for whether a payment succeeded.
  • Use idempotency keys on every charge-creation request to make retries safe.
  • Build a dunning flow for failed recurring charges before launch, not after the first revenue loss.
Real-World ExampleBlush Café & Dessert's Mobile Money integration confirms reservation payment status only after the Mobile Money provider's own callback is received — never from the customer's browser alone — following exactly this pattern to avoid marking an order paid when it wasn't.