docs: add storefront guide select customer address during checkout (#11288)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
---
|
||||
sidebar_label: "Checkout Step 2: Set Address"
|
||||
tags:
|
||||
- cart
|
||||
- storefront
|
||||
@@ -7,16 +8,20 @@ tags:
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Checkout Step 2: Enter Address`,
|
||||
title: `Checkout Step 2: Set Shipping and Billing Addresses`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The second step of the checkout flow is to ask the customer for their address.
|
||||
The second step of the checkout flow is to ask the customer for their address. A cart has shipping and billing addresses that customers need to set.
|
||||
|
||||
{/* TODO add how to list addresses of logged in customer. */}
|
||||
You can either show a form to enter the address, or, if the customer is logged in, allow them to pick an address from their account.
|
||||
|
||||
A cart has shipping and billing addesses. Use the [Update Cart API route]() to update the cart's addresses.
|
||||
This guide shows you how to implement both approaches. You can choose either or combine them, based on your use case.
|
||||
|
||||
## Approach One: Address Form
|
||||
|
||||
The first approach to setting the cart's shipping and billing addresses is to show a form to the customer to enter their address details. To update the cart's address, use the [Update Cart API route](!api!/store#carts_postcartsid) to update the cart's addresses.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -210,5 +215,191 @@ In the example above:
|
||||
|
||||
- The same address is used for shipping and billing for simplicity. You can provide the option to enter both addresses instead.
|
||||
- You send the address to the Update Cart API route under the `shipping_address` and `billing_address` request body parameters.
|
||||
- The updated cart object is retuned in the response.
|
||||
- The updated cart object is returned in the response.
|
||||
- **React example:** in the address, the chosen country must be in the cart's region. So, only the countries part of the cart's region are shown.
|
||||
|
||||
---
|
||||
|
||||
## Approach Two: Select Customer Address
|
||||
|
||||
The second approach to setting the cart's shipping and billing addresses is to allow the logged-in customer to select an address they added previously to their account. To retrieve the customer's addresses, use the [List Customer Addresses API route](!api!/store#customers_getcustomersmeaddresses). Then, once the customer selects an address, use the [Update Cart API route](!api!/store#carts_postcartsid) to update the cart's addresses.
|
||||
|
||||
<Note title="Good to Know">
|
||||
|
||||
A customer's address and a cart's address are represented by different data models in the Medusa application, as they're managed by the [Customer Module](../../../commerce-modules/customer/page.mdx) and the [Cart Module](../../../commerce-modules/cart/page.mdx), respectively. So, addresses that the customer used previously during checkout aren't automatically saved to their account. You need to save the customer's address using the [Create Customer Address API route](!api!/store#customers_postcustomersmeaddresses).
|
||||
|
||||
</Note>
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetch2Highlights = [
|
||||
["1", "cartId", "Assuming the cart's ID is stored in the database."],
|
||||
["3", "retrieveCustomerAddresses", "Retrieve the customer's addresses."],
|
||||
["18", "updateCartAddress", "Update the cart's address with the selected customer address."],
|
||||
["19", "address", "Map the customer address to the expected cart address."],
|
||||
["39", "shipping_address", "Pass the selected address as a shipping address."],
|
||||
["40", "billing_address", "Pass the selected address as a billing address."],
|
||||
]
|
||||
|
||||
```ts highlights={fetch2Highlights}
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
|
||||
const retrieveCustomerAddresses = () => {
|
||||
fetch("http://localhost:9000/store/customers/me/addresses", {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ addresses }) => {
|
||||
// use addresses...
|
||||
console.log(addresses)
|
||||
})
|
||||
}
|
||||
|
||||
const updateCartAddress = (customerAddress: Record<string, unknown>) => {
|
||||
const address = {
|
||||
first_name: customerAddress.first_name || "",
|
||||
last_name: customerAddress.last_name || "",
|
||||
address_1: customerAddress.address_1 || "",
|
||||
company: customerAddress.company || "",
|
||||
postal_code: customerAddress.postal_code || "",
|
||||
city: customerAddress.city || "",
|
||||
country_code: customerAddress.country_code || cart.region?.countries?.[0].iso_2,
|
||||
province: customerAddress.province || "",
|
||||
phone: customerAddress.phone || ""
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cart.id}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
shipping_address: address,
|
||||
billing_address: address
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: updatedCart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const react2Highlights = [
|
||||
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
|
||||
["5", "useCustomer", "The `useCustomer` hook was defined in the Customer React Context documentation."],
|
||||
["11", "selectedAddress", "Store the ID of the address that the customer selects."],
|
||||
["20", "updateAddress", "Update the cart's shipping and billing addresses based on the selected address."],
|
||||
["31", "address", "Map the customer address to the expected cart address."],
|
||||
["51", "shipping_address", "Pass the selected address as a shipping address."],
|
||||
["52", "billing_address", "Pass the selected address as a billing address."],
|
||||
["66", "select", "Show a dropdown to select the customer's address."],
|
||||
]
|
||||
|
||||
```tsx highlights={react2Highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCart } from "../../../providers/cart";
|
||||
import { useCustomer } from "../../../providers/customer";
|
||||
|
||||
export default function CheckoutAddressStep () {
|
||||
const { cart, setCart } = useCart()
|
||||
const { customer } = useCustomer()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedAddress, setSelectedAddress] = useState(customer?.addresses[0]?.id || "")
|
||||
|
||||
useEffect(() => {
|
||||
if (!customer) {
|
||||
// TODO you can redirect here to another page or component that shows the address form
|
||||
}
|
||||
setSelectedAddress(customer?.addresses[0]?.id || "")
|
||||
}, [customer])
|
||||
|
||||
const updateAddress = (
|
||||
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => {
|
||||
e.preventDefault()
|
||||
|
||||
const customerAddress = customer?.addresses.find((address) => address.id === selectedAddress)
|
||||
if (!cart || !customerAddress) {
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
|
||||
const address = {
|
||||
first_name: customerAddress.first_name || "",
|
||||
last_name: customerAddress.last_name || "",
|
||||
address_1: customerAddress.address_1 || "",
|
||||
company: customerAddress.company || "",
|
||||
postal_code: customerAddress.postal_code || "",
|
||||
city: customerAddress.city || "",
|
||||
country_code: customerAddress.country_code || cart.region?.countries?.[0].iso_2,
|
||||
province: customerAddress.province || "",
|
||||
phone: customerAddress.phone || ""
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cart.id}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
shipping_address: address,
|
||||
billing_address: address
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: updatedCart }) => {
|
||||
setCart(updatedCart)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
{!cart && <span>Loading...</span>}
|
||||
{!customer?.addresses.length && <span>Customer doesn't have addresses</span>}
|
||||
<select value={selectedAddress} onChange={(e) => setSelectedAddress(e.target.value)}>
|
||||
{customer?.addresses.map((address) => (
|
||||
<option value={address.id} key={address.id}>{address.country_code}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
disabled={!cart || loading || !selectedAddress}
|
||||
onClick={updateAddress}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
In the example above, you retrieve the customer's addresses and, when the customer selects an address, you update the cart's shipping and billing addresses with the selected address.
|
||||
|
||||
In the React example, you use the [Customer React Context](../../customers/context/page.mdx) to retrieve the logged-in customer, who has a list of addresses. You show a dropdown to select the address, and when the customer selects an address, you send a request to update the cart's addresses.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
For both examples, you send a request as an authenticated customer using the cookie session. Learn about other options to send an authenticated request in [this guide](../../customers/login/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
Reference in New Issue
Block a user