docs: publish restructure (#3496)
* docs: added features and guides overview page * added image * added version 2 * added version 3 * added version 4 * docs: implemented new color scheme * docs: redesigned sidebar (#3193) * docs: redesigned navbar for restructure (#3199) * docs: redesigned footer (#3209) * docs: redesigned cards (#3230) * docs: redesigned admonitions (#3231) * docs: redesign announcement bar (#3236) * docs: redesigned large cards (#3239) * docs: redesigned code blocks (#3253) * docs: redesigned search modal and page (#3264) * docs: redesigned doc footer (#3268) * docs: added new sidebars + refactored css and assets (#3279) * docs: redesigned api reference sidebar * docs: refactored css * docs: added code tabs transition * docs: added new sidebars * removed unused assets * remove unusued assets * Fix deploy errors * fix incorrect link * docs: fixed code responsivity + missing icons (#3283) * docs: changed icons (#3296) * docs: design fixes to the sidebar (#3297) * redesign fixes * docs: small design fixes * docs: several design fixes after restructure (#3299) * docs: bordered icon fixes * docs: desgin fixes * fixes to code blocks and sidebar scroll * design adjustments * docs: restructured homepage (#3305) * docs: restructured homepage * design fixes * fixed core concepts icon * docs: added core concepts page (#3318) * docs: restructured homepage * design fixes * docs: added core concepts page * changed text of different components * docs: added architecture link * added missing prop for user guide * docs: added regions overview page (#3327) * docs: added regions overview * moved region pages to new structure * docs: fixed description of regions architecture page * small changes * small fix * docs: added customers overview page (#3331) * docs: added regions overview * moved region pages to new structure * docs: fixed description of regions architecture page * small changes * small fix * docs: added customers overview page * fix link * resolve link issues * docs: updated regions architecture image * docs: second-iteration fixes (#3347) * docs: redesigned document * design fixes * docs: added products overview page (#3354) * docs: added carts overview page (#3363) * docs: added orders overview (#3364) * docs: added orders overview * added links in overview * docs: added vercel redirects * docs: added soon badge for cards (#3389) * docs: resolved feedback changes + organized troubleshooting pages (#3409) * docs: resolved feedback changes * added extra line * docs: changed icons for restructure (#3421) * docs: added taxes overview page (#3422) * docs: added taxes overview page * docs: fix sidebar label * added link to taxes overview page * fixed link * docs: fixed sidebar scroll (#3429) * docs: added discounts overview (#3432) * docs: added discounts overview * fixed links * docs: added gift cards overview (#3433) * docs: added price lists overview page (#3440) * docs: added price lists overview page * fixed links * docs: added sales channels overview page (#3441) * docs: added sales overview page * fixed links * docs: added users overview (#3443) * docs: fixed sidebar border height (#3444) * docs: fixed sidebar border height * fixed svg markup * docs: added possible solutions to feedback component (#3449) * docs: added several overview pages + restructured files (#3463) * docs: added several overview pages * fixed links * docs: added feature flags + PAK overview pages (#3464) * docs: added feature flags + PAK overview pages * fixed links * fix link * fix link * fixed links colors * docs: added strategies overview page (#3468) * docs: automated upgrade guide (#3470) * docs: automated upgrade guide * fixed vercel redirect * docs: restructured files in docs codebase (#3475) * docs: restructured files * docs: fixed eslint exception * docs: finished restructure loose-ends (#3493) * fixed uses of backend * docs: finished loose ends * eslint fixes * fixed links * merged master * added update instructions for v1.7.12
This commit is contained in:
@@ -0,0 +1,650 @@
|
||||
---
|
||||
description: 'Learn how to implement the cart functionality in your storefront using the REST APIs. This includes creating a cart, updating a cart, adding products to the cart, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Add Cart Functionality
|
||||
|
||||
This document guides you through how you can add cart-related functionalities to your storefront. That includes creating and updating a cart and managing items in the cart.
|
||||
|
||||
## Overview
|
||||
|
||||
Carts are necessary for ecommerce platforms to allow customers to buy products. Each customer, whether logged in or as a guest, should have a cart associated with them. The customer can then add products to the cart.
|
||||
|
||||
This document helps you understand how to add the cart functionality to your storefront. This is helpful if you’re creating the storefront from scratch, or you want to understand how the process generally works in Medusa’s starter storefronts.
|
||||
|
||||
:::note
|
||||
|
||||
This document does not cover implementing the checkout flow. You can refer to [this documentation instead to learn how to implement the checkout flow](./implement-checkout-flow.mdx).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 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 either the [Next.js](../../../starters/nextjs-medusa-starter.mdx) or [Gatsby](../../../starters/gatsby-medusa-starter.mdx) storefronts.
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.
|
||||
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
## Create a Cart
|
||||
|
||||
You can create a cart with the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.create()
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, createCart } = useCart()
|
||||
|
||||
const handleCreateCart = () => {
|
||||
createCart.mutate(
|
||||
{}, // create an empty cart
|
||||
{
|
||||
onSuccess: ({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request does not require any parameters. It returns the created cart in the response.
|
||||
|
||||
The cart by default will have a random region assigned to it. You can specify the cart's region by passing in the request's body a `region_id` parameter:
|
||||
|
||||
Otherwise, you can assign it a specific region during creation:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.carts.create({
|
||||
region_id,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, createCart } = useCart()
|
||||
|
||||
const handleCreateCart = () => {
|
||||
createCart.mutate(
|
||||
{
|
||||
region_id,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URLL>/store/carts`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => {
|
||||
localStorage.setItem("cart_id", cart.id)
|
||||
// assuming you have a state variable to store the cart
|
||||
setCart(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Check out the [API Reference](/api/store/#tag/Cart/operation/PostCart) for a full list of available request body parameters.
|
||||
|
||||
:::note
|
||||
|
||||
The region a cart is associated with determines the currency the cart uses, the tax, payment, and fulfillment providers, and other details and options. So, make sure you use the correct region for a cart.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Cart
|
||||
|
||||
Notice that in the previous code snippets, you set the cart’s ID in the local storage. This is helpful to persist the customer’s cart even when they leave the website and come back later.
|
||||
|
||||
You can retrieve the cart at any given point using its ID with the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
const id = localStorage.getItem("cart_id")
|
||||
|
||||
if (id) {
|
||||
medusa.carts.retrieve(id)
|
||||
.then(({ cart }) => setCart(cart))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React" default>
|
||||
|
||||
```tsx
|
||||
import { useGetCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
const { cart, isLoading } = useGetCart(cart_id)
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
const id = localStorage.getItem("cart_id")
|
||||
|
||||
if (id) {
|
||||
fetch(`<BACKEND_URLL>/store/carts/${id}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter and returns the cart of that ID.
|
||||
|
||||
You can run this code snippet every time the storefront is opened. If a customer has a cart ID stored in their local storage, it’s loaded from the backend.
|
||||
|
||||
:::tip
|
||||
|
||||
Make sure to remove the ID from the local storage after the customer places an order with this cart.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Update a Cart
|
||||
|
||||
A cart has different data associated with it including the region, email, address, customer, and more.
|
||||
|
||||
You can use the following snippet to update any of the cart’s data:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
region_id,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeRegionId = (region_id: string) => {
|
||||
updateCart.mutate({
|
||||
region_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter. In its body, you can pass any data you want to update in the cart such as the region.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
Check out the full list of available request body parameters in the [API Reference](/api/store/#tag/Cart/operation/PostCartsCart).
|
||||
|
||||
### Associate a Logged-In Customer with the Cart
|
||||
|
||||
A customer might add items to their cart, then creates an account or log in. In that case, you should ensure that the cart is associated with the logged-in customer moving forward.
|
||||
|
||||
You can do that using the same update operation:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
customer_id,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeCustomerId = (customer_id: string) => {
|
||||
updateCart.mutate({
|
||||
customer_id,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customer_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This updates the `customer_id` associated with the cart to make sure it belongs to a specific customer.
|
||||
|
||||
### Associate Guest Customers with a Cart using Email
|
||||
|
||||
In case the customer doesn't want to use their own account, you must at least associate an email address with the cart before completing the cart and placing the order.
|
||||
|
||||
You can do that using the same update operation:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.update(cartId, {
|
||||
email: "user@example.com",
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const { updateCart } = useCart()
|
||||
|
||||
const changeEmail = (email: string) => {
|
||||
updateCart.mutate({
|
||||
email,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "user@example.com",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Line Item to the Cart
|
||||
|
||||
To create a line item of a product and add it to a cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```jsx
|
||||
medusa.carts.lineItems.create(cartId, {
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useCreateLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const createLineItem = useCreateLineItem(cart_id)
|
||||
|
||||
const handleAddItem = () => {
|
||||
createLineItem.mutate({
|
||||
variant_id,
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```jsx
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
variant_id,
|
||||
quantity: 1,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart as a path parameter. In the body, it's required to send the ID of the product variant you want to add to the cart and its quantity.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
This adds a new line item to the cart. Line items can be accessed using `cart.items` which is an array that holds all line items in the cart. You can learn more about what properties line items have in the [API reference](/api/store/#tag/Cart/operation/PostCartsCartLineItems).
|
||||
|
||||
:::note
|
||||
|
||||
If you’re using Sales Channels, make sure that the cart and the product belong to the same sales channel. You can update the cart’s sales channel by [updating the cart](#update-a-cart).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Update Line Item in the Cart
|
||||
|
||||
To update a line item's quantity in the cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.lineItems.update(cartId, lineItemId, {
|
||||
quantity: 3,
|
||||
})
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useUpdateLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const updateLineItem = useUpdateLineItem(cart_id)
|
||||
|
||||
const handleUpdateItem = () => {
|
||||
updateLineItem.mutate({
|
||||
lineId,
|
||||
quantity: 3,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items/${lineItemId}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
quantity: 3,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the line item as path parameters. In the body, it accepts the quantity of the line item.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Line Item from the Cart
|
||||
|
||||
To delete a line item from the cart, you can use the following code snippet:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.carts.lineItems.delete(cartId, lineItemId)
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useDeleteLineItem } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
// ...
|
||||
|
||||
const deleteLineItem = useDeleteLineItem(cart_id)
|
||||
|
||||
const handleDeleteItem = () => {
|
||||
deleteLineItem.mutate({
|
||||
lineId,
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Cart
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URLL>/store/carts/${cartId}/line-items/${lineItemId}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ cart }) => setCart(cart))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the line item as path parameters.
|
||||
|
||||
It returns the updated cart.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Implement the checkout flow in your storefront](./implement-checkout-flow.mdx)
|
||||
@@ -0,0 +1,599 @@
|
||||
---
|
||||
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 provider.
|
||||
|
||||
## 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 provider, 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 either the [Next.js](../../../starters/nextjs-medusa-starter.mdx) or [Gatsby](../../../starters/gatsby-medusa-starter.mdx) storefronts.
|
||||
|
||||
### 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" wrapperClassName="code-tabs">
|
||||
<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">
|
||||
|
||||
```tsx
|
||||
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" wrapperClassName="code-tabs">
|
||||
<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" wrapperClassName="code-tabs">
|
||||
<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">
|
||||
|
||||
```tsx
|
||||
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 providers is done differently for each provider, so this section will just show the general steps you should follow when implementing this step.
|
||||
|
||||
### Initialize Payment Sessions
|
||||
|
||||
When the page opens and before the payment providers are displayed to the customer to choose from, you must initialize 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" wrapperClassName="code-tabs">
|
||||
<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 providers</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 provider they want to complete purchase with, you should select the payment session associated with that payment provider. 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" wrapperClassName="code-tabs">
|
||||
<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">
|
||||
|
||||
```tsx
|
||||
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 provider 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 provider or if only one payment provider 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 providers. 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 Provider 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" wrapperClassName="code-tabs">
|
||||
<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">
|
||||
|
||||
```tsx
|
||||
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 provider as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment provider.
|
||||
|
||||
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 provider 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" wrapperClassName="code-tabs">
|
||||
<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">
|
||||
|
||||
```tsx
|
||||
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.
|
||||
Reference in New Issue
Block a user