UX FIX #17
|
|
@ -44,26 +44,16 @@ export function PaymentSheet({ merchantName = 'Simaya', orderId, amount, expireA
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold text-sm sm:text-base">{merchantName}</div>
|
<div className="font-semibold text-sm sm:text-base">{merchantName}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 sm:gap-3">
|
<button
|
||||||
<div
|
aria-label={expanded ? 'Collapse' : 'Expand'}
|
||||||
className="text-xs sm:text-sm text-white/80"
|
aria-expanded={expanded}
|
||||||
role="timer"
|
onClick={() => setExpanded((v) => !v)}
|
||||||
aria-live="polite"
|
className={`text-white/80 transition-transform ${expanded ? '' : 'rotate-180'} focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-white/80 focus-visible:ring-offset-2`}
|
||||||
aria-label={`Kedaluwarsa dalam ${countdown}`}
|
>
|
||||||
>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden>
|
||||||
Kedaluwarsa dalam <span className="font-semibold text-white">{countdown}</span>
|
<path d="M7 14L12 9L17 14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
</div>
|
</svg>
|
||||||
<button
|
</button>
|
||||||
aria-label={expanded ? 'Collapse' : 'Expand'}
|
|
||||||
aria-expanded={expanded}
|
|
||||||
onClick={() => setExpanded((v) => !v)}
|
|
||||||
className={`text-white/80 transition-transform ${expanded ? '' : 'rotate-180'} focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-white/80 focus-visible:ring-offset-2`}
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
||||||
<path d="M7 14L12 9L17 14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{expanded && (
|
{expanded && (
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ export interface PaymentStatusResponse {
|
||||||
// Common
|
// Common
|
||||||
transactionTime?: string
|
transactionTime?: string
|
||||||
grossAmount?: string
|
grossAmount?: string
|
||||||
|
customerName?: string // From localStorage, not from Midtrans API
|
||||||
// Bank transfer
|
// Bank transfer
|
||||||
vaNumber?: string
|
vaNumber?: string
|
||||||
bank?: string
|
bank?: string
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,17 @@ function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError, onModa
|
||||||
Logger.paymentInfo('checkout.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' })
|
Logger.paymentInfo('checkout.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' })
|
||||||
console.log('Token berhasil dibuat:', token)
|
console.log('Token berhasil dibuat:', token)
|
||||||
|
|
||||||
|
// Store customer name in localStorage for status page
|
||||||
|
if (customer?.name) {
|
||||||
|
try {
|
||||||
|
const customerCache = JSON.parse(localStorage.getItem('customerCache') || '{}')
|
||||||
|
customerCache[orderId] = { name: customer.name, timestamp: Date.now() }
|
||||||
|
localStorage.setItem('customerCache', JSON.stringify(customerCache))
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to cache customer name:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify Snap.js is loaded
|
// Verify Snap.js is loaded
|
||||||
console.log('window.snap:', window.snap)
|
console.log('window.snap:', window.snap)
|
||||||
console.log('window.snap.pay:', window.snap?.pay)
|
console.log('window.snap.pay:', window.snap?.pay)
|
||||||
|
|
@ -105,6 +116,7 @@ function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError, onModa
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
Logger.paymentInfo('checkout.auto.snap.popup.closed', { orderId })
|
Logger.paymentInfo('checkout.auto.snap.popup.closed', { orderId })
|
||||||
|
console.log('🔵 Snap modal closed - calling onModalClosed')
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
onModalClosed?.() // Enable status button when modal closed
|
onModalClosed?.() // Enable status button when modal closed
|
||||||
}
|
}
|
||||||
|
|
@ -128,6 +140,7 @@ function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError, onModa
|
||||||
}
|
}
|
||||||
|
|
||||||
onError?.(e)
|
onError?.(e)
|
||||||
|
onModalClosed?.() // Enable status button on error
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -315,6 +328,13 @@ export function CheckoutPage() {
|
||||||
{(() => {
|
{(() => {
|
||||||
console.log('Rendering step 2 - AutoSnapPayment', { orderId, amount, currentStep })
|
console.log('Rendering step 2 - AutoSnapPayment', { orderId, amount, currentStep })
|
||||||
Logger.info('checkout.step2.render', { orderId, amount })
|
Logger.info('checkout.step2.render', { orderId, amount })
|
||||||
|
// Fallback: Force show status button after 3 seconds if callback not called
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!modalClosed) {
|
||||||
|
console.log('⚠️ Fallback timer: Forcing status button to show')
|
||||||
|
setModalClosed(true)
|
||||||
|
}
|
||||||
|
}, 3000)
|
||||||
return null
|
return null
|
||||||
})()}
|
})()}
|
||||||
<AutoSnapPayment
|
<AutoSnapPayment
|
||||||
|
|
@ -335,6 +355,7 @@ export function CheckoutPage() {
|
||||||
// Handle payment error
|
// Handle payment error
|
||||||
}}
|
}}
|
||||||
onModalClosed={() => {
|
onModalClosed={() => {
|
||||||
|
console.log('🟢 onModalClosed callback fired - setting modalClosed to TRUE')
|
||||||
setModalClosed(true) // Enable status button when modal closed
|
setModalClosed(true) // Enable status button when modal closed
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,17 @@ function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError }: Auto
|
||||||
Logger.paymentInfo('paypage.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' })
|
Logger.paymentInfo('paypage.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' })
|
||||||
console.log('[PayPage] Token received:', token)
|
console.log('[PayPage] Token received:', token)
|
||||||
|
|
||||||
|
// Store customer name in localStorage for status page
|
||||||
|
if (customer?.name) {
|
||||||
|
try {
|
||||||
|
const customerCache = JSON.parse(localStorage.getItem('customerCache') || '{}')
|
||||||
|
customerCache[orderId] = { name: customer.name, timestamp: Date.now() }
|
||||||
|
localStorage.setItem('customerCache', JSON.stringify(customerCache))
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to cache customer name:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!window.snap || typeof window.snap.pay !== 'function') {
|
if (!window.snap || typeof window.snap.pay !== 'function') {
|
||||||
throw new Error(`Snap.js not loaded: hasSnap=${!!window.snap}`)
|
throw new Error(`Snap.js not loaded: hasSnap=${!!window.snap}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useParams, useSearchParams } from 'react-router-dom'
|
import { useParams, useSearchParams } from 'react-router-dom'
|
||||||
import { usePaymentNavigation } from '../features/payments/lib/navigation'
|
|
||||||
import { usePaymentStatus } from '../features/payments/lib/usePaymentStatus'
|
import { usePaymentStatus } from '../features/payments/lib/usePaymentStatus'
|
||||||
import type { PaymentStatusResponse } from '../features/payments/lib/midtrans'
|
import type { PaymentStatusResponse } from '../features/payments/lib/midtrans'
|
||||||
import { Logger } from '../lib/logger'
|
import { Logger } from '../lib/logger'
|
||||||
import { CountdownRedirect } from '../components/CountdownRedirect'
|
|
||||||
|
|
||||||
export function PaymentStatusPage() {
|
export function PaymentStatusPage() {
|
||||||
const { orderId } = useParams()
|
const { orderId } = useParams()
|
||||||
const nav = usePaymentNavigation()
|
|
||||||
const [search] = useSearchParams()
|
const [search] = useSearchParams()
|
||||||
const method = (search.get('m') ?? undefined) as ('bank_transfer' | 'gopay' | 'qris' | 'cstore' | 'credit_card' | undefined)
|
const method = (search.get('m') ?? undefined) as ('bank_transfer' | 'gopay' | 'qris' | 'cstore' | 'credit_card' | undefined)
|
||||||
const { data, isLoading, error } = usePaymentStatus(orderId)
|
const { data, isLoading, error } = usePaymentStatus(orderId)
|
||||||
|
|
@ -63,9 +60,15 @@ export function PaymentStatusPage() {
|
||||||
const [qrSrc, setQrSrc] = React.useState<string>('')
|
const [qrSrc, setQrSrc] = React.useState<string>('')
|
||||||
React.useEffect(() => { setQrSrc(qrCandidates[0] || '') }, [statusText, method, orderId, data, qrCandidates])
|
React.useEffect(() => { setQrSrc(qrCandidates[0] || '') }, [statusText, method, orderId, data, qrCandidates])
|
||||||
|
|
||||||
function handleRedirect() {
|
// Get customer name from localStorage
|
||||||
nav.toHistory()
|
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
|
// Logs for debugging status lifecycle
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -155,6 +158,13 @@ export function PaymentStatusPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{customerName ? (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="text-xs text-gray-500 uppercase tracking-wide mb-1">Nama Pelanggan</div>
|
||||||
|
<div className="text-sm font-medium text-gray-900">{customerName}</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{method || data?.method ? (
|
{method || data?.method ? (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="text-xs text-gray-500 uppercase tracking-wide mb-1">Metode Pembayaran</div>
|
<div className="text-xs text-gray-500 uppercase tracking-wide mb-1">Metode Pembayaran</div>
|
||||||
|
|
@ -164,18 +174,6 @@ export function PaymentStatusPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isSuccess ? (
|
|
||||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className="text-2xl">🎉</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="font-semibold text-green-900 mb-1">Transaksi Selesai!</div>
|
|
||||||
<div className="text-sm text-green-700">Anda akan diarahkan ke halaman utama dalam beberapa detik</div>
|
|
||||||
<CountdownRedirect seconds={5} destination="dashboard" onComplete={handleRedirect} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{/* Payment Instructions - Only show for pending status */}
|
{/* Payment Instructions - Only show for pending status */}
|
||||||
{!isLoading && !error && data && statusText === 'pending' ? (
|
{!isLoading && !error && data && statusText === 'pending' ? (
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden mb-4">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden mb-4">
|
||||||
|
|
@ -183,43 +181,85 @@ export function PaymentStatusPage() {
|
||||||
<div className="text-sm font-semibold text-blue-900">📝 Cara Pembayaran</div>
|
<div className="text-sm font-semibold text-blue-900">📝 Cara Pembayaran</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
{(!method || method === 'bank_transfer') && data.vaNumber ? (
|
{/* Bank Transfer OR Mandiri E-Channel */}
|
||||||
|
{(!method || method === 'bank_transfer' || data.method === 'bank_transfer' || data.method === 'echannel') && (data.vaNumber || (data.billKey && data.billerCode)) ? (
|
||||||
<>
|
<>
|
||||||
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
{data.vaNumber ? (
|
||||||
<div className="text-xs text-gray-500 uppercase tracking-wide mb-2">Nomor Virtual Account</div>
|
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||||
<div className="flex items-center justify-between bg-white rounded border border-gray-300 px-4 py-3">
|
<div className="text-xs text-gray-500 uppercase tracking-wide mb-2">Nomor Virtual Account</div>
|
||||||
<div className="font-mono text-lg font-bold text-gray-900">{data.vaNumber}</div>
|
<div className="flex items-center justify-between bg-white rounded border border-gray-300 px-4 py-3">
|
||||||
<button
|
<div className="font-mono text-lg font-bold text-gray-900">{data.vaNumber}</div>
|
||||||
onClick={() => navigator.clipboard.writeText(data.vaNumber || '')}
|
<button
|
||||||
className="text-xs bg-blue-600 text-white px-3 py-1.5 rounded hover:bg-blue-700"
|
onClick={() => navigator.clipboard.writeText(data.vaNumber || '')}
|
||||||
>
|
className="text-xs bg-blue-600 text-white px-3 py-1.5 rounded hover:bg-blue-700"
|
||||||
📋 Salin
|
>
|
||||||
</button>
|
📋 Salin
|
||||||
</div>
|
</button>
|
||||||
{data.bank ? (
|
|
||||||
<div className="mt-2">
|
|
||||||
<div className="text-xs text-gray-500">Bank</div>
|
|
||||||
<div className="text-sm font-semibold text-gray-900 uppercase">{data.bank}</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
{data.bank ? (
|
||||||
</div>
|
<div className="mt-2">
|
||||||
<div className="text-sm text-gray-600 space-y-2">
|
<div className="text-xs text-gray-500">Bank</div>
|
||||||
<p className="font-medium text-gray-900">Langkah pembayaran:</p>
|
<div className="text-sm font-semibold text-gray-900 uppercase">{data.bank}</div>
|
||||||
<ol className="list-decimal list-inside space-y-1 ml-2">
|
</div>
|
||||||
<li>Buka aplikasi mobile banking atau ATM</li>
|
) : null}
|
||||||
<li>Pilih menu Transfer / Bayar</li>
|
|
||||||
<li>Masukkan nomor Virtual Account di atas</li>
|
|
||||||
<li>Konfirmasi pembayaran</li>
|
|
||||||
<li>Simpan bukti transaksi</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
{data.billKey && data.billerCode ? (
|
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 text-sm">
|
|
||||||
<div className="font-medium text-yellow-900 mb-1">Khusus Mandiri E-Channel:</div>
|
|
||||||
<div className="text-yellow-800">Kode Biller: <span className="font-mono font-semibold">{data.billerCode}</span></div>
|
|
||||||
<div className="text-yellow-800">Kode Bayar: <span className="font-mono font-semibold">{data.billKey}</span></div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{/* Mandiri E-Channel specific */}
|
||||||
|
{data.billKey && data.billerCode ? (
|
||||||
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||||
|
<div className="font-semibold text-yellow-900 mb-3 text-base">💳 Mandiri E-Channel</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-yellow-700 uppercase tracking-wide mb-1">Kode Perusahaan (Biller Code)</div>
|
||||||
|
<div className="flex items-center justify-between bg-white rounded border border-yellow-300 px-4 py-3">
|
||||||
|
<div className="font-mono text-lg font-bold text-gray-900">{data.billerCode}</div>
|
||||||
|
<button
|
||||||
|
onClick={() => navigator.clipboard.writeText(data.billerCode || '')}
|
||||||
|
className="text-xs bg-yellow-600 text-white px-3 py-1.5 rounded hover:bg-yellow-700"
|
||||||
|
>
|
||||||
|
📋 Salin
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-yellow-700 uppercase tracking-wide mb-1">Kode Bayar (Bill Key)</div>
|
||||||
|
<div className="flex items-center justify-between bg-white rounded border border-yellow-300 px-4 py-3">
|
||||||
|
<div className="font-mono text-lg font-bold text-gray-900">{data.billKey}</div>
|
||||||
|
<button
|
||||||
|
onClick={() => navigator.clipboard.writeText(data.billKey || '')}
|
||||||
|
className="text-xs bg-yellow-600 text-white px-3 py-1.5 rounded hover:bg-yellow-700"
|
||||||
|
>
|
||||||
|
📋 Salin
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="text-sm text-gray-600 space-y-2">
|
||||||
|
<p className="font-medium text-gray-900">Langkah pembayaran:</p>
|
||||||
|
{data.billKey && data.billerCode ? (
|
||||||
|
<ol className="list-decimal list-inside space-y-1 ml-2">
|
||||||
|
<li>Buka aplikasi Livin' by Mandiri atau ATM Mandiri</li>
|
||||||
|
<li>Pilih menu <strong>Bayar</strong> / <strong>Multi Payment</strong></li>
|
||||||
|
<li>Pilih penyedia jasa: <strong>Midtrans</strong> (atau cari dengan Biller Code)</li>
|
||||||
|
<li>Masukkan Kode Perusahaan: <strong>{data.billerCode}</strong></li>
|
||||||
|
<li>Masukkan Kode Bayar: <strong>{data.billKey}</strong></li>
|
||||||
|
<li>Periksa detail tagihan dan konfirmasi pembayaran</li>
|
||||||
|
<li>Simpan bukti transaksi</li>
|
||||||
|
</ol>
|
||||||
|
) : (
|
||||||
|
<ol className="list-decimal list-inside space-y-1 ml-2">
|
||||||
|
<li>Buka aplikasi mobile banking atau ATM</li>
|
||||||
|
<li>Pilih menu Transfer / Bayar</li>
|
||||||
|
<li>Masukkan nomor Virtual Account di atas</li>
|
||||||
|
<li>Konfirmasi pembayaran</li>
|
||||||
|
<li>Simpan bukti transaksi</li>
|
||||||
|
</ol>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{(!method || method === 'cstore') && (data.store || data.paymentCode) ? (
|
{(!method || method === 'cstore') && (data.store || data.paymentCode) ? (
|
||||||
|
|
@ -257,7 +297,7 @@ export function PaymentStatusPage() {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{(!method || method === 'gopay' || method === 'qris') && (qrSrc || (Array.isArray(data?.actions) && data.actions.length > 0)) ? (
|
{(!method || method === 'gopay' || method === 'qris' || data.method === 'qris' || data.method === 'gopay') && (qrSrc || (Array.isArray(data?.actions) && data.actions.length > 0)) ? (
|
||||||
<>
|
<>
|
||||||
{qrSrc ? (
|
{qrSrc ? (
|
||||||
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||||
|
|
@ -273,7 +313,7 @@ export function PaymentStatusPage() {
|
||||||
<div className="text-sm text-gray-600 space-y-2">
|
<div className="text-sm text-gray-600 space-y-2">
|
||||||
<p className="font-medium text-gray-900">Langkah pembayaran:</p>
|
<p className="font-medium text-gray-900">Langkah pembayaran:</p>
|
||||||
<ol className="list-decimal list-inside space-y-1 ml-2">
|
<ol className="list-decimal list-inside space-y-1 ml-2">
|
||||||
<li>Buka aplikasi {method === 'gopay' ? 'GoPay/Gojek' : 'e-wallet atau m-banking'}</li>
|
<li>Buka aplikasi {method === 'gopay' || data.method === 'gopay' ? 'GoPay/Gojek' : 'e-wallet atau m-banking yang mendukung QRIS'}</li>
|
||||||
<li>Pilih menu Scan QR atau QRIS</li>
|
<li>Pilih menu Scan QR atau QRIS</li>
|
||||||
<li>Arahkan kamera ke QR code di atas</li>
|
<li>Arahkan kamera ke QR code di atas</li>
|
||||||
<li>Konfirmasi pembayaran</li>
|
<li>Konfirmasi pembayaran</li>
|
||||||
|
|
@ -295,6 +335,25 @@ export function PaymentStatusPage() {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
) : (data.method === 'qris' || data.method === 'gopay') ? (
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="text-2xl">📱</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="font-semibold text-blue-900 mb-2">QR Code Pembayaran</div>
|
||||||
|
<div className="text-sm text-blue-700 space-y-2">
|
||||||
|
<p>QR code untuk pembayaran ini ditampilkan di jendela pembayaran Snap.</p>
|
||||||
|
<p>Jika Anda menutup jendela tersebut, silakan:</p>
|
||||||
|
<ol className="list-decimal list-inside ml-2 mt-2 space-y-1">
|
||||||
|
<li>Kembali ke halaman checkout</li>
|
||||||
|
<li>Buat pembayaran baru dengan order ID yang sama</li>
|
||||||
|
<li>QR code akan muncul kembali di jendela Snap</li>
|
||||||
|
</ol>
|
||||||
|
<p className="mt-3 text-blue-600 italic">Atau tunggu hingga pembayaran kedaluwarsa dan buat transaksi baru.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{(!method || method === 'credit_card') && data.maskedCard ? (
|
{(!method || method === 'credit_card') && data.maskedCard ? (
|
||||||
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"mercant_id": "REFNO-003",
|
"mercant_id": "REFNO-004",
|
||||||
"timestamp": 1733331600000,
|
"timestamp": 1733331600000,
|
||||||
"deskripsi": "Bayar Internet",
|
"deskripsi": "Bayar Internet",
|
||||||
"nominal": 250000,
|
"nominal": 250000,
|
||||||
"nama": "Test User 3",
|
"nama": "Test User 4",
|
||||||
"no_telepon": "081234567891",
|
"no_telepon": "081234567892",
|
||||||
"email": "test3@example.com",
|
"email": "test4@example.com",
|
||||||
"item": [
|
"item": [
|
||||||
{ "item_id": "TKG-2512042", "nama": "Internet Desember Premium", "harga": 250000, "qty": 1 }
|
{ "item_id": "TKG-2512043", "nama": "Internet Desember Premium", "harga": 250000, "qty": 1 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue