Medusa Checkout Flow: Step-by-Step Guide to Building a Complete E-Commerce Checkout



This content originally appeared on DEV Community and was authored by Michał Miler

Building an e-commerce storefront requires implementing a robust checkout flow that handles cart management, shipping, and payment processing. Medusa.js provides a comprehensive set of Store APIs that make this process straightforward. In this article, we’ll explore the complete Medusa checkout flow by walking through each API endpoint required to take a customer from an empty cart to a completed order.

Understanding the Medusa Checkout Process

The Medusa checkout flow consists of seven essential steps:

  1. Cart Creation – Initialize a new shopping cart
  2. Add Items – Add products to the cart
  3. Set Shipping Address – Configure delivery information
  4. Select Shipping Method – Choose delivery options
  5. Payment Collection – Initialize payment processing
  6. Payment Session – Set up payment provider
  7. Complete Order – Finalize the purchase

Each step builds upon the previous one, creating a seamless user experience while maintaining data integrity throughout the process.

Prerequisites and Setup

Before diving into the implementation, ensure you have:

  • A running Medusa v2 application
  • Valid publishable API key
  • Product variants, regions, and shipping options configured
  • Payment provider set up (we’ll use the system default)
  • curl and jq installed for making API requests and parsing JSON responses

curl and jq Installation

# macOS (using Homebrew)

brew install curl jq

# Ubuntu/Debian

sudo apt update
sudo apt install curl jq

# Windows (using Chocolatey)

choco install curl jq

Environment Variables

Set the following variables before running the checkout flow:

export BASE_URL="<http://localhost:9000>"
export PUBLISHABLE_KEY="pk_<your_publishable_key_here>"
export REGION_ID="reg_<your_region_id>"
export PRODUCT_VARIANT_ID="variant_<your_variant_id>"
export SHIPPING_OPTION_ID="so_<your_shipping_option_id>"
export PAYMENT_PROVIDER_ID="pp_system_default"

Note: The CART_ID and PAYMENT_COLLECTION_ID variables should be set during the checkout process after cart creation and payment collection initialization.

Step-by-Step Implementation

1. Create a New Cart

The checkout journey begins with creating a new cart associated with a specific region:

curl -X POST "$BASE_URL/store/carts" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "region_id": "'$REGION_ID'" }'

Key Points:

  • Each cart gets a unique identifier used in subsequent API calls
  • Regions define currency, tax rates, and fulfillment settings

2. Add Products to Cart

Once the cart exists, add product variants with specified quantities:

curl -X POST "$BASE_URL/store/carts/$CART_ID/line-items" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "variant_id": "'$PRODUCT_VARIANT_ID'", "quantity": 1 }'

Key Points:

  • Use variant_id rather than product_id for specific product configurations
  • Quantities can be updated by calling this endpoint again
  • Inventory levels are automatically checked during this step

3. Set Shipping Address and Customer Information

Configure the delivery address and customer contact details:

curl -X POST "$BASE_URL/store/carts/$CART_ID" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{
    "shipping_address": {
      "first_name": "John",
      "last_name": "Doe",
      "address_1": "Nordmarksvej 9",
      "city": "Billund",
      "country_code": "dk",
      "postal_code": "7190",
      "phone": "1234567890"
    },
    "email": "john.doe@example.com"
  }'

Key Points:

  • country_code must match the region’s available countries
  • Email is required for order confirmation and customer account association
  • Billing address can be set separately if different from shipping address

4. Select Shipping Method

Choose from available shipping options for the cart’s region and address:

curl -X POST "$BASE_URL/store/carts/$CART_ID/shipping-methods" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "option_id": "'$SHIPPING_OPTION_ID'" }'

5. Create Payment Collection

Initialize the payment processing system for the cart:

curl -X POST "$BASE_URL/store/payment-collections" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "cart_id": "'$CART_ID'" }'

6. Initialize Payment Session

Set up the payment provider session for processing:

curl -X POST "$BASE_URL/store/payment-collections/$PAYMENT_COLLECTION_ID/payment-sessions" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "provider_id": "'$PAYMENT_PROVIDER_ID'" }'

Key Points:

  • Payment providers (Stripe, PayPal, etc.) require specific session initialization
  • Provider ID must match configured payment providers in your Medusa setup
  • Session contains payment intent or equivalent for the provider

7. Complete the Order

Finalize the checkout process and create the order:

curl -X POST "$BASE_URL/store/carts/$CART_ID/complete" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY"

Key Points:

  • This step validates all cart information and processes payment
  • Inventory is reserved and order confirmation is triggered
  • The cart is converted to an immutable order record

Complete Implementation Example

Here’s a complete shell script implementing the entire checkout flow:

#!/bin/bash

# Configuration
BASE_URL="<http://localhost:9000>"
PUBLISHABLE_KEY="pk_"
REGION_ID="reg_"
PRODUCT_VARIANT_ID="variant_"
SHIPPING_OPTION_ID="so_"
PAYMENT_PROVIDER_ID="pp_system_default"

# 1. Create cart
echo "Creating cart..."
CART_RESPONSE=$(curl -s -X POST "$BASE_URL/store/carts" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "region_id": "'$REGION_ID'" }')

CART_ID=$(echo $CART_RESPONSE | jq -r '.cart.id')
echo "Cart created with ID: $CART_ID"

# 2. Add item to cart
echo "Adding item to cart..."
curl -s -X POST "$BASE_URL/store/carts/$CART_ID/line-items" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "variant_id": "'$PRODUCT_VARIANT_ID'", "quantity": 1 }' > /dev/null

# 3. Set shipping address and email
echo "Setting shipping address..."
curl -s -X POST "$BASE_URL/store/carts/$CART_ID" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{
    "shipping_address": {
      "first_name": "John",
      "last_name": "Doe",
      "address_1": "Nordmarksvej 9",
      "city": "Billund",
      "country_code": "dk",
      "postal_code": "7190",
      "phone": "1234567890"
    },
    "email": "john.doe@example.com"
  }' > /dev/null

# 4. Set shipping method
echo "Setting shipping method..."
curl -s -X POST "$BASE_URL/store/carts/$CART_ID/shipping-methods" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "option_id": "'$SHIPPING_OPTION_ID'" }' > /dev/null

# 5. Create payment collection
echo "Creating payment collection..."
PAYMENT_COLLECTION_RESPONSE=$(curl -s -X POST "$BASE_URL/store/payment-collections" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "cart_id": "'$CART_ID'" }')

PAYMENT_COLLECTION_ID=$(echo $PAYMENT_COLLECTION_RESPONSE | jq -r '.payment_collection.id')
echo "Payment collection created with ID: $PAYMENT_COLLECTION_ID"

# 6. Initialize payment session
echo "Initializing payment session..."
curl -s -X POST "$BASE_URL/store/payment-collections/$PAYMENT_COLLECTION_ID/payment-sessions" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY" \
  -d '{ "provider_id": "'$PAYMENT_PROVIDER_ID'" }' > /dev/null

# 7. Complete the cart
echo "Completing order..."
ORDER_RESPONSE=$(curl -s -X POST "$BASE_URL/store/carts/$CART_ID/complete" \
  -H "Content-Type: application/json" \
  -H "x-publishable-api-key: $PUBLISHABLE_KEY")

ORDER_ID=$(echo $ORDER_RESPONSE | jq -r '.order.id')
echo "Order completed successfully: $ORDER_ID"

Troubleshooting

  1. Check Service Health: Test if the service is responding by visiting http://localhost:9000/store/products in your browser. You should see a JSON response with available products.
  2. Validate API Key: Confirm your publishable API key is correct and has proper permissions for store operations.
  3. No Products Available: Verify products exist in your store by checking the /store/products endpoint. Ensure products are published and have available variants.
  4. Invalid Shipping Address:
    • Validate address format matches the expected country standards
    • Ensure the country_code is listed under the region’s allowed countries
    • Check /store/regions/{region_id} to see available countries for your region
  5. Shipping Option Not Available: Verify the shipping option is enabled for the selected region and address. Check /store/shipping-options for available options in your region.
  6. Insufficient Inventory: Check stock levels before adding items. The API will return inventory errors if requested quantities exceed available stock.
  7. Payment Provider Issues:
    • Ensure the payment provider is properly configured in your Medusa setup
    • Handle payment failures gracefully with appropriate error messaging
    • Verify payment provider credentials are valid

Conclusion

The Medusa checkout flow provides a robust foundation for e-commerce applications with its well-structured API endpoints. By following this seven-step process, you can build reliable checkout experiences that handle modern e-commerce requirements.

Key takeaways:

  • Each step depends on the previous one’s completion
  • Implement proper validation and retry logic
  • Track cart state throughout the process
  • Provide user feedback at each step

This approach supports various checkout scenarios including guest flows, multiple payment methods, and subscription purchases.


This content originally appeared on DEV Community and was authored by Michał Miler