Payments & Checkout

Stripe Checkout integration and payment flow

Payments & Checkout

What's Included

  • Stripe Checkout session creation
  • Plan listing with pricing display
  • Success and cancel payment pages
  • Support for both authenticated and guest checkout

Checkout Composable (useCheckout)

const {
  createCheckoutSession, // (planId, interval?) → redirects to Stripe
  isProcessing,          // Ref<boolean>
  error                  // Ref<string | null>
} = useCheckout()

The composable creates a Stripe Checkout session via the backend API and redirects the user to Stripe's hosted checkout page.

Plans Composable (usePlans)

const {
  plans,          // Ref<Plan[]> — all plans from backend
  oneTimePlans,   // Computed — filtered for one-time payment plans
  isLoading,      // boolean
  error           // Error | null
} = usePlans()

Plans are fetched from /api/v1/plans with SSR caching. The pricing section on the landing page uses this composable.

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

Authenticated vs Guest Checkout

  • Authenticated — email is pre-filled, user_id is included in the session metadata
  • Guest — Stripe collects the email, user account is created via webhook

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. 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

Payment Pages

Success Page (/payment/success)

Displayed after a successful Stripe payment. Shows a confirmation message and extracts the session_id from the URL query parameters.

Cancel Page (/payment/cancel)

Displayed when the user cancels the Stripe checkout. Shows a message with a link back to the pricing section.

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

Pricing Section

The PricingSection component on the landing page:

  1. Fetches plans via usePlans()
  2. Renders pricing cards with plan details
  3. Maps backend plans to Stripe price IDs
  4. Handles checkout button clicks
  5. Shows loading skeletons while plans are loading

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.