Skip to content

10 — Billing

Add a free/pro billing model: free orgs can have up to 3 projects, pro orgs get unlimited. Use @cruzjs/pro and Stripe.

Terminal window
npm install @cruzjs/pro
apps/web/src/app.server.ts
import { BillingModule } from '@cruzjs/pro';
export default createCruzApp({
modules: [
BillingModule,
// ...
],
});

Create apps/web/src/billing.config.ts:

import { definePlans } from '@cruzjs/pro';
export const PLANS = definePlans({
free: {
name: 'Free',
priceId: null,
limits: {
projects: 3,
},
},
pro: {
name: 'Pro',
priceId: process.env.STRIPE_PRO_PRICE_ID!,
limits: {
projects: Infinity,
},
},
});

In TasksService.createProject:

async createProject(orgId: string, input: CreateProjectInput) {
const [{ count }] = await this.db
.select({ count: sql<number>`count(*)` })
.from(projects)
.where(eq(projects.orgId, orgId));
const subscription = await this.billingService.getSubscription(orgId);
const plan = subscription?.plan ?? 'free';
const limit = PLANS[plan].limits.projects;
if (count >= limit) {
throw new TRPCError({
code: 'FORBIDDEN',
message: `Free plan allows ${limit} projects. Upgrade to Pro for unlimited projects.`,
});
}
// ... create project
}

The upgrade flow is pre-built in @cruzjs/pro. Add the billing routes to your app:

apps/web/src/app.server.ts
import { BillingModule, BILLING_ROUTES } from '@cruzjs/pro';
export default createCruzApp({
modules: [BillingModule],
routes: [...BILLING_ROUTES],
});

Now /billing shows the current plan and an upgrade button. /billing/success is the Stripe redirect after payment.

Add to .dev.vars (local) and cruz secrets set (production):

STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRO_PRICE_ID=price_...

The webhook handler is included in BillingModule — it listens for Stripe events and updates the org’s subscription status in D1.

  1. Create 3 projects as a free org — they all work
  2. Try to create a 4th — you get a “Upgrade to Pro” error
  3. Click the upgrade button → Stripe Checkout
  4. Use test card 4242 4242 4242 4242, expiry 12/34, CVC 123
  5. Payment completes → redirect to /billing/success → org is now pro
  6. Create a 4th project — it works
  • Free/pro plan definitions with project limits
  • Server-side limit enforcement in createProject
  • Stripe checkout flow with pre-built billing pages

Next: Chapter 11 — Deployment