www/apps/resources/app/storefront-development/checkout/address/page.mdx
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: Checkout Step 2: Set Shipping and Billing Addresses,
}
In this guide, you'll learn how to set the cart's shipping and billing addresses. This typically should be the second step of the checkout flow, but you can also change the steps of the checkout flow as you see fit.
A cart has shipping and billing addresses that customers need to set. You can either:
This guide shows you how to implement both approaches. You can choose either or combine them, based on your use case.
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.
Then, to update the cart's address, use the Update Cart API route.
For example:
<Note title="Tip">useCart hook defined in the Cart React Context guide.export const highlights = [
["4", "useCart", "The useCart hook was defined in the Cart React Context documentation."],
["30", "address", "Assemble the address object to be used for both shipping and billing addresses."],
["96", "", "The address's country can only be within the cart's region."]
]
"use client" // include with Next.js 13+
import { useState } from "react"
import { useCart } from "@/providers/cart"
import { sdk } from "@/lib/sdk"
export default function CheckoutAddressStep() {
const { cart, setCart } = useCart()
const [loading, setLoading] = useState(false)
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [address1, setAddress1] = useState("")
const [company, setCompany] = useState("")
const [postalCode, setPostalCode] = useState("")
const [city, setCity] = useState("")
const [countryCode, setCountryCode] = useState("")
const [province, setProvince] = useState("")
const [phoneNumber, setPhoneNumber] = useState("")
const updateAddress = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
if (!cart) {
return
}
e.preventDefault()
setLoading(true)
const address = {
first_name: firstName,
last_name: lastName,
address_1: address1,
company,
postal_code: postalCode,
city,
country_code: countryCode || cart.region?.countries?.[0].iso_2,
province,
phone: phoneNumber,
}
sdk.store.cart.update(cart.id, {
shipping_address: address,
billing_address: address,
})
.then(({ cart: updatedCart }) => {
setCart(updatedCart)
console.log(updatedCart)
})
.finally(() => setLoading(false))
}
return (
<form>
{!cart && <span>Loading...</span>}
<input
type="text"
placeholder="First Name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type="text"
placeholder="Last Name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input
type="text"
placeholder="Address Line"
value={address1}
onChange={(e) => setAddress1(e.target.value)}
/>
<input
type="text"
placeholder="Company"
value={company}
onChange={(e) => setCompany(e.target.value)}
/>
<input
type="text"
placeholder="Postal Code"
value={postalCode}
onChange={(e) => setPostalCode(e.target.value)}
/>
<input
type="text"
placeholder="City"
value={city}
onChange={(e) => setCity(e.target.value)}
/>
<select
value={countryCode}
onChange={(e) => setCountryCode(e.target.value)}
>
{cart?.region?.countries?.map((country) => (
<option
key={country.iso_2}
value={country.iso_2}
>
{country.display_name}
</option>
))}
</select>
<input
type="text"
placeholder="Province"
value={province}
onChange={(e) => setProvince(e.target.value)}
/>
<input
type="tel"
placeholder="Phone Number"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
/>
<button
disabled={!cart || loading}
onClick={updateAddress}
>
Save
</button>
</form>
)
}
const cartId = localStorage.getItem("cart_id")
const address = {
first_name,
last_name,
address_1,
company,
postal_code,
city,
country_code,
province,
phone,
}
sdk.store.cart.update(cart.id, {
shipping_address: address,
billing_address: address,
})
.then(({ cart }) => {
// use cart...
console.log(cart)
})
In the example above:
shipping_address and billing_address request body parameters.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 logged-in customer's addresses, use the List Customer Addresses API route. Then, once the customer selects an address, use the Update Cart API route 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 and the Cart Module, 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.
</Note>For example:
<Note title="Tip">useCart hook defined in the Cart React Context guide.useCustomer hook defined in the Customer React Context guide.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."],
["12", "selectedAddress", "Store the ID of the address that the customer selects."],
["21", "updateAddress", "Update the cart's shipping and billing addresses based on the selected address."],
["32", "address", "Map the customer address to the expected cart address."],
["45", "shipping_address", "Pass the selected address as a shipping address."],
["46", "billing_address", "Pass the selected address as a billing address."],
["58", "select", "Show a select input to select from the customer's addresses."],
]
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"
import { useCart } from "@/providers/cart"
import { useCustomer } from "@/providers/customer"
import { sdk } from "@/lib/sdk"
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 || "",
}
sdk.store.cart.update(cart.id, {
shipping_address: address,
billing_address: address,
})
.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>
)
}
export const fetch2Highlights = [ ["1", "cartId", "Assuming the cart's ID is stored in the local storage."], ["3", "retrieveCustomerAddresses", "Retrieve the customer's addresses from Medusa."], ["11", "updateCartAddress", "Update the cart's address with the selected customer address."], ["12", "address", "Map the customer address to the expected cart address."], ["25", "shipping_address", "Pass the selected address as a shipping address."], ["26", "billing_address", "Pass the selected address as a billing address."], ]
const cartId = localStorage.getItem("cart_id")
const retrieveCustomerAddresses = () => {
sdk.store.customer.listAddress()
.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 || "",
}
sdk.store.cart.update(cart.id, {
shipping_address: address,
billing_address: address,
})
.then(({ cart: updatedCart }) => {
// use cart...
console.log(cart)
})
}
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.
<Note title="Tip">The JS SDK automatically sends an authenticated request as the logged-in customer as explained in the Login Customer guide. If you're using the Fetch API, you can either use credentials: include if the customer is already authenticated with a cookie session, or pass the Authorization Bearer token in the request's header.
In the React example, you use the Customer React Context to retrieve the logged-in customer, who has a list of addresses. You show a select input to select an address.
When the customer selects an address, you send a request to Update Cart API route passing the selected address as a shipping and billing address.