www/apps/resources/app/storefront-development/checkout/payment/page.mdx
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: Checkout Step 4: Choose Payment Provider,
}
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 payment step requires implementing the following flow:
initiatePaymentSession function.For example, to implement the payment step flow:
<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."],
["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 system provider."],
["77", "default", "Handle unrecognized providers."],
["112", "getPaymentUi", "If a provider is chosen, render its UI."]
]
"use client" // include with Next.js 13+
import { useCallback, useEffect, useState } from "react"
import { useCart } from "@/providers/cart"
import { HttpTypes } from "@medusajs/types"
import { sdk } from "@/lib/sdk"
export default function CheckoutPaymentStep() {
const { cart, setCart } = useCart()
const [paymentProviders, setPaymentProviders] = useState<
HttpTypes.StorePaymentProvider[]
>([])
const [
selectedPaymentProvider,
setSelectedPaymentProvider,
] = useState<string | undefined>()
const [loading, setLoading] = useState(false)
useEffect(() => {
if (!cart) {
return
}
sdk.store.payment.listPaymentProviders({
region_id: cart.region_id || "",
})
.then(({ payment_providers }) => {
setPaymentProviders(payment_providers)
setSelectedPaymentProvider(
cart.payment_collection?.payment_sessions?.[0]?.id
)
})
}, [cart])
const handleSelectProvider = async (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault()
if (!cart || !selectedPaymentProvider) {
return
}
setLoading(true)
await sdk.store.payment.initiatePaymentSession(cart, {
provider_id: selectedPaymentProvider,
})
// re-fetch cart
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]
if (!activePaymentSession) {
return
}
switch(true) {
case activePaymentSession.provider_id.startsWith("pp_stripe_"):
return (
<span>
You chose stripe!
</span>
)
case activePaymentSession.provider_id
.startsWith("pp_system_default"):
return (
<span>
You chose manual payment! No additional actions required.
</span>
)
default:
return (
<span>
You chose {activePaymentSession.provider_id} which is
in development.
</span>
)
}
} , [cart])
return (
<div>
<form>
<select
value={selectedPaymentProvider}
onChange={(e) => setSelectedPaymentProvider(e.target.value)}
>
{paymentProviders.map((provider) => (
<option
key={provider.id}
value={provider.id}
>
{provider.id}
</option>
))}
</select>
<button
disabled={loading}
onClick={async (e) => {
await handleSelectProvider(e)
}}
>
Submit
</button>
</form>
{getPaymentUi()}
</div>
)
}
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 system provider."],
["45", "default", "Handle unrecognized providers."],
["52", "handlePayment", "The function that handles the payment process using the above functions."]
]
// 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()
}
In the example above, you:
initiatePaymentSession function to create a payment collection and initialize the payment session for the chosen provider.
In the Fetch API example, the handlePayment function implements this flow by calling the different functions in the correct order.
If your cart has a total of 0, you might encounter an unknown error when trying to create a payment session.
Some payment providers, such as Stripe, require a non-zero amount to create a payment session. So, if your cart has a total of 0, the error will be thrown on the payment provider's side.
In those cases, you can either:
0.If you're integrating Stripe in your Medusa application and storefront, refer to the Stripe guide for an example of how to handle the payment process using Stripe.