The checkout flow guides users through a streamlined three-step process to complete their purchase, with visual progress tracking and order summary at each stage.
Overview
The checkout process consists of three distinct steps:
Shopping Cart
Review items and quantities before proceeding
Shipping Information
Enter delivery address and contact details
Payment
Provide payment method and complete purchase
Checkout Stepper Component
All checkout pages include a visual progress indicator:
< CheckoutStepper step = { 1 } />
The stepper highlights the current step and shows completed/upcoming steps, helping users understand their position in the checkout flow.
Covered in detail in the Shopping Cart documentation.
Key actions:
Review all cart items
Adjust quantities or remove items
View order summary with totals
Click “Proceed to Checkout” to continue
Page Overview
The shipping page collects delivery information with a clean form interface:
src/pages/checkoutShipping/CheckoutShipping.jsx
export const CheckoutShipping = () => {
const navigate = useNavigate ();
const { user } = useAuth0 ();
const shipping = useUserStore (( state ) => state . shipping );
const setShipping = useUserStore (( state ) => state . setShipping );
const hydrateFromAuth0 = useUserStore (( state ) => state . hydrateFromAuth0 );
useEffect (() => {
if ( user ) {
hydrateFromAuth0 ( user );
}
}, [ user ]);
const handleSubmit = ( e ) => {
e . preventDefault ();
navigate ( "/checkout/payment" );
};
return (
< div className = "container shipping-container" >
< CheckoutStepper step = { 2 } />
< h2 > Shipping Information </ h2 >
{ /* Form content */ }
</ div >
);
};
Full Name Customer’s complete name for delivery
Address Street address for shipment
City City for delivery location
State Management
Shipping data is stored in the user store with persistence:
src/store/useUserStore.js
shipping : {
fullName : "" ,
address : "" ,
city : "" ,
},
setShipping : ( data ) =>
set (( state ) => ({
shipping: { ... state . shipping , ... data },
}))
src/pages/checkoutShipping/CheckoutShipping.jsx
const handleChange = ( e ) => {
const { name , value } = e . target ;
setShipping ({ [name]: value });
};
< form onSubmit = { handleSubmit } >
< input
name = "fullName"
placeholder = "Full name"
value = { shipping . fullName }
onChange = { handleChange }
required
/>
< input
name = "address"
placeholder = "Address"
value = { shipping . address }
onChange = { handleChange }
required
/>
< input
name = "city"
placeholder = "City"
value = { shipping . city }
onChange = { handleChange }
required
/>
< button type = "submit" >
Continue to Payment
</ button >
</ form >
Auth0 Integration
If the user is authenticated via Auth0, their name is automatically populated in the Full Name field.
src/store/useUserStore.js
hydrateFromAuth0 : ( user ) =>
set (( state ) => ({
shipping: {
... state . shipping ,
fullName: state . shipping . fullName || user ?. name || "" ,
},
}))
Layout with Order Summary
The page uses a two-column layout:
src/pages/checkoutShipping/CheckoutShipping.jsx
< div className = "row" >
< div className = "col-md-6" >
{ /* Shipping form */ }
</ div >
< div className = "col-md-5 offset-md-1" >
< OrderSummary />
</ div >
</ div >
Step 3: Payment
The payment page collects credit card information with validation:
src/pages/checkoutpayment/CheckoutPayment.jsx
export const CheckoutPayment = () => {
const navigate = useNavigate ();
const { isAuthenticated , loginWithRedirect } = useAuth0 ();
const [ processing , setProcessing ] = useState ( false );
const [ errors , setErrors ] = useState ({});
const payment = useUserStore (( state ) => state . payment );
const setPayment = useUserStore (( state ) => state . setPayment );
const addOrder = useUserStore (( state ) => state . addOrder );
const cart = useCartStore (( state ) => state . cart );
const clearCart = useCartStore (( state ) => state . clearCart );
const [ formData , setFormData ] = useState ( payment );
// Form handling and validation...
};
Payment Fields
Card Number 16-digit card number with auto-formatting (spaces every 4 digits)
Expiry Date MM/YY format with validation for expired cards
Payment inputs are automatically formatted as users type:
src/pages/checkoutpayment/CheckoutPayment.jsx
const handleChange = ( e ) => {
const { name , value } = e . target ;
let formattedValue = value ;
if ( name === "cardNumber" ) {
const digits = value . replace ( / \D / g , "" ). substring ( 0 , 16 );
formattedValue = digits . replace ( / ( \d {4} )(?= \d ) / g , "$1 " );
}
if ( name === "expiryDate" ) {
const digits = value . replace ( / \D / g , "" ). substring ( 0 , 4 );
formattedValue =
digits . length > 2
? ` ${ digits . substring ( 0 , 2 ) } / ${ digits . substring ( 2 , 4 ) } `
: digits ;
}
if ( name === "cvc" ) {
formattedValue = value . replace ( / \D / g , "" ). substring ( 0 , 3 );
}
setFormData (( prev ) => {
const updated = { ... prev , [name]: formattedValue };
setPayment ( updated );
return updated ;
});
};
Validation
Comprehensive validation ensures data integrity:
src/pages/checkoutpayment/CheckoutPayment.jsx
const validateExpiry = ( value ) => {
if ( value . length < 5 ) return "Format: MM/YY" ;
const [ month , year ] = value . split ( "/" ). map ( Number );
const currentYear = new Date (). getFullYear () % 100 ;
const currentMonth = new Date (). getMonth () + 1 ;
if ( month < 1 || month > 12 ) return "Invalid month" ;
if ( year < currentYear ) return "Year expired" ;
if ( year === currentYear && month < currentMonth ) return "Month expired" ;
return null ;
};
const handlePayment = ( e ) => {
e . preventDefault ();
const newErrors = {};
if ( formData . cardNumber . length < 19 )
newErrors . cardNumber = "Full card number required" ;
const expiryError = validateExpiry ( formData . expiryDate );
if ( expiryError ) newErrors . expiryDate = expiryError ;
if ( formData . cvc . length < 3 ) newErrors . cvc = "CVC required" ;
if ( Object . keys ( newErrors ). length > 0 ) {
setErrors ( newErrors );
return ;
}
// Process payment...
};
All validation errors are displayed inline below the respective input fields for immediate user feedback.
Authentication Check
Users must be authenticated to complete payment:
src/pages/checkoutpayment/CheckoutPayment.jsx
if ( ! isAuthenticated ) {
loginWithRedirect ({
appState: { returnTo: "/checkout/payment" },
});
return ;
}
After authentication, users are redirected back to the payment page with their cart intact.
Order Processing
Creating the Order
When payment is submitted, the system:
Generate Order ID
Create unique 8-character alphanumeric order identifier
Show Processing State
Display “Processing…” on submit button for 1.5 seconds
Save Order
Add order to user’s order history with all items and totals
Clear Cart
Empty the shopping cart after successful order
Navigate to Success
Redirect to success page with order ID
src/pages/checkoutpayment/CheckoutPayment.jsx
if ( cart . length === 0 ) return navigate ( "/" );
const orderId = Math . random (). toString ( 36 ). substring ( 2 , 10 ). toUpperCase ();
setProcessing ( true );
setTimeout (() => {
addOrder ({
id: orderId ,
date: new Date (). toISOString (),
items: [ ... cart ],
total: cart . reduce (( acc , item ) => acc + item . price * item . qty , 0 ),
});
clearCart ();
navigate ( "/payment/success" , { state: { orderId } });
}, 1500 );
Order Storage
Orders are stored in the user store with persistence:
src/store/useUserStore.js
orders : [],
addOrder : ( order ) =>
set (( state ) => ({
orders: [ ... state . orders , order ],
}))
Order Summary Component
Both shipping and payment pages display a real-time order summary:
< div className = "col-md-5" >
< OrderSummary />
</ div >
The summary shows:
All cart items with quantities
Subtotal calculation
Shipping cost (Free)
Final total
The order summary updates automatically if users navigate back to edit their cart.
User Experience Features
Progress Tracking
Visual Stepper Always visible progress indicator
Back Navigation Users can navigate backward to edit information
Forward Only Cannot skip steps - must complete in order
Data Persistence
All shipping and payment form data is persisted to localStorage, allowing users to resume checkout after closing the browser.
Responsive Design
The checkout flow uses responsive Bootstrap grid layouts:
Desktop : Side-by-side form and order summary
Mobile : Stacked layout with form above summary
< div className = "row" >
< div className = "col-md-7" >
{ /* Form */ }
</ div >
< div className = "col-md-5" >
{ /* Summary */ }
</ div >
</ div >
Error Handling
Validation Errors
src/pages/checkoutpayment/CheckoutPayment.jsx
< input
className = { `form-control ${ errors . cardNumber ? "is-invalid" : "" } ` }
name = "cardNumber"
value = { formData . cardNumber }
onChange = { handleChange }
required
/>
< div className = "error-container" > { errors . cardNumber } </ div >
Empty Cart Protection
src/pages/checkoutpayment/CheckoutPayment.jsx
if ( cart . length === 0 ) return navigate ( "/" );
If the cart is empty when accessing payment page, users are automatically redirected to the home page.
Best Practices
Implementation Guidelines
Always validate on both client side (immediate feedback) and before submission
Auto-format inputs to match expected patterns (card numbers, dates)
Display inline error messages below fields
State Management
Use persistent stores for form data to survive page refreshes
Clear sensitive payment data after order completion
Keep cart in sync across all checkout steps
User Experience
Show processing states during async operations
Prevent double-submission with disabled buttons
Maintain order summary visible throughout checkout
Security
Require authentication before payment
Validate expiry dates against current date
Never store complete card numbers in localStorage
The checkout flow is designed to minimize cart abandonment with clear progress indicators, persistent data, and inline validation.