Payment Flow

End-to-end Stripe payment flow from checkout to subscription

Payment Flow

End-to-End Checkout Flow

1. User views pricing on landing page (PricingSection)
   │
2. usePlans() fetches plans from GET /api/v1/plans
   │
3. User clicks "Get Started" on a paid plan
   │
4. useCheckout().createCheckoutSession(planId)
   │
5. POST /api/v1/subscription/checkout
   │   { priceId, successUrl, cancelUrl }
   │   (+ Authorization header if authenticated)
   │
6. Backend creates Stripe Checkout session
   │   ├── Authenticated: email pre-filled, user_id in metadata
   │   └── Guest: Stripe collects email
   │
7. Frontend redirects to Stripe Checkout URL
   │
8. User completes payment on Stripe
   │
9. Stripe redirects to /payment/success?session_id=...
   │
10. Stripe sends webhook to backend
    │
11. POST /api/v1/webhooks/stripe
    │  Event: checkout.session.completed
    │
12. Backend creates/upgrades subscription
    │
13. SubscriptionPaid event → GitHub invite (optional)

What Happens After Payment

Successful Payment

  1. User sees the success page (/payment/success)
  2. Backend receives checkout.session.completed webhook
  3. Subscription is created with the paid plan
  4. If configured, user is invited to GitHub organization
  5. User can check their subscription on the profile page

Failed Payment (Recurring)

  1. Backend receives invoice.payment_failed webhook
  2. Subscription status is set to past_due
  3. Stripe retries the payment according to its retry schedule
  4. If payment eventually succeeds: invoice.paid webhook reactivates the subscription
  5. If all retries fail: customer.subscription.deleted webhook cancels the subscription

Cancelled Checkout

  1. User clicks "Back" or closes the Stripe checkout
  2. Stripe redirects to /payment/cancel
  3. User sees a "Payment cancelled" message with a link back to pricing

Subscription Status on Frontend

The profile page shows subscription details via useSubscription():

const { subscription } = useSubscription()

// subscription.value contains:
// - planType: 'free' | 'starter' | 'pro'
// - status: 'active' | 'canceled' | 'past_due' | 'expired'
// - paymentType: 'recurring' | 'one_time'
// - currentPeriodEnd: string (date)

The SubscriptionCard component displays this with a color-coded status badge:

  • Active — green badge
  • Canceled — red badge
  • Past Due — yellow badge
  • Trial — blue badge

Stripe Test Cards

Use these test card numbers during development:

Card NumberResult
4242 4242 4242 4242Successful payment
4000 0000 0000 0002Card declined
4000 0000 0000 32203D Secure authentication

Use any future expiry date and any 3-digit CVC.