← Pattern library

Commerce

Checkout

Takes payment from the user via a payment provider (Stripe, LINE Pay, 토스), confirms the order, and triggers fulfillment.

🌿

When to use this

Required for any e-commerce or paid-service app. Must integrate with a PCI-compliant payment provider, never handle card data yourself.

commercecheckoutpaymentstripetransaction
✨ Built using these library patterns:
checkout

What I assumed

I made these guesses to fill gaps. Let me know if any are wrong.

    Flow diagram

    Step-by-step recipe

    Copy this and paste into Cursor, Claude Code, or v0.

    PATTERN: Checkout
    INPUT: cart, payment_method, billing_info, shipping_address (if physical)
    OUTPUT: order_confirmation | payment_error
    
    STEPS:
      1. User clicks "Checkout" from cart
      2. Validate cart still valid (re-check stock, prices)
      3. Show order summary: items, subtotal, tax, shipping, total
      4. Collect billing info (name, address) — pre-fill if logged in
      5. IF physical goods → collect shipping address
      6. Show payment method selection (card, wallet, bank transfer)
      7. Initialize payment provider (Stripe Elements, LINE Pay redirect, 토스 SDK)
      8. User enters payment details in provider's iframe (NEVER your form)
      9. Tokenize payment (provider returns token, NOT raw card number)
      10. Server creates payment intent with provider
      11. Provider processes payment (may redirect for 3DS auth)
      12. Listen for webhook: payment_succeeded OR payment_failed
      13. IF succeeded → create order record, decrement stock, trigger fulfillment
      14. IF failed → show specific error, return to step 6
      15. Send order confirmation email
      16. Redirect to "Thank you" page with order number
    
    ERROR_HANDLING:
      - Card declined → show provider's error verbatim ("Insufficient funds", etc.)
      - 3DS authentication fails → return to payment method selection
      - Stock depleted between cart and payment → "Sorry, [item] just sold out. Issue refund?"
      - Network timeout during payment → show "Don't refresh — checking status..." and poll provider
      - Webhook never arrives → fallback poll after 30s, manual reconciliation alert
    
    EXTENSION_POINTS:
      - Apply coupons (composable_with: ["coupon"])
      - Allow no-account purchase (composable_with: ["guest-checkout"])
      - Recurring subscription (composable_with: ["subscription"])
      - Refund flow (composable_with: ["refund"])
    

    States — how things change

    StateDescriptionTransitions
    Reviewing orderUser sees summary, hasn't paid yet
    • Proceed to paymentAwaiting payment
    Awaiting paymentPayment iframe shown, user entering details
    • SubmitProcessing
    • CancelReviewing order
    ProcessingProvider processing payment
    • SucceededConfirmed
    • FailedAwaiting payment
    ConfirmedOrder created, fulfillment startedterminal

    Easy-to-miss situations

    The kinds of edge cases that break demos.

    • What if payment succeeds but webhook never arrives?

      high

      User charged but no order created — they'll dispute or churn.

      Suggested handling: After 30s without webhook, fallback poll Stripe API for status. Daily reconciliation job comparing Stripe charges vs your orders, alerts on mismatches.

    • What if user double-clicks Pay button?

      high

      Two charges, two orders for one purchase.

      Suggested handling: Disable button on first click. Use idempotency key (random UUID) on payment intent — provider rejects duplicates with same key. Server-side dedupe by idempotency key.

    • What if 3DS challenge times out?

      medium

      User waits, browser hangs, no clear status.

      Suggested handling: Set 5-minute timeout on 3DS step. After timeout, void the payment intent and show "Authentication failed, try again or different card."

    • What if currency conversion creates rounding errors?

      medium

      Display $9.99, charge $10.00, customer files complaint.

      Suggested handling: Always charge in the user's local currency from the start. Use provider's currency conversion (Stripe handles correctly). Show charge currency clearly: "$9.99 USD".

    • What if a 3rd party (parent, employer) is using the user's card?

      medium

      Chargeback risk if cardholder doesn't recognize the charge.

      Suggested handling: Use clear merchant descriptor on statement (your brand name). Send detailed receipt email immediately. For B2B, support multiple billing emails per account.

    Composes well with

    Combine these patterns when you need a richer flow.

    Build a flow starting from this pattern →