10 — Billing
Chapter 10 — Billing
Section titled “Chapter 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.
Install the billing package
Section titled “Install the billing package”npm install @cruzjs/proWire BillingModule
Section titled “Wire BillingModule”import { BillingModule } from '@cruzjs/pro';
export default createCruzApp({ modules: [ BillingModule, // ... ],});Configure plans
Section titled “Configure plans”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, }, },});Enforce the project limit
Section titled “Enforce the project limit”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}Add the billing UI
Section titled “Add the billing UI”The upgrade flow is pre-built in @cruzjs/pro. Add the billing routes to your app:
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.
Configure Stripe
Section titled “Configure Stripe”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.
Test the upgrade flow
Section titled “Test the upgrade flow”- Create 3 projects as a free org — they all work
- Try to create a 4th — you get a “Upgrade to Pro” error
- Click the upgrade button → Stripe Checkout
- Use test card
4242 4242 4242 4242, expiry12/34, CVC123 - Payment completes → redirect to
/billing/success→ org is now pro - Create a 4th project — it works
What we built
Section titled “What we built”- 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