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
- User sees the success page (
/payment/success) - Backend receives
checkout.session.completedwebhook - Subscription is created with the paid plan
- If configured, user is invited to GitHub organization
- User can check their subscription on the profile page
Failed Payment (Recurring)
- Backend receives
invoice.payment_failedwebhook - Subscription status is set to
past_due - Stripe retries the payment according to its retry schedule
- If payment eventually succeeds:
invoice.paidwebhook reactivates the subscription - If all retries fail:
customer.subscription.deletedwebhook cancels the subscription
Cancelled Checkout
- User clicks "Back" or closes the Stripe checkout
- Stripe redirects to
/payment/cancel - 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 Number | Result |
|---|---|
4242 4242 4242 4242 | Successful payment |
4000 0000 0000 0002 | Card declined |
4000 0000 0000 3220 | 3D Secure authentication |
Use any future expiry date and any 3-digit CVC.