docs: update storefront development guides to use JS SDK [2] (#12015)
This commit is contained in:
@@ -13,71 +13,51 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
## Approaches to Set the Cart's Addresses
|
||||
|
||||
A cart has shipping and billing addresses that customers need to set. You can either:
|
||||
|
||||
- [Show a form to enter the address](#approach-one-address-form);
|
||||
- Or [allow the customer to pick an address from their account](#approach-two-select-customer-address).
|
||||
|
||||
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.
|
||||
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](!api!/store#carts_postcartsid).
|
||||
|
||||
For example:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useCart` hook defined in the [Cart React Context guide](../../cart/context/page.mdx).
|
||||
- Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
```ts
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
|
||||
const address = {
|
||||
first_name,
|
||||
last_name,
|
||||
address_1,
|
||||
company,
|
||||
postal_code,
|
||||
city,
|
||||
country_code,
|
||||
province,
|
||||
phone,
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}`, {
|
||||
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 }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
|
||||
["29", "address", "Assemble the address object to be used for both shipping and billing addresses."],
|
||||
["41"], ["42"], ["43"], ["44"], ["45"], ["46"], ["47"], ["48"],
|
||||
["49"], ["50"], ["51"], ["52"], ["53"], ["54"], ["55"], ["56"],
|
||||
["103", "", "The address's country can only be within the cart's region."]
|
||||
["30", "address", "Assemble the address object to be used for both shipping and billing addresses."],
|
||||
["42"], ["43"], ["44"], ["45"], ["46"], ["47"], ["48"],
|
||||
["49"], ["50"],
|
||||
["96", "", "The address's country can only be within the cart's region."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { useCart } from "../../../providers/cart"
|
||||
import { useCart } from "@/providers/cart"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function CheckoutAddressStep() {
|
||||
const { cart, setCart } = useCart()
|
||||
@@ -109,26 +89,18 @@ export const highlights = [
|
||||
company,
|
||||
postal_code: postalCode,
|
||||
city,
|
||||
country_code: countryCode,
|
||||
country_code: countryCode || cart.region?.countries?.[0].iso_2,
|
||||
province,
|
||||
phone: phoneNumber,
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
sdk.store.cart.update(cart.id, {
|
||||
shipping_address: address,
|
||||
billing_address: address,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: updatedCart }) => {
|
||||
setCart(updatedCart)
|
||||
console.log(updatedCart)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
@@ -208,6 +180,34 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
```ts
|
||||
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)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
@@ -216,13 +216,15 @@ 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 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.
|
||||
- **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 in the Country input.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
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](!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">
|
||||
|
||||
@@ -232,89 +234,34 @@ A customer's address and a cart's address are represented by different data mode
|
||||
|
||||
For example:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useCart` hook defined in the [Cart React Context guide](../../cart/context/page.mdx).
|
||||
- This example uses the `useCustomer` hook defined in the [Customer React Context guide](../../customers/context/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<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."],
|
||||
["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."],
|
||||
]
|
||||
|
||||
```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"
|
||||
import { useCart } from "@/providers/cart"
|
||||
import { useCustomer } from "@/providers/customer"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function CheckoutAddressStep() {
|
||||
const { cart, setCart } = useCart()
|
||||
@@ -352,19 +299,10 @@ export default function CheckoutAddressStep() {
|
||||
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,
|
||||
}),
|
||||
sdk.store.cart.update(cart.id, {
|
||||
shipping_address: address,
|
||||
billing_address: address,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: updatedCart }) => {
|
||||
setCart(updatedCart)
|
||||
})
|
||||
@@ -389,6 +327,53 @@ export default function CheckoutAddressStep() {
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
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."],
|
||||
]
|
||||
|
||||
```ts highlights={fetch2Highlights}
|
||||
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)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
@@ -396,10 +381,12 @@ export default function CheckoutAddressStep() {
|
||||
|
||||
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).
|
||||
The JS SDK automatically sends an authenticated request as the logged-in customer as explained in the [Login Customer guide](../../customers/login/page.mdx). 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.
|
||||
|
||||
</Note>
|
||||
|
||||
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 select input to select an address.
|
||||
|
||||
When the customer selects an address, you send a request to [Update Cart API route](!api!/store#carts_postcartsid) passing the selected address as a shipping and billing address.
|
||||
|
||||
@@ -14,32 +14,30 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you'll learn how to complete the cart and place the order. This is the last step of your checkout flow.
|
||||
|
||||
## How to Complete Cart in Storefront Checkout
|
||||
|
||||
Once you finish any required actions with the third-party payment provider, you can complete the cart and place the order.
|
||||
|
||||
To complete the cart, send a request to the [Complete Cart API route](!api!/store#carts_postcartsidcomplete).
|
||||
To complete the cart, send a request to the [Complete Cart API route](!api!/store#carts_postcartsidcomplete). For example:
|
||||
|
||||
For example:
|
||||
<Note title="Tip">
|
||||
|
||||
Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
```ts
|
||||
fetch(
|
||||
`http://localhost:9000/store/carts/${cartId}/complete`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
method: "POST",
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(({ type, cart, order, error }) => {
|
||||
if (type === "cart" && cart) {
|
||||
sdk.store.cart.complete(cart.id)
|
||||
.then((data) => {
|
||||
if (data.type === "cart" && data.cart) {
|
||||
// an error occured
|
||||
console.error(error)
|
||||
} else if (type === "order" && order) {
|
||||
console.error(data.error)
|
||||
} else if (data.type === "order" && data.order) {
|
||||
// TODO redirect to order success page
|
||||
alert("Order placed.")
|
||||
console.log(order)
|
||||
console.log(data.order)
|
||||
// unset cart ID from local storage
|
||||
localStorage.removeItem("cart_id")
|
||||
}
|
||||
@@ -59,22 +57,29 @@ When the cart completion is successful, it's important to unset the cart ID from
|
||||
|
||||
For example, to complete the cart when the default system payment provider is used:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
This example uses the `useCart` hook defined in the [Cart React Context guide](../../cart/context/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
export const highlights = [
|
||||
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
|
||||
["10", "handlePayment", "This function sends the request\nto the Medusa application to complete the cart."],
|
||||
["21", "TODO", "If you're integrating a third-party payment provider,\nyou perform the custom logic before completing the cart."],
|
||||
["24", "fetch", "Send a request to the Medusa application\nto complete the cart and place the order."],
|
||||
["36", `type === "cart"`, "If the `type` returned is `cart`,\nit means an error occurred and the cart wasn't completed."],
|
||||
["39", `type === "order"`, "If the `type` returned is `order`,\nit means the cart was completed and the order was placed successfully."],
|
||||
["43", "refreshCart", "Unset and reset the cart."],
|
||||
["50", "button", "This button triggers the `handlePayment` function when clicked."]
|
||||
["11", "handlePayment", "This function sends the request\nto the Medusa application to complete the cart."],
|
||||
["22", "TODO", "If you're integrating a third-party payment provider,\nyou perform the custom logic before completing the cart."],
|
||||
["25", "complete", "Send a request to the Medusa application\nto complete the cart and place the order."],
|
||||
["27", `data.type === "cart"`, "If the `type` returned is `cart`,\nit means an error occurred and the cart wasn't completed."],
|
||||
["30", `type === "order"`, "If the `type` returned is `order`,\nit means the cart was completed and the order was placed successfully."],
|
||||
["34", "refreshCart", "Unset and reset the cart."],
|
||||
["41", "button", "This button triggers the `handlePayment` function when clicked."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { useCart } from "../../providers/cart"
|
||||
import { useCart } from "@/providers/cart"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function SystemDefaultPayment() {
|
||||
const { cart, refreshCart } = useCart()
|
||||
@@ -94,25 +99,15 @@ export default function SystemDefaultPayment() {
|
||||
// TODO perform any custom payment handling logic
|
||||
|
||||
// complete the cart
|
||||
fetch(
|
||||
`http://localhost:9000/store/carts/${cart.id}/complete`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
method: "POST",
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(({ type, cart, order, error }) => {
|
||||
if (type === "cart" && cart) {
|
||||
sdk.store.cart.complete(cart.id)
|
||||
.then((data) => {
|
||||
if (data.type === "cart" && data.cart) {
|
||||
// an error occured
|
||||
console.error(error)
|
||||
} else if (type === "order" && order) {
|
||||
console.error(data.error)
|
||||
} else if (data.type === "order" && data.order) {
|
||||
// TODO redirect to order success page
|
||||
alert("Order placed.")
|
||||
console.log(order)
|
||||
console.log(data.order)
|
||||
refreshCart()
|
||||
}
|
||||
})
|
||||
@@ -133,13 +128,13 @@ export default function SystemDefaultPayment() {
|
||||
In the example above, you create a `handlePayment` function in the payment component. In this function, you:
|
||||
|
||||
- Optionally perform any required actions with the third-party payment provider. For example, authorize the payment. For the default system payment provider, no actions are required.
|
||||
- Send a request to the Complete Cart API route once all actions with the third-party payment provider are performed.
|
||||
- Send a request to the [Complete Cart API route](!api!/store#carts_postcartsidcomplete) once all actions with the third-party payment provider are performed.
|
||||
- In the received response of the request, if the `type` is `cart`, it means that the cart completion failed. The error is set in the `error` response field.
|
||||
- If the `type` is `order`, it means the card was completed and the order was placed successfully. You can access the order in the `order` response field.
|
||||
- When the order is placed, you must unset the `cart_id` from the `localStorage`. You can redirect the customer to an order success page at this point.
|
||||
- When the order is placed, you must unset the `cart_id` from the `localStorage`. You can redirect the customer to an order success page at this point. The redirection logic depends on the framework you're using.
|
||||
|
||||
---
|
||||
|
||||
## React Example with Third-Party Provider
|
||||
## React Example with Third-Party Payment Provider
|
||||
|
||||
Refer to the [Stripe guide](../payment/stripe/page.mdx) for an example on integrating a third-party provider and implementing card completion.
|
||||
|
||||
@@ -12,7 +12,9 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The first step of the checkout flow is to enter the customer's email. Then, use the [Update Cart API route](!api!/store#carts_postcartsid) to update the cart with the email.
|
||||
In this guide, you'll learn how to add an email step to the checkout flow. This typically would be the first step of the checkout flow, but you can also change the steps of the checkout flow as you see fit.
|
||||
|
||||
When the user enters their email, use the [Update Cart API route](!api!/store#carts_postcartsid) to update the cart with the email.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
@@ -22,45 +24,28 @@ If the customer is logged-in, you can pre-fill the email with the customer's ema
|
||||
|
||||
For example:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useCart` hook defined in the [Cart React Context guide](../../cart/context/page.mdx).
|
||||
- Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
```ts
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${cartId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
|
||||
["13", "TODO", "Cart must have at least one item. If not, redirect to another page."],
|
||||
["27"], ["28"], ["29"], ["30"], ["31"], ["32"], ["33"], ["34"],
|
||||
["35"], ["36"], ["37"], ["38"], ["39"], ["40"], ["41"]
|
||||
["14", "TODO", "Cart must have at least one item. If not, redirect to another page."],
|
||||
["28"], ["29"], ["30"], ["31"], ["32"], ["33"], ["34"],
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useState } from "react"
|
||||
import { useCart } from "../../../providers/cart"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useCart } from "@/providers/cart"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function CheckoutEmailStep() {
|
||||
const { cart, setCart } = useCart()
|
||||
@@ -83,18 +68,9 @@ export const highlights = [
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
||||
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({
|
||||
email,
|
||||
}),
|
||||
sdk.store.cart.update(cart.id, {
|
||||
email,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: updatedCart }) => {
|
||||
setCart(updatedCart)
|
||||
})
|
||||
@@ -122,9 +98,24 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
```ts
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
|
||||
sdk.store.cart.update(cart.id, {
|
||||
email,
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
After the customer enters and submits their email, you send a request to the Update Cart API route passing it the email in the request body.
|
||||
After the customer enters and submits their email, you send a request to the [Update Cart API route](!api!/store#carts_postcartsid) passing it the email in the request body.
|
||||
|
||||
Notice that if the cart doesn't have items, you should redirect to another page as the checkout requires at least one item in the cart.
|
||||
Notice that if the cart doesn't have items, you should redirect to another page as the checkout requires at least one item in the cart. Redirecting to another page is not covered in this guide as this depends on the framework you're using.
|
||||
|
||||
+21
-27
@@ -12,10 +12,10 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
After the customer completes the checkout process and places an order, you can show an order confirmation page to display the order details.
|
||||
|
||||
In this guide, you'll learn how to show the different order details on the order confirmation page.
|
||||
|
||||
After the customer completes the checkout process and places an order, you can show an order confirmation page to display the order details.
|
||||
|
||||
## Retrieve Order Details
|
||||
|
||||
To show the order details, you need to retrieve the order by sending a request to the [Get an Order API route](!api!store#orders_getordersid).
|
||||
@@ -24,25 +24,13 @@ You need the order's ID to retrieve the order. You can pass it from the [complet
|
||||
|
||||
The following example assumes you already have the order ID:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
```ts
|
||||
// orderId is the order ID which you can get from the complete cart step
|
||||
fetch(`http://localhost:9000/store/orders/${orderId}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ order }) => {
|
||||
// use order...
|
||||
console.log(order)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
```tsx
|
||||
@@ -57,13 +45,7 @@ export function OrderConfirmation({ id }: { id: string }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`http://localhost:9000/store/orders/${id}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
sdk.store.order.retrieve(id)
|
||||
.then(({ order: dataOrder }) => {
|
||||
setOrder(dataOrder)
|
||||
setLoading(false)
|
||||
@@ -85,6 +67,18 @@ export function OrderConfirmation({ id }: { id: string }) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
```ts
|
||||
// orderId is the order ID which you can get from the complete cart step
|
||||
sdk.store.order.retrieve(orderId)
|
||||
.then(({ order }) => {
|
||||
// use order...
|
||||
console.log(order)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
|
||||
@@ -13,11 +13,9 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The last step before completing the order is choosing the payment provider and performing any necessary actions.
|
||||
In this guide, you'll learn how to implement the last step of the checkout flow, where the customer chooses the payment provider and performs any necessary actions. This is typically the fourth step of the checkout flow, but you can change the steps of the checkout flow as you see fit.
|
||||
|
||||
The actions required after choosing the payment provider are different for each provider. So, this guide doesn't cover that.
|
||||
|
||||
## Payment Step Flow
|
||||
## Payment Step Flow in Storefront Checkout
|
||||
|
||||
The payment step requires implementing the following flow:
|
||||
|
||||
@@ -25,175 +23,51 @@ The payment step requires implementing the following flow:
|
||||
|
||||
1. Retrieve the payment providers using the [List Payment Providers API route](!api!/store#payment-providers_getpaymentproviders).
|
||||
2. Customer chooses the payment provider to use.
|
||||
3. If the cart doesn't have an associated payment collection, create a payment collection for it.
|
||||
3. If the cart doesn't have an associated payment collection, create a payment collection for it using the [Create Payment Collection API route](!api!/store#payment-collections_postpaymentcollections).
|
||||
4. Initialize the payment sessions of the cart's payment collection using the [Initialize Payment Sessions API route](!api!/store#payment-collections_postpaymentcollectionsidpaymentsessions).
|
||||
5. Optionally perform additional actions for payment based on the chosen payment provider. For example, if the customer chooses stripe, you show them the UI to enter their card details.
|
||||
- If you're using the JS SDK, it combines the third and fourth steps in a single `initiatePaymentSession` function.
|
||||
5. Optionally perform additional actions for payment based on the chosen payment provider. For example, if the customer chooses Stripe, you show them the UI to enter their card details.
|
||||
- You can refer to the [Stripe guide](./stripe/page.mdx) for an example of how to implement this.
|
||||
|
||||
---
|
||||
|
||||
## Code Example
|
||||
## How to Implement the Payment Step Flow
|
||||
|
||||
For example, to implement the payment step flow:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useCart` hook defined in the [Cart React Context guide](../../cart/context/page.mdx).
|
||||
- Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["6", "retrievePaymentProviders", "This function retrieves the payment provider that the customer can choose from."],
|
||||
["7", "fetch", "Retrieve available payment providers."],
|
||||
["21", "selectPaymentProvider", "This function is executed when the customer submits their chosen payment provider."],
|
||||
["28", "fetch", "Create a payment collection for the cart when it doesn't have one."],
|
||||
["48", "fetch", "Initialize the payment session in the payment collection for the chosen provider."],
|
||||
["67", "fetch", "Retrieve the cart again to update its data."],
|
||||
["81", "getPaymentUi", "This function shows the necessary UI based on the selected payment provider."],
|
||||
["82", "activePaymentSession", "The active session is the first in the payment collection's sessions."],
|
||||
["88", "", "Test which payment provider is chosen based on the prefix of the provider ID."],
|
||||
["89", `"pp_stripe_"`, "Check if the chosen provider is Stripe."],
|
||||
["93", `"pp_system_default"`, "Check if the chosen provider is the default systen provider."],
|
||||
["95", "default", "Handle unrecognized providers."],
|
||||
["102", "handlePayment", "The function that handles the payment process using the above functions."]
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
// assuming the cart is previously fetched
|
||||
const cart = {
|
||||
// cart object...
|
||||
}
|
||||
|
||||
const retrievePaymentProviders = async () => {
|
||||
const { payment_providers } = await fetch(
|
||||
`http://localhost:9000/store/payment-providers?region_id=${
|
||||
cart.region_id
|
||||
}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
||||
return payment_providers
|
||||
}
|
||||
|
||||
const selectPaymentProvider = async (
|
||||
selectedPaymentProviderId: string
|
||||
) => {
|
||||
let paymentCollectionId = cart.payment_collection?.id
|
||||
|
||||
if (!paymentCollectionId) {
|
||||
// create payment collection
|
||||
const { payment_collection } = await fetch(
|
||||
`http://localhost:9000/store/payment-collections`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
cart_id: cart.id,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
paymentCollectionId = payment_collection.id
|
||||
}
|
||||
|
||||
// initialize payment session
|
||||
await fetch(`http://localhost:9000/store/payment-collections/${
|
||||
paymentCollectionId
|
||||
}/payment-sessions`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
provider_id: selectedPaymentProviderId,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
||||
// re-fetch cart
|
||||
const {
|
||||
cart: updatedCart,
|
||||
} = await fetch(
|
||||
`http://localhost:9000/store/carts/${cart.id}`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
return updatedCart
|
||||
}
|
||||
|
||||
const getPaymentUi = () => {
|
||||
const activePaymentSession = cart?.payment_collection?.
|
||||
payment_sessions?.[0]
|
||||
if (!activePaymentSession) {
|
||||
return
|
||||
}
|
||||
|
||||
switch(true) {
|
||||
case activePaymentSession.provider_id.startsWith("pp_stripe_"):
|
||||
// TODO handle Stripe UI
|
||||
return "You chose stripe!"
|
||||
case activePaymentSession.provider_id
|
||||
.startsWith("pp_system_default"):
|
||||
return "You chose manual payment! No additional actions required."
|
||||
default:
|
||||
return `You chose ${
|
||||
activePaymentSession.provider_id
|
||||
} which is in development.`
|
||||
}
|
||||
}
|
||||
|
||||
const handlePayment = () => {
|
||||
retrievePaymentProviders()
|
||||
|
||||
// ... customer chooses payment provider
|
||||
// const providerId = ...
|
||||
|
||||
selectPaymentProvider(providerId)
|
||||
|
||||
getPaymentUi()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
|
||||
["23", "fetch", "Retrieve available payment providers."],
|
||||
["34", "setSelectedPaymentProvider", "If a payment provider was selected before, pre-fill it."],
|
||||
["40", "handleSelectProvider", "This function is executed when the customer submits their chosen payment provider."],
|
||||
["54", "fetch", "Create a payment collection for the cart when it doesn't have one."],
|
||||
["74", "fetch", "Initialize the payment session in the payment collection for the chosen provider."],
|
||||
["93", "fetch", "Retrieve the cart again to update its data."],
|
||||
["108", "getPaymentUi", "This function shows the necessary UI based on the selected payment provider."],
|
||||
["109", "activePaymentSession", "The active session is the first in the payment collection's sessions."],
|
||||
["115", "", "Test which payment provider is chosen based on the prefix of the provider ID."],
|
||||
["116", `"pp_stripe_"`, "Check if the chosen provider is Stripe."],
|
||||
["124", `"pp_system_default"`, "Check if the chosen provider is the default systen provider."],
|
||||
["130", "default", "Handle unrecognized providers."],
|
||||
["165", "getPaymentUi", "If a provider is chosen, render its UI."]
|
||||
["24", "listPaymentProviders", "Retrieve available payment providers."],
|
||||
["29", "setSelectedPaymentProvider", "If a payment provider was selected before, pre-fill it."],
|
||||
["35", "handleSelectProvider", "This function is executed when the customer submits their chosen payment provider."],
|
||||
["45", "initiatePaymentSession", "Create a payment collection and initialize the payment session for the chosen provider."],
|
||||
["50", "retrieve", "Retrieve the cart again to update its data."],
|
||||
["56", "getPaymentUi", "This function shows the necessary UI based on the selected payment provider."],
|
||||
["57", "activePaymentSession", "The active session is the first in the payment collection's sessions."],
|
||||
["62", "", "Test which payment provider is chosen based on the prefix of the provider ID."],
|
||||
["63", `"pp_stripe_"`, "Check if the chosen provider is Stripe."],
|
||||
["71", `"pp_system_default"`, "Check if the chosen provider is the default systen provider."],
|
||||
["77", "default", "Handle unrecognized providers."],
|
||||
["112", "getPaymentUi", "If a provider is chosen, render its UI."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useCart } from "../../../providers/cart"
|
||||
import { useCart } from "@/providers/cart"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function CheckoutPaymentStep() {
|
||||
const { cart, setCart } = useCart()
|
||||
@@ -211,15 +85,9 @@ export default function CheckoutPaymentStep() {
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`http://localhost:9000/store/payment-providers?region_id=${
|
||||
cart.region_id
|
||||
}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
sdk.store.payment.listPaymentProviders({
|
||||
region_id: cart.region_id || "",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ payment_providers }) => {
|
||||
setPaymentProviders(payment_providers)
|
||||
setSelectedPaymentProvider(
|
||||
@@ -236,69 +104,21 @@ export default function CheckoutPaymentStep() {
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
setLoading(true)
|
||||
|
||||
let paymentCollectionId = cart.payment_collection?.id
|
||||
|
||||
if (!paymentCollectionId) {
|
||||
// create payment collection
|
||||
const { payment_collection } = await fetch(
|
||||
`http://localhost:9000/store/payment-collections`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
cart_id: cart.id,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
paymentCollectionId = payment_collection.id
|
||||
}
|
||||
|
||||
// initialize payment session
|
||||
await fetch(`http://localhost:9000/store/payment-collections/${
|
||||
paymentCollectionId
|
||||
}/payment-sessions`,
|
||||
{
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
provider_id: selectedPaymentProvider,
|
||||
}),
|
||||
await sdk.store.payment.initiatePaymentSession(cart, {
|
||||
provider_id: selectedPaymentProvider,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
||||
// re-fetch cart
|
||||
const {
|
||||
cart: updatedCart,
|
||||
} = await fetch(
|
||||
`http://localhost:9000/store/carts/${cart.id}`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
const { cart: updatedCart } = await sdk.store.cart.retrieve(cart.id)
|
||||
|
||||
setCart(updatedCart)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const getPaymentUi = useCallback(() => {
|
||||
const activePaymentSession = cart?.payment_collection?.
|
||||
payment_sessions?.[0]
|
||||
const activePaymentSession = cart?.payment_collection?.payment_sessions?.[0]
|
||||
if (!activePaymentSession) {
|
||||
return
|
||||
}
|
||||
@@ -357,6 +177,88 @@ export default function CheckoutPaymentStep() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["8", "retrievePaymentProviders", "This function retrieves the payment provider that the customer can choose from."],
|
||||
["9", "listPaymentProviders", "Retrieve available payment providers."],
|
||||
["16", "selectPaymentProvider", "This function is executed when the customer submits their chosen payment provider."],
|
||||
["19", "initiatePaymentSession", "Create a payment collection and initialize the payment session for the chosen provider."],
|
||||
["26", "retrieve", "Retrieve the cart again to update its data."],
|
||||
["31", "getPaymentUi", "This function shows the necessary UI based on the selected payment provider."],
|
||||
["32", "activePaymentSession", "The active session is the first in the payment collection's sessions."],
|
||||
["38", "", "Test which payment provider is chosen based on the prefix of the provider ID."],
|
||||
["39", `"pp_stripe_"`, "Check if the chosen provider is Stripe."],
|
||||
["43", `"pp_system_default"`, "Check if the chosen provider is the default systen provider."],
|
||||
["45", "default", "Handle unrecognized providers."],
|
||||
["52", "handlePayment", "The function that handles the payment process using the above functions."]
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
// assuming the cart is previously fetched
|
||||
const cart = {
|
||||
id: "cart_123",
|
||||
region_id: "reg_123",
|
||||
// cart object...
|
||||
}
|
||||
|
||||
const retrievePaymentProviders = async () => {
|
||||
const { payment_providers } = await sdk.store.payment.listPaymentProviders({
|
||||
region_id: cart.region_id || "",
|
||||
})
|
||||
|
||||
return payment_providers
|
||||
}
|
||||
|
||||
const selectPaymentProvider = async (
|
||||
selectedPaymentProviderId: string
|
||||
) => {
|
||||
await sdk.store.payment.initiatePaymentSession(cart, {
|
||||
provider_id: selectedPaymentProviderId,
|
||||
})
|
||||
|
||||
// re-fetch cart
|
||||
const {
|
||||
cart: updatedCart,
|
||||
} = await sdk.store.cart.retrieve(cart.id)
|
||||
|
||||
return updatedCart
|
||||
}
|
||||
|
||||
const getPaymentUi = () => {
|
||||
const activePaymentSession = cart?.payment_collection?.
|
||||
payment_sessions?.[0]
|
||||
if (!activePaymentSession) {
|
||||
return
|
||||
}
|
||||
|
||||
switch(true) {
|
||||
case activePaymentSession.provider_id.startsWith("pp_stripe_"):
|
||||
// TODO handle Stripe UI
|
||||
return "You chose stripe!"
|
||||
case activePaymentSession.provider_id
|
||||
.startsWith("pp_system_default"):
|
||||
return "You chose manual payment! No additional actions required."
|
||||
default:
|
||||
return `You chose ${
|
||||
activePaymentSession.provider_id
|
||||
} which is in development.`
|
||||
}
|
||||
}
|
||||
|
||||
const handlePayment = () => {
|
||||
retrievePaymentProviders()
|
||||
|
||||
// ... customer chooses payment provider
|
||||
// const providerId = ...
|
||||
|
||||
selectPaymentProvider(providerId)
|
||||
|
||||
getPaymentUi()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
@@ -364,10 +266,15 @@ export default function CheckoutPaymentStep() {
|
||||
|
||||
In the example above, you:
|
||||
|
||||
- Retrieve the payment providers from the Medusa application. You use those to show the customer the available options.
|
||||
- When the customer chooses a payment provider, you:
|
||||
1. Check whether the cart has a payment collection. If not, create one using the [Create Payment Collection API route](!api!/store#payment-collections_postpaymentcollections).
|
||||
2. Initialize the payment session for the chosen payment provider using the [Initialize Payment Session API route](!api!/store#payment-collections_postpaymentcollectionsidpaymentsessions).
|
||||
- Once the cart has a payment session, you optionally render the UI to perform additional actions. For example, if the customer chose stripe, you can show them the card form to enter their credit card.
|
||||
- Retrieve the payment providers from the Medusa application using the [List Payment Providers API route](!api!/store#payment-providers_getpaymentproviders). You use those to show the customer the available options.
|
||||
- When the customer chooses a payment provider, you use the `initiatePaymentSession` function to create a payment collection and initialize the payment session for the chosen provider.
|
||||
- If you're not using the JS SDK, you need to create a payment collection using the [Create Payment Collection API route](!api!/store#payment-collections_postpaymentcollections) if the cart doesn't have one. Then, you need to initialize the payment session using the [Initialize Payment Session API route](!api!/store#payment-collections_postpaymentcollectionsidpaymentsessions).
|
||||
- Once the cart has a payment session, you optionally render the UI to perform additional actions. For example, if the customer chose Stripe, you can show them the card form to enter their credit card.
|
||||
|
||||
In the `Fetch API` example, the `handlePayment` function implements this flow.
|
||||
In the `Fetch API` example, the `handlePayment` function implements this flow by calling the different functions in the correct order.
|
||||
|
||||
---
|
||||
|
||||
## Stripe Example
|
||||
|
||||
If you're integrating Stripe in your Medusa application and storefront, refer to the [Stripe guide](./stripe/page.mdx) for an example of how to handle the payment process using Stripe.
|
||||
|
||||
@@ -14,11 +14,11 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how to use Stripe for payment during checkout in a React-based storefront.
|
||||
In this guide, you'll learn how to use Stripe for payment during checkout in a React-based storefront.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
For other types of storefronts, the steps are similar. However, refer to [Stripe's documentation](https://docs.stripe.com/) for available tools for your tech stack.
|
||||
For other types of frameworks or tech stacks, the steps are similar. Refer to [Stripe's documentation](https://docs.stripe.com/) for available tools for your tech stack.
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -67,31 +67,34 @@ For Next.js storefronts, the environment variable's name must be prefixed with `
|
||||
|
||||
## 3. Create Stripe Component
|
||||
|
||||
Then, create a file holding the following Stripe component:
|
||||
You can now create a Stripe component that renders the Stripe UI to accept payment.
|
||||
|
||||
<Note>
|
||||
For example, you can create a file holding the following Stripe component:
|
||||
|
||||
This snippet assumes you're using the provider from the [Cart Context guide](../../../cart/context/page.mdx) in your storefront.
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useCart` hook defined in the [Cart React Context guide](../../../cart/context/page.mdx).
|
||||
- Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
export const highlights = [
|
||||
["10", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
|
||||
["13", "stripePromise", "Initialize stripe using the environment variable added in the previous step."],
|
||||
["19", "clientSecret", "After initializing the payment session of Stripe in the Medusa application,\nthe client secret is available in the payment session's `data`."],
|
||||
["27", "StripeForm", "The actual form must be a different component nested inside `Elements`."],
|
||||
["44", "handlePayment", "This function is used to show Stripe's UI to accept payment,\nthen send the request to the Medusa application to complete the cart."],
|
||||
["61", "confirmCardPayment", "This function shows the UI to the customer to accept the card payment."],
|
||||
["78", "", "Once the customer enters their card details and submits the form,\nthe Promise resolves and executes this function."],
|
||||
["85", "fetch", "Send a request to the Medusa application\nto complete the cart and place the order."],
|
||||
["97", `type === "cart"`, "If the `type` returned is `cart`,\nit means an error occurred and the cart wasn't completed."],
|
||||
["100", `type === "order"`, "If the `type` returned is `order`,\nit means the cart was completed and the order was placed successfully."],
|
||||
["104", "refreshCart", "Unset and reset the cart."],
|
||||
["114", "button", "This button triggers the `handlePayment` function when clicked."]
|
||||
["14", "stripe", "Initialize stripe using the environment variable added in the previous step."],
|
||||
["20", "clientSecret", "After initializing the payment session of Stripe in the Medusa application,\nthe client secret is available in the payment session's `data`."],
|
||||
["34", "StripeForm", "The actual form must be a different component nested inside `Elements`."],
|
||||
["45", "handlePayment", "This function is used to show Stripe's UI to accept payment,\nthen send the request to the Medusa application to complete the cart."],
|
||||
["62", "confirmCardPayment", "This function shows the UI to the customer to accept the card payment."],
|
||||
["79", "then", "Once the customer enters their card details and submits the form,\nthe Promise resolves and executes this function."],
|
||||
["86", "complete", "Send a request to the Medusa application\nto complete the cart and place the order."],
|
||||
["88", `data.type === "cart"`, "If the `type` returned is `cart`,\nit means an error occurred and the cart wasn't completed."],
|
||||
["91", `data.type === "order"`, "If the `type` returned is `order`,\nit means the cart was completed and the order was placed successfully."],
|
||||
["95", "refreshCart", "Unset and reset the cart."],
|
||||
["105", "button", "This button triggers the `handlePayment` function when clicked."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
"use client"
|
||||
|
||||
import {
|
||||
CardElement,
|
||||
@@ -100,10 +103,11 @@ import {
|
||||
useStripe,
|
||||
} from "@stripe/react-stripe-js"
|
||||
import { loadStripe } from "@stripe/stripe-js"
|
||||
import { useCart } from "../../providers/cart"
|
||||
import { useCart } from "@/providers/cart"
|
||||
import { useState } from "react"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
const stripePromise = loadStripe(
|
||||
const stripe = loadStripe(
|
||||
process.env.NEXT_PUBLIC_STRIPE_PK || "temp"
|
||||
)
|
||||
|
||||
@@ -114,7 +118,7 @@ export default function StripePayment() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Elements stripe={stripePromise} options={{
|
||||
<Elements stripe={stripe} options={{
|
||||
clientSecret,
|
||||
}}>
|
||||
<StripeForm clientSecret={clientSecret} />
|
||||
@@ -175,25 +179,15 @@ const StripeForm = ({
|
||||
return
|
||||
}
|
||||
|
||||
fetch(
|
||||
`http://localhost:9000/store/carts/${cart.id}/complete`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
method: "POST",
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(({ type, cart, order, error }) => {
|
||||
if (type === "cart" && cart) {
|
||||
sdk.store.cart.complete(cart.id)
|
||||
.then((data) => {
|
||||
if (data.type === "cart" && data.cart) {
|
||||
// an error occured
|
||||
console.error(error)
|
||||
} else if (type === "order" && order) {
|
||||
console.error(data.error)
|
||||
} else if (data.type === "order" && data.order) {
|
||||
// TODO redirect to order success page
|
||||
alert("Order placed.")
|
||||
console.log(order)
|
||||
console.log(data.order)
|
||||
refreshCart()
|
||||
}
|
||||
})
|
||||
@@ -218,20 +212,20 @@ const StripeForm = ({
|
||||
In the code snippet above, you:
|
||||
|
||||
1. Create a `StripePayment` component that wraps the actual form with Stripe's `Elements` component.
|
||||
- In the `StripePayment` component, you obtain the client secret from the payment session's `data` field. This is set in the Medusa application.
|
||||
- In the `StripePayment` component, you obtain the client secret from the payment session's `data` field. This is set in the Medusa application after you initialize the payment session using the [Initialize Payment Sessions API route](!api!/store#payment-collections_postpaymentcollectionsidpaymentsessions).
|
||||
2. Create a `StripeForm` component that holds the actual form. In this component, you implement a `handlePayment` function that does the following:
|
||||
- Use Stripe's `confirmCardPayment` method to accept the card details from the customer.
|
||||
- Once the customer enters their card details and submit their order, the resolution function of the `confirmCardPayment` method is executed.
|
||||
- In the resolution function, you send a request to the [Complete Cart API route](!api!/store#carts_postcartsidcomplete) to complete the cart and place the order.
|
||||
- In the received response of the request, if the `type` is `cart`, it means that the cart completion failed. The error is set in the `error` response field.
|
||||
- If the `type` is `order`, it means the card was completed and the order was placed successfully. You can access the order in the `order` response field.
|
||||
- When the order is placed, you refresh the cart. You can redirect the customer to an order success page at this point.
|
||||
- When the order is placed, you refresh the cart. You can redirect the customer to an order success page at this point. The redirection logic depends on the framework you're using.
|
||||
|
||||
---
|
||||
|
||||
## 4. Use the Stripe Component
|
||||
|
||||
You can now use the Stripe component in the checkout flow. You should render it after the customer chooses Stripe as a payment provider.
|
||||
Finally, use the Stripe component in the checkout flow. You should render it after the customer chooses Stripe as a payment provider.
|
||||
|
||||
For example, you can use it in the `getPaymentUi` function defined in the [Payment Checkout Step guide](../page.mdx):
|
||||
|
||||
|
||||
@@ -13,152 +13,51 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In the third step of the checkout flow, the customer chooses the shipping method to receive their order's items.
|
||||
In this guide, you'll learn how to implement the third step of the checkout flow, where the customer chooses the shipping method to receive their order's items. While this is typically the third step of the checkout flow, you can change the steps of the checkout flow as you see fit.
|
||||
|
||||
To do that, you:
|
||||
## Shipping Flow in Storefront Checkout
|
||||
|
||||
To allow the customer to choose a shipping method, you:
|
||||
|
||||

|
||||
|
||||
1. Retrieve the available shipping options for the cart using the [List Shipping Options API route](!api!/store#shipping-options_getshippingoptions) and show them to the customer.
|
||||
2. For shipping options whose `price_type=calculated`, you retrieve their calculated price using the [Calculate Shipping Option Price API Route](!api!/store#shipping-options_postshippingoptionsidcalculate). The Medusa application calculates the price using the associated fulfillment provider's logic, which may require sending a request to a third-party service.
|
||||
2. For shipping options whose `price_type=calculated`, you retrieve their calculated price using the [Calculate Shipping Option Price API Route](!api!/store#shipping-options_postshippingoptionsidcalculate).
|
||||
- The Medusa application calculates the price using the associated fulfillment provider's logic, which may require sending a request to a third-party service.
|
||||
3. When the customer chooses a shipping option, you use the [Add Shipping Method to Cart API route](!api!/store#carts_postcartsidshippingmethods) to set the cart's shipping method.
|
||||
|
||||
---
|
||||
|
||||
## How to Implement the Shipping Flow in Storefront Checkout?
|
||||
|
||||
For example:
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
- This example uses the `useCart` hook defined in the [Cart React Context guide](../../cart/context/page.mdx).
|
||||
- Learn how to install and configure the JS SDK in the [JS SDK documentation](../../../js-sdk/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs group="store-request">
|
||||
<CodeTab label="Fetch API" value="fetch">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["5", "retrieveShippingOptions", "This function retrieves the shipping options of the customer's cart."],
|
||||
["21", "calculateShippingOptionPrices", "This function retrieves the prices of shipping options of type `calculated`."],
|
||||
["34", "data", "Pass in this property any data relevant to the fulfillment provider."],
|
||||
["56", "formatPrice", "This function formats a price based on the cart's currency."],
|
||||
["65", "getShippingOptionPrice", "This function gets the price of a shipping option based on its type."],
|
||||
["77", "setShippingMethod", "This function sets the shipping method of the cart using the selected shipping option."],
|
||||
["91", "data", "Pass in this property any data relevant to the fulfillment provider."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
let shippingOptions = []
|
||||
const calculatedPrices: Record<string, number> = {}
|
||||
|
||||
const retrieveShippingOptions = () => {
|
||||
const { shipping_options } = await fetch(
|
||||
`http://localhost:9000/store/shipping-options?cart_id=${
|
||||
cart.id
|
||||
}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
|
||||
shippingOptions = shipping_options
|
||||
}
|
||||
|
||||
const calculateShippingOptionPrices = () => {
|
||||
const promises = shippingOptions
|
||||
.filter((shippingOption) => shippingOption.price_type === "calculated")
|
||||
.map((shippingOption) =>
|
||||
fetch(`http://localhost:9000/store/shipping-options/${shippingOption.id}/calculate`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
cart_id: cart.id,
|
||||
data: {
|
||||
// pass any data useful for calculation with third-party provider.
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
)
|
||||
|
||||
if (promises.length) {
|
||||
Promise.allSettled(promises).then((res) => {
|
||||
res
|
||||
.filter((r) => r.status === "fulfilled")
|
||||
.forEach(
|
||||
(p) => (
|
||||
calculatedPrices[p.value?.shipping_option.id || ""] =
|
||||
p.value?.shipping_option.amount
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const formatPrice = (amount: number): string => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
// assuming you have access to the cart object.
|
||||
currency: cart?.currency_code,
|
||||
})
|
||||
.format(amount)
|
||||
}
|
||||
|
||||
const getShippingOptionPrice = (shippingOption: HttpTypes.StoreCartShippingOption) => {
|
||||
if (shippingOption.price_type === "flat") {
|
||||
return formatPrice(shippingOption.amount)
|
||||
}
|
||||
|
||||
if (!calculatedPrices[shippingOption.id]) {
|
||||
return
|
||||
}
|
||||
|
||||
return formatPrice(calculatedPrices[shippingOption.id])
|
||||
}
|
||||
|
||||
const setShippingMethod = (
|
||||
selectedShippingOptionId: string
|
||||
) => {
|
||||
fetch(`http://localhost:9000/store/carts/${
|
||||
cart.id
|
||||
}/shipping-methods`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
option_id: selectedShippingOptionId,
|
||||
data: {
|
||||
// TODO add any data necessary for
|
||||
// fulfillment provider
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="React" value="react">
|
||||
|
||||
export const highlights = [
|
||||
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
|
||||
["25", "fetch", "Retrieve available shipping methods of the customer's cart."],
|
||||
["47", "fetch", "Retrieve the price of every shipping method that has a calculated price."],
|
||||
["56", "data", "Pass in this property any data relevant to the fulfillment provider."],
|
||||
["86", "fetch", "Set the cart's shipping method using the selected shipping option."],
|
||||
["97", "data", "Pass in this property any data relevant to the fulfillment provider."]
|
||||
["26", "listCartOptions", "Retrieve available shipping methods of the customer's cart."],
|
||||
["42", "calculate", "Retrieve the price of every shipping method that has a calculated price."],
|
||||
["44", "data", "Pass in this property any data relevant to the fulfillment provider."],
|
||||
["72", "addShippingMethod", "Set the cart's shipping method using the selected shipping option."],
|
||||
["74", "data", "Pass in this property any data relevant to the fulfillment provider."]
|
||||
]
|
||||
|
||||
```tsx highlights={highlights}
|
||||
"use client" // include with Next.js 13+
|
||||
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useCart } from "../../../providers/cart"
|
||||
import { useCart } from "@/providers/cart"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { sdk } from "@/lib/sdk"
|
||||
|
||||
export default function CheckoutShippingStep() {
|
||||
const { cart, setCart } = useCart()
|
||||
@@ -178,15 +77,9 @@ export const highlights = [
|
||||
if (!cart) {
|
||||
return
|
||||
}
|
||||
fetch(`http://localhost:9000/store/shipping-options?cart_id=${
|
||||
cart.id
|
||||
}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
},
|
||||
sdk.store.fulfillment.listCartOptions({
|
||||
cart_id: cart.id,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ shipping_options }) => {
|
||||
setShippingOptions(shipping_options)
|
||||
})
|
||||
@@ -200,21 +93,12 @@ export const highlights = [
|
||||
const promises = shippingOptions
|
||||
.filter((shippingOption) => shippingOption.price_type === "calculated")
|
||||
.map((shippingOption) =>
|
||||
fetch(`http://localhost:9000/store/shipping-options/${shippingOption.id}/calculate`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
sdk.store.fulfillment.calculate(shippingOption.id, {
|
||||
cart_id: cart.id,
|
||||
data: {
|
||||
// pass any data useful for calculation with third-party provider.
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
cart_id: cart.id,
|
||||
data: {
|
||||
// pass any data useful for calculation with third-party provider.
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
)
|
||||
|
||||
if (promises.length) {
|
||||
@@ -239,24 +123,13 @@ export const highlights = [
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
||||
fetch(`http://localhost:9000/store/carts/${
|
||||
cart.id
|
||||
}/shipping-methods`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
|
||||
sdk.store.cart.addShippingMethod(cart.id, {
|
||||
option_id: selectedShippingOption,
|
||||
data: {
|
||||
// TODO add any data necessary for
|
||||
// fulfillment provider
|
||||
},
|
||||
body: JSON.stringify({
|
||||
option_id: selectedShippingOption,
|
||||
data: {
|
||||
// TODO add any data necessary for
|
||||
// fulfillment provider
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(({ cart: updatedCart }) => {
|
||||
setCart(updatedCart)
|
||||
})
|
||||
@@ -319,17 +192,109 @@ export const highlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="JS SDK" value="js-sdk">
|
||||
|
||||
export const fetchHighlights = [
|
||||
["5", "retrieveShippingOptions", "This function retrieves the shipping options of the customer's cart."],
|
||||
["21", "calculateShippingOptionPrices", "This function retrieves the prices of shipping options of type `calculated`."],
|
||||
["34", "data", "Pass in this property any data relevant to the fulfillment provider."],
|
||||
["56", "formatPrice", "This function formats a price based on the cart's currency."],
|
||||
["65", "getShippingOptionPrice", "This function gets the price of a shipping option based on its type."],
|
||||
["77", "setShippingMethod", "This function sets the shipping method of the cart using the selected shipping option."],
|
||||
["91", "data", "Pass in this property any data relevant to the fulfillment provider."],
|
||||
]
|
||||
|
||||
```ts highlights={fetchHighlights}
|
||||
const cartId = localStorage.getItem("cart_id")
|
||||
let shippingOptions = []
|
||||
const calculatedPrices: Record<string, number> = {}
|
||||
|
||||
const retrieveShippingOptions = () => {
|
||||
const { shipping_options } = await sdk.store.fulfillment.listCartOptions({
|
||||
cart_id: cartId,
|
||||
})
|
||||
|
||||
shippingOptions = shipping_options
|
||||
}
|
||||
|
||||
const calculateShippingOptionPrices = () => {
|
||||
const promises = shippingOptions
|
||||
.filter((shippingOption) => shippingOption.price_type === "calculated")
|
||||
.map((shippingOption) =>
|
||||
sdk.store.fulfillment.calculate(shippingOption.id, {
|
||||
cart_id: cartId,
|
||||
data: {
|
||||
// pass any data useful for calculation with third-party provider.
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (promises.length) {
|
||||
Promise.allSettled(promises).then((res) => {
|
||||
res
|
||||
.filter((r) => r.status === "fulfilled")
|
||||
.forEach(
|
||||
(p) => (
|
||||
calculatedPrices[p.value?.shipping_option.id || ""] =
|
||||
p.value?.shipping_option.amount
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const formatPrice = (amount: number): string => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
// assuming you have access to the cart object.
|
||||
currency: cart?.currency_code,
|
||||
})
|
||||
.format(amount)
|
||||
}
|
||||
|
||||
const getShippingOptionPrice = (shippingOption: HttpTypes.StoreCartShippingOption) => {
|
||||
if (shippingOption.price_type === "flat") {
|
||||
return formatPrice(shippingOption.amount)
|
||||
}
|
||||
|
||||
if (!calculatedPrices[shippingOption.id]) {
|
||||
return
|
||||
}
|
||||
|
||||
return formatPrice(calculatedPrices[shippingOption.id])
|
||||
}
|
||||
|
||||
const setShippingMethod = (
|
||||
selectedShippingOptionId: string
|
||||
) => {
|
||||
sdk.store.cart.addShippingMethod(cartId, {
|
||||
option_id: selectedShippingOptionId,
|
||||
data: {
|
||||
// TODO add any data necessary for
|
||||
// fulfillment provider
|
||||
},
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
// use cart...
|
||||
console.log(cart)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
In the example above, you:
|
||||
|
||||
- Retrieve the available shipping options of the cart to allow the customer to select from them.
|
||||
- For each shipping option, you retrieve its calculated price from the Medusa application.
|
||||
- Once the customer selects a shipping option, you send a request to the Add Shipping Method to Cart API route to update the cart's shipping method using the selected shipping option.
|
||||
- Retrieve the available shipping options of the cart to allow the customer to select from them using the [List Shipping Options API route](!api!/store#shipping-options_getshippingoptions).
|
||||
- For each shipping option, you retrieve its calculated price from the Medusa application using the [Calculate Shipping Option Price API Route](!api!/store#shipping-options_postshippingoptionsidcalculate).
|
||||
- Once the customer selects a shipping option, you send a request to the [Add Shipping Method to Cart API route](!api!/store#carts_postcartsidshippingmethods) to update the cart's shipping method using the selected shipping option.
|
||||
|
||||
## data Request Body Parameter
|
||||
### data Request Body Parameter
|
||||
|
||||
When calculating a shipping option's price using the Calculate Shipping Option Price API route, or when setting the shipping method using the Add Shipping Method to Cart API route, you can pass a `data` request body parameter that holds data relevant for the fulfillment provider.
|
||||
When calculating a shipping option's price using the [Calculate Shipping Option Price API Route](!api!/store#shipping-options_postshippingoptionsidcalculate), or when setting the shipping method using the [Add Shipping Method to Cart API route](!api!/store#carts_postcartsidshippingmethods), you can pass a `data` request body parameter that holds data relevant for the fulfillment provider.
|
||||
|
||||
This isn't implemented here as it's different for each provider. Refer to the provider's documentation on details of expected data, if any.
|
||||
For example, you may pass a custom carrier code to the `data` parameter to identify the carrier of the shipping option if your fulfillment provider requires it.
|
||||
|
||||
This isn't implemented here as it's different for each provider. Refer to your fulfillment provider's documentation on details of expected data, if any.
|
||||
|
||||
Reference in New Issue
Block a user