The checkout flow is the most consequential part of any e-commerce store. It is the last thing standing between a buyer and a completed purchase, and every unnecessary step, every confusing field, every extra page load is a place where someone leaves. Medusa.js gives you complete control over how your checkout works, which is both its greatest advantage and where most teams run into trouble.
A custom checkout flow in Medusa.js means you are not working within the constraints of a hosted checkout page. You own the logic, the UI, and the progression. This guide walks through the architecture of a checkout flow that handles address collection, shipping method selection, and payment in a single cohesive experience without relying on redirects or third-party hosted pages.
How Medusa.js Models a Cart and Checkout
Before writing any code, it helps to understand how Medusa models the checkout process. In Medusa.js, a cart is the central object throughout checkout. It starts as a collection of line items and progressively accumulates everything needed to become an order: a shipping address, a billing address, a shipping method, and a payment session.
The cart object is not finalised until all required fields are populated and a payment is authorised. This means your frontend can drive the user through whatever flow you design, updating the cart at each step, and Medusa handles the state without the flow needing to match any particular page sequence.
This architecture is what makes building a single-page or multi-step checkout equally straightforward. You can read more about the Medusa.js core architecture and how its module system underpins this flexibility across commerce logic.
Step 1: Creating and Persisting the Cart
Cart creation happens when a user adds their first item. The Medusa storefront SDK or a direct API call to the cart endpoint creates a cart object and returns a cart ID. This cart ID should be persisted in the user's session or local state throughout the checkout process, as every subsequent step references it.
Associate the cart with the correct region early. Region determines currency, tax behaviour, and which shipping options and payment providers are available. If your store operates across multiple regions, prompt for the user's country before or during cart creation rather than at the end of checkout.
Step 2: Collecting Shipping and Billing Address
Medusa accepts shipping and billing addresses as separate fields on the cart. The minimum required fields are first name, last name, address line 1, city, country code, and postal code. Phone is optional but recommended for delivery notifications.
Design the address form to capture both shipping and billing in one view with a checkbox that copies the shipping address to billing when they match, which is the case for the majority of orders. This eliminates the need for a separate billing address step for most users while still handling split addresses correctly.
Update the cart with the address using a PATCH request to the cart endpoint as soon as the address form is completed, not just when the user clicks a continue button. This allows Medusa to immediately return available shipping methods for the destination, reducing the perceived wait time in the next step.
Step 3: Fetching and Selecting Shipping Methods
Once a shipping address is set, Medusa can return the available shipping options for that cart and region. These are configured in the Medusa admin under the relevant shipping profiles and fulfillment providers. Your frontend fetches these and presents them to the user.
For most Indian e-commerce setups, you will have two to three shipping options at most: standard delivery, express delivery, and possibly free shipping above a threshold. Keep the selection UI simple. A radio button group with the option name, estimated delivery time, and price is sufficient. Avoid presenting more than five options, as choice overload increases drop-off.
Once the user selects a method, update the cart with the selected shipping option ID. The cart total updates to include the shipping cost, and the order summary in your UI should reflect this immediately without a page reload.
Step 4: Initialising a Payment Session
Payment in Medusa is handled through payment providers that you configure in the backend. Common choices for Indian storefronts are Razorpay and Stripe. Each provider integrates through a Medusa payment module that handles session creation, authorisation, and capture.
Initialising a payment session on the cart creates a session object specific to that provider. For Razorpay, this generates an order ID that your frontend uses to trigger the Razorpay checkout widget. For Stripe, it creates a payment intent that the Stripe Elements component uses to collect card details.
Step 5: Handling Payment Authorisation and Order Placement
After the user completes payment on the provider's widget or form, your frontend receives a confirmation callback. At this point, you call Medusa's complete cart endpoint, which authorises the payment session, validates the cart, and converts it into an order. The response returns the completed order object including the order ID.
Error handling here is critical. Payment failures, network timeouts, and duplicate submission attempts are all real scenarios. Implement a loading state that disables the place order button after the first click. On failure, surface a clear error message and allow the user to retry without losing their cart state. On success, redirect to an order confirmation page and clear the cart from the user's session.
Building It as a Single Page vs a Multi-Step Flow
Whether you implement checkout as a single page or a stepped wizard depends on your product and audience. Single-page checkouts with progressive disclosure, where sections expand as the user completes each part, work well for low-SKU stores and returning buyers who want speed. Multi-step flows with clear progress indicators work better for first-time buyers and stores with more complex shipping or customisation options.
In Medusa.js, the backend does not enforce either approach. The cart update API is the same regardless of how your frontend structures the flow. This means you can run A/B tests between a single-page and multi-step version by swapping only the frontend component, keeping the cart logic identical.
For teams building with Next.js App Router as the storefront framework, the Medusa.js Next.js starter provides a solid base implementation of this checkout architecture that you can adapt to your design and business logic requirements rather than starting from scratch.
Common Checkout Implementation Mistakes to Avoid
Not persisting cart state across page reloads is one of the most common issues in early implementations. The cart ID should survive a browser refresh. Store it in a cookie or server-side session, not just in React state.
Another frequent issue is not handling the region and currency setting early enough. If a user adds items to a cart in one region and then selects a shipping address in a different region, the available shipping methods and tax calculations can differ significantly. Resolve region before cart creation where possible.
Finally, do not wait for a full checkout redesign to improve your payment UX. Payment provider widgets like Razorpay and Stripe have their own UX defaults. Test the exact flow your buyers will see on mobile, because the majority of Indian e-commerce transactions happen on mobile devices where the default widget layout may not render optimally within your checkout page.
The Medusa.js documentation on checkout flows covers the full API surface for cart and payment management, including edge cases around multi-currency and tax-inclusive pricing that are worth reviewing before going to production.
A well-implemented checkout flow in Medusa.js gives you the conversion advantages of a fully custom experience without sacrificing the commerce logic that a purpose-built platform handles for you. The architecture described here scales from a simple single-product store to a complex multi-region catalogue without requiring changes to your backend setup.
Written by
Kannan Rajendiran
CEO
