import React from 'react' import { useParams, useSearchParams } from 'react-router-dom' import { usePaymentStatus } from '../features/payments/lib/usePaymentStatus' import type { PaymentStatusResponse } from '../features/payments/lib/midtrans' import { Logger } from '../lib/logger' export function PaymentStatusPage() { const { orderId } = useParams() const [search] = useSearchParams() const method = (search.get('m') ?? undefined) as ('bank_transfer' | 'gopay' | 'qris' | 'cstore' | 'credit_card' | undefined) const { data, isLoading, error } = usePaymentStatus(orderId) // Check if error is "transaction not found" from Midtrans const errorData = (error as any)?.response?.data const isTransactionNotFound = error && (String(error).includes("doesn't exist") || String(error).includes("404") || String(error).includes("Transaction doesn't exist") || errorData?.message?.includes("doesn't exist") || errorData?.message?.includes("404")) const statusText = data?.status ?? 'pending' const isFinal = ['settlement', 'capture', 'expire', 'cancel', 'deny', 'refund', 'chargeback'].includes(statusText) const isSuccess = statusText === 'settlement' || statusText === 'capture' function sanitizeUrl(u?: string) { return (u || '').replace(/[`\s]+$/g, '').replace(/^\s+|\s+$/g, '').replace(/`/g, '') } function pickQrFromCache(id?: string) { try { const raw = localStorage.getItem('qrisCache') if (!raw) return '' const map = JSON.parse(raw) as Record return sanitizeUrl(map[id || '']?.url) } catch { return '' } } function qrFromData(d?: PaymentStatusResponse) { if (!d) return '' const img = sanitizeUrl(d.imageUrl) if (img) return img const s = d.qrString if (typeof s === 'string' && s.length > 0) { return `https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=${encodeURIComponent(s)}` } return '' } function collectQrActionUrls(acts: Array<{ name?: string; method?: string; url: string }> | undefined) { const list = Array.isArray(acts) ? acts : [] const urls = list .filter((a) => /qr/i.test(a.name ?? '') || /qr-code/i.test(a.name ?? '')) .map((a) => sanitizeUrl(a.url)) .filter((u) => !!u) urls.sort((x, y) => { const xv4 = x.includes('/v4/') ? 1 : 0 const yv4 = y.includes('/v4/') ? 1 : 0 return yv4 - xv4 }) return urls } const qrCandidates = [qrFromData(data), ...collectQrActionUrls(data?.actions), pickQrFromCache(orderId || undefined)].filter((u) => !!u) const [qrSrc, setQrSrc] = React.useState('') React.useEffect(() => { setQrSrc(qrCandidates[0] || '') }, [statusText, method, orderId, data, qrCandidates]) // Get customer name from localStorage const customerName = React.useMemo(() => { try { const customerCache = JSON.parse(localStorage.getItem('customerCache') || '{}') return customerCache[orderId || '']?.name } catch { return undefined } }, [orderId]) // Logs for debugging status lifecycle React.useEffect(() => { Logger.info('status.init', { orderId, method }) }, []) React.useEffect(() => { if (!isLoading && !error && data) { Logger.info('status.update', { orderId: data.orderId, status: data.status }) } }, [isLoading, error, data]) // User-friendly status messages function getStatusMessage(s: PaymentStatusResponse['status']) { switch (s) { case 'pending': return { title: 'Menunggu Pembayaran', desc: 'Silakan selesaikan pembayaran Anda', icon: 'âŗ', color: 'yellow' } case 'settlement': case 'capture': return { title: 'Pembayaran Berhasil', desc: 'Terima kasih! Pembayaran Anda telah dikonfirmasi', icon: '✅', color: 'green' } case 'deny': return { title: 'Pembayaran Ditolak', desc: 'Maaf, pembayaran Anda ditolak. Silakan coba metode lain', icon: '❌', color: 'red' } case 'cancel': return { title: 'Pembayaran Dibatalkan', desc: 'Transaksi telah dibatalkan', icon: 'đŸšĢ', color: 'red' } case 'expire': return { title: 'Pembayaran Kedaluwarsa', desc: 'Waktu pembayaran habis. Silakan buat transaksi baru', icon: '⏰', color: 'red' } case 'refund': return { title: 'Pembayaran Dikembalikan', desc: 'Dana telah dikembalikan ke rekening Anda', icon: 'â†Šī¸', color: 'blue' } default: return { title: 'Status Tidak Diketahui', desc: 'Hubungi customer service untuk bantuan', icon: 'â„šī¸', color: 'gray' } } } const statusMsg = getStatusMessage(statusText) return (
{/* Header Card */}
{isLoading ? ( <>
âŗ
Memuat status...
Mohon tunggu sebentar
) : isTransactionNotFound ? ( <>
📋
Transaksi Belum Dibuat
Silakan kembali ke halaman checkout untuk membuat pembayaran
) : error ? ( <>
âš ī¸
Gagal Memuat Status
Terjadi kesalahan. Silakan refresh halaman
) : ( <>
{statusMsg.icon}
{statusMsg.title}
{statusMsg.desc}
)}
{/* Order Info */}
ID Pesanan
{orderId}
{!isLoading && !isFinal && !isTransactionNotFound && (
Memperbarui otomatis...
)}
{customerName ? (
Nama Pelanggan
{customerName}
) : null} {method || data?.method ? (
Metode Pembayaran
{(data?.method ?? method)?.replace('_', ' ')}
) : null}
{/* Payment Instructions - Only show for pending status */} {!isLoading && !error && data && statusText === 'pending' ? (
📝 Cara Pembayaran
{/* Bank Transfer OR Mandiri E-Channel */} {(!method || method === 'bank_transfer' || data.method === 'bank_transfer' || data.method === 'echannel') && (data.vaNumber || (data.billKey && data.billerCode)) ? ( <> {data.vaNumber ? (
Nomor Virtual Account
{data.vaNumber}
{data.bank ? (
Bank
{data.bank}
) : null}
) : null} {/* Mandiri E-Channel specific */} {data.billKey && data.billerCode ? (
đŸ’ŗ Mandiri E-Channel
Kode Perusahaan (Biller Code)
{data.billerCode}
Kode Bayar (Bill Key)
{data.billKey}
) : null}

Langkah pembayaran:

{data.billKey && data.billerCode ? (
  1. Buka aplikasi Livin' by Mandiri atau ATM Mandiri
  2. Pilih menu Bayar / Multi Payment
  3. Pilih penyedia jasa: Midtrans (atau cari dengan Biller Code)
  4. Masukkan Kode Perusahaan: {data.billerCode}
  5. Masukkan Kode Bayar: {data.billKey}
  6. Periksa detail tagihan dan konfirmasi pembayaran
  7. Simpan bukti transaksi
) : (
  1. Buka aplikasi mobile banking atau ATM
  2. Pilih menu Transfer / Bayar
  3. Masukkan nomor Virtual Account di atas
  4. Konfirmasi pembayaran
  5. Simpan bukti transaksi
)}
) : null} {(!method || method === 'cstore') && (data.store || data.paymentCode) ? ( <>
{data.store ? (
Toko
{data.store}
) : null} {data.paymentCode ? ( <>
Kode Pembayaran
{data.paymentCode}
) : null}

Langkah pembayaran:

  1. Kunjungi toko {data.store || 'convenience store'} terdekat
  2. Berikan kode pembayaran kepada kasir
  3. Lakukan pembayaran tunai
  4. Simpan bukti pembayaran
) : null} {(!method || method === 'gopay' || method === 'qris' || data.method === 'qris' || data.method === 'gopay') && (qrSrc || (Array.isArray(data?.actions) && data.actions.length > 0)) ? ( <> {qrSrc ? (
Scan QR Code
QR Code Pembayaran { const next = qrCandidates.find((u) => u !== e.currentTarget.src) if (next) setQrSrc(next) }} />
) : null}

Langkah pembayaran:

  1. Buka aplikasi {method === 'gopay' || data.method === 'gopay' ? 'GoPay/Gojek' : 'e-wallet atau m-banking yang mendukung QRIS'}
  2. Pilih menu Scan QR atau QRIS
  3. Arahkan kamera ke QR code di atas
  4. Konfirmasi pembayaran
{(Array.isArray(data?.actions) && data.actions.length > 0) ? (
{data.actions.map((a, i) => ( 📱 {a.name || 'Buka Aplikasi'} ))}
) : null} ) : (data.method === 'qris' || data.method === 'gopay') ? (
📱
QR Code Pembayaran

QR code untuk pembayaran ini ditampilkan di jendela pembayaran Snap.

Jika Anda menutup jendela tersebut, silakan:

  1. Kembali ke halaman checkout
  2. Buat pembayaran baru dengan order ID yang sama
  3. QR code akan muncul kembali di jendela Snap

Atau tunggu hingga pembayaran kedaluwarsa dan buat transaksi baru.

) : null} {(!method || method === 'credit_card') && data.maskedCard ? (
Kartu Kredit/Debit
{data.maskedCard}
Pembayaran dengan kartu telah diproses. Tunggu konfirmasi dari bank Anda.
) : null}
) : null} {/* Help Section */} {!isLoading && !error && (

💡 Butuh bantuan?

  • â€ĸ Jika pembayaran belum terkonfirmasi dalam 24 jam, hubungi customer service
  • â€ĸ Simpan nomor pesanan untuk referensi
  • â€ĸ Halaman ini akan diperbarui otomatis saat status berubah
)}
) }