Falsafa
FrontendHigh-Level Design

Payment Flow

Stripe checkout integration: PaymentIntent creation, webhook processing with signature verification, library assignment on purchase completion, and debug-mode mock payments.

All payments are one-time book purchases. There is no subscription or recurring billing.

Flow

User                  Frontend                  Stripe                  Supabase
 |                       |                        |                        |
 |  1. Click Buy         |                        |                        |
 |---------------------->|                        |                        |
 |                       |  2. Create PaymentIntent|                       |
 |                       |------------------------>|                       |
 |                       |  3. client_secret       |                       |
 |                       |<------------------------|                       |
 |                       |  4. Render              |                       |
 |                       |     Stripe Elements     |                       |
 |  5. Enter card        |                        |                        |
 |---------------------->|                        |                        |
 |                       |  6. Confirm card        |                       |
 |                       |------------------------>|                       |
 |                       |  7. Result              |                       |
 |                       |<------------------------|                       |
 |  8. Show success      |                        |                        |
 |<----------------------|                        |                        |
 |                       |                        |  9. Webhook fires     |
 |                       |                        |------------------------>|
 |                       |                        | 10. Insert purchase +  |
 |                       |                        |     add to library     |

PaymentIntent Creation

When the user clicks "Buy" on a book detail page, the frontend calls POST /api/payments/create-intent with { bookId }.

The API route:

  1. Authenticates the user via auth().
  2. Fetches the book's price from the books table.
  3. Creates a Stripe PaymentIntent with amount, currency, and metadata: { bookId, userId }.
  4. Returns { clientSecret } to the frontend.

The frontend then renders a Stripe Elements <CardElement> and calls stripe.confirmCardPayment(clientSecret). On confirmation, the user is shown a success page.

Webhook Processing

Stripe sends webhooks to POST /api/payments/webhook. The endpoint:

  1. Reads the raw body and verifies the Stripe signature using the webhook secret from the STRIPE_WEBHOOK_SECRET environment variable.
  2. Handles checkout.session.completed:
    • Extracts bookId and userId from metadata.
    • Inserts a row into the purchases table with the Stripe session ID, amount, and status.
    • Inserts a row into user_library granting the user access to the book.
    • Returns a 200 OK to Stripe.

Failed or duplicate webhooks are handled idempotently: the purchases table has a unique constraint on the Stripe session ID, so repeated webhook deliveries do not create duplicate library entries.

Error Handling

  • Payment failure: Stripe Elements handles card errors (insufficient funds, expired card, etc.) client-side. The user sees the error on the checkout form and can retry.
  • Webhook failure: Stripe retries the webhook up to three times. If all retries fail, the user's purchased book will not appear in their library - an admin can manually add it using the Stripe dashboard session ID.
  • Network error: If the create-intent call fails, the user sees a generic error and can retry.
  • Book out of stock: The API route checks if the book exists before creating the intent. If the book does not exist or has no price, it returns a 400 error.

Debug Mode

When NEXT_PUBLIC_DEBUG_MODE=true:

  • The checkout page shows a mock Stripe Elements form instead of a real one.
  • POST /api/payments/create-intent returns a mock clientSecret.
  • The webhook endpoint is not hit - the frontend simulates a successful payment by directly calling the library assignment logic.

This allows payment development and testing without a real Stripe account.

On this page