/* Multi-step Xendit renewal checkout flow */
const { useState: useStateCheckout, useMemo: useMemoCheckout } = React;
const DURATIONS = [
{ id: 1, label: "1 Bulan", months: 1 },
{ id: 3, label: "3 Bulan", months: 3, badge: "Hemat 3%", discount: 0.03 },
{ id: 6, label: "6 Bulan", months: 6, badge: "Hemat 5%", discount: 0.05 },
{ id: 12, label: "12 Bulan", months: 12, badge: "Hemat 10%", discount: 0.10, recommended: true },
];
const PAYMENT_METHODS = [
{ id:"bca", group:"va", label:"BCA Virtual Account", fee:4000, iconBg:"#0060AF", iconText:"BCA" },
{ id:"mandiri", group:"va", label:"Mandiri Virtual Account", fee:4000, iconBg:"#003D7A", iconText:"M" },
{ id:"bni", group:"va", label:"BNI Virtual Account", fee:4000, iconBg:"#F46524", iconText:"BNI" },
{ id:"bri", group:"va", label:"BRI Virtual Account", fee:4000, iconBg:"#005CAB", iconText:"BRI" },
{ id:"qris", group:"qris", label:"QRIS", fee:0, iconBg:"#1A1A1A", iconText:"QR" },
{ id:"ovo", group:"ewallet", label:"OVO", fee:0, iconBg:"#4C2A85", iconText:"O" },
{ id:"gopay", group:"ewallet", label:"GoPay", fee:0, iconBg:"#00AED6", iconText:"G" },
{ id:"dana", group:"ewallet", label:"DANA", fee:0, iconBg:"#118EEA", iconText:"D" },
{ id:"shopeepay", group:"ewallet", label:"ShopeePay", fee:0, iconBg:"#EE4D2D", iconText:"S" },
];
function CheckoutView() {
const { route, machines, navigate, client, toast } = useApp();
const m = machines.find(x => x.id === route.id) || machines[0];
const [step, setStep] = useStateCheckout(1);
const [duration, setDuration] = useStateCheckout(DURATIONS[2]);
const [addons, setAddons] = useStateCheckout({
cloud: m.addons.cloud,
refill: m.addons.refill,
cloudApi: m.addons.cloudApi,
cloudYearly: true,
});
const [method, setMethod] = useStateCheckout(null);
// Pricing
const calc = useMemoCheckout(() => {
const cloudRate = addons.cloudYearly ? 350000 : 400000;
const monthlyItems = [
{ label:"Sewa Pokok", desc:m.model, qty:duration.months, unit:m.rates.sewa, total:duration.months*m.rates.sewa, required:true },
...(addons.cloud ? [{ label:"Cloud Server / Dashboard", desc:addons.cloudYearly?"tahunan":"bulanan", qty:duration.months, unit:cloudRate, total:duration.months*cloudRate }] : []),
...(addons.refill ? [{ label:"Jasa Refill", desc:"max 10x/bln", qty:duration.months, unit:1500000, total:duration.months*1500000 }] : []),
...(addons.cloudApi ? [{ label:"Cloud API", desc:"integrasi data", qty:duration.months, unit:100000, total:duration.months*100000 }] : []),
];
const subtotal = monthlyItems.reduce((a,b) => a+b.total, 0);
const discount = Math.round(subtotal * (duration.discount || 0));
const afterDisc = subtotal - discount;
const ppn = Math.round(afterDisc * 0.11);
const adminFee = method?.fee || 0;
const total = afterDisc + ppn + adminFee;
return { items: monthlyItems, subtotal, discount, afterDisc, ppn, adminFee, total };
}, [duration, addons, method, m]);
// Compute new end date
const newEnd = useMemoCheckout(() => {
const d = new Date(m.contractEnd);
d.setMonth(d.getMonth() + duration.months);
return d.toISOString().split("T")[0];
}, [duration, m]);
return (
{step === 1 && setStep(2)} calc={calc}/>}
{step === 2 && setStep(1)} onNext={() => setStep(3)}/>}
{step === 3 && setStep(2)} onPay={() => {
if (!method) { toast("Pilih metode pembayaran dulu.", "warning"); return; }
navigate({ name: "payment-success", id: m.id, method: method.label, total: calc.total, newEnd });
}}/>}
);
}
function Stepper({ step }) {
const steps = [
{ id:1, label:"Pilih Durasi" },
{ id:2, label:"Ringkasan" },
{ id:3, label:"Pembayaran" },
];
return (
{steps.map((s, i) => (
s.id ? "bg-emerald-50 text-emerald-700" : "bg-stone-100 text-stone-500"
}`}>
s.id ? "bg-emerald-600 text-white" : step===s.id?"bg-white text-[#FF2D55]":"bg-white text-stone-500"}`}>
{step > s.id ? : s.id}
{s.label}
{i < steps.length - 1 && s.id ? "bg-emerald-300" : "bg-stone-200"}`}>
}
))}
);
}
/* STEP 1 — Pilih durasi + add-ons */
function Step1({ m, duration, setDuration, addons, setAddons, onNext, calc }) {
return (
Step 1
Pilih Durasi Perpanjangan
Semakin lama, semakin hemat.
{DURATIONS.map(d => {
const active = duration.id === d.id;
return (
);
})}
Add-on
Layanan Tambahan
Pilih sesuai kebutuhan operasional Anda.
setAddons({...addons, cloud:v})}
icon="cpu"
title="Cloud Server / Dashboard"
desc="Real-time monitoring penjualan, stok, dan status mesin."
price={addons.cloudYearly?350000:400000}
extra={
{[{id:true,l:"Tahunan",p:"Rp 350k/bln"},{id:false,l:"Bulanan",p:"Rp 400k/bln"}].map(o => (
))}
}
/>
setAddons({...addons, refill:v})}
icon="shoppingBag"
title="Jasa Refill"
desc="Tim kami yang refill produk. Maksimal 10× per bulan."
price={1500000}
/>
setAddons({...addons, cloudApi:v})}
icon="zap"
title="Cloud API"
desc="Integrasi data penjualan ke sistem Anda."
price={100000}
/>
);
}
function AddonRow({ checked, onChange, icon, title, desc, price, extra }) {
return (
);
}
/* Sidebar summary */
function SummarySidebar({ m, duration, calc, onNext, ctaLabel }) {
return (
Ringkasan
Total Bayar
Subtotal ({duration.months} bln){formatRp(calc.subtotal)}
{calc.discount > 0 && (
Diskon durasi−{formatRp(calc.discount)}
)}
PPN 11%{formatRp(calc.ppn)}
{calc.adminFee > 0 && (
Biaya admin{formatRp(calc.adminFee)}
)}
Total
{formatRp(calc.total)}
untuk {duration.months} bulan
Pembayaran aman via Xendit
);
}
/* STEP 2 — Ringkasan order */
function Step2({ m, duration, addons, calc, newEnd, onBack, onNext }) {
return (
Step 2
Ringkasan Order
Periksa detail sebelum melanjutkan ke pembayaran.
{m.model}
{m.id}
{m.location}
Periode baru
{formatDateID(m.contractEnd)}
s/d
{formatDateID(newEnd)}
{calc.items.map((it, i) => (
×{it.qty}
{formatRp(it.unit)}
{formatRp(it.total)}
))}
Subtotal{formatRp(calc.subtotal)}
{calc.discount > 0 &&
Diskon {Math.round((duration.discount||0)*100)}%−{formatRp(calc.discount)}
}
PPN 11%{formatRp(calc.ppn)}
Total
{formatRp(calc.total - (calc.adminFee||0))}
+ biaya admin tergantung metode pembayaran
Konfirmasi
Siap bayar?
Pilih metode pembayaran di langkah berikutnya.
Invoice akan dibuat via Xendit dan dikirim ke email Anda.
);
}
/* STEP 3 — Pembayaran */
function Step3({ m, duration, addons, calc, method, setMethod, client, onBack, onPay }) {
const groups = [
{ id:"va", label:"Transfer Virtual Account", desc:"Bayar via ATM, mobile/internet banking." },
{ id:"qris", label:"QRIS", desc:"Scan dengan aplikasi e-wallet atau mobile banking." },
{ id:"ewallet", label:"E-Wallet", desc:"OVO, GoPay, DANA, ShopeePay." },
];
return (
Step 3
Metode Pembayaran
Powered by Xendit. Aman, instan, dan otomatis update status.
{groups.map(g => (
{g.label}
{g.desc}
{PAYMENT_METHODS.filter(pm => pm.group === g.id).map(pm => {
const active = method?.id === pm.id;
return (
);
})}
))}
Xendit Invoice Payload (dev)
{`POST https://api.xendit.co/v2/invoices
{
"external_id": "yappari-${m.id}-${Date.now()}",
"amount": ${calc.total},
"currency": "IDR",
"description": "Perpanjangan ${duration.label} — ${m.model}",
"invoice_duration": 86400,
"customer": {
"given_names": "${client.name}",
"email": "${client.email}",
"mobile_number": "${client.phone}"
},
"payment_methods": [${method ? `"${method.id.toUpperCase()}"` : "..."}]
}`}
Ringkasan Final
Total
{m.id} × {duration.months} bln{formatRp(calc.subtotal)}
{calc.discount > 0 &&
Diskon−{formatRp(calc.discount)}
}
PPN 11%{formatRp(calc.ppn)}
{calc.adminFee > 0 &&
Admin {method?.label}{formatRp(calc.adminFee)}
}
Total bayar
{formatRp(calc.total)}
Anda akan diarahkan ke halaman pembayaran Xendit.
);
}
/* ===== PAYMENT SUCCESS ===== */
function PaymentSuccessView() {
const { route, navigate } = useApp();
return (
完
Pembayaran Berhasil
Terima kasih!
Kontrak mesin {route.id} berhasil diperpanjang sampai {formatDateID(route.newEnd)}.
Total bayar
{formatRp(route.total)}
Invoice ID
yappari-{route.id}-{Date.now().toString().slice(-6)}
Email konfirmasi sudah dikirim. Receipt PDF tersedia di Detail Mesin.
);
}
/* ===== PAYMENT FAILED ===== */
function PaymentFailedView() {
const { route, navigate } = useApp();
return (
Pembayaran Gagal
Mohon dicoba lagi.
Pembayaran tidak berhasil diproses. Coba metode lain atau hubungi tim kami.
);
}
Object.assign(window, { CheckoutView, PaymentSuccessView, PaymentFailedView });