3.3 KiB
3.3 KiB
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:
- Add
STRIPE_WEBHOOK_SECRETto.env.exampleand validate inenv.ts - Implement
createCheckoutSession(planId, customerId?, trial?)inbilling.service.ts - Implement
POST /api/webhooks/striperoute handler with signature verification - Handle events:
checkout.session.completed,invoice.payment_succeeded,invoice.payment_failed,customer.subscription.updated,customer.subscription.deleted - Update subscription record in database on each event (status, tier, period end, payment method)
- Implement
createCustomerPortalSession(customerId)for subscription management - Add trial logic: create subscription with
trial_end, handle trial-to-paid transition - Add proration logic for tier upgrades/downgrades using
proration_behavior: 'create_prorations' - Update billing router tRPC procedures:
getCheckoutUrl,getPortalUrl,getSubscription,cancelSubscription - 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_failedsets 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.completedin 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 instripe.ts) - Webhook endpoint must be publicly accessible for Stripe to deliver — use ngrok for local dev
- Store
stripeCustomerIdandstripeSubscriptionIdon user/subscription records - Use
stripe-webhookevent type in database for audit trail