payments · tutorial · paywall
requirePaidUser(): One Boolean, Zero Paywall Holes
Every paid page and API behind one guard: 401 no session, 403 no purchase, access object otherwise. The 10-line paywall inside.

Access logic scattered across fifty files
It starts with a paid check in the dashboard. Then a slightly different check in the API route.
Then a third in the download handler, written by an agent that never saw the first two.
Then a new feature ships with no check at all. Free users export premium CSVs for a month before anyone notices.
Scattered access is perforated access.
Every new surface re-decides who pays, and each decision drifts.
Trial users here.
Lifetime there.
Admins everywhere but untested.
The audit that finds the holes takes a weekend. The holes took thirty seconds each to drill.
Put the decision in one place. New pages do not decide access. They ask.
Scattered checks or one guard
| Pain | Scattered checks | One shared guard |
|---|---|---|
| New page coverage | The author remembers, sometimes | Call the guard, always |
| 401 and 403 meaning | Mixed up per file | Fixed: log in or pay |
| Agent-added routes | Often unguarded | An unguarded route fails review |
| Lifetime and subscription logic | Duplicated per surface | One switch, one place |
| Audit cost | A weekend of reading | A search for the guard call |
Scattered access is worse than it looks. Three reasons.
Agents skip what they cannot see. A new route generated without the guard pattern has no paywall. Not malice. Absence. A written convention plus review turns absence into a visible omission.
401 and 403 confusion leaks information. A 403 to a logged-out user tells attackers the resource exists. A 401 to a non-payer sends buyers to login instead of checkout. Per-file improvisation gets both wrong.
Trials and lifetimes fork the logic. A paid-or-trial-or-lifetime test copied across files drifts until one surface honors trials and another forgets. One switch, computed once, means every surface agrees.
Paywalls are not features. They are the boundary between business and charity. Drawn once, in one place.
What to do
Write one guard every surface calls. Three outcomes.
It checks for a session first.
No session means "log in", a 401.
A session whose user does not exist is treated as signed out, which stops stale sessions from passing.
A user without paid access means "pay", a 403.
Otherwise the visitor is let in.
The guard only reads.
Payment webhooks switch access on exactly once per payment, and the guard never writes.
One writer.
Many readers.
Branch pages. Return codes from APIs.
Pages redirect people somewhere useful: sign-in for strangers, pricing for free users, never a blank error. APIs return the numeric code for clients to handle.
Same guard. Two presentations. One meaning.
Make unguarded paid routes fail review.
Every new paid page or API must call the guard. A reviewer checks the changed files for it. Agents learn the pattern within a couple of sessions, because blocked diffs teach faster than documentation.
Audit day becomes a quick search for paid surfaces that do not call it.
Guarded or scattered
| Audit question | Scattered checks | One guard |
|---|---|---|
| Are new routes covered? | Whoever remembers | Review-blocked if missing |
| Do 401 and 403 mean the same everywhere? | Mixed up per file | Log in or pay, fixed |
| Is the trial honored on every surface? | Some forget | Computed once |
| How long does an audit take? | A weekend of reading | A short search |
The trade
A central guard is a single point of failure. A bug in it locks out everyone instead of one page.
So armor the center. One heavily tested function that every paid surface reads beats two dozen lightly tested copies.
Know this first
A central guard is a single point of failure. A bug locks out everyone.
BoringPush does not ship trials, teams or quotas.
Why BoringPush is the way to do this
One guard. Every surface.
Pages, APIs and payment webhooks all use the same paid-access check.
Three outcomes.
Log in.
Pay.
Welcome.
A new page asks. It never decides.
FAQ
Does the guard handle trials, teams and admin overrides?
Upstream of the switch. A trial sets access with an expiry the guard or a wrapper checks. Teams resolve to a payer before the switch.
Admins are listed separately by email. BoringPush does not ship trials or teams, so those are additions you make while keeping the guard's contract the same.
How do 401 and 403 avoid leaking which routes exist?
Logged-out visitors get a 401, which reveals nothing about the resource. Logged-in non-payers get a 403 with a pricing redirect, because the upsell is the point.
For truly sensitive endpoints, return a 404 to non-payers instead. It is the same logic with a stricter presentation.
Should pages or middleware enforce the paywall?
Pages and layouts, which can branch with redirects and avoid a flash of gated content. Middleware is for coarse routing only. One enforcement layer with full context beats two layers disagreeing at the boundary.
What turns access back off for cancels, refunds and disputes?
The payment provider's events. A cancellation or refund marks the order and removes access unless another active order remains. Test these paths in the provider's test mode, because they rot silently when success means never using them.
Can free tiers coexist with the switch?
Yes. The switch gates premium, and free surfaces skip the guard.
Usage caps such as exports per month would sit in a separate limits check beside the guard. BoringPush does not ship quota metering, so that is yours to add.
How do I test paywalls without paying repeatedly?
Use the development logins on your own machine. One signs in as a free user and one as a paying user, so you can walk through every paid surface offline. Add fixtures for a trial and an expired user if you build those states.
Keep reading
- Next.js Stripe Trap: The Retry That Double-Charges You — the grant side of the boolean
- Every Webhook Fires Twice (Dedupe or Double-Charge) — exactly-once grants behind the guard
+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)
Command from your phone.
Built so autonomous AI agents build what you want without breaking what's already paying you.
- Built-in analytics — cookie-free mode, $0 forever, no PostHog bill
- SQLite ↔ Supabase: one setting, same field names
- Full digital marketplace & feedback boards included
- 3 payment rails: Stripe, LemonSqueezy & Dodo Payments
- Agent fences: lint gate, read-only data access, auto-rollback
- Ship from your phone via SSH & WireGuard/Tailscale in under a minute
$199
Get BoringPushOne-time payment · Lifetime updates · Unlimited projects.
Keep reading


