Midtrans-Middleware/src/features/payments/components/InlinePaymentStatus.tsx

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>
)
}