const CheckoutPage = ({ cart, setCart, setPage, lang, user }) => { const t = (en, id) => lang === "id" ? id : en; const [step, setStep] = React.useState(1); const [orderNumber, setOrderNumber] = React.useState(""); const [form, setForm] = React.useState({ name: user?.name || "", email: user?.email || "", phone: "", address: "", province: "", city: "", district: "", postal: "", notes: "" }); const [courier, setCourier] = React.useState(null); const [couriers, setCouriers] = React.useState([]); const [shippingLoad, setShippingLoad] = React.useState(false); const [shippingErr, setShippingErr] = React.useState(""); const [payment, setPayment] = React.useState("duitku"); const [postalQueried, setPostalQueried] = React.useState(""); const [submitting, setSubmitting] = React.useState(false); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); // Validasi nomor HP Indonesia: harus diawali 08, total 10–13 digit. // Terima juga input berformat 62.. / +62.. / 8.. lalu dinormalkan ke 08.. const [phoneErr, setPhoneErr] = React.useState(""); const normalizePhone = (raw) => { let d = (raw || "").replace(/[^0-9]/g, ""); if (d.startsWith("62")) d = "0" + d.slice(2); else if (d.startsWith("8")) d = "0" + d; return d; }; const isValidPhone = (raw) => /^08\d{8,11}$/.test(normalizePhone(raw)); // InitiateCheckout — sekali saat halaman checkout dibuka React.useEffect(() => { if (typeof fbq === 'function') { fbq('track', 'InitiateCheckout', { content_ids: cart.map(i => String(i.id)), content_type: 'product', num_items: cart.reduce((s, i) => s + i.qty, 0), value: cart.reduce((s, i) => s + i.price * i.qty, 0), currency: 'IDR' }); } }, []); // Load alamat tersimpan saat pertama buka React.useEffect(() => { if (!user) return; fetch("api/account.php", { credentials:"include" }) .then(r => r.json()) .then(d => { if (d.user?.saved_address) { const a = d.user.saved_address; setForm(f => ({ ...f, address: a.address || f.address, district: a.district || f.district, city: a.city || f.city, province: a.province || f.province, postal: a.postal || f.postal, })); } if (d.user?.phone) setForm(f => ({ ...f, phone: f.phone || d.user.phone })); }).catch(() => {}); }, [user]); const subtotal = cart.reduce((s, i) => s + i.price * i.qty, 0); // Voucher state const [voucherCode, setVoucherCode] = React.useState(""); const [voucherInput, setVoucherInput] = React.useState(""); const [voucher, setVoucher] = React.useState(null); // { code, type, value, discount } const [voucherErr, setVoucherErr] = React.useState(""); const [voucherChecking, setVoucherChecking] = React.useState(false); // Free shipping state const [freeShip, setFreeShip] = React.useState(null); // { eligible, rule } | null // Re-validate voucher kalau subtotal berubah (qty change) React.useEffect(() => { if (!voucher) return; fetch("api/promo.php?action=validate_voucher", { method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ code: voucher.code, subtotal }) }).then(r => r.json()).then(d => { if (d.success) setVoucher({ ...voucher, discount: d.discount }); else { setVoucher(null); setVoucherErr(d.error || ""); } }); // eslint-disable-next-line }, [subtotal]); // Check free shipping eligibility kalau cart/postal berubah React.useEffect(() => { if (!form.postal || cart.length === 0) { setFreeShip(null); return; } fetch("api/promo.php?action=check_free_shipping", { method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ items: cart.map(i => ({ id:i.id, qty:i.qty, price:i.price })), subtotal, postal: form.postal }) }).then(r => r.json()).then(d => setFreeShip(d)).catch(() => {}); }, [form.postal, cart.length, subtotal]); const applyVoucher = async () => { const code = voucherInput.trim().toUpperCase(); if (!code) { setVoucherErr("Masukkan kode voucher"); return; } setVoucherChecking(true); setVoucherErr(""); try { const r = await fetch("api/promo.php?action=validate_voucher", { method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ code, subtotal }) }); const d = await r.json(); if (d.success) { setVoucher({ ...d.voucher, discount: d.discount }); setVoucherCode(code); setVoucherErr(""); } else { setVoucherErr(d.error || "Voucher tidak valid"); setVoucher(null); } } catch (e) { setVoucherErr("Gagal cek voucher"); } finally { setVoucherChecking(false); } }; const removeVoucher = () => { setVoucher(null); setVoucherCode(""); setVoucherInput(""); setVoucherErr(""); try { localStorage.removeItem("lopobya_pending_voucher"); } catch {} }; // Auto-apply voucher dari Welcome Popup (Meta ads) — sekali saja const autoVoucherTried = React.useRef(false); React.useEffect(() => { if (autoVoucherTried.current || voucher || subtotal <= 0) return; let code = ""; try { code = (localStorage.getItem("lopobya_pending_voucher") || "").trim().toUpperCase(); } catch {} if (!code) return; autoVoucherTried.current = true; fetch("api/promo.php?action=validate_voucher", { method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ code, subtotal }) }).then(r => r.json()).then(d => { if (d.success) { setVoucher({ ...d.voucher, discount: d.discount }); setVoucherCode(code); } }).catch(() => {}); }, [subtotal, voucher]); // Hitung ongkir & total dengan voucher + free shipping // max_subsidy = batas subsidi ongkir (0 = gratis penuh; >0 = gratis s/d nominal itu) const courierPrice = courier ? courier.price : 0; const isFreeShip = freeShip?.eligible === true; const shipCap = +(freeShip?.rule?.max_subsidy || 0); const shipSubsidy = isFreeShip ? (shipCap > 0 ? Math.min(courierPrice, shipCap) : courierPrice) : 0; const ongkir = Math.max(0, courierPrice - shipSubsidy); const isFullyFree = isFreeShip && courierPrice > 0 && ongkir === 0; const discount = voucher?.discount || 0; const codFee = (payment === "cod" && courier) ? (Number(courier.cash_on_delivery_fee) || 0) : 0; const total = Math.max(0, subtotal - discount + ongkir + codFee); const inputStyle = { width:"100%", padding:"12px 14px", background:"#fff", border:"1px solid #e8e6e2", fontFamily:"'DM Sans',sans-serif", fontSize:13, color:"#111", outline:"none", boxSizing:"border-box", transition:"border-color 0.2s", borderRadius:2 }; const labelStyle = { fontFamily:"'DM Sans',sans-serif", fontSize:10, letterSpacing:"0.14em", textTransform:"uppercase", color:"#888", display:"block", marginBottom:8, fontWeight:600 }; const btnPrimary = { padding:"14px 32px", background:"#111", border:"none", cursor:"pointer", fontFamily:"'DM Sans',sans-serif", fontSize:11, letterSpacing:"0.16em", textTransform:"uppercase", color:"#f9f8f6", fontWeight:600, borderRadius:2 }; const btnSecondary = { padding:"14px 24px", background:"none", border:"1px solid #e8e6e2", cursor:"pointer", fontFamily:"'DM Sans',sans-serif", fontSize:11, letterSpacing:"0.12em", textTransform:"uppercase", color:"#888", borderRadius:2 }; // Fetch ongkir dari Biteship const fetchRates = async () => { if (!form.postal || form.postal.length !== 5) { setShippingErr("Kode pos harus 5 digit"); return; } if (form.postal === postalQueried) return; // sudah pernah dicek setShippingLoad(true); setShippingErr(""); setCouriers([]); setCourier(null); try { const res = await fetch("api/shipping.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ destination_postal: form.postal, items: cart.map(i => ({ id: i.id, name: i.name, price: i.price, qty: i.qty })), destination_cash_on_delivery: Math.max(0, subtotal - discount) }) }); const data = await res.json(); if (!res.ok || data.error) { setShippingErr(data.error || "Gagal mengambil tarif pengiriman"); return; } const pricing = data.pricing || []; if (!pricing.length) { setShippingErr("Tidak ada kurir tersedia untuk kode pos ini"); return; } // Buang layanan kargo/trucking (freight) yg minimal 10kg — nggak masuk akal // buat paket kecil & bikin ongkir kelihatan mahal (mis. JNE Trucking 55rb). // Sisakan hanya layanan parcel reguler/express. const parcelOnly = pricing.filter(p => (p.shipping_type || "parcel") !== "freight"); const usable = parcelOnly.length ? parcelOnly : pricing; // Deduplikasi: ambil harga termurah per kurir+service const seen = new Set(); const clean = usable .filter(p => { const key = p.courier_code + "-" + p.courier_service_code; if (seen.has(key)) return false; seen.add(key); return true; }) .sort((a, b) => a.price - b.price); setCouriers(clean); setCourier(clean[0]); // default pilih yang termurah setPostalQueried(form.postal); } catch(e) { setShippingErr("Gagal terhubung ke server pengiriman"); } finally { setShippingLoad(false); } }; // Step 4 — Success if (step === 4) return (

{t("Order Placed!","Pesanan Diterima!")}

{t(`Thank you, ${form.name}! We'll confirm your order shortly.`, `Terima kasih, ${form.name}! Kami akan segera konfirmasi pesananmu.`)}

{t("Confirmation will be sent to","Konfirmasi dikirim ke")} {form.email}

{t("Order Summary","Ringkasan Pesanan")}

{cart.map(item => (
{item.name}{item.selectedVariant ? " — " + item.selectedVariant : ""} ×{item.qty} {formatPrice(item.price * item.qty)}
))}
Subtotal {formatPrice(subtotal)}
{courier && (
{courier.courier_name} {courier.courier_service_name} {formatPrice(courier.price)}
)} {payment === "cod" && courier && Number(courier.cash_on_delivery_fee) > 0 && (
Biaya COD {formatPrice(courier.cash_on_delivery_fee)}
)}
Total {formatPrice(total)}
{orderNumber && (

Nomor Pesananmu:

{orderNumber}

🚚 Lacak Pesanan
)}
); const steps = [t("Information","Informasi"), t("Shipping","Pengiriman"), t("Payment","Pembayaran")]; return (
{/* Header */}
LOPOBYA
{steps.map((s, i) => (
i+1 ? "#111" : step === i+1 ? "#111" : "#e8e6e2", display:"flex", alignItems:"center", justifyContent:"center" }}> {step > i+1 ? : {i+1} }
{s}
{i < steps.length-1 &&
i+1?"#111":"#e8e6e2", margin:"0 8px", marginBottom:24, flexShrink:0 }} />} ))}
{/* Form */}
{/* ── Step 1: Informasi Kontak ── */} {step === 1 && (

{t("Contact Information","Informasi Kontak")}

set("name",e.target.value)} placeholder={t("Your full name","Nama lengkap kamu")} />
set("email",e.target.value)} placeholder="email@example.com" />
{ set("phone", e.target.value); if (phoneErr) setPhoneErr(""); }} placeholder="08xx xxxx xxxx" inputMode="numeric" /> {phoneErr &&

{phoneErr}

}
)} {/* ── Step 2: Alamat + Kurir ── */} {step === 2 && (

{t("Shipping Address","Alamat Pengiriman")}

set("address",e.target.value)} placeholder={t("Street, No., RT/RW","Jalan, No., RT/RW")} />
{ set("city", city); set("district", district); set("province", province); if (postal) { set("postal", postal); setPostalQueried(""); setCouriers([]); setCourier(null); } }} />
{ set("postal",e.target.value.replace(/\D/g,"")); setPostalQueried(""); setCouriers([]); setCourier(null); }} maxLength={5} placeholder="16680" onKeyDown={e => e.key==="Enter" && fetchRates()} />