"use client"; import Image from "next/image"; import Link from "next/link"; import { ArrowLeft, Landmark, MapPin, MessageCircle, Truck } from "lucide-react"; import { type FormEvent, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ciudadesPorDepartamento, departamentos } from "@/lib/data/colombia"; import { formatMoney } from "@/lib/utils"; import { useCartStore } from "@/store/cart-store"; const SELECT_CLASS = "h-12 w-full rounded-full border border-slate-200 bg-white px-5 text-sm text-slate-950 outline-none transition focus:border-cyan-500 focus:ring-4 focus:ring-cyan-300/30 disabled:opacity-50"; // ADR 0009: direccion + envio + pago real, todo contra el Store API real. // El pago es ePayco/PSE (modulo custom del backend, ver // workloads/commerce-backend/src/modules/epayco-pse) -- redirige al banco // del comprador, la confirmacion real llega despues via webhook async // (nunca por esta pantalla). WhatsApp queda como respaldo manual si la // iniciacion del pago falla, ya no como camino principal. interface ShippingOption { id: string; name: string; amount: number; } interface PseBank { pseCode: string; description: string; } type Step = "address" | "shipping" | "payment"; async function postJson(url: string, body: unknown) { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); return response.json(); } export function CheckoutClient() { const { cartId, items, hasHydrated } = useCartStore(); const [step, setStep] = useState("address"); const [form, setForm] = useState({ firstName: "", lastName: "", address1: "", // Se guarda el id DIVIPOLA seleccionado, no el texto libre -- ver // lib/data/colombia.ts. El nombre real se deriva de este id para // mandarlo a Medusa (que solo espera texto en shipping_address). departmentId: null as number | null, cityId: null as number | null, phone: "", email: "", }); const availableCities = form.departmentId ? ciudadesPorDepartamento(form.departmentId) : []; const selectedDepartamento = departamentos.find((departamento) => departamento.id === form.departmentId); const selectedCiudad = availableCities.find((ciudad) => ciudad.id === form.cityId); const [shippingOptions, setShippingOptions] = useState([]); const [selectedOptionId, setSelectedOptionId] = useState(null); const [shippingTotal, setShippingTotal] = useState(0); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [banks, setBanks] = useState([]); const [bankCode, setBankCode] = useState(""); const [userType, setUserType] = useState<"0" | "1">("0"); const [docType, setDocType] = useState<"CC" | "CE" | "NIT" | "TI" | "PP">("CC"); const [docNumber, setDocNumber] = useState(""); const itemsSubtotal = items.reduce((sum, item) => sum + (item.product.price ?? 0) * item.quantity, 0); const currency = items[0]?.product.currency; async function submitAddress(event: FormEvent) { event.preventDefault(); if (!cartId || !selectedCiudad || !selectedDepartamento) return; setLoading(true); setError(null); try { const cart = await postJson("/api/cart/address", { cartId, firstName: form.firstName, lastName: form.lastName, address1: form.address1, city: selectedCiudad.name, province: selectedDepartamento.name, phone: form.phone, email: form.email, }); if (cart.error) throw new Error(cart.error); const response = await fetch(`/api/cart/shipping-options?cartId=${cartId}`); const data = await response.json(); const options: ShippingOption[] = data.options ?? []; setShippingOptions(options); setSelectedOptionId(options[0]?.id ?? null); setStep("shipping"); } catch (submitError) { setError((submitError as Error).message); } finally { setLoading(false); } } async function submitShipping() { if (!cartId || !selectedOptionId) return; setLoading(true); setError(null); try { const cart = await postJson("/api/cart/shipping-method", { cartId, optionId: selectedOptionId }); if (cart.error) throw new Error(cart.error); setShippingTotal(cart.shippingTotal ?? 0); setTotal(cart.total ?? itemsSubtotal); const banksResponse = await fetch("/api/cart/payment/banks"); const banksData = await banksResponse.json(); const bankList: PseBank[] = banksData.banks ?? []; setBanks(bankList); setBankCode(bankList[0]?.pseCode ?? ""); setStep("payment"); } catch (submitError) { setError((submitError as Error).message); } finally { setLoading(false); } } async function submitPayment(event: FormEvent) { event.preventDefault(); if (!cartId || !bankCode || !docNumber || !selectedCiudad || !selectedDepartamento) return; setLoading(true); setError(null); try { const session = await postJson("/api/cart/payment/session", { cartId, bankCode, userType, docType, docNumber, firstName: form.firstName, lastName: form.lastName, email: form.email, phone: form.phone, city: selectedCiudad.name, }); if (session.error) throw new Error(session.error); const completion = await postJson("/api/cart/complete", { cartId }); if (completion.error) throw new Error(completion.error); // Navegacion completa, no iframe -- asi lo exige ePayco para el // flujo de PSE. La confirmacion real llega despues via webhook, // esta pagina nunca vuelve a cargar sola. window.location.href = session.bankUrl; } catch (submitError) { setError((submitError as Error).message); setLoading(false); } } function continueOnWhatsApp() { const lines = items.map((item) => `• ${item.quantity} x ${item.product.name}`).join("\n"); const message = `Hola Ari Shopping 👋\nQuiero confirmar mi pedido:\n\n${lines}\n\nSubtotal: ${formatMoney(itemsSubtotal, currency)}\nEnvío: ${formatMoney(shippingTotal, currency)}\nTotal: ${formatMoney(total, currency)}\n\nNombre: ${form.firstName} ${form.lastName}\nDirección: ${form.address1}, ${selectedCiudad?.name ?? ""}${selectedDepartamento ? `, ${selectedDepartamento.name}` : ""}\nTeléfono: ${form.phone}`; void navigator.clipboard?.writeText(message); window.open(process.env.NEXT_PUBLIC_WHATSAPP_URL ?? "https://wa.link/rvcqqc", "_blank", "noopener,noreferrer"); } if (!hasHydrated) return null; if (items.length === 0) { return (

Tu carrito está vacío.

); } return (
Seguir comprando

Confirmar pedido

{step === "address" && (
setForm({ ...form, firstName: event.target.value })} /> setForm({ ...form, lastName: event.target.value })} />
setForm({ ...form, address1: event.target.value })} />
setForm({ ...form, phone: event.target.value })} /> setForm({ ...form, email: event.target.value })} />
{error &&

{error}

}
)} {step === "shipping" && (

{form.address1}, {selectedCiudad?.name}{selectedDepartamento ? `, ${selectedDepartamento.name}` : ""}

{shippingOptions.map((option) => ( ))} {shippingOptions.length === 0 &&

No hay opciones de envĂ­o disponibles para esta direcciĂłn.

}
{error &&

{error}

}
)} {step === "payment" && (

Entrega

{form.firstName} {form.lastName}

{form.address1}, {selectedCiudad?.name}{selectedDepartamento ? `, ${selectedDepartamento.name}` : ""}

{form.phone}

Paga con PSE (transferencia desde tu banco)

{banks.length > 0 ? ( ) : (

No pudimos cargar la lista de bancos — probá de nuevo en un momento, o confirmá tu pedido por WhatsApp abajo.

)}
setDocNumber(event.target.value)} />
{error &&

{error}

}
ÂżPrefieres otra vĂ­a?
)}

Tu pedido

{items.map((item) => (
{item.product.name}

{item.product.name}

Cantidad: {item.quantity}

{formatMoney((item.product.price ?? 0) * item.quantity, item.product.currency)}

))}
Subtotal{formatMoney(itemsSubtotal, currency)}
{step === "payment" &&
EnvĂ­o{formatMoney(shippingTotal, currency)}
}
Total{formatMoney(step === "payment" ? total : itemsSubtotal, currency)}
); }