One-time licence · Next.js + Supabase + Stripe
Everything you need to ship a SaaS this weekend.
Auth, Stripe subscriptions, a credit ledger, emails, and the pages around them — wired together and ready to deploy. And the part every other kit skips: the billing and ledger paths ship with a test suite that runs against a real Postgres, because the bugs worth catching are the ones a mock cannot reproduce.
A real deployment against a real Postgres. Sign up with an email, start a run, and watch the credits move into a hold and back out. Stripe runs in test mode, so nothing you do there charges anything.
- Framework
- Next.js App Router, TypeScript strict
- Database
- Supabase Postgres, RLS on every table
- Payments
- Stripe Checkout, portal, webhooks
- Tests
- Vitest, plus SQL against a real Postgres
01 — The problem
Billing bugs do not crash. They move money quietly.
Most subscription starter kits ship a webhook handler that was tested by clicking through Stripe once. That is enough to look correct and nowhere near enough to be correct. Four failures account for almost all of it, and none of them raise an error.
01The webhook arrives twice
Stripe retries any delivery it did not get a 2xx for, and it retries on its own schedule. A handler with no idempotency record grants the credits again. The customer paid once and is holding twice the balance, and nothing in the app looks broken.
02Two requests spend the same credits
Both read a balance of 10, both check that 8 is affordable, both write. The account ends at 2 instead of refusing the second request. Read-then-write in application code cannot prevent this at any level of care; only a conditional update inside the database can.
03A hold is never released
A long job reserves credits and the process dies before it settles. The credits are neither spent nor spendable. Without something watching for holds that stopped reporting activity, they stay stranded until a human is told.
04The balance and the ledger disagree
Every entry looks right and the stored total is still wrong, because one path wrote the balance without writing an entry. With no reconciliation pass, the first person to notice the drift is a customer.
02 — In the box
Everything in the repository, and what checks it.
| Module | What ships | Covered by |
|---|---|---|
| Auth | Email and password, Google OAuth, SSR sessions, RLS on every table, profile row on signup | Route-guard and policy tests |
| Subscriptions | Stripe Checkout, customer portal, idempotent webhook handler, plan gating | Replayed webhook fixtures |
| Credit ledger | Append-only entries, atomic check-and-decrement, reserve to settle to release holds, idle reaper, admin grants, reconciliation | Concurrency tests on a real Postgres |
| Metered endpoint | One credit-gated model call wired through the full hold lifecycle | Failure-path tests |
| Resend transactional templates for welcome and receipt | Template + no-op tests | |
| Docs | Setup, deploy guides for Vercel and Render, env reference, prompts written for coding agents | Not tested |
- Auth
- Email and password, Google OAuth, SSR sessions, RLS on every table, profile row on signup
- Covered byRoute-guard and policy tests
- Subscriptions
- Stripe Checkout, customer portal, idempotent webhook handler, plan gating
- Covered byReplayed webhook fixtures
- Credit ledger
- Append-only entries, atomic check-and-decrement, reserve to settle to release holds, idle reaper, admin grants, reconciliation
- Covered byConcurrency tests on a real Postgres
- Metered endpoint
- One credit-gated model call wired through the full hold lifecycle
- Covered byFailure-path tests
- Resend transactional templates for welcome and receipt
- Covered byTemplate + no-op tests
- Docs
- Setup, deploy guides for Vercel and Render, env reference, prompts written for coding agents
- Covered byNot tested
Two rows say “not tested” because they are not. A kit that claims full coverage of everything is telling you nothing about the parts that matter.
03 — The ledger
Reserve, settle, release. One transaction each.
Long jobs cannot charge up front and cannot charge at the end. Up front over-bills every failure; at the end lets a user start work they cannot pay for. A hold does neither: the credits leave the spendable balance when the job starts, and what the job did not use comes back when it finishes.
-- Reserve before the job starts. The balance check and the decrement are the
-- same UPDATE ... WHERE, so two concurrent callers can never both pass it.
select ledger_reserve(p_user => :user_id, p_cost => 40) as run_id;
-- Claim the right to deliver. Exactly one caller wins; the loser throws its
-- output away instead of charging the user twice for duplicated work.
select ledger_begin_delivery(p_run => :run_id);
-- Delivered. Charges the reservation and writes an append-only entry carrying
-- the balance it produced, so any single row can be audited on its own.
select * from ledger_settle(p_run => :run_id);
-- Or it failed. Returns the whole hold with the reason on the entry. Runs that
-- stop heartbeating are released by the reaper without anyone noticing first.
select * from ledger_release(p_run => :run_id, p_reason => 'provider_failed');- Application code never writes a balance. Every movement goes through a SECURITY DEFINER function, so there is one place to audit and one place to test.
- Entries are append-only and carry the balance they produced, so a single row can be checked without replaying the table.
- A reconciliation query re-derives every balance from its entries. If it disagrees with the stored value, that is a failing test rather than a support ticket.
None of that is worth taking on trust. Open the demo and run one: the credits leave your spendable balance, the run shows its id, its heartbeat and the deadline the reaper is holding it to, and the entry it writes carries the balance it produced.
04 — Questions
What this is, and what it is not.
- Can I try it before buying?
- Yes, and you should. There is a live deployment of this exact codebase at /demo: sign up with an email, start a run, and watch credits move into a hold and back out of a real Postgres. Stripe runs there in test mode, so nothing charges anything.
- What do I actually get?
- Access to a private Git repository holding the complete application: the Next.js app, the SQL migrations, the money-path test suite and the setup docs. You clone it, rename it, and it is your codebase from that point on.
- What this is not.
- It is not a hosted service, a no-code builder, an admin panel generator, or a CRM. It does not write your product. It removes the weeks you would otherwise spend building auth, billing and a credit system you do not fully trust.
- Do I need my own Stripe and Supabase accounts?
- Yes, and a Resend account if you want transactional email. All three have free tiers that cover development. Every variable the app reads is listed in .env.example with a note on what breaks when it is wrong.
- Can I use it without the credit ledger?
- Yes. The ledger is a self-contained migration plus the routes that call it. Subscriptions work without it. It is the reason to buy this kit rather than another one, but nothing forces you to use it.
- How long do updates last?
- One year: stack major versions and security bumps. The repository is yours permanently either way. A stated year beats a “forever” nobody in this category has kept.
- Refunds.
- Fourteen days, no questions. Repository access is revoked when a refund is issued.
- Licence.
- One developer, unlimited products, client work included. Reselling the kit itself is not permitted.
- Is there support?
- No SLA. Bugs against the kit get fixed and you get the fix. Questions about your own architecture get answered when there is time.
Start from a codebase you can trust with money.
One-time purchase. Fourteen-day refunds, no questions asked.