'use client' import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' import { ArrowLeft, CircleCheck, Loader2 } from 'lucide-react' import { medusa } from '@/lib/medusa' import { broadcastCartCount } from '@/hooks/useMedusaCart' const CURRENCY_SYMBOLS: Record = { zar: 'R', usd: '$', eur: '€', gbp: '£', kes: 'KSh', ngn: '₦', ghs: '₵' } const formatPrice = (amount: number, code?: string) => { const sym = CURRENCY_SYMBOLS[(code ?? '').toLowerCase()] ?? (code?.toUpperCase() ?? '') return `${sym}${(amount / 100).toFixed(2)}` } type Step = 'details' | 'review' | 'success' interface Addr { first_name: string; last_name: string; address_1: string; city: string; country_code: string; postal_code: string; phone: string } interface CartItem { id: string; title: string; quantity: number; unit_price: number; thumbnail: string | null; variant: { title: string } | null } interface Cart { id: string; items: CartItem[]; subtotal: number; region: { currency_code: string } | null } export default function CheckoutPage() { const router = useRouter() const [step, setStep] = useState('details') const [cart, setCart] = useState(null) const [loading, setLoading] = useState(true) const [placing, setPlacing] = useState(false) const [orderError, setOrderError] = useState(null) const [email, setEmail] = useState('') const [addr, setAddr] = useState({ first_name: '', last_name: '', address_1: '', city: '', country_code: 'za', postal_code: '', phone: '' }) const cartId = typeof window !== 'undefined' ? localStorage.getItem('medusa_cart_id') : null useEffect(() => { if (!cartId) { setLoading(false); return } medusa.store.cart.retrieve(cartId) .then(({ cart: c }) => setCart(c as Cart)) .catch(() => setCart(null)) .finally(() => setLoading(false)) }, [cartId]) const handleDetailsSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!cartId) return try { await (medusa.store.cart.update as Function)(cartId, { email, shipping_address: addr }) } catch {} setStep('review') } const handlePlaceOrder = async () => { setPlacing(true) setOrderError(null) try { if (!cartId) return const pubKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || '' const h = { 'Content-Type': 'application/json', 'x-publishable-api-key': pubKey } // Step 1: find first available payment provider for this cart's region. // Default to pp_system_default (Medusa v2 built-in manual provider) — always // available when provisioning has run. pp_test_test does not exist. let providerId = 'pp_system_default' try { const cartRes = await fetch(`/api/medusa/store/carts/${cartId}`, { headers: h }) const { cart: cartData } = await cartRes.json() if (cartData?.region_id) { const provRes = await fetch(`/api/medusa/store/payment-providers?region_id=${cartData.region_id}`, { headers: h }) const provData = await provRes.json() const first = provData?.payment_providers?.[0] if (first?.id) providerId = first.id } } catch {} // Step 2: create payment collection (Medusa v2 — must come before session) const pcRes = await fetch('/api/medusa/store/payment-collections', { method: 'POST', headers: h, body: JSON.stringify({ cart_id: cartId }), }) if (!pcRes.ok) throw new Error(`Payment setup failed (${pcRes.status})`) const { payment_collection } = await pcRes.json() // Step 3: create payment session on the collection const psRes = await fetch(`/api/medusa/store/payment-collections/${payment_collection.id}/payment-sessions`, { method: 'POST', headers: h, body: JSON.stringify({ provider_id: providerId }), }) if (!psRes.ok) throw new Error(`Payment session failed (${psRes.status})`) // Step 4: complete cart — creates the order in Medusa const result = await (medusa.store.cart.complete as Function)(cartId) const order = result?.order ?? result localStorage.removeItem('medusa_cart_id') localStorage.removeItem('medusa_cart_count') broadcastCartCount(0) if (order?.id) { router.push(`/order/${order.id}`) return } // Completed without an order id — treat as success (e.g. pending payment) setStep('success') } catch (err: any) { console.error('Checkout error:', err) setOrderError(err?.message || 'Something went wrong. Please try again.') } finally { setPlacing(false) } } const currency = cart?.region?.currency_code?.toUpperCase() ?? 'ZAR' const items = cart?.items ?? [] const subtotal = cart?.subtotal ?? 0 if (loading) return
if (step === 'success') return (

Order Placed!

Thank you{email ? ', ' + email.split('@')[0] : ''}. Your order has been received.

Continue Shopping
) return (
Back to Cart

Checkout

1. Your Details 2. Review {'&'} Confirm
{step === 'details' && (

Contact

setEmail(e.target.value)} placeholder="Email address" className="w-full border border-border rounded-lg px-3 py-2 text-sm bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary" />

Shipping Address

{([['first_name','First name',false],['last_name','Last name',false],['address_1','Street address',true],['city','City',false],['postal_code','Postal code',false],['phone','Phone (optional)',true]] as [keyof Addr, string, boolean][]).map(([key, label, full]) => ( setAddr(p => ({ ...p, [key]: e.target.value }))} placeholder={label} required={key !== 'phone'} className={(full ? 'col-span-2 ' : '') + 'border border-border rounded-lg px-3 py-2 text-sm bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary'} /> ))}
)} {step === 'review' && (

Delivery Details

{email}

{addr.first_name} {addr.last_name}, {addr.address_1}, {addr.city} {addr.postal_code}

{orderError && (
{orderError}
)}

Your order is confirmed and payment processed securely.

)}

Order Summary

{items.map(item => (
{item.thumbnail && {(item}

{(item as any).product_title || item.title}

Qty: {item.quantity}

{formatPrice(item.unit_price * item.quantity, cart?.region?.currency_code)}

))}
Subtotal{formatPrice(subtotal, cart?.region?.currency_code)}
ShippingTBD
Total{formatPrice(subtotal, cart?.region?.currency_code)}
) }