Initial commit
This commit is contained in:
@@ -0,0 +1,486 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user