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.
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
| State | Description | Transitions |
|---|---|---|
| Reviewing order | User sees summary, hasn't paid yet |
|
| Awaiting payment | Payment iframe shown, user entering details |
|
| Processing | Provider processing payment |
|
| Confirmed | Order created, fulfillment started | terminal |
Easy-to-miss situations
The kinds of edge cases that break demos.
What if payment succeeds but webhook never arrives?
highUser 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?
highTwo 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?
mediumUser 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?
mediumDisplay $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?
mediumChargeback 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.