Compare commits

..

No commits in common. "c6225e3d35f42cc324bc6a7c6561f520d173525e" and "4bca71aeb3ef52924650d6538e624915064b76b5" have entirely different histories.

11 changed files with 50 additions and 627 deletions

View File

@ -25,12 +25,11 @@ export interface PaymentSheetProps {
orderId: string
amount: number
expireAt: number // epoch ms
customerName?: string
children?: React.ReactNode
showStatusCTA?: boolean
}
export function PaymentSheet({ merchantName = 'Simaya', orderId, amount, expireAt, customerName, children, showStatusCTA = true }: PaymentSheetProps) {
export function PaymentSheet({ merchantName = 'Simaya', orderId, amount, expireAt, children, showStatusCTA = true }: PaymentSheetProps) {
const countdown = useCountdown(expireAt)
const [expanded, setExpanded] = React.useState(true)
return (
@ -72,7 +71,6 @@ export function PaymentSheet({ merchantName = 'Simaya', orderId, amount, expireA
<div className="text-xs text-black">Total</div>
<div className="text-xl font-semibold">{formatCurrencyIDR(amount)}</div>
<div className="text-xs text-black/60">Order ID #{orderId}</div>
{customerName && <div className="text-xs text-black/60 mt-1">Nama: {customerName}</div>}
</div>
</div>
)}

View File

@ -202,3 +202,17 @@ function SnapHostedPayment({ orderId, amount, customer, onSuccess, onError }: Om
</div>
)
}
// Type declaration for window.snap
declare global {
interface Window {
snap?: {
pay: (token: string, options: {
onSuccess: (result: any) => void
onPending: (result: any) => void
onError: (result: any) => void
onClose: () => void
}) => void
}
}
}

View File

@ -1,132 +0,0 @@
import { Env } from './env'
import { Logger } from './logger'
let snapLoaded = false
let snapPromise: Promise<void> | null = null
/**
* Dynamically loads Midtrans Snap.js script
* Returns a promise that resolves when Snap.js is ready
*/
export function loadSnapScript(): Promise<void> {
// Return existing promise if already loading
if (snapPromise) {
return snapPromise
}
// Already loaded
if (snapLoaded && window.snap) {
return Promise.resolve()
}
// Start loading
snapPromise = new Promise((resolve, reject) => {
try {
const clientKey = Env.MIDTRANS_CLIENT_KEY
const midtransEnv = Env.MIDTRANS_ENV || 'sandbox'
if (!clientKey) {
const error = 'MIDTRANS_CLIENT_KEY not configured'
Logger.error('snap.load.error', { error })
reject(new Error(error))
return
}
// Determine Snap.js URL based on environment
const snapUrl = midtransEnv === 'production'
? 'https://app.midtrans.com/snap/snap.js'
: 'https://app.sandbox.midtrans.com/snap/snap.js'
Logger.info('snap.load.start', { snapUrl, clientKey: clientKey.substring(0, 10) + '...' })
// Check if script already exists
const existingScript = document.querySelector(`script[src="${snapUrl}"]`)
if (existingScript) {
Logger.info('snap.load.exists', { snapUrl })
// Wait a bit and check if window.snap is available
setTimeout(() => {
if (window.snap) {
snapLoaded = true
Logger.info('snap.load.ready', { hasSnap: true })
resolve()
} else {
Logger.error('snap.load.error', { error: 'Script loaded but window.snap not available' })
reject(new Error('Snap.js loaded but window.snap not available'))
}
}, 500)
return
}
// Create script element
const script = document.createElement('script')
script.src = snapUrl
script.setAttribute('data-client-key', clientKey)
script.onload = () => {
Logger.info('snap.script.loaded', { snapUrl })
console.log('Snap.js script loaded, waiting for initialization...')
// Wait a bit for Snap to initialize
setTimeout(() => {
console.log('After 500ms delay - window.snap:', window.snap)
console.log('After 500ms delay - window.snap?.pay:', window.snap?.pay)
if (window.snap && typeof window.snap.pay === 'function') {
snapLoaded = true
Logger.info('snap.load.success', { hasSnap: true, hasPay: true })
console.log('✓ Snap.js ready!')
resolve()
} else {
const error = 'Snap.js loaded but window.snap.pay not available'
Logger.error('snap.load.error', { error, hasSnap: !!window.snap })
console.error('✗ Snap.js error:', error, { hasSnap: !!window.snap, snapObj: window.snap })
reject(new Error(error))
}
}, 500)
}
script.onerror = (error) => {
Logger.error('snap.script.error', { error, snapUrl })
reject(new Error('Failed to load Snap.js script'))
}
// Append script to document
document.head.appendChild(script)
Logger.info('snap.script.appended', { snapUrl })
} catch (error: any) {
Logger.error('snap.load.exception', { error: error.message })
reject(error)
}
})
return snapPromise
}
/**
* Check if Snap.js is already loaded and ready
*/
export function isSnapReady(): boolean {
return snapLoaded && !!window.snap && typeof window.snap.pay === 'function'
}
/**
* Wait for Snap.js to be ready, with timeout
*/
export function waitForSnap(timeoutMs: number = 5000): Promise<void> {
return new Promise((resolve, reject) => {
if (isSnapReady()) {
resolve()
return
}
const startTime = Date.now()
const checkInterval = setInterval(() => {
if (isSnapReady()) {
clearInterval(checkInterval)
resolve()
} else if (Date.now() - startTime > timeoutMs) {
clearInterval(checkInterval)
reject(new Error(`Snap.js not ready after ${timeoutMs}ms`))
}
}, 100)
})
}

View File

@ -1,191 +1,13 @@
import React from 'react'
import { Alert } from '../components/alert/Alert'
import { Button } from '../components/ui/button'
import { Env } from '../lib/env'
import { Logger } from '../lib/logger'
import { loadSnapScript } from '../lib/snapLoader'
import { PaymentSheet } from '../features/payments/components/PaymentSheet'
import { usePaymentConfig } from '../features/payments/lib/usePaymentConfig'
import { PaymentMethodList } from '../features/payments/components/PaymentMethodList'
import type { PaymentMethod } from '../features/payments/components/PaymentMethodList'
import { SnapTokenService } from '../features/payments/snap/SnapTokenService'
interface AutoSnapPaymentProps {
orderId: string
amount: number
customer?: { name?: string; phone?: string; email?: string }
onChargeInitiated?: () => void
onSuccess?: (result: any) => void
onError?: (error: any) => void
}
function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError }: Omit<AutoSnapPaymentProps, 'onChargeInitiated'>) {
const [loading, setLoading] = React.useState(false)
const [error, setError] = React.useState('')
const [paymentTriggered, setPaymentTriggered] = React.useState(false)
// Debug log immediately on component mount
console.log('AutoSnapPayment mounted with:', { orderId, amount, customer })
Logger.info('autosnapPayment.mount', { orderId, amount, hasCustomer: !!customer })
React.useEffect(() => {
console.log('AutoSnapPayment useEffect triggered', { orderId, amount, paymentTriggered })
// Only trigger when we have valid orderId and amount and not already triggered
if (!orderId || !amount || paymentTriggered) {
console.log('AutoSnapPayment useEffect early return', { hasOrderId: !!orderId, hasAmount: !!amount, alreadyTriggered: paymentTriggered })
return
}
const triggerPayment = async () => {
console.log('triggerPayment function called!')
setPaymentTriggered(true) // Mark as triggered immediately
try {
setLoading(true)
setError('')
Logger.paymentInfo('checkout.auto.snap.init', { orderId, amount, customer })
// Load Snap.js first
Logger.paymentInfo('checkout.auto.snap.loading_script', { orderId })
await loadSnapScript()
Logger.paymentInfo('checkout.auto.snap.script_loaded', { orderId, hasSnap: !!window.snap })
// Create Snap transaction token
Logger.paymentInfo('checkout.auto.snap.calling_api', { orderId, amount })
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('checkout.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' })
console.log('Token berhasil dibuat:', token)
// Verify Snap.js is loaded
console.log('window.snap:', window.snap)
console.log('window.snap.pay:', window.snap?.pay)
console.log('typeof window.snap?.pay:', typeof window.snap?.pay)
if (!window.snap || typeof window.snap.pay !== 'function') {
const errorMsg = `Snap.js not properly loaded: hasSnap=${!!window.snap}, hasPay=${typeof window.snap?.pay}`
console.error(errorMsg)
throw new Error(errorMsg)
}
// Auto-trigger Snap payment popup
console.log('Memanggil window.snap.pay dengan token:', token.substring(0, 20) + '...')
console.log('Full token:', token)
setLoading(false) // Stop loading indicator before showing modal
window.snap.pay(token, {
onSuccess: (result: any) => {
Logger.paymentInfo('checkout.auto.snap.payment.success', { orderId, transactionId: result.transaction_id })
onSuccess?.(result)
},
onPending: (result: any) => {
Logger.paymentInfo('checkout.auto.snap.payment.pending', { orderId, transactionId: result.transaction_id })
},
onError: (result: any) => {
Logger.paymentError('checkout.auto.snap.payment.error', { orderId, error: result })
const message = 'Pembayaran gagal. Silakan coba lagi.'
setError(message)
setLoading(false)
onError?.(result)
},
onClose: () => {
Logger.paymentInfo('checkout.auto.snap.popup.closed', { orderId })
setLoading(false)
}
})
} catch (e: any) {
Logger.paymentError('checkout.auto.snap.payment.error', { orderId, error: e.message, stack: e.stack })
console.error('Error membuat token Snap:', e)
// Handle specific error: order_id already taken
const errorMessage = e.response?.data?.message || e.message || ''
const isOrderTaken = errorMessage.includes('already been taken') ||
errorMessage.includes('order_id has already been taken')
if (isOrderTaken) {
const message = 'Order ID sudah digunakan. Pembayaran untuk order ini sudah dibuat. Silakan cek halaman status pembayaran.'
setError(message)
} else {
const message = e.response?.data?.message || e.message || 'Gagal memuat pembayaran. Silakan refresh halaman.'
setError(message)
}
onError?.(e)
setLoading(false)
}
}
// Small delay to ensure UI is rendered
console.log('Setting timeout to call triggerPayment in 500ms...')
const timer = setTimeout(triggerPayment, 500)
return () => {
console.log('Cleanup: clearing timeout')
clearTimeout(timer)
}
}, [orderId, amount, customer, paymentTriggered, onSuccess, onError])
// Don't render anything until we have valid data
if (!orderId || !amount) {
return (
<div className="text-center">
<div className="space-y-2">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent mx-auto"></div>
<p className="text-sm text-gray-600">Memuat data pembayaran...</p>
</div>
</div>
)
}
return (
<div className="space-y-4">
{error && (
<Alert title="Pembayaran Gagal">
<div className="space-y-2">
<p>{error}</p>
<details className="text-xs">
<summary className="cursor-pointer">Detail Error</summary>
<pre className="mt-2 bg-gray-100 p-2 rounded overflow-auto">{JSON.stringify({ orderId, amount, customer }, null, 2)}</pre>
</details>
</div>
</Alert>
)}
<div className="text-center">
{loading ? (
<div className="space-y-2">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent mx-auto"></div>
<p className="text-sm text-gray-600">Menyiapkan pembayaran...</p>
</div>
) : error ? (
<div className="space-y-2">
<p className="text-sm text-red-600">Gagal memuat pembayaran</p>
<button
onClick={() => window.location.reload()}
className="text-sm text-blue-600 underline"
>
Coba lagi
</button>
</div>
) : null}
</div>
</div>
)
}
import { SnapPaymentTrigger } from '../features/payments/snap/SnapPaymentTrigger'
import { usePaymentConfig } from '../features/payments/lib/usePaymentConfig'
import { Logger } from '../lib/logger'
import React from 'react'
export function CheckoutPage() {
const apiBase = Env.API_BASE_URL
@ -202,6 +24,7 @@ export function CheckoutPage() {
const amount = 3500000
const expireAt = Date.now() + 59 * 60 * 1000 + 32 * 1000 // 00:59:32
const [selected, setSelected] = React.useState<PaymentMethod | null>(null)
const [locked, setLocked] = React.useState(false)
const [currentStep, setCurrentStep] = React.useState<1 | 2>(1)
const [isBusy, setIsBusy] = React.useState(false)
const [form, setForm] = React.useState<{ name: string; contact: string; address: string; notes: string }>({
@ -243,7 +66,7 @@ export function CheckoutPage() {
</Alert>
)}
<PaymentSheet merchantName="Simaya" orderId={orderId} amount={amount} expireAt={expireAt} customerName={form.name} showStatusCTA={currentStep === 2}>
<PaymentSheet merchantName="Simaya" orderId={orderId} amount={amount} expireAt={expireAt} showStatusCTA={currentStep === 2}>
{/* Wizard 2 langkah: Step 1 (Form Dummy) → Step 2 (Payment - Snap/Core auto-detect) */}
{currentStep === 1 && (
<div className="space-y-3">
@ -309,12 +132,7 @@ export function CheckoutPage() {
{currentStep === 2 && (
<div className="space-y-3" aria-live="polite">
{(() => {
console.log('Rendering step 2 - AutoSnapPayment', { orderId, amount, currentStep })
Logger.info('checkout.step2.render', { orderId, amount })
return null
})()}
<AutoSnapPayment
<SnapPaymentTrigger
orderId={orderId}
amount={amount}
customer={{
@ -322,6 +140,8 @@ export function CheckoutPage() {
email: form.contact.includes('@') ? form.contact : undefined,
phone: !form.contact.includes('@') ? form.contact : undefined
}}
paymentMethod={'bank_transfer'}
onChargeInitiated={() => setLocked(true)}
onSuccess={(result) => {
Logger.info('checkout.payment.success', { orderId, result })
// Handle successful payment
@ -341,3 +161,12 @@ export function CheckoutPage() {
</div>
)
}
function defaultEnabled(): Record<PaymentMethod, boolean> {
return {
bank_transfer: Env.ENABLE_BANK_TRANSFER,
credit_card: Env.ENABLE_CREDIT_CARD,
gopay: Env.ENABLE_GOPAY,
cstore: Env.ENABLE_CSTORE,
cpay: Env.ENABLE_CPAY,
}
}

View File

@ -2,179 +2,16 @@ import { useEffect, useMemo, useState } from 'react'
import { useParams } from 'react-router-dom'
import { PaymentSheet } from '../features/payments/components/PaymentSheet'
import type { PaymentMethod } from '../features/payments/components/PaymentMethodList'
import { SnapPaymentTrigger } from '../features/payments/snap/SnapPaymentTrigger'
import { usePaymentConfig } from '../features/payments/lib/usePaymentConfig'
import { Alert } from '../components/alert/Alert'
import { Button } from '../components/ui/button'
import { getPaymentLinkPayload } from '../services/api'
import { isOrderLocked, lockOrder } from '../features/payments/lib/chargeLock'
import { usePaymentNavigation } from '../features/payments/lib/navigation'
import { Logger } from '../lib/logger'
import { loadSnapScript } from '../lib/snapLoader'
import { SnapTokenService } from '../features/payments/snap/SnapTokenService'
import React from 'react'
type Method = PaymentMethod | null
interface AutoSnapPaymentProps {
orderId: string
amount: number
customer?: { name?: string; phone?: string; email?: string }
onSuccess?: (result: any) => void
onError?: (error: any) => void
}
function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError }: AutoSnapPaymentProps) {
const [loading, setLoading] = React.useState(false)
const [error, setError] = React.useState('')
const [paymentTriggered, setPaymentTriggered] = React.useState(false)
console.log('[PayPage] AutoSnapPayment mounted:', { orderId, amount, customer })
Logger.info('paypage.autosnapPayment.mount', { orderId, amount, hasCustomer: !!customer })
React.useEffect(() => {
console.log('[PayPage] useEffect triggered', { orderId, amount, paymentTriggered })
if (!orderId || !amount || paymentTriggered) {
console.log('[PayPage] Early return', { hasOrderId: !!orderId, hasAmount: !!amount, alreadyTriggered: paymentTriggered })
return
}
const triggerPayment = async () => {
console.log('[PayPage] triggerPayment called')
setPaymentTriggered(true)
try {
setLoading(true)
setError('')
Logger.paymentInfo('paypage.auto.snap.init', { orderId, amount, customer })
// Load Snap.js first
await loadSnapScript()
Logger.paymentInfo('paypage.auto.snap.script_loaded', { orderId, hasSnap: !!window.snap })
// Create Snap transaction token
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('paypage.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' })
console.log('[PayPage] Token received:', token)
if (!window.snap || typeof window.snap.pay !== 'function') {
throw new Error(`Snap.js not loaded: hasSnap=${!!window.snap}`)
}
console.log('[PayPage] Calling window.snap.pay')
setLoading(false)
window.snap.pay(token, {
onSuccess: (result: any) => {
Logger.paymentInfo('paypage.auto.snap.payment.success', { orderId, transactionId: result.transaction_id })
onSuccess?.(result)
},
onPending: (result: any) => {
Logger.paymentInfo('paypage.auto.snap.payment.pending', { orderId, transactionId: result.transaction_id })
},
onError: (result: any) => {
Logger.paymentError('paypage.auto.snap.payment.error', { orderId, error: result })
setError('Pembayaran gagal. Silakan coba lagi.')
setLoading(false)
onError?.(result)
},
onClose: () => {
Logger.paymentInfo('paypage.auto.snap.popup.closed', { orderId })
setLoading(false)
}
})
} catch (e: any) {
Logger.paymentError('paypage.auto.snap.payment.error', { orderId, error: e.message })
console.error('[PayPage] Error:', e)
// Handle specific error: order_id already taken (payment already exists)
const errorMessage = e.response?.data?.message || e.message || ''
const isOrderTaken = errorMessage.includes('already been taken') ||
errorMessage.includes('order_id has already been taken')
if (isOrderTaken) {
// Order already has payment, redirect to status page
Logger.paymentInfo('paypage.order.already_exists', { orderId })
console.log('[PayPage] Order already has payment, redirecting to status...')
// Show message briefly then redirect
setError('Pembayaran untuk order ini sudah dibuat. Mengalihkan ke halaman status...')
setTimeout(() => {
window.location.href = `/payments/${orderId}/status`
}, 2000)
} else {
setError(e.response?.data?.message || e.message || 'Gagal memuat pembayaran')
}
onError?.(e)
setLoading(false)
}
}
console.log('[PayPage] Setting timeout')
const timer = setTimeout(triggerPayment, 500)
return () => clearTimeout(timer)
}, [orderId, amount, customer, paymentTriggered, onSuccess, onError])
// Don't render anything until we have valid data
if (!orderId || !amount) {
return (
<div className="text-center">
<div className="space-y-2">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent mx-auto"></div>
<p className="text-sm text-gray-600">Memuat data pembayaran...</p>
</div>
</div>
)
}
return (
<div className="space-y-4">
{error && (
<Alert title="Pembayaran Gagal">
{error}
</Alert>
)}
<div className="text-center">
{loading ? (
<div className="space-y-2">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent mx-auto"></div>
<p className="text-sm text-gray-600">Menyiapkan pembayaran...</p>
</div>
) : error ? (
<div className="space-y-2">
<p className="text-sm text-red-600">Gagal memuat pembayaran</p>
<button
onClick={() => window.location.reload()}
className="text-sm text-blue-600 underline"
>
Coba lagi
</button>
</div>
) : null}
</div>
</div>
)
}
export function PayPage() {
const { token } = useParams()
const nav = usePaymentNavigation()
@ -184,8 +21,9 @@ export function PayPage() {
const [selectedMethod] = useState<Method>(null)
const [locked, setLocked] = useState<boolean>(false)
const [customer, setCustomer] = useState<{ name?: string; phone?: string; email?: string } | undefined>(undefined)
const [allowedMethods, setAllowedMethods] = useState<string[] | undefined>(undefined)
const [error, setError] = useState<{ code?: string; message?: string } | null>(null)
usePaymentConfig()
const { data: runtimeCfg } = usePaymentConfig()
const currentStep = 2
useEffect(() => {
@ -199,6 +37,7 @@ export function PayPage() {
setAmount(payload.nominal)
setExpireAt(payload.expire_at ?? Date.now() + 24 * 60 * 60 * 1000)
setCustomer(payload.customer)
setAllowedMethods(payload.allowed_methods)
setError(null)
if (isOrderLocked(payload.order_id)) setLocked(true)
} catch {
@ -269,14 +108,17 @@ export function PayPage() {
)}
{currentStep === 2 && (
<div className="space-y-3" aria-live="polite">
<AutoSnapPayment
<SnapPaymentTrigger
orderId={orderId}
amount={amount}
customer={customer}
onSuccess={(result) => {
console.log('[PayPage] Payment success:', result)
paymentMethod={selectedMethod || 'bank_transfer'}
onChargeInitiated={() => {
lockOrder(orderId)
setLocked(true)
}}
onSuccess={(result) => {
console.log('[PayPage] Payment success:', result)
nav.toStatus(orderId, selectedMethod || undefined)
}}
onError={(error) => {

View File

@ -53,15 +53,7 @@ api.interceptors.response.use(
const url = error.config?.url || ''
const status = error.response?.status
const fullUrl = `${baseURL}${url}`
const responseData = error.response?.data
Logger.error('api.error', { baseURL, url, fullUrl, status, message: error.message, responseData })
console.error('API Error:', {
fullUrl,
status,
message: error.message,
responseData,
config: error.config
})
Logger.error('api.error', { baseURL, url, fullUrl, status, message: error.message })
throw error
}
)

17
src/types/snap.d.ts vendored
View File

@ -1,17 +0,0 @@
// Midtrans Snap.js type definitions
interface SnapPaymentOptions {
onSuccess?: (result: any) => void
onPending?: (result: any) => void
onError?: (result: any) => void
onClose?: () => void
}
interface Snap {
pay: (token: string, options?: SnapPaymentOptions) => void
hide: () => void
show: () => void
}
interface Window {
snap?: Snap
}

View File

@ -1,34 +0,0 @@
const axios = require('axios');
const fs = require('fs');
async function createPaymentLink() {
// Read file and remove BOM if present
let jsonContent = fs.readFileSync('c:/laragon/www/core-midtrans-cifo/tmp-createtransaksi.json', 'utf8');
// Remove BOM
if (jsonContent.charCodeAt(0) === 0xFEFF) {
jsonContent = jsonContent.slice(1);
}
const payload = JSON.parse(jsonContent);
try {
console.log('Creating payment link...');
console.log('Payload:', JSON.stringify(payload, null, 2));
const response = await axios.post('http://localhost:8000/createtransaksi', payload, {
headers: {
'Content-Type': 'application/json',
'X-API-KEY': 'dev-key'
}
});
console.log('\n✓ Success!');
console.log('Response:', JSON.stringify(response.data, null, 2));
console.log('\n🔗 Payment URL:', response.data.data.url);
} catch (error) {
console.log('✗ Error:', error.response?.status, error.response?.data);
console.log('Full error:', error.message);
}
}
createPaymentLink();

View File

@ -1,35 +0,0 @@
const axios = require('axios');
async function testFrontendPayload() {
// Simulate the exact payload sent from CheckoutPage.tsx AutoSnapPayment
const payload = {
transaction_details: {
order_id: 'order-1733280000000-12345', // example orderId
gross_amount: 3500000
},
customer_details: {
first_name: 'Demo User',
email: 'demo@example.com',
phone: undefined // as sent from frontend when contact is email
},
item_details: [{
id: 'order-1733280000000-12345',
name: 'Payment',
price: 3500000,
quantity: 1
}]
};
try {
console.log('Testing frontend-like payload...');
console.log('Payload:', JSON.stringify(payload, null, 2));
const response = await axios.post('http://localhost:8000/api/payments/snap/token', payload);
console.log('Success:', response.data);
} catch (error) {
console.log('Error:', error.response?.status, error.response?.data);
console.log('Full error:', error.message);
}
}
testFrontendPayload();

View File

@ -1,34 +0,0 @@
const axios = require('axios');
async function testSnapToken() {
const payload = {
transaction_details: {
order_id: 'test-order-123',
gross_amount: 100000
},
customer_details: {
first_name: 'Test User',
email: 'test@example.com',
phone: '08123456789'
},
item_details: [{
id: 'test-order-123',
name: 'Test Payment',
price: 100000,
quantity: 1
}]
};
try {
console.log('Testing Snap token creation...');
console.log('Payload:', JSON.stringify(payload, null, 2));
const response = await axios.post('http://localhost:8000/api/payments/snap/token', payload);
console.log('Success:', response.data);
} catch (error) {
console.log('Error:', error.response?.status, error.response?.data);
console.log('Full error:', error.message);
}
}
testSnapToken();

View File

@ -1,12 +1,12 @@
{
"mercant_id": "REFNO-002",
"timestamp": 1733283600000,
"mercant_id": "REFNO-001",
"timestamp": 1731300000000,
"deskripsi": "Bayar Internet",
"nominal": 200000,
"nama": "Demo User 2",
"nama": "Demo User",
"no_telepon": "081234567890",
"email": "demo2@example.com",
"email": "demo@example.com",
"item": [
{ "item_id": "TKG-2512041", "nama": "Internet Desember", "harga": 200000, "qty": 1 }
{ "item_id": "TKG-2511131", "nama": "Internet", "harga": 200000, "qty": 1 }
]
}