# 01. Stripe Checkout, Webhooks, and Subscription State Management meta: id: core-services-01 feature: core-services-implementation priority: P0 depends_on: [] tags: [billing, stripe, payments, foundation] objective: - Enable paid customer acquisition by implementing complete Stripe payment lifecycle — checkout, webhook handling, subscription state machine, and customer portal. deliverables: - Stripe Checkout session creation for each plan tier (Shield, Guard, Fortress, Family Fortress) - Webhook endpoint handling all critical Stripe events - Subscription state machine in Drizzle ORM - Customer portal (billing settings, plan change, cancellation) - Trial period support (14-day free trial) steps: 1. Add `STRIPE_WEBHOOK_SECRET` to `.env.example` and validate in `env.ts` 2. Implement `createCheckoutSession(planId, customerId?, trial?)` in `billing.service.ts` 3. Implement `POST /api/webhooks/stripe` route handler with signature verification 4. Handle events: `checkout.session.completed`, `invoice.payment_succeeded`, `invoice.payment_failed`, `customer.subscription.updated`, `customer.subscription.deleted` 5. Update subscription record in database on each event (status, tier, period end, payment method) 6. Implement `createCustomerPortalSession(customerId)` for subscription management 7. Add trial logic: create subscription with `trial_end`, handle trial-to-paid transition 8. Add proration logic for tier upgrades/downgrades using `proration_behavior: 'create_prorations'` 9. Update billing router tRPC procedures: `getCheckoutUrl`, `getPortalUrl`, `getSubscription`, `cancelSubscription` 10. Add rate limiting on checkout creation (prevent abuse) tests: - Unit: Mock Stripe API responses, verify database state transitions for each webhook event - Integration: Create real Stripe test-mode checkout session, complete payment, verify subscription activation - E2E: End-to-end checkout flow from dashboard → Stripe Checkout → webhook → active subscription acceptance_criteria: - [ ] Customer can click "Subscribe" on Shield plan and be redirected to Stripe Checkout - [ ] After successful payment, webhook creates active subscription record in database - [ ] Customer can access billing portal to view invoices, change plan, or cancel - [ ] Trial subscription auto-converts to paid or suspends after trial ends - [ ] Tier upgrade creates prorated invoice and updates subscription immediately - [ ] `invoice.payment_failed` sets grace period status and sends retry email - [ ] All webhook events are idempotent (duplicate events don't create duplicate records) - [ ] Webhook handler returns 200 for handled events, 400 for invalid signatures validation: - Run `stripe trigger checkout.session.completed` in Stripe CLI, verify database record - Run `stripe trigger invoice.payment_failed`, verify grace period status - Create test checkout, pay with `4242 4242 4242 4242`, verify active subscription in dashboard - Run test suite: `vitest run billing.test.ts` notes: - Stripe API version: `2026-04-22.dahlia` (already configured in `stripe.ts`) - Webhook endpoint must be publicly accessible for Stripe to deliver — use ngrok for local dev - Store `stripeCustomerId` and `stripeSubscriptionId` on user/subscription records - Use `stripe-webhook` event type in database for audit trail