Skip to content

Commit e0851b2

Browse files
authored
docs: add storefront guide select customer address during checkout (medusajs#11288)
1 parent 178a7f4 commit e0851b2

File tree

6 files changed

+202
-11
lines changed

6 files changed

+202
-11
lines changed

www/apps/resources/app/storefront-development/checkout/address/page.mdx

Lines changed: 196 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
---
2+
sidebar_label: "Checkout Step 2: Set Address"
23
tags:
34
- cart
45
- storefront
@@ -7,16 +8,20 @@ tags:
78
import { CodeTabs, CodeTab } from "docs-ui"
89

910
export const metadata = {
10-
title: `Checkout Step 2: Enter Address`,
11+
title: `Checkout Step 2: Set Shipping and Billing Addresses`,
1112
}
1213

1314
# {metadata.title}
1415

15-
The second step of the checkout flow is to ask the customer for their address.
16+
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.
1617

17-
{/* TODO add how to list addresses of logged in customer. */}
18+
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.
1819

19-
A cart has shipping and billing addesses. Use the [Update Cart API route]() to update the cart's addresses.
20+
This guide shows you how to implement both approaches. You can choose either or combine them, based on your use case.
21+
22+
## Approach One: Address Form
23+
24+
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.
2025

2126
For example:
2227

@@ -210,5 +215,191 @@ In the example above:
210215

211216
- The same address is used for shipping and billing for simplicity. You can provide the option to enter both addresses instead.
212217
- You send the address to the Update Cart API route under the `shipping_address` and `billing_address` request body parameters.
213-
- The updated cart object is retuned in the response.
218+
- The updated cart object is returned in the response.
214219
- **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.
220+
221+
---
222+
223+
## Approach Two: Select Customer Address
224+
225+
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.
226+
227+
<Note title="Good to Know">
228+
229+
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).
230+
231+
</Note>
232+
233+
For example:
234+
235+
<CodeTabs group="store-request">
236+
<CodeTab label="Fetch API" value="fetch">
237+
238+
export const fetch2Highlights = [
239+
["1", "cartId", "Assuming the cart's ID is stored in the database."],
240+
["3", "retrieveCustomerAddresses", "Retrieve the customer's addresses."],
241+
["18", "updateCartAddress", "Update the cart's address with the selected customer address."],
242+
["19", "address", "Map the customer address to the expected cart address."],
243+
["39", "shipping_address", "Pass the selected address as a shipping address."],
244+
["40", "billing_address", "Pass the selected address as a billing address."],
245+
]
246+
247+
```ts highlights={fetch2Highlights}
248+
const cartId = localStorage.getItem("cart_id")
249+
250+
const retrieveCustomerAddresses = () => {
251+
fetch("http://localhost:9000/store/customers/me/addresses", {
252+
credentials: "include",
253+
headers: {
254+
"Content-Type": "application/json",
255+
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
256+
},
257+
})
258+
.then((res) => res.json())
259+
.then(({ addresses }) => {
260+
// use addresses...
261+
console.log(addresses)
262+
})
263+
}
264+
265+
const updateCartAddress = (customerAddress: Record<string, unknown>) => {
266+
const address = {
267+
first_name: customerAddress.first_name || "",
268+
last_name: customerAddress.last_name || "",
269+
address_1: customerAddress.address_1 || "",
270+
company: customerAddress.company || "",
271+
postal_code: customerAddress.postal_code || "",
272+
city: customerAddress.city || "",
273+
country_code: customerAddress.country_code || cart.region?.countries?.[0].iso_2,
274+
province: customerAddress.province || "",
275+
phone: customerAddress.phone || ""
276+
}
277+
278+
fetch(`http://localhost:9000/store/carts/${cart.id}`, {
279+
credentials: "include",
280+
method: "POST",
281+
headers: {
282+
"Content-Type": "application/json",
283+
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
284+
},
285+
body: JSON.stringify({
286+
shipping_address: address,
287+
billing_address: address
288+
})
289+
})
290+
.then((res) => res.json())
291+
.then(({ cart: updatedCart }) => {
292+
// use cart...
293+
console.log(cart)
294+
})
295+
}
296+
```
297+
298+
</CodeTab>
299+
<CodeTab label="React" value="react">
300+
301+
export const react2Highlights = [
302+
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
303+
["5", "useCustomer", "The `useCustomer` hook was defined in the Customer React Context documentation."],
304+
["11", "selectedAddress", "Store the ID of the address that the customer selects."],
305+
["20", "updateAddress", "Update the cart's shipping and billing addresses based on the selected address."],
306+
["31", "address", "Map the customer address to the expected cart address."],
307+
["51", "shipping_address", "Pass the selected address as a shipping address."],
308+
["52", "billing_address", "Pass the selected address as a billing address."],
309+
["66", "select", "Show a dropdown to select the customer's address."],
310+
]
311+
312+
```tsx highlights={react2Highlights}
313+
"use client" // include with Next.js 13+
314+
315+
import { useEffect, useState } from "react";
316+
import { useCart } from "../../../providers/cart";
317+
import { useCustomer } from "../../../providers/customer";
318+
319+
export default function CheckoutAddressStep () {
320+
const { cart, setCart } = useCart()
321+
const { customer } = useCustomer()
322+
const [loading, setLoading] = useState(false)
323+
const [selectedAddress, setSelectedAddress] = useState(customer?.addresses[0]?.id || "")
324+
325+
useEffect(() => {
326+
if (!customer) {
327+
// TODO you can redirect here to another page or component that shows the address form
328+
}
329+
setSelectedAddress(customer?.addresses[0]?.id || "")
330+
}, [customer])
331+
332+
const updateAddress = (
333+
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
334+
) => {
335+
e.preventDefault()
336+
337+
const customerAddress = customer?.addresses.find((address) => address.id === selectedAddress)
338+
if (!cart || !customerAddress) {
339+
return
340+
}
341+
setLoading(true)
342+
343+
const address = {
344+
first_name: customerAddress.first_name || "",
345+
last_name: customerAddress.last_name || "",
346+
address_1: customerAddress.address_1 || "",
347+
company: customerAddress.company || "",
348+
postal_code: customerAddress.postal_code || "",
349+
city: customerAddress.city || "",
350+
country_code: customerAddress.country_code || cart.region?.countries?.[0].iso_2,
351+
province: customerAddress.province || "",
352+
phone: customerAddress.phone || ""
353+
}
354+
355+
fetch(`http://localhost:9000/store/carts/${cart.id}`, {
356+
credentials: "include",
357+
method: "POST",
358+
headers: {
359+
"Content-Type": "application/json",
360+
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
361+
},
362+
body: JSON.stringify({
363+
shipping_address: address,
364+
billing_address: address
365+
})
366+
})
367+
.then((res) => res.json())
368+
.then(({ cart: updatedCart }) => {
369+
setCart(updatedCart)
370+
})
371+
.finally(() => setLoading(false))
372+
}
373+
374+
return (
375+
<form>
376+
{!cart && <span>Loading...</span>}
377+
{!customer?.addresses.length && <span>Customer doesn't have addresses</span>}
378+
<select value={selectedAddress} onChange={(e) => setSelectedAddress(e.target.value)}>
379+
{customer?.addresses.map((address) => (
380+
<option value={address.id} key={address.id}>{address.country_code}</option>
381+
))}
382+
</select>
383+
<button
384+
disabled={!cart || loading || !selectedAddress}
385+
onClick={updateAddress}
386+
>
387+
Save
388+
</button>
389+
</form>
390+
)
391+
}
392+
```
393+
394+
</CodeTab>
395+
</CodeTabs>
396+
397+
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.
398+
399+
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.
400+
401+
<Note title="Tip">
402+
403+
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).
404+
405+
</Note>

www/apps/resources/generated/edit-dates.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export const generatedEditDates = {
142142
"app/storefront-development/cart/retrieve/page.mdx": "2025-01-06T15:58:13.885Z",
143143
"app/storefront-development/cart/update/page.mdx": "2025-01-06T16:01:33.752Z",
144144
"app/storefront-development/cart/page.mdx": "2024-06-11T11:56:37+03:00",
145-
"app/storefront-development/checkout/address/page.mdx": "2025-01-06T16:02:20.872Z",
145+
"app/storefront-development/checkout/address/page.mdx": "2025-02-03T16:32:02.682Z",
146146
"app/storefront-development/checkout/complete-cart/page.mdx": "2024-12-19T16:30:41.019Z",
147147
"app/storefront-development/checkout/email/page.mdx": "2024-12-19T16:30:40.122Z",
148148
"app/storefront-development/checkout/payment/stripe/page.mdx": "2024-12-19T16:30:39.173Z",

www/apps/resources/generated/sidebar.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,7 +1210,7 @@ export const generatedSidebar = [
12101210
"loaded": true,
12111211
"isPathHref": true,
12121212
"type": "ref",
1213-
"title": "Checkout Step 2: Enter Address",
1213+
"title": "Checkout Step 2: Set Address",
12141214
"path": "/storefront-development/checkout/address",
12151215
"children": []
12161216
},
@@ -16329,7 +16329,7 @@ export const generatedSidebar = [
1632916329
"isPathHref": true,
1633016330
"type": "link",
1633116331
"path": "/storefront-development/checkout/address",
16332-
"title": "2. Enter Address",
16332+
"title": "2. Set Address",
1633316333
"children": []
1633416334
},
1633516335
{

www/apps/resources/sidebars/storefront.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export const storefrontGuidesSidebar = [
169169
{
170170
type: "link",
171171
path: "/storefront-development/checkout/address",
172-
title: "2. Enter Address",
172+
title: "2. Set Address",
173173
},
174174
{
175175
type: "link",

www/packages/tags/src/tags/cart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const cart = [
2020
"path": "/storefront-development/cart/update"
2121
},
2222
{
23-
"title": "Checkout Step 2: Enter Address",
23+
"title": "Checkout Step 2: Set Address",
2424
"path": "/storefront-development/checkout/address"
2525
},
2626
{

www/packages/tags/src/tags/storefront.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const storefront = [
2424
"path": "/storefront-development/cart/update"
2525
},
2626
{
27-
"title": "Checkout Step 2: Enter Address",
27+
"title": "Checkout Step 2: Set Address",
2828
"path": "/storefront-development/checkout/address"
2929
},
3030
{

0 commit comments

Comments
 (0)