100 lines
5.6 KiB
TypeScript
100 lines
5.6 KiB
TypeScript
import { Button } from '../../../components/ui/button'
|
|
import { usePaymentNavigation } from '../lib/navigation'
|
|
import { usePaymentStatus } from '../lib/usePaymentStatus'
|
|
import type { PaymentStatusResponse } from '../lib/midtrans'
|
|
|
|
function formatIDR(amount?: string) {
|
|
if (!amount) return ''
|
|
const n = Number(amount)
|
|
if (Number.isNaN(n)) return amount
|
|
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(Math.round(n))
|
|
}
|
|
|
|
export function InlinePaymentStatus({ orderId, method, compact }: { orderId: string; method?: string; compact?: boolean }) {
|
|
const nav = usePaymentNavigation()
|
|
const { data, isLoading, error, refetch, isRefetching } = usePaymentStatus(orderId)
|
|
const status = (data?.status ?? 'pending') as PaymentStatusResponse['status']
|
|
const isSuccess = status === 'settlement' || status === 'capture'
|
|
const isFailure = ['deny', 'cancel', 'expire', 'refund', 'chargeback'].includes(status)
|
|
|
|
return (
|
|
<div className={`rounded border ${compact ? 'p-2' : 'p-3'} border-black/10 bg-white`} aria-live="polite">
|
|
{/* Header minimal tanpa detail teknis */}
|
|
<div className="text-sm font-medium">Status pembayaran</div>
|
|
|
|
{/* Konten berdasarkan status */}
|
|
{isLoading ? (
|
|
<div className="mt-2 text-sm">
|
|
<span className="inline-flex items-center gap-2" role="status">
|
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-black/40 border-t-transparent" aria-hidden />
|
|
Mengecek pembayaran…
|
|
</span>
|
|
<div className="mt-1 text-[11px] text-black/60">Kami memeriksa otomatis setiap 3 detik.</div>
|
|
</div>
|
|
) : error ? (
|
|
<div className="mt-2 text-sm text-brand-600">Gagal memuat status. Coba refresh.</div>
|
|
) : isSuccess ? (
|
|
<div className="mt-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-green-500/15 text-green-600">
|
|
{/* check icon */}
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" className="animate-[pop_200ms_ease-out]">
|
|
<path d="M20 6L9 17L4 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</span>
|
|
<div className="text-base font-semibold">Pembayaran berhasil</div>
|
|
</div>
|
|
{data?.grossAmount ? (
|
|
<div className="mt-1 text-sm text-black/70">Total dibayar: {formatIDR(data.grossAmount)}</div>
|
|
) : null}
|
|
<div className="mt-1 text-xs text-black/60">Terima kasih! Pesanan Anda sedang diproses.</div>
|
|
<div className="mt-3 flex flex-wrap gap-2">
|
|
<Button className="w-full sm:w-auto" onClick={() => nav.toHistory()}>Lihat riwayat pembayaran</Button>
|
|
<Button variant="outline" className="w-full sm:w-auto" onClick={() => nav.toCheckout()}>Kembali ke checkout</Button>
|
|
<Button variant="outline" className="w-full sm:w-auto" onClick={() => nav.toStatus(orderId, method)}>Lihat detail status</Button>
|
|
</div>
|
|
</div>
|
|
) : isFailure ? (
|
|
<div className="mt-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-red-500/15 text-red-600">
|
|
{/* x icon */}
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
<path d="M18 6L6 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
|
<path d="M6 6L18 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
|
</svg>
|
|
</span>
|
|
<div className="text-base font-semibold">Pembayaran belum berhasil</div>
|
|
</div>
|
|
<div className="mt-1 text-xs text-black/60">Silakan coba lagi atau pilih metode lain.</div>
|
|
<div className="mt-3 flex flex-wrap gap-2">
|
|
<Button className="w-full sm:w-auto" onClick={() => nav.toCheckout()}>Coba lagi</Button>
|
|
<Button variant="outline" className="w-full sm:w-auto" onClick={() => nav.toStatus(orderId, method)}>Lihat detail status</Button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="mt-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-yellow-500/15 text-yellow-700">
|
|
{/* hourglass/spinner icon */}
|
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-yellow-600/50 border-t-transparent" aria-hidden />
|
|
</span>
|
|
<div className="text-base font-semibold">Menunggu pembayaran</div>
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-black/60">Kami memeriksa otomatis setiap 3 detik sampai selesai.</div>
|
|
<div className="mt-3 flex flex-wrap gap-2">
|
|
<Button variant="outline" className="w-full sm:w-auto" onClick={() => refetch()} aria-busy={isRefetching} disabled={isRefetching}>
|
|
{isRefetching ? (
|
|
<span className="inline-flex items-center gap-2" role="status" aria-live="polite">
|
|
<span className="h-3 w-3 animate-spin rounded-full border-2 border-black/40 border-t-transparent" aria-hidden />
|
|
Memuat…
|
|
</span>
|
|
) : 'Refresh sekarang'}
|
|
</Button>
|
|
<Button variant="outline" className="w-full sm:w-auto" onClick={() => nav.toStatus(orderId, method)}>Buka halaman status</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
} |