diff --git a/www/apps/resources/app/storefront-development/checkout/address/page.mdx b/www/apps/resources/app/storefront-development/checkout/address/page.mdx index 11d16a9574..de49d00633 100644 --- a/www/apps/resources/app/storefront-development/checkout/address/page.mdx +++ b/www/apps/resources/app/storefront-development/checkout/address/page.mdx @@ -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. + + + +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). + + + +For example: + + + + +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) => { + 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) + }) +} +``` + + + + +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 + ) => { + 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 ( +
+ {!cart && Loading...} + {!customer?.addresses.length && Customer doesn't have addresses} + + +
+ ) +} +``` + +
+
+ +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. + + + +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). + + diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index a893729f5c..044c74ff5a 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -142,7 +142,7 @@ export const generatedEditDates = { "app/storefront-development/cart/retrieve/page.mdx": "2025-01-06T15:58:13.885Z", "app/storefront-development/cart/update/page.mdx": "2025-01-06T16:01:33.752Z", "app/storefront-development/cart/page.mdx": "2024-06-11T11:56:37+03:00", - "app/storefront-development/checkout/address/page.mdx": "2025-01-06T16:02:20.872Z", + "app/storefront-development/checkout/address/page.mdx": "2025-02-03T16:32:02.682Z", "app/storefront-development/checkout/complete-cart/page.mdx": "2024-12-19T16:30:41.019Z", "app/storefront-development/checkout/email/page.mdx": "2024-12-19T16:30:40.122Z", "app/storefront-development/checkout/payment/stripe/page.mdx": "2024-12-19T16:30:39.173Z", diff --git a/www/apps/resources/generated/sidebar.mjs b/www/apps/resources/generated/sidebar.mjs index 9b0de1c826..749415f77f 100644 --- a/www/apps/resources/generated/sidebar.mjs +++ b/www/apps/resources/generated/sidebar.mjs @@ -1210,7 +1210,7 @@ export const generatedSidebar = [ "loaded": true, "isPathHref": true, "type": "ref", - "title": "Checkout Step 2: Enter Address", + "title": "Checkout Step 2: Set Address", "path": "/storefront-development/checkout/address", "children": [] }, @@ -16329,7 +16329,7 @@ export const generatedSidebar = [ "isPathHref": true, "type": "link", "path": "/storefront-development/checkout/address", - "title": "2. Enter Address", + "title": "2. Set Address", "children": [] }, { diff --git a/www/apps/resources/sidebars/storefront.mjs b/www/apps/resources/sidebars/storefront.mjs index 8e8983de96..694c76cfc8 100644 --- a/www/apps/resources/sidebars/storefront.mjs +++ b/www/apps/resources/sidebars/storefront.mjs @@ -169,7 +169,7 @@ export const storefrontGuidesSidebar = [ { type: "link", path: "/storefront-development/checkout/address", - title: "2. Enter Address", + title: "2. Set Address", }, { type: "link", diff --git a/www/packages/tags/src/tags/cart.ts b/www/packages/tags/src/tags/cart.ts index 30dfd239c9..081086d768 100644 --- a/www/packages/tags/src/tags/cart.ts +++ b/www/packages/tags/src/tags/cart.ts @@ -20,7 +20,7 @@ export const cart = [ "path": "/storefront-development/cart/update" }, { - "title": "Checkout Step 2: Enter Address", + "title": "Checkout Step 2: Set Address", "path": "/storefront-development/checkout/address" }, { diff --git a/www/packages/tags/src/tags/storefront.ts b/www/packages/tags/src/tags/storefront.ts index 80770477ac..9d2dee3543 100644 --- a/www/packages/tags/src/tags/storefront.ts +++ b/www/packages/tags/src/tags/storefront.ts @@ -24,7 +24,7 @@ export const storefront = [ "path": "/storefront-development/cart/update" }, { - "title": "Checkout Step 2: Enter Address", + "title": "Checkout Step 2: Set Address", "path": "/storefront-development/checkout/address" }, {