Why Your Billing System Should Not Block Your Login Logic
Signal-only primitives. Self-hosted. Deterministic. No magic.
This note describes a failure pattern. It is not a tutorial, not best practice, and not integration guidance.
Claim
Billing truth (Stripe, invoices, subscriptions) is state. Authentication and access checks are enforcement.
If billing truth directly blocks login, you have coupled state ingestion to a user-facing enforcement path.
The common failure mode
Teams do this:
- Stripe webhook arrives
- DB updates a “paid” boolean
- Login middleware checks “paid”
- If false → block
This looks clean. It is not clean. It converts “stale billing state” into “customer cannot log in.”
Why this breaks in production
- Webhook delays (Stripe outage, retries, queue lag)
- Clock skew and ordering issues
- Transient DB failures
- Partial deploys / migration windows
- Data mismatch between Stripe truth and your local cache
These are normal events. When billing is welded to login enforcement, normal events become user-facing lockouts.
Signal-only framing
A safer posture is to treat billing as a signal your system reads, then decide how your system behaves when the signal is missing, stale, or contradictory.
- “Plan is Standard” is a fact.
- “User may access feature X” is enforcement.
- “User may log in” is enforcement.
Your system defines fail-open vs fail-closed per surface area. (Login is not the same as premium feature gating.)
What this does NOT mean
- It does not mean “allow unpaid users forever.”
- It does not mean “ignore billing truth.”
- It does not mean “SimpleStates enforces anything.”
It means: keep state reading separate from enforcement decisions, and be explicit about degraded modes.