Files
Kordant/tasks/core-services-implementation/01-stripe-checkout-webhooks.md
2026-05-31 22:03:18 -04:00

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:

  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