SaaS billing is a state-synchronization problem
Billing is often presented as “connect Stripe and add plans”. Production SaaS has more state than a payment provider can own: product catalog, subscription lifecycle, invoice/payment state, feature entitlements, usage, credits, tax context, dunning and operational access. The architecture becomes reliable when ownership of each state is explicit and provider events are treated as inputs to the product—not as the product database.
Define the state owners first
| State | Primary owner | Why |
|---|---|---|
| Product / price catalog | Product + billing configuration | Defines what can be purchased |
| Subscription | Billing domain synchronized with provider | Tracks lifecycle and contractual period |
| Invoice / payment | Provider + normalized local record | Financial event needs reconciliation and history |
| Entitlement | Application domain | Controls what the tenant may use now |
| Usage | Product metering | Measured close to the feature being consumed |
| Audit / support history | Application | Explains why access or balance changed |
A common failure is deriving feature access directly from the latest webhook or from a single subscription.status field. Entitlements deserve an explicit product model, especially when trials, grace periods, manual contracts, credits or multiple payment rails exist.
Use an idempotent webhook inbox
Payment webhooks can be retried, delayed and delivered out of the order your UI expects. Verify provider signatures, store the event identity, acknowledge duplicates and process through an idempotent handler. Keep the raw provider reference for debugging while normalizing the business facts you need into local records.
The handler should not assume one event tells the complete story. Where the provider offers an authoritative resource, fetch or reconcile current state when sequence ambiguity matters.
Entitlements should be explicit product state
An entitlement answers a product question: may tenant X use capability Y, under what limits, until when and why? The answer can come from an active subscription, a paid add-on, a trial, a negotiated enterprise contract or a temporary grace rule. This abstraction keeps feature gates independent of provider-specific status names.
Server-side entitlement checks should guard sensitive APIs and expensive operations. Frontend feature visibility can mirror the decision for UX, but cannot be the enforcement layer.
Plan changes are workflows, not field updates
Upgrade, downgrade, cancellation and renewal require explicit timing semantics. Decide whether a change is immediate or effective next period, how proration is handled, when entitlements change and what happens to existing usage. Persist the requested transition so support can explain the resulting invoice and access state.
For enterprise SaaS, a scheduled downgrade may also require validation: the tenant can be over the lower plan’s user, storage or facility limit. The billing workflow should resolve that condition rather than silently removing data.
Reconciliation closes the gap between providers and internal state
Even correct webhook handling benefits from reconciliation. Periodically compare provider subscriptions, invoices and payments against local billing records and raise explicit discrepancies. This catches missed events, manual dashboard changes, deployment outages and historical bugs.
Reconciliation should be repair-oriented. Store the discrepancy class and the safe corrective action instead of simply logging that two values differ.
Normalize multiple payment rails behind one billing domain
Cards, bank payments, CoinGate or direct stablecoin settlement have different provider mechanics, but the product should normalize them into common concepts: payment intent/obligation, received amount, currency/asset, finality state, allocation, refund/credit and reconciliation evidence.
This is where our crypto payments architecture and CoinGate integration work connects to SaaS billing. A blockchain transaction or gateway callback should not directly toggle application features. It should settle a billing obligation, after which the entitlement model changes through the same domain rules used by other rails.
Keep an auditable path from payment to access
When support asks “why did this customer lose access?”, the system should reconstruct subscription period, invoice/payment facts, provider events, reconciliation, entitlement transition and any manual override. Manual interventions need actor, reason and expiry so temporary support fixes do not become invisible permanent state.
First-party product pattern: vertical SaaS
Vertical systems make billing complexity visible because access often maps to operational units rather than a single seat count. In Rentya, product logic spans operators, facilities, inventory and booking workflows. That kind of platform benefits from explicit entitlements and tenant-aware billing rather than UI-only plan flags.
This pattern is part of our broader SaaS development approach: billing, permissions and tenant lifecycle are one product architecture, not three unrelated integrations.
Failure modes to test
- The same paid event is delivered repeatedly.
- A cancellation event arrives before an older update.
- A payment succeeds while the application is unavailable.
- The provider state changes manually outside the application.
- A downgrade conflicts with current usage limits.
- A crypto/gateway payment settles a different amount than expected.
- A manual entitlement override never expires.
- Provider and local records diverge without an alert.
