Product · Seat-based billing
A SvelteKit SaaS starter where seat billing is a seam, a gate, and a set of tests — not a month
All three kits ship the billing architecture: a provider-neutral adapter, seat limits enforced in exactly one place, subscription states that block new seats, errors your UI can branch on, and tests for the unhappy paths. The payment provider adapter is the one thing you write. That is stated up front, not in the small print.
The short answer
What ships: the adapter interface every part of the app consumes, a single seat-limit gate at invite acceptance, five subscription states, machine-readable billing errors, and 357 / 390 / 325 tests covering plan changes, seat boundaries and adapter failures.
What does not ship: the adapter that talks to your payment provider. The bundled mock reports every org as active on a plan of MOCK_PLAN_SEATS (default 3) and returns null from createCheckoutUrl, because there is nothing external to link to. Each kit documents the four things a real adapter has to do.
So: if "billing" on your list means "the seat logic, the states, the failure modes and the tests are done", buy any of the three. If it means "a customer can enter a card today", nobody here can promise you that yet, and a page that pretended otherwise would waste your week.
What the billing layer actually does
Read the last column before choosing a kit. The guarantee is not uniform across the three databases, and a comparison that hid that would cost you a race condition in production.
| Area | What is shipped | Where the guarantee holds |
|---|---|---|
| The integration seam | A BillingAdapter interface the product consumes everywhere. Services and routes never import a payment SDK; the implementation is swapped in one file. | Holds identically on all three kits. This is the part that does not change whichever one you buy. |
| Seat limit enforcement | One gate, at invite acceptance: the app checks subscription state and used seats, and refuses with an actionable error naming the fix. Inviting past your seat count is allowed on purpose — the limit surfaces when the person tries to join. | Holds on all three. It is one function, and it is the only place seat limits are decided. |
| Subscription states that block new seats | Five states — none, active, trialing, past_due, canceled. Only active and trialing let anybody join. A past-due or canceled org is refused with a message that says to update billing. | Holds on all three. It is the behaviour that stops a lapsed customer from adding users for free. |
| Machine-readable errors | BillingError carries a stable code (seat_limit, subscription_required, and on the Supabase kit also no_plan and adapter_error), so your UI branches on the code instead of matching message strings. | Holds on all three. The Supabase kit adds two codes on top, so your error handling is a superset if you pick it. |
| Atomicity of the seat check | On the Postgres kit, invite acceptance runs as one transaction: a per-org row lock, then the seat check, then the single-use claim, then the membership, then the audit row. | POSTGRES ONLY. The guarantee is Postgres-only by construction — SQLite has one writer, Cloudflare D1 offers batch() atomicity but no interactive transactions, and PostgREST has no multi-statement transaction without an RPC. On those, seat limits need a database-side constraint or an RPC instead. |
| The payment provider adapter | Not shipped. The mock reports every org as active on a plan of MOCK_PLAN_SEATS (default 3) and returns null from createCheckoutUrl, because there is nothing external to link to. | This is the gap, and it is the same in all three kits. Each kit documents exactly what a real adapter has to do — map provider webhooks to a local subscription row, create a checkout session, add a webhook route that verifies a signature, and never trust a client-side success redirect for an entitlement. |
| Sales tax and merchant of record | Checkout is designed to run through a merchant of record, so the provider is the legal seller and handles tax — you are not registering as a merchant in every country your customers are in. | A design decision, documented per kit. Which provider you pick stays your call, and the product does not depend on the choice. |
Why pay for this instead of writing it
The decisions are already made, and they are the decisions that hurt
Where the limit is enforced, which subscription states block a join, what an error looks like, why checkout goes through a merchant of record, and why a client-side success redirect is never an entitlement. Those are not hard to write once you know them — they are hard because each one is a question you have to ask first, and the wrong default is discovered by a customer.
Every state is a test, and the tests already exist
Seat limits, plan changes, adapter failures and boundary conditions are covered in the suites all three kits ship, alongside a 95% coverage floor the build enforces. The cases that matter here are the unhappy ones: an org with no plan, an org past due, a downgrade below current usage, an adapter that throws. Those are exactly the rows that get skipped when you write billing yourself.
The adapter is one file, not a refactor
Each kit has a single wiring point for the adapter, and the services that consume it are unchanged when you swap it. The remaining work is the provider adapter itself, and the kit tells you what it has to do.
Auditing the money path comes free
The same transaction that claims a seat writes the audit row. When a customer asks "who added this user and when", the answer is in a table with no update or delete path, not in your email.
Which kit — the billing is the same in all three
| Kit | Database | Tests | Billing | Buy |
|---|---|---|---|---|
| SvelteKit + Postgres Starter | Any Postgres — provider-neutral | 325 | Strongest seat guarantee — the seat check and the invite claim run in one transaction under a per-org row lock. Postgres-only by construction. | $79 |
| SvelteKit + Supabase Starter | Supabase (managed Auth, RLS, realtime) | 390 | Same seam and one gate; the adapter interface adds no_plan and adapter_error codes on top. | $99 |
| Multi-tenant SvelteKit Starter | SQLite — one file, no database to run | 357 | Same seam and one gate. A single writer serialises writes, so a concurrent double-accept cannot interleave either. | $79 |
One-time price, one licence, unlimited projects, commercial use permitted, lifetime updates, 30-day refund. A Team Licence is $249 for 3+ seats, invoiced individually.
Buy
Prefer one decision and the docs? All three kits compared ·pricing ·licence. Demos: SQLite · Supabase · Postgres.
Risks of choosing this
A buyer who finds these after paying is a refund. A buyer who finds them before paying is a customer.
No real payment adapter ships — you write it
This is the one thing on this page that could surprise you, so it is not buried. The seam, the enforcement gate, the states, the errors and the tests are all in the box; the adapter that talks to Lemon Squeezy, Paddle, Stripe or your merchant of record is not. Budget a focused day for it, plus a day to get a real webhook firing. If you would rather not, that is a legitimate reason to choose a product that ships it — and we do not have one.
The mock is permissive on purpose
Out of the box every organization looks active with 3 seats and checkout returns null. That is what makes the test suite hermetic, and it is also why the kit looks like it "works" before you have wired anything. Do not ship with the mock still installed.
Seat enforcement is not billing enforcement
The kit stops a 4th member joining a 3-seat plan. It does not charge anyone, retry a failed card, dunning a lapsed subscription or issue a refund — those are provider-side jobs. `past_due` is handled in the sense that it blocks new seats; what happens to the customer relationship is yours.
The strongest atomicity guarantee is Postgres-only
If you deploy on Cloudflare D1 you lose the row lock around invite acceptance, and with it the guarantee that two simultaneous accepts cannot both pass a seat check. The kit documents the fallback — enforce it with a database-side constraint or an RPC. If concurrent invites at the limit are a scenario you expect, this is the detail to design around.
FAQ
Does the kit include Stripe?
No. It includes the interface a payment provider plugs into, plus a mock implementation. The design assumes a merchant of record — a provider that acts as the legal seller and handles sales tax — rather than raw card processing, and the provider is swappable by design.
Why is the limit enforced when someone accepts an invite instead of when you send one?
Because that is where the seat is actually consumed. Blocking at invite creation means an owner cannot pre-invite a colleague before upgrading, and the failure lands on the wrong person. The kit allows the invite and refuses the acceptance with a message that says to upgrade or free a seat.
Can I use usage-based billing instead of seats?
The seam is the seam — the adapter returns a subscription state, so metering is an implementation of that interface rather than a change to the app. Each kit also ships a usage-billing pattern document for metered features. The enforcement gate itself is seat-shaped, which is the common case for B2B teams.
Which kit should I buy for billing specifically?
The billing code is the same in all three, so pick on the database and auth instead. If you want a seat guarantee that holds under concurrent invites, choose the Postgres kit — that atomicity is Postgres-only by construction. If you want Supabase Auth and RLS, choose the Supabase kit.
Is there a refund if it is not what I expected?
Thirty days, full refund, no questions. The price is one-time and the licence covers unlimited projects with lifetime updates.
Read the implementation
The billing design is documented in the open, so you can judge it before paying. Every link below is a page on this site, not a gated document.
The billing pattern
- Seat-based billing behind an adapter — the pattern, in full
- Selling without raw card access — the merchant-of-record path
- Supabase seat billing — one adapter, one gate
- Single-use invite links — where the seat is actually consumed
The parts billing depends on
- Who is allowed to remove a member — RBAC around the seat
- Append-only audit log — the record of who changed a plan
- Multi-tenant design with Drizzle
- What to check before you buy any starter
- All three kits and prices
Get in touch
Questions about the product, team licenses, or anything else? We'll aim to respond within 48 hours.