// disbursement.jsx — Disbursement Management Page for Hibank QRIS BackOffice function DisbursementPage({ merchants, primary, secondary, merchantView = false }) { const TEAL = primary || '#0056D2'; const ORANGE = secondary || '#cf5a27'; const GREEN = '#74b50c'; const RED = '#ed3151'; const AMBER = '#d9920b'; // State const [payouts, setPayouts] = React.useState([ { id: 'DISB-2606-0012', mid: 'MRC-00244', name: 'Batik Lestari Pekalongan', bank: 'Bank Mandiri', account: '1310022419', gross: 8500000, mdr: 59500, net: 8440500, time: '2026-06-24T11:00:00', mode: 'Batch', status: 'success', refNo: 'REF-MND-8812739' }, { id: 'DISB-2606-0013', mid: 'MRC-00245', name: 'Toko Bangunan Maju Jaya', bank: 'Bank BCA', account: '0127889123', gross: 12500000, mdr: 87500, net: 12412500, time: '2026-06-24T11:15:00', mode: 'Batch', status: 'success', refNo: 'REF-BCA-8812741' }, { id: 'DISB-2606-0014', mid: 'MRC-00246', name: 'Kopi Senja Nusantara', bank: 'Bank Hibank', account: '8820145566', gross: 3500000, mdr: 24500, net: 3475500, time: '2026-06-24T11:20:00', mode: 'Instant', status: 'failed', refNo: '—', failReason: 'API Gateway timeout from destination bank' }, { id: 'DISB-2606-0010', mid: 'MRC-00244', name: 'Batik Lestari Pekalongan', bank: 'Bank Mandiri', account: '1310022419', gross: 6200000, mdr: 43400, net: 6156600, time: '2026-06-23T11:00:00', mode: 'Batch', status: 'success', refNo: 'REF-MND-8811091' }, { id: 'DISB-2606-0011', mid: 'MRC-00245', name: 'Toko Bangunan Maju Jaya', bank: 'Bank BCA', account: '0127889123', gross: 10400000, mdr: 72800, net: 10327200, time: '2026-06-23T11:15:00', mode: 'Batch', status: 'success', refNo: 'REF-BCA-8811105' } ]); const [selectedPayout, setSelectedPayout] = React.useState(null); const [showFormModal, setShowFormModal] = React.useState(false); const [toast, setToast] = React.useState(null); // Form states const [formMerchantId, setFormMerchantId] = React.useState(''); const [formAmount, setFormAmount] = React.useState(''); const [formNotes, setFormNotes] = React.useState(''); const activeMerchants = merchants ? merchants.filter(m => m.status === 'approved' || m.status === 'active' || m.status === 'pending_l2') : []; const fmtRp = (v) => { if (v === null || v === undefined) return '—'; return 'Rp ' + Number(v).toLocaleString('id-ID'); }; const fmtDate = (iso) => { const d = new Date(iso); return d.toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }) + ' ' + d.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }); }; const triggerToast = (msg, kind = 'success') => { setToast({ msg, kind }); setTimeout(() => setToast(null), 3000); }; // Action: Retry failed payout const handleRetryPayout = (payoutId) => { setPayouts(prev => prev.map(p => { if (p.id !== payoutId) return p; return { ...p, status: 'processing', failReason: null }; })); triggerToast(`Memproses ulang pencairan ${payoutId}...`, 'info'); // Simulate success after 1.5 seconds setTimeout(() => { setPayouts(prev => prev.map(p => { if (p.id !== payoutId) return p; const randRef = 'REF-' + Math.random().toString(36).substring(2, 9).toUpperCase(); return { ...p, status: 'success', refNo: randRef }; })); triggerToast(`Pencairan ${payoutId} sukses dikirim ke bank tujuan.`, 'success'); }, 1500); }; // Action: Submit manual instant payout const handleInstantPayoutSubmit = (e) => { e.preventDefault(); if (!formMerchantId || !formAmount.trim()) return; const m = activeMerchants.find(merchant => merchant.id === formMerchantId); if (!m) return; const gross = Number(formAmount); // MDR simulation: 0.7% standard const mdr = Math.round(gross * 0.007); const net = gross - mdr; const newId = 'DISB-2606-' + Math.floor(1000 + Math.random() * 9000); const newPayout = { id: newId, mid: m.id, name: m.merchantName, bank: m.bankName || 'Bank Hibank', account: m.accountNumber || '8820145566', gross: gross, mdr: mdr, net: net, time: new Date().toISOString(), mode: 'Instant', status: 'processing', refNo: '—' }; setPayouts(prev => [newPayout, ...prev]); setShowFormModal(false); triggerToast(`Mengirim pencairan dana instan untuk ${m.merchantName}...`, 'info'); // Reset Form setFormMerchantId(''); setFormAmount(''); setFormNotes(''); // Simulate success after 2 seconds setTimeout(() => { setPayouts(prev => prev.map(p => { if (p.id !== newId) return p; const randRef = 'REF-' + Math.random().toString(36).substring(2, 9).toUpperCase(); return { ...p, status: 'success', refNo: randRef }; })); triggerToast(`Dana instan ${newId} berhasil ditransfer.`, 'success'); }, 2000); }; // Totals calculations const visiblePayouts = merchantView ? payouts.filter(p => p.status === 'success') : payouts; const totalVolume = visiblePayouts.filter(p => p.status === 'success').reduce((acc, curr) => acc + curr.net, 0); const successCount = visiblePayouts.filter(p => p.status === 'success').length; const processingCount = visiblePayouts.filter(p => p.status === 'processing').length; const failedCount = visiblePayouts.filter(p => p.status === 'failed').length; return (
{/* Header */}
Settlement Operations

{merchantView ? 'Disbursement — Dana Tercairkan' : 'Pencairan Dana (Disbursement)'}

{merchantView ? 'Riwayat dana settlement QRIS Anda yang berhasil dicairkan ke rekening bank terdaftar.' : 'Kelola transfer hasil transaksi QRIS (settlement) ke rekening bank terdaftar merchant secara instan maupun terjadwal.'}

{!merchantView && ( )}
{/* Stats Summary Cards */}
{[ { label: 'Total Volume Pencairan', value: fmtRp(totalVolume), sub: 'Dana sukses didepositkan', icon: 'bx-wallet', color: TEAL }, { label: 'Disbursement Sukses', value: `${successCount} Batch`, sub: 'Transaksi pencairan sukses', icon: 'bx-check-double', color: GREEN }, { label: 'Payout Processing', value: processingCount, sub: 'Dana sedang ditransfer', icon: 'bx-loader-circle bx-spin', color: AMBER }, { label: 'Payout Gagal', value: failedCount, sub: 'Butuh transfer ulang manual', icon: 'bx-error-circle', color: failedCount > 0 ? RED : '#a7a8a8' } ].map((card, i) => (
{card.label}
{card.value}
{card.sub}
))}
{/* Payout Table */}

Antrean & Riwayat Pencairan Dana

{visiblePayouts.map(p => { const isSuccess = p.status === 'success'; const isFailed = p.status === 'failed'; const isProcessing = p.status === 'processing'; return ( e.currentTarget.style.background = '#fafbfc'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}> ); })}
ID Batch Nama Merchant Rekening Tujuan Nominal Bersih (Net) Waktu Disburse Metode Status Aksi
{p.id}
{p.name}
{p.mid}
{p.bank}
{p.account}
{fmtRp(p.net)}
Kotor: {fmtRp(p.gross)}
{fmtDate(p.time)} {p.mode} {p.status.toUpperCase()}
{isFailed && ( )}
{/* Modal: Payout Detail Receipt */} {selectedPayout && (
setSelectedPayout(null)} style={{ position: 'absolute', inset: 0, background: 'rgba(23,25,25,0.4)', backdropFilter: 'blur(2px)' }} />
{/* Logo and Close */}

Bukti Pencairan Dana (Disbursement)

{/* Receipt Content */}
{/* Receipt status header */}
ID Batch Pencairan
{selectedPayout.id}
{selectedPayout.status.toUpperCase()}
{/* Payout Details */}
Nama Merchant {selectedPayout.name}
Bank Tujuan {selectedPayout.bank}
Rekening Penerima {selectedPayout.account}
Waktu Eksekusi {fmtDate(selectedPayout.time)}
Metode Pencairan {selectedPayout.mode} Settlement
{/* Financial calculations */}
Volume Transaksi Kotor (Gross) {fmtRp(selectedPayout.gross)}
Potongan MDR (0.7%) -{fmtRp(selectedPayout.mdr)}
Biaya Administrasi Bank Rp 0
Nominal Pencairan Bersih (Net) {fmtRp(selectedPayout.net)}
{/* Audit reference trail */}
No. Referensi Bank {selectedPayout.refNo}
{selectedPayout.status === 'failed' && (
Kegagalan Transfer: {selectedPayout.failReason || 'Gagal tersambung ke jaringan switching core.'}
)}
{selectedPayout.status === 'failed' && ( )}
)} {/* Modal: Instant Payout Trigger Form */} {showFormModal && (
setShowFormModal(false)} style={{ position: 'absolute', inset: 0, background: 'rgba(23,25,25,0.4)', backdropFilter: 'blur(2px)' }} />

Trigger Pencairan Dana Instan

{formMerchantId && ( <>
Rp setFormAmount(e.target.value)} placeholder="Masukkan nominal pencairan..." style={{ width: '100%', padding: '10px 12px 10px 32px', borderRadius: 8, border: '1.5px solid #efefef', fontSize: 13, color: '#171919', outline: 'none' }} onFocus={e => e.target.style.borderColor = TEAL} onBlur={e => e.target.style.borderColor = '#efefef'} />
Nominal net transfer akan dipotong MDR sebesar 0.7% secara otomatis.