# Checkout Page Layout

## Contents

- [Overview](#overview)
- [Decision: Single-Page vs Multi-Step](#decision-single-page-vs-multi-step)
- [Guest vs Logged-In Checkout](#guest-vs-logged-in-checkout)
- [Component Architecture](#component-architecture-recommended)
- [Checkout Flow](#checkout-flow)
- [Key Ecommerce Considerations](#key-ecommerce-considerations)
- [Backend Integration](#backend-integration)
- [Mobile Checkout](#mobile-checkout)
- [Trust and Conversion Optimization](#trust-and-conversion-optimization)
- [Error Handling](#error-handling)
- [Checklist](#checklist)

## Overview

Final step in conversion funnel where customers provide shipping and payment information. Must be optimized for completion with minimal friction.

**⚠️ CRITICAL: Always fetch shipping methods AND payment methods from backend. Users must be able to select from available options - never skip payment method selection.**

### Key Requirements

- Clear steps and progress indication
- Guest checkout option (if backend supports it)
- Shipping address and method selection
- **Shipping methods fetched from backend (vary by address/region)**
- **Payment methods fetched from backend (user must select preferred method)**
- Payment processing
- Order review before submission
- Trust signals throughout
- Mobile-optimized (60%+ traffic is mobile)
- Fast loading and submission

## Decision: Single-Page vs Multi-Step

**Use Single-Page Checkout when:**
- Simple products with few shipping options
- Mobile-heavy traffic (>60% mobile users)
- Fewer form fields required (<15 total)
- Startup or new store (minimize friction)
- Fast checkout is prioritized
- Low average order value (<$50)

**Benefits:**
- Fewer clicks (no step navigation)
- User sees full scope upfront
- Faster on mobile (no page loads)
- Lower perceived friction

**Use Multi-Step Checkout when:**
- Complex shipping (international, multiple carriers)
- B2B customers (need detailed information)
- Many form fields required (>15 total)
- High-value products (>$100, thoroughness expected)
- Established brand (customers trust process)
- Need clear progress indication

**Benefits:**
- Less overwhelming (one step at a time)
- Progress indicator reduces anxiety
- Easier step-by-step validation
- Better for complex forms

**Recommended: Hybrid Approach**
- Single-page scroll layout on desktop
- Accordion-based sections on mobile (expand/collapse)
- Progressive disclosure of sections
- Best of both worlds

**Common steps:**
1. Shipping Information (address)
2. Delivery (shipping method selection)
3. Payment (payment method and details)
4. Review (final review before submission)

## Guest vs Logged-In Checkout

**IMPORTANT:** Guest checkout availability depends on backend support.

**Guest checkout (recommended if backend supports it):**
- Reduces friction (no signup barrier)
- "Checkout as Guest" option prominent
- Email required for order confirmation
- Optional "Create account?" checkbox after order
- Don't force account creation

**Logged-in checkout:**
- Pre-fill saved addresses and payment methods
- "Returning customer? Log in" link at top
- Allow seamless switch between guest/login

## Component Architecture (RECOMMENDED)

**Build separate components for each checkout step for better maintainability and reusability.**

✅ **Create individual step components:**
- `ShippingInformationStep` - Contact and shipping address form
- `DeliveryMethodStep` - Shipping method selection
- `PaymentInformationStep` - Payment method and details
- `OrderReviewStep` - Final review before submission

**Benefits of component separation:**
- **Maintainability**: Fix bugs or update one step without affecting others
- **Reusability**: Reuse shipping address component in account settings, checkout, etc.
- **Testability**: Test each step independently
- **Code organization**: Clearer separation of concerns (validation, submission logic per step)
- **Easier debugging**: Isolate issues to specific steps
- **Flexibility**: Easy to reorder steps or add/remove steps based on requirements
- **Performance**: Lazy load steps or split bundles for faster initial load

**What to separate:**
- Main checkout page/container component
- Individual step components (ShippingInformationStep, DeliveryMethodStep, etc.)
- Reusable order summary component (shown on all steps)

**Component communication:**
Each step component should accept:
- Current step data (form values)
- Callback to update data (e.g., `onShippingUpdate`)
- Callback to proceed to next step (e.g., `onContinue`)
- Loading/error states
- Validation errors

**Shared components:**
- Address form (used in shipping and billing)
- Payment method selector
- Order summary (sidebar, shown on all steps)

**Works for both single-page and multi-step:**
- Single-page: Render all steps at once, scroll-based navigation
- Multi-step: Show one component at a time, controlled by step state
- Accordion: Expand/collapse components as sections

**Common mistake:**
- ❌ Building entire checkout as one massive component with all form fields, logic, and validation mixed together
- ✅ Separate components for each step, shared state management in parent

## Checkout Flow

### Complete Checkout Flow Diagram

```
┌─────────────────────────────────────────────────────────────────────┐
│                          CHECKOUT PROCESS                            │
└─────────────────────────────────────────────────────────────────────┘

    ┌──────────────────────────────────────────────────────┐
    │  Optional: Guest Checkout vs Login                   │
    │  • Guest: Enter email only                           │
    │  • Logged-in: Pre-fill saved data                    │
    └────────────────────┬─────────────────────────────────┘
                         │
                         ▼
    ┌──────────────────────────────────────────────────────┐
    │  STEP 1: Shipping Information                        │
    │  ├─ Contact: Email, Phone                            │
    │  ├─ Shipping Address: Name, Address, City, etc.      │
    │  └─ Billing Address: □ Same as shipping / Different  │
    └────────────────────┬─────────────────────────────────┘
                         │
                         ▼
    ┌──────────────────────────────────────────────────────┐
    │  STEP 2: Delivery                                    │
    │  • Fetch shipping methods from backend               │
    │  • Display: Standard, Express, Overnight             │
    │  • Show: Cost + Delivery estimate                    │
    │  • Update order total                                │
    └────────────────────┬─────────────────────────────────┘
                         │
                         ▼
    ┌──────────────────────────────────────────────────────┐
    │  STEP 3: Payment Information                         │
    │  • Fetch payment methods from backend                │
    │  • Options: Card, PayPal, Apple Pay, etc.            │
    │  • Enter: Card details (tokenized)                   │
    │  • Use billing address from Step 1                   │
    └────────────────────┬─────────────────────────────────┘
                         │
                         ▼
    ┌──────────────────────────────────────────────────────┐
    │  STEP 4: Order Review                                │
    │  • Review: Items, addresses, shipping, payment       │
    │  • Optional: □ Agree to Terms and Conditions         │
    │  • Click: [Place Order] Button                       │
    │  → Payment processing triggered                      │
    └────────────────────┬─────────────────────────────────┘
                         │
                         ▼
    ┌──────────────────────────────────────────────────────┐
    │  Loading: Processing payment...                      │
    │  • Authorize/capture payment via gateway             │
    │  • Create order in backend                           │
    │  • Send confirmation email                           │
    └────────────────────┬─────────────────────────────────┘
                         │
                    ┌────┴────┐
                    │         │
              Success       Failure
                    │         │
                    ▼         ▼
    ┌───────────────────┐   ┌──────────────────────┐
    │ Order Confirmation│   │ Show Error Message   │
    │ • Order number    │   │ • Retry payment      │
    │ • Details         │   │ • Keep form data     │
    │ • Tracking link   │   │ • Suggest solutions  │
    └───────────────────┘   └──────────────────────┘
```

## Key Ecommerce Considerations

### Shipping Address Collection

Collect:
- Required: email, name, address, city, state/zip, country
- Optional: phone.

**Key ecommerce considerations:**
- Email placement: First if guest checkout (identifies customer)
- Country placement: Early if shipping methods vary by country (affects available shipping)
- Phone: Optional to reduce friction, but recommended for delivery coordination
- Billing address: "Same as shipping" checkbox (default checked)

**For Medusa backends:**
- Country dropdown: Show only countries from cart's region (don't show all countries globally)
- Get countries from: `cart.region.countries` or `sdk.store.region.retrieve(cart.region_id)`
- Medusa regions contain specific countries - limiting options ensures correct pricing and shipping
- If user needs different country, they must change region first (typically via country selector component)

### Shipping Method Selection

**Fetch from backend after address provided** (shipping methods vary by address/region):
- Display as radio buttons with cost + delivery estimate
- Update order total immediately when method changes
- Highlight free shipping if available
- Show "Add $X for free shipping" if close to threshold
- Handle unavailable shipping: show message, suggest alternatives

### Payment Method Selection

**CRITICAL: Always fetch payment methods from backend and allow user to select from available options.**

Payment methods vary by store configuration (backend settings). NEVER assume which payment methods are available or hardcode payment options. Users MUST be able to choose their preferred payment method.

**Fetch available methods from backend:**
```typescript
// ALWAYS fetch payment providers from backend
// For Medusa:
const { payment_providers } = await sdk.store.payment.listPaymentProviders()

// For other backends:
// Change based on the integrated backend
const paymentMethods = await fetch(`${apiUrl}/payment-methods`)
// Returns: card, paypal, apple_pay, google_pay, stripe, etc.
```

**Display payment method selection UI:**
- Show all enabled payment providers returned by backend
- Allow user to select their preferred method (radio buttons or cards)
- Don't skip selection step - user must actively choose
- Map backend codes to display names in the storefront. For example `pp_system_manual` -> `Manual payment`.
- Common options: Credit/Debit Card, PayPal, Apple Pay, Google Pay, Buy Now Pay Later

**Available payment methods (examples, actual options come from backend):**
- Credit/Debit Card (most common, via Stripe/Braintree/other gateway)
- PayPal (redirect or in-context)
- Apple Pay (Safari, iOS only)
- Google Pay (Chrome, Android)
- Buy Now Pay Later (Affirm, Klarna - if enabled by store)
- Manual payment (bank transfer, cash on delivery - if enabled)

**Why backend fetching is required:**
- Store admin controls which payment providers are enabled
- Payment methods vary by region, currency, order value
- Test vs production mode affects available methods
- Can't assume all stores use the same payment gateway

**For Medusa backends - Payment flow:**

1. **List available payment providers:**
```typescript
const { payment_providers } = await sdk.store.payment.listPaymentProviders({
  region_id: cart.region_id // Required to get region-specific providers
})
```

2. **Display providers and allow user to select:**
Show payment providers as radio buttons or cards. User must actively select one.

3. **Initialize payment session after selection:**
```typescript
// When user selects a provider
await sdk.store.payment.initiatePaymentSession(cart, {
  provider_id: selectedProvider.id // e.g., "pp_stripe_stripe", "pp_system_default"
})

// Re-fetch cart to get updated payment session data
const { cart: updatedCart } = await sdk.store.cart.retrieve(cart.id)
```

4. **Render provider-specific UI:**
- Stripe providers (`pp_stripe_*`): Render Stripe Elements card UI
- Manual payment (`pp_system_default`): No additional UI needed
- Other providers: Implement according to provider requirements

**Important:** Payment provider IDs are returned from the backend (e.g., `pp_stripe_stripe`, `pp_system_manual`). Map these to user-friendly display names in your UI.

**Digital wallets (mobile priority):**
- Apple Pay / Google Pay should be prominent on mobile
- One-click payment (pre-filled shipping)
- Significantly faster checkout
- Higher conversion on mobile

**Card payment:**
- Use payment gateway (Stripe Elements, Braintree)
- Never handle raw card data (PCI compliance)
- Tokenize card data before submission
- Auto-detect card type (show logo)

### Order Review

**Display for final confirmation:**
Cart items, addresses, shipping method/cost, payment method, order total breakdown.

**Key elements:**
- "Edit" link next to each section (returns to step or edits inline)
- Terms checkbox (if required): "I agree to Terms and Conditions"
- Place Order button: Large (48-56px), shows total, loading state on submit

### Order Summary Sidebar

**Desktop:** Sticky sidebar with items, prices, totals. Updates in real-time.
**Mobile:** Collapsible at top ("Show order summary" toggle). Keeps focus on form.

## Backend Integration

**Address validation (optional):**
- Use address lookup APIs (Google, SmartyStreets) for higher accuracy
- Tradeoff: accuracy vs friction. Consider for high-value orders.

**Payment processing flow:**
1. Frontend tokenizes payment (Stripe Elements, Braintree)
2. Send token + order details to backend
3. Backend authorizes/captures payment & creates order
4. Redirect to confirmation page

**Never:** Send raw card data, store cards without PCI compliance, process payments client-side.

**On payment failure:** Show specific error, keep form data, allow retry without re-entering.

### Order Completion and Cart Cleanup (CRITICAL)

**After order is successfully placed, you MUST reset the cart state:**

**Common issue:** Cart popup and cart state still show old cart content after order is placed. This happens because the global cart state (Context, Zustand, Redux) isn't cleared after checkout completion.

**Required actions on successful order:**

1. **Clear cart from global state:**
   - Reset cart state in Context/Zustand/Redux to null or empty
   - Update cart count to 0 in navbar
   - Prevent old cart items from showing in cart popup

2. **Clear localStorage cart ID:**
   - Remove cart ID from localStorage: `localStorage.removeItem('cart_id')`
   - Or create new cart and update cart ID in localStorage
   - Ensures fresh cart for next shopping session

3. **Invalidate cart queries (if using TanStack Query):**
   - `queryClient.invalidateQueries({ queryKey: ['cart'] })`
   - Or `queryClient.removeQueries({ queryKey: ['cart', cartId] })`
   - Prevents stale cart data from cache

4. **Redirect to order confirmation page:**
   - Navigate to `/order-confirmation/[order_id]` or `/thank-you/[order_id]`
   - Show order details, tracking info, confirmation

**Pattern:**
```typescript
// After successful order placement
async function onOrderSuccess(order) {
  // 1. Clear cart state
  setCart(null) // or clearCart() from context

  // 2. Clear localStorage
  localStorage.removeItem('cart_id')

  // 3. Invalidate queries (if using TanStack Query)
  queryClient.invalidateQueries({ queryKey: ['cart'] })

  // 4. Redirect to confirmation
  router.push(`/order-confirmation/${order.id}`)
}
```

**Why this is critical:**
- Without clearing cart state, cart popup shows old items after order
- User sees "phantom cart" if they click cart icon after checkout
- Creates confusion and poor UX
- May prevent user from starting new shopping session

## Mobile Checkout

**Key optimizations:**
- Digital wallets prominent (Apple Pay/Google Pay) - significantly faster checkout
- Single-column layout, 44-48px touch targets
- Appropriate keyboard types, autocomplete attributes enabled
- Collapsible order summary at top (shows total, expands on tap)
- Sticky Place Order button at bottom (always accessible, shows total)
- Accordion sections (one step at a time, reduces cognitive load)

**For detailed mobile patterns and safe area insets**, see `reference/mobile-responsiveness.md`.

## Trust and Conversion Optimization

**Trust signals (critical for conversion):**
- "Secure Checkout" badge, payment provider logos (Visa, Mastercard)
- Return policy link visible, customer support contact
- Near Place Order: "100% secure checkout", guarantees/free returns if offered
- For new brands: Customer review count, social proof

**Reduce abandonment:**
- Progress indicator (shows steps remaining)
- Auto-save form data, clear pricing (no surprise fees)
- Minimal required fields, smart defaults, autocomplete enabled

**Reduce perceived friction:**
- "No account required" (guest checkout)
- "Free shipping" highlighted
- Time estimate: "Less than 2 minutes"

## Error Handling

**Form validation:**
- Validate on blur, show error below field
- User-friendly messages ("Please enter a valid email address")
- Scroll to first error on submit

**Payment errors:**
- Card declined: "Your card was declined. Please try another payment method."
- Keep form data, suggest alternatives (try another card, PayPal)
- Network timeout: Show retry option without re-entering data

**Stock availability errors:**
- Out of stock: Remove item, recalculate, allow continue with remaining items
- Quantity reduced: Update automatically, show message, allow continue

## Checklist

**Essential checkout features:**

- [ ] **RECOMMENDED: Separate components created for each checkout step**
- [ ] Components: ShippingInformationStep, DeliveryMethodStep, PaymentInformationStep, OrderReviewStep
- [ ] Decision made: Single-page or multi-step (based on complexity)
- [ ] Guest checkout option (if backend supports it)
- [ ] Email field first (if guest checkout)
- [ ] Shipping address form with autocomplete attributes
- [ ] "Billing same as shipping" checkbox (default checked)
- [ ] Shipping methods fetched from backend dynamically
- [ ] Shipping cost updates order total in real-time
- [ ] **CRITICAL: Payment methods fetched from backend (NEVER assume or hardcode)**
- [ ] **CRITICAL: Payment method selection UI shown to user (user must select from available options)**
- [ ] Payment methods: show only enabled providers returned by backend
- [ ] For Medusa: Payment session initialized after user selects provider (sdk.store.payment.initiatePaymentSession)
- [ ] For Medusa: Country dropdown limited to cart's region countries
- [ ] Digital wallets prominent on mobile (Apple Pay, Google Pay)
- [ ] Payment tokenization (never send raw card data)
- [ ] Order review section before submission
- [ ] Order summary sidebar (sticky on desktop, collapsible on mobile)
- [ ] Promo code input (if not applied in cart)
- [ ] Trust signals throughout (secure checkout, return policy)
- [ ] Terms and conditions checkbox (if required)
- [ ] Place Order button prominent (48-56px, shows total)
- [ ] Loading state during payment processing
- [ ] Progress indicator (if multi-step)
- [ ] Clear error messages for validation failures
- [ ] Error handling for payment failures (keep form data)
- [ ] Stock availability check before order creation
- [ ] Mobile optimized (44-48px touch targets, single column)
- [ ] Autocomplete enabled on all form fields
- [ ] Keyboard accessible (tab through fields, enter to submit)
- [ ] ARIA labels on form fields (aria-required, aria-invalid)
- [ ] Redirect to order confirmation on success
- [ ] **CRITICAL: Clear cart state after successful order** (reset cart in Context/Zustand, remove cart ID from localStorage, invalidate cart queries)
- [ ] Cart popup shows empty cart after order completion (not old items)
