CommerceStorefront Development
Checkout
Implement the complete checkout flow in your storefront
The checkout flow converts a cart into a paid order through four steps: address, shipping, payment, and completion.
Checkout Flow
1. Shipping Address ──► 2. Shipping Method ──► 3. Payment ──► 4. ConfirmationStep 1: Shipping Address
async function setShippingAddress(cartId: string, address: Address) {
const cart = await commerce.carts.update(cartId, {
shippingAddress: {
line1: address.line1,
line2: address.line2,
city: address.city,
state: address.state,
postalCode: address.postalCode,
country: address.country,
},
})
// Cart now includes calculated tax and available shipping options
return cart
}Step 2: Shipping Method
After setting an address, fetch available shipping options:
const options = await commerce.carts.getShippingOptions(cartId)
// Returns: [
// { id: "opt_std", name: "Standard", price: 799, estimatedDays: "5-7" },
// { id: "opt_exp", name: "Express", price: 1499, estimatedDays: "2-3" }
// ]
// Select a shipping option
const cart = await commerce.carts.setShippingOption(cartId, 'opt_std')Step 3: Payment
Stripe Payment
Use Stripe Elements for PCI-compliant card collection:
import { loadStripe } from '@stripe/stripe-js'
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js'
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY!)
function PaymentForm({ cartId }: { cartId: string }) {
const stripe = useStripe()
const elements = useElements()
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (!stripe || !elements) return
// Create payment intent via Commerce API
const { clientSecret } = await commerce.checkout.createPaymentSession({
cartId,
provider: 'stripe',
})
// Confirm with Stripe
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{ payment_method: { card: elements.getElement(CardElement)! } }
)
if (error) {
console.error(error.message)
return
}
// Complete the cart
const order = await commerce.carts.complete(cartId)
window.location.href = `/order/${order.id}/confirmation`
}
return (
<form onSubmit={handleSubmit}>
<CardElement />
<button type="submit">Pay Now</button>
</form>
)
}
export function CheckoutPayment({ cartId }: { cartId: string }) {
return (
<Elements stripe={stripePromise}>
<PaymentForm cartId={cartId} />
</Elements>
)
}PayPal Payment
const { redirectUrl } = await commerce.checkout.createPaymentSession({
cartId,
provider: 'paypal',
returnUrl: `${window.location.origin}/checkout/complete`,
cancelUrl: `${window.location.origin}/checkout/payment`,
})
// Redirect to PayPal
window.location.href = redirectUrlStep 4: Confirmation
export default async function ConfirmationPage({
params,
}: {
params: { orderId: string }
}) {
const order = await commerce.orders.get(params.orderId)
return (
<div>
<h1>Order Confirmed</h1>
<p>Order #{order.id}</p>
<p>Total: ${(order.total / 100).toFixed(2)} {order.currency}</p>
<div>
{order.items.map(item => (
<div key={item.variantId}>
<p>{item.productName} x {item.quantity}</p>
</div>
))}
</div>
</div>
)
}Complete Checkout Page
'use client'
import { useState } from 'react'
import { useCart } from '@/components/cart-provider'
type Step = 'address' | 'shipping' | 'payment' | 'confirmation'
export function CheckoutPage() {
const { cart } = useCart()
const [step, setStep] = useState<Step>('address')
if (!cart) return <p>No items in cart</p>
return (
<div>
<div className="flex gap-4 mb-8">
{['address', 'shipping', 'payment', 'confirmation'].map(s => (
<span key={s} className={step === s ? 'font-bold' : 'text-gray-400'}>
{s.charAt(0).toUpperCase() + s.slice(1)}
</span>
))}
</div>
{step === 'address' && <AddressForm onNext={() => setStep('shipping')} />}
{step === 'shipping' && <ShippingSelect onNext={() => setStep('payment')} />}
{step === 'payment' && <CheckoutPayment cartId={cart.id} />}
</div>
)
}Never send raw card numbers to your server. Always use tokenized payment methods (Stripe Elements, PayPal redirect) to maintain PCI compliance.
How is this guide?
Last updated on