---
description: 'Learn how to implement the checkout flow in your storefront using the REST APIs. This includes adding steps related to shipping and payment, then placing the order.'
addHowToData: true
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# How to Implement Checkout Flow

This document will guide you through the steps needed to implement the checkout flow in a Medusa storefront, including steps related to adding a custom payment processor.

## Overview

A checkout flow is composed of the necessary steps to allow a customer to perform a successful checkout. It’s generally made up of two primary steps: the shipping and payment steps.

This document will take you through the general process of a checkout flow. You should follow along with this document if you’re creating a custom storefront, if you’re adding a custom payment processor, or if you’re just interested in learning more about how checkout works in Medusa.

:::note

It’s recommended to go through the [Shipping Architecture Overview](../shipping.md) and [Payment Architecture Overview](../payment.md) first to have a better understanding of Medusa’s architecture.

:::

---

## Prerequisites

### Medusa Components

It's assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../../../development/backend/install.mdx) to get started.

It is also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js starter storefront](../../../starters/nextjs-medusa-starter.mdx).

### JS Client

This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client and JavaScript’s Fetch API.

If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) and have [created an instance of the client](../../../js-client/overview.md#configuration).

### Medusa React

This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.

If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).

It's also assumed you already have [used CartProvider higher in your component tree](../../../medusa-react/overview.md#cartprovider).

### Previous Steps

This document assumes you’ve already taken care of the add-to-cart flow. So, you should have a [cart created](/api/store/#tag/Cart/operation/PostCart) for the customer with at least [one product in it](/api/store/#tag/Cart/operation/PostCartsCartLineItems).

You can learn how to implement the cart flow using [this documentation](./implement-cart.mdx).

---

## Shipping Step

In this step, the customer generally enters their shipping info, then chooses the available shipping option based on the entered info.

### Add Shipping Address

After the customer enters their shipping address information, you must send a `POST` request to the [Update a Cart](/api/store/#tag/Cart/operation/PostCartsCart) API endpoint:

<Tabs groupId="request-type" isCodeTabs={true}>
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.carts.update(cartId, {
  shipping_address: {
    company,
    first_name,
    last_name,
    address_1,
    address_2,
    city,
    country_code,
    province,
    postal_code,
    phone,
  },
})
.then(({ cart }) => {
  console.log(cart.shipping_address)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```ts
import { useCart } from "medusa-react"

const Cart = () => {
  // ...

  const { updateCart } = useCart()

  const addShippingAddress = 
    (address: Record<string, string>) => {
      updateCart.mutate({
        shipping_address: {
          company: address.company,
          first_name: address.first_name,
          last_name: address.last_name,
          address_1: address.address_1,
          address_2: address.address_2,
          city: address.city,
          country_code: address.country_code,
          province: address.province,
          postal_code: address.postal_code,
          phone: address.phone,
        },
      })
    }

  // ...
}

export default Cart
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/store/carts/${cartId}`, {
  method: "POST",
  credentials: "include",
  body: JSON.stringify({
    shipping_address: {
      company,
      first_name,
      last_name,
      address_1,
      address_2,
      city,
      country_code,
      province,
      postal_code,
      phone,
    },
  }),
  headers: {
    "Content-Type": "application/json",
  },
})
.then((response) => response.json())
.then(({ cart }) => {
  console.log(cart.shipping_address)
})
```

</TabItem>
</Tabs>

This request accepts the ID of the cart as a path parameter and the new shipping address in the request body.

The request returns the updated cart, with the new shipping address available in `cart.shipping_address`.

---

### List Shipping Options

After updating the cart with the customer’s address, the list of available [shipping options](../shipping.md#shipping-option) for that cart might change. So, you should retrieve the updated list of options. 

You can retrieve the list of shipping options by sending a `GET` request to the [Retrieve Shipping Options for Cart API](/api/store/#tag/Shipping-Option/operation/GetShippingOptionsCartId) endpoint:

<Tabs groupId="request-type" isCodeTabs={true}>
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.shippingOptions.listCartOptions(cartId)
.then(({ shipping_options }) => {
  console.log(shipping_options.length)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { ShippingOption } from "@medusajs/medusa"
import { useCartShippingOptions } from "medusa-react"

type Props = {
  cartId: string
}

const ShippingOptions = ({ cartId }: Props) => {
  const { shipping_options, isLoading } =
    useCartShippingOptions(cartId)

  return (
    <div>
      {isLoading && <span>Loading...</span>}
      {shipping_options && !shipping_options.length && (
        <span>No shipping options</span>
      )}
      {shipping_options && (
        <ul>
          {shipping_options.map(
            (shipping_option: ShippingOption) => (
              <li key={shipping_option.id}>
                {shipping_option.name}
              </li>
            )
          )}
        </ul>
      )}
    </div>
  )
}

export default ShippingOptions
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/store/shipping-options/${cartId}`, {
  credentials: "include",
})
.then((response) => response.json())
.then(({ shipping_options }) => {
  console.log(shipping_options.length)
})
```

</TabItem>
</Tabs>

The request accepts the ID of the cart as a path parameter. It returns the array of [shipping options](/api/store/#tag/Shipping-Option/operation/GetShippingOptions). Typically you would display those options to the customer to choose from.

### Choose Shipping Option

Once the customer chooses one of the available shipping options, send a `POST` request to the [Add a Shipping Method](/api/store/#tag/Cart/operation/PostCartsCartShippingMethod) API endpoint. This will create a [shipping method](../shipping.md#shipping-method) based on the shipping option chosen and will associate it with the customer’s cart:

<Tabs groupId="request-type" isCodeTabs={true}>
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.carts.addShippingMethod(cartId, {
  option_id: shippingOptionId, // the ID of the selected option
})
.then(({ cart }) => {
  console.log(cart.shipping_methods)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```ts
import { useAddShippingMethodToCart } from "medusa-react"
// ...

const ShippingOptions = ({ cartId }: Props) => {
  // ...
  const addShippingMethod = useAddShippingMethodToCart(cartId)

  const handleAddShippingMethod = (option_id: string) => {
    addShippingMethod.mutate({
      option_id,
    })
  }

  // ...
}

export default ShippingOptions
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/store/carts/${cartId}/shipping-methods`, {
  method: "POST",
  credentials: "include",
  body: JSON.stringify({
    option_id: shippingOptionId, // ID of the selected option
  }),
  headers: {
    "Content-Type": "application/json",
  },
})
.then((response) => response.json())
.then(({ cart }) => {
  console.log(cart.shipping_methods)
})
```

</TabItem>
</Tabs>

The request accepts the ID of the cart as a path parameter and its body the ID of the selected shipping option.

It returns the updated cart, with the created shipping method available in the array `cart.shipping_methods`.

---

## Payment Step

In this step, the customer generally chooses a payment method to complete their purchase. The implementation of payment processors is done differently for each processor, so this section will just show the general steps you should follow when implementing this step.

### Display Payment Methods

When the page opens and before the payment providers are displayed to the customer to choose from, you must create the [payment sessions](../payment.md#payment-session) for the current cart. Each payment provider will have a payment session associated with it. These payment sessions will be used later when the customer chooses the payment provider they want to complete their purchase with.

To initialize the payment sessions, send a `POST` request to the [Initialize Payment Sessions](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions) API endpoint:

<Tabs groupId="request-type" isCodeTabs={true}>
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.carts.createPaymentSessions(cartId)
.then(({ cart }) => {
  console.log(cart.payment_sessions)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { PaymentSession } from "@medusajs/medusa"
import { useCart } from "medusa-react"
import { useEffect } from "react"

const PaymentProviders = () => {
  const { cart, startCheckout } = useCart()

  useEffect(() => {
    startCheckout.mutate()
  }, [])

  return (
    <div>
      {!cart?.payment_sessions.length && (
        <span>No payment processors</span>
      )}
      <ul>
        {cart?.payment_sessions.map(
          (paymentSession: PaymentSession) => (
            <li key={paymentSession.id}>
              {paymentSession.provider_id}
            </li>
          )
        )}
      </ul>
    </div>
  )
}

export default PaymentProviders
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/store/carts/${cartId}/payment-sessions`, {
  method: "POST",
  credentials: "include",
})
.then((response) => response.json())
.then(({ cart }) => {
  console.log(cart.payment_sessions)
})
```

</TabItem>
</Tabs>

This endpoint accepts the ID of the cart as a path parameter. It returns the updated cart with the initialized payment sessions available on `cart.payment_sessions`.

### Select Payment Session

When the customer chooses the payment processor they want to complete purchase with, you should select the payment session associated with that payment processor. To do that, send a `POST` request to the [Select a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) API endpoint:

<Tabs groupId="request-type" isCodeTabs={true}>
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.carts.setPaymentSession(cartId, {
  // retrieved from the payment session selected by the customer
  provider_id: paymentProviderId,
})
.then(({ cart }) => {
  console.log(cart.payment_session)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```ts
import { useCart } from "medusa-react"

const PaymentProviders = () => {
  const { cart, startCheckout, pay } = useCart()
  // ...

  const handleSetPaymentSession = (provider_id: string) => {
    pay.mutate({
      provider_id,
    })
  }
  
  // ...
}

export default PaymentProviders
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/store/carts/${cartId}/payment-session`, {
  method: "POST",
  credentials: "include",
  body: JSON.stringify({
    // the payment session selected by the customer
    provider_id: paymentProviderId,
  }),
  headers: {
    "Content-Type": "application/json",
  },
})
.then((response) => response.json())
.then(({ cart }) => {
  console.log(cart.payment_session)
})
```

</TabItem>
</Tabs>

The request accepts the ID of the cart as a path parameter, and the ID of the payment processor in the request's body.

It returns the updated cart, with the selected payment session available under `cart.payment_session`.

:::tip

If you have one payment processor or if only one payment processor is available for the current cart, its payment session will be automatically selected in the “[Initialize Payment Session](#initialize-payment-sessions)” step and this step becomes unnecessary. You can check whether there is a payment session selected or not by checking whether `cart.payment_session` is `null` or not.

:::

### Update Payment Session

This step is optional and is only necessary for some payment processors. As mentioned in the [Payment Architecture](../payment.md#overview) documentation, the `PaymentSession` model has a `data` attribute that holds any data required for the Payment Processor to perform payment operations such as capturing payment.

If you need to update that data at any point before the purchase is made, send a request to [Update a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) API endpoint:

<Tabs groupId="request-type" isCodeTabs={true}>
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.carts.updatePaymentSession(cartId, paymentProviderId, {
  data: {
    // pass any data you want to add in the `data` attribute
    // for example:
    "test": true,
  },
})
.then(({ cart }) => {
  console.log(cart.payment_session.data)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```ts
import { useUpdatePaymentSession, useCart } from "medusa-react"
// ...

const PaymentProviders = () => {
  const { cart } = useCart()
  const updatePaymentSession = useUpdatePaymentSession(cart.id)
  // ...

  const handleUpdatePaymentSession = (
    provider_id: string, 
    data: Record<string, any>
  ) => {
    updatePaymentSession.mutate({
      provider_id,
      data,
    })
  }

  // ...
}

export default PaymentProviders
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

<!-- eslint-disable max-len -->

```ts
fetch(
  `<BACKEND_URL>/store/carts/${cartId}/payment-sessions/${paymentProviderId}`, 
  {
    method: "POST",
    credentials: "include",
    body: JSON.stringify({
      data: {
        // pass any data you want to add in the `data` attribute
        // for example:
        "test": true,
      },
    }),
    headers: {
      "Content-Type": "application/json",
    },
  }
)
.then((response) => response.json())
.then(({ cart }) => {
  console.log(cart.payment_session.data)
})
```

</TabItem>
</Tabs>

This request accepts the ID of the cart and the ID of the payment session's payment processor as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment processor.

It returns the updated cart. You can access the payment session's data on `cart.payment_session.data`.

### Complete Cart

The last step is to place the order by completing the cart. When you complete the cart, your Medusa backend will try to authorize the payment first, then place the order if the authorization is successful. So, you should perform any necessary action with your payment processor first to make sure the authorization is successful when you send the request to complete the cart.

To complete a cart, send a `POST` request to the [Complete a Cart](/api/store/#tag/Cart/operation/PostCartsCartComplete) API endpoint:

<Tabs groupId="request-type" isCodeTabs={true}>
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.carts.complete(cartId)
.then(({ type, data }) => {
  console.log(type, data)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```ts
import { useCart } from "medusa-react"
// ...

const Cart = () => {
  const { completeCheckout } = useCart()
  // ...

  const handleStartCheckout = () => {
    startCheckout.mutate()
  }

  // ...
}

export default Cart
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/store/carts/${cartId}/complete`, {
  method: "POST",
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
  },
})
.then((response) => response.json())
.then(({ type, data }) => {
  console.log(type, data)
})
```

</TabItem>
</Tabs>

This request accepts the ID of the cart as a path parameter.

The request returns two properties: `type` and `data`. If the order was placed successfully, `type` will be `order` and `data` will be the order's data.

If an error occurred while placing the order, `type` will be `cart` and `data` will be the cart's data.