204 lines
6.0 KiB
TypeScript
204 lines
6.0 KiB
TypeScript
import React from 'react'
|
|
import { Button } from '../../../components/ui/button'
|
|
import { getPaymentMode } from '../lib/paymentMode'
|
|
import { Alert } from '../../../components/alert/Alert'
|
|
import { LoadingOverlay } from '../../../components/LoadingOverlay'
|
|
import { mapErrorToUserMessage } from '../../../lib/errorMessages'
|
|
import { Logger } from '../../../lib/logger'
|
|
import { SnapTokenService } from './SnapTokenService'
|
|
|
|
interface SnapPaymentTriggerProps {
|
|
orderId: string
|
|
amount: number
|
|
customer?: { name?: string; phone?: string; email?: string }
|
|
paymentMethod?: string
|
|
onSuccess?: (result: any) => void
|
|
onError?: (error: any) => void
|
|
onChargeInitiated?: () => void
|
|
}
|
|
|
|
export function SnapPaymentTrigger({
|
|
orderId,
|
|
amount,
|
|
customer,
|
|
paymentMethod,
|
|
onSuccess,
|
|
onError,
|
|
onChargeInitiated
|
|
}: SnapPaymentTriggerProps) {
|
|
const mode = getPaymentMode()
|
|
|
|
// If Core mode, render the appropriate Core component
|
|
if (mode === 'CORE') {
|
|
return (
|
|
<CorePaymentComponent
|
|
paymentMethod={paymentMethod || 'bank_transfer'}
|
|
orderId={orderId}
|
|
amount={amount}
|
|
onChargeInitiated={onChargeInitiated}
|
|
/>
|
|
)
|
|
}
|
|
|
|
// Snap mode - use hosted payment interface
|
|
return (
|
|
<SnapHostedPayment
|
|
orderId={orderId}
|
|
amount={amount}
|
|
customer={customer}
|
|
onSuccess={onSuccess}
|
|
onError={onError}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function CorePaymentComponent({ paymentMethod, orderId, amount, onChargeInitiated }: {
|
|
paymentMethod: string
|
|
orderId: string
|
|
amount: number
|
|
onChargeInitiated?: () => void
|
|
}) {
|
|
const [Component, setComponent] = React.useState<React.ComponentType<any> | null>(null)
|
|
const [loading, setLoading] = React.useState(true)
|
|
|
|
React.useEffect(() => {
|
|
const loadComponent = async () => {
|
|
try {
|
|
let componentModule: any
|
|
|
|
switch (paymentMethod) {
|
|
case 'bank_transfer':
|
|
componentModule = await import('../core/BankTransferPanel')
|
|
setComponent(() => componentModule.BankTransferPanel)
|
|
break
|
|
case 'credit_card':
|
|
componentModule = await import('../core/CardPanel')
|
|
setComponent(() => componentModule.CardPanel)
|
|
break
|
|
case 'gopay':
|
|
componentModule = await import('../core/GoPayPanel')
|
|
setComponent(() => componentModule.GoPayPanel)
|
|
break
|
|
case 'cstore':
|
|
componentModule = await import('../core/CStorePanel')
|
|
setComponent(() => componentModule.CStorePanel)
|
|
break
|
|
default:
|
|
componentModule = await import('../core/BankTransferPanel')
|
|
setComponent(() => componentModule.BankTransferPanel)
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load payment component:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
loadComponent()
|
|
}, [paymentMethod])
|
|
|
|
if (loading) {
|
|
return <div>Loading payment component...</div>
|
|
}
|
|
|
|
if (!Component) {
|
|
return <div>Payment method not available</div>
|
|
}
|
|
|
|
return <Component orderId={orderId} amount={amount} onChargeInitiated={onChargeInitiated} />
|
|
}
|
|
|
|
function SnapHostedPayment({ orderId, amount, customer, onSuccess, onError }: Omit<SnapPaymentTriggerProps, 'paymentMethod' | 'onChargeInitiated'>) {
|
|
const [loading, setLoading] = React.useState(false)
|
|
const [error, setError] = React.useState('')
|
|
|
|
const handleSnapPayment = async () => {
|
|
try {
|
|
setLoading(true)
|
|
setError('')
|
|
|
|
Logger.paymentInfo('snap.payment.init', { orderId, amount, customer })
|
|
|
|
// Create Snap transaction token using service
|
|
const token = await SnapTokenService.createToken({
|
|
transaction_details: {
|
|
order_id: orderId,
|
|
gross_amount: amount
|
|
},
|
|
customer_details: customer ? {
|
|
first_name: customer.name,
|
|
email: customer.email,
|
|
phone: customer.phone
|
|
} : undefined,
|
|
item_details: [{
|
|
id: orderId,
|
|
name: 'Payment',
|
|
price: amount,
|
|
quantity: 1
|
|
}]
|
|
})
|
|
|
|
Logger.paymentInfo('snap.token.received', { orderId, token: token.substring(0, 10) + '...' })
|
|
|
|
// Trigger Snap payment popup
|
|
if (window.snap && typeof window.snap.pay === 'function') {
|
|
window.snap.pay(token, {
|
|
onSuccess: (result: any) => {
|
|
Logger.paymentInfo('snap.payment.success', { orderId, transactionId: result.transaction_id })
|
|
onSuccess?.(result)
|
|
},
|
|
onPending: (result: any) => {
|
|
Logger.paymentInfo('snap.payment.pending', { orderId, transactionId: result.transaction_id })
|
|
// Handle pending state
|
|
},
|
|
onError: (result: any) => {
|
|
Logger.paymentError('snap.payment.error', { orderId, error: result })
|
|
const message = mapErrorToUserMessage(result)
|
|
setError(message)
|
|
onError?.(result)
|
|
},
|
|
onClose: () => {
|
|
Logger.paymentInfo('snap.popup.closed', { orderId })
|
|
// User closed the popup without completing payment
|
|
}
|
|
})
|
|
} else {
|
|
throw new Error('Snap.js not loaded')
|
|
}
|
|
|
|
} catch (e: any) {
|
|
Logger.paymentError('snap.payment.error', { orderId, error: e.message })
|
|
const message = mapErrorToUserMessage(e)
|
|
setError(message)
|
|
onError?.(e)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{error && (
|
|
<Alert title="Pembayaran Gagal">
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
<div className="text-center">
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
Klik tombol di bawah untuk melanjutkan pembayaran dengan Midtrans Snap
|
|
</p>
|
|
|
|
<Button
|
|
onClick={handleSnapPayment}
|
|
disabled={loading}
|
|
className="w-full max-w-xs"
|
|
>
|
|
{loading ? 'Memproses...' : 'Bayar Sekarang'}
|
|
</Button>
|
|
</div>
|
|
|
|
{loading && <LoadingOverlay isLoading={loading} />}
|
|
</div>
|
|
)
|
|
} |