diff --git a/src/features/payments/components/PaymentSheet.tsx b/src/features/payments/components/PaymentSheet.tsx index 14cecf6..e7eec97 100644 --- a/src/features/payments/components/PaymentSheet.tsx +++ b/src/features/payments/components/PaymentSheet.tsx @@ -25,11 +25,12 @@ 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, children, showStatusCTA = true }: PaymentSheetProps) { +export function PaymentSheet({ merchantName = 'Simaya', orderId, amount, expireAt, customerName, children, showStatusCTA = true }: PaymentSheetProps) { const countdown = useCountdown(expireAt) const [expanded, setExpanded] = React.useState(true) return ( @@ -71,6 +72,7 @@ export function PaymentSheet({ merchantName = 'Simaya', orderId, amount, expireA
Total
{formatCurrencyIDR(amount)}
Order ID #{orderId}
+ {customerName &&
Nama: {customerName}
} )} diff --git a/src/features/payments/snap/SnapPaymentTrigger.tsx b/src/features/payments/snap/SnapPaymentTrigger.tsx index bd4695f..0befb0e 100644 --- a/src/features/payments/snap/SnapPaymentTrigger.tsx +++ b/src/features/payments/snap/SnapPaymentTrigger.tsx @@ -201,18 +201,4 @@ function SnapHostedPayment({ orderId, amount, customer, onSuccess, onError }: Om {loading && } ) -} - -// 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 - } - } } \ No newline at end of file diff --git a/src/lib/snapLoader.ts b/src/lib/snapLoader.ts new file mode 100644 index 0000000..ccb5c20 --- /dev/null +++ b/src/lib/snapLoader.ts @@ -0,0 +1,132 @@ +import { Env } from './env' +import { Logger } from './logger' + +let snapLoaded = false +let snapPromise: Promise | null = null + +/** + * Dynamically loads Midtrans Snap.js script + * Returns a promise that resolves when Snap.js is ready + */ +export function loadSnapScript(): Promise { + // 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 { + 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) + }) +} diff --git a/src/pages/CheckoutPage.tsx b/src/pages/CheckoutPage.tsx index 0f72ed5..ee9554e 100644 --- a/src/pages/CheckoutPage.tsx +++ b/src/pages/CheckoutPage.tsx @@ -1,11 +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 { PaymentMethodList } from '../features/payments/components/PaymentMethodList' -import type { PaymentMethod } from '../features/payments/components/PaymentMethodList' -import { SnapPaymentTrigger } from '../features/payments/snap/SnapPaymentTrigger' import { usePaymentConfig } from '../features/payments/lib/usePaymentConfig' +import type { PaymentMethod } from '../features/payments/components/PaymentMethodList' +import { SnapTokenService } from '../features/payments/snap/SnapTokenService' interface AutoSnapPaymentProps { orderId: string amount: number @@ -15,27 +17,39 @@ interface AutoSnapPaymentProps { onError?: (error: any) => void } -function AutoSnapPayment({ orderId, amount, customer, onChargeInitiated, onSuccess, onError }: AutoSnapPaymentProps) { +function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError }: Omit) { const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState('') - const hasTriggered = React.useRef(false) + 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(() => { - // Only trigger when we have valid orderId and amount - if (!orderId || !amount || hasTriggered.current) return - hasTriggered.current = true + 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 }) - // Import SnapTokenService dynamically to avoid circular deps - const { SnapTokenService } = await import('../features/payments/snap/SnapTokenService') + // 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, @@ -55,45 +69,75 @@ function AutoSnapPayment({ orderId, amount, customer, onChargeInitiated, onSucce }) Logger.paymentInfo('checkout.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' }) + console.log('Token berhasil dibuat:', token) - // Auto-trigger Snap payment popup - if (window.snap && typeof window.snap.pay === 'function') { - 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) - onError?.(result) - }, - onClose: () => { - Logger.paymentInfo('checkout.auto.snap.popup.closed', { orderId }) - } - }) - } else { - throw new Error('Snap.js not loaded') + // 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 }) - const message = 'Gagal memuat pembayaran. Silakan refresh halaman.' - setError(message) + 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) - } finally { setLoading(false) } } // Small delay to ensure UI is rendered + console.log('Setting timeout to call triggerPayment in 500ms...') const timer = setTimeout(triggerPayment, 500) - return () => clearTimeout(timer) - }, [orderId, amount, customer, onChargeInitiated, onSuccess, onError]) + 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) { @@ -111,7 +155,13 @@ function AutoSnapPayment({ orderId, amount, customer, onChargeInitiated, onSucce
{error && ( - {error} +
+

{error}

+
+ Detail Error +
{JSON.stringify({ orderId, amount, customer }, null, 2)}
+
+
)} @@ -121,11 +171,17 @@ function AutoSnapPayment({ orderId, amount, customer, onChargeInitiated, onSucce

Menyiapkan pembayaran...

- ) : ( -

- Membuka halaman pembayaran Midtrans... -

- )} + ) : error ? ( +
+

Gagal memuat pembayaran

+ +
+ ) : null} ) @@ -146,7 +202,6 @@ export function CheckoutPage() { const amount = 3500000 const expireAt = Date.now() + 59 * 60 * 1000 + 32 * 1000 // 00:59:32 const [selected, setSelected] = React.useState(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 }>({ @@ -188,7 +243,7 @@ export function CheckoutPage() { )} - + {/* Wizard 2 langkah: Step 1 (Form Dummy) → Step 2 (Payment - Snap/Core auto-detect) */} {currentStep === 1 && (
@@ -254,6 +309,11 @@ export function CheckoutPage() { {currentStep === 2 && (
+ {(() => { + console.log('Rendering step 2 - AutoSnapPayment', { orderId, amount, currentStep }) + Logger.info('checkout.step2.render', { orderId, amount }) + return null + })()} setLocked(true)} onSuccess={(result) => { Logger.info('checkout.payment.success', { orderId, result }) // Handle successful payment @@ -281,13 +340,4 @@ export function CheckoutPage() {
) -} -function defaultEnabled(): Record { - 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, - } } \ No newline at end of file diff --git a/src/pages/PayPage.tsx b/src/pages/PayPage.tsx index 1f0ffba..ff2ded5 100644 --- a/src/pages/PayPage.tsx +++ b/src/pages/PayPage.tsx @@ -8,6 +8,9 @@ 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 @@ -16,30 +19,37 @@ 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, onChargeInitiated, onSuccess, onError }: AutoSnapPaymentProps) { +function AutoSnapPayment({ orderId, amount, customer, onSuccess, onError }: AutoSnapPaymentProps) { const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState('') - const hasTriggered = React.useRef(false) + 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(() => { - // Only trigger when we have valid orderId and amount - if (!orderId || !amount || hasTriggered.current) return - hasTriggered.current = true + 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('') - console.log('[PayPage] Auto-triggering Snap payment:', { orderId, amount, customer }) + Logger.paymentInfo('paypage.auto.snap.init', { orderId, amount, customer }) - // Import SnapTokenService dynamically to avoid circular deps - const { SnapTokenService } = await import('../features/payments/snap/SnapTokenService') + // 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({ @@ -60,46 +70,68 @@ function AutoSnapPayment({ orderId, amount, customer, onChargeInitiated, onSucce }] }) - console.log('[PayPage] Snap token received:', token.substring(0, 10) + '...') + Logger.paymentInfo('paypage.auto.snap.token.received', { orderId, token: token.substring(0, 10) + '...' }) + console.log('[PayPage] Token received:', token) - // Auto-trigger Snap payment popup - if (window.snap && typeof window.snap.pay === 'function') { - window.snap.pay(token, { - onSuccess: (result: any) => { - console.log('[PayPage] Payment success:', result) - onSuccess?.(result) - }, - onPending: (result: any) => { - console.log('[PayPage] Payment pending:', result) - }, - onError: (result: any) => { - console.error('[PayPage] Payment error:', result) - const message = 'Pembayaran gagal. Silakan coba lagi.' - setError(message) - onError?.(result) - }, - onClose: () => { - console.log('[PayPage] Snap popup closed') - } - }) - } else { - throw new Error('Snap.js not loaded') + 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) { - console.error('[PayPage] Auto-payment error:', e.message) - const message = 'Gagal memuat pembayaran. Silakan refresh halaman.' - setError(message) + 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) - } finally { setLoading(false) } } - // Small delay to ensure UI is rendered + console.log('[PayPage] Setting timeout') const timer = setTimeout(triggerPayment, 500) return () => clearTimeout(timer) - }, [orderId, amount, customer, onChargeInitiated, onSuccess, onError]) + }, [orderId, amount, customer, paymentTriggered, onSuccess, onError]) // Don't render anything until we have valid data if (!orderId || !amount) { @@ -127,11 +159,17 @@ function AutoSnapPayment({ orderId, amount, customer, onChargeInitiated, onSucce

Menyiapkan pembayaran...

- ) : ( -

- Membuka halaman pembayaran Midtrans... -

- )} + ) : error ? ( +
+

Gagal memuat pembayaran

+ +
+ ) : null} ) @@ -146,9 +184,8 @@ export function PayPage() { const [selectedMethod] = useState(null) const [locked, setLocked] = useState(false) const [customer, setCustomer] = useState<{ name?: string; phone?: string; email?: string } | undefined>(undefined) - const [allowedMethods, setAllowedMethods] = useState(undefined) const [error, setError] = useState<{ code?: string; message?: string } | null>(null) - const { data: runtimeCfg } = usePaymentConfig() + usePaymentConfig() const currentStep = 2 useEffect(() => { @@ -162,7 +199,6 @@ 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 { @@ -237,12 +273,10 @@ export function PayPage() { orderId={orderId} amount={amount} customer={customer} - onChargeInitiated={() => { - lockOrder(orderId) - setLocked(true) - }} onSuccess={(result) => { console.log('[PayPage] Payment success:', result) + lockOrder(orderId) + setLocked(true) nav.toStatus(orderId, selectedMethod || undefined) }} onError={(error) => { diff --git a/src/services/api.ts b/src/services/api.ts index 1420c05..ef8dc65 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -53,7 +53,15 @@ api.interceptors.response.use( const url = error.config?.url || '' const status = error.response?.status const fullUrl = `${baseURL}${url}` - Logger.error('api.error', { baseURL, url, fullUrl, status, message: error.message }) + 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 + }) throw error } ) diff --git a/src/types/snap.d.ts b/src/types/snap.d.ts new file mode 100644 index 0000000..830568b --- /dev/null +++ b/src/types/snap.d.ts @@ -0,0 +1,17 @@ +// 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 +} diff --git a/test-create-payment-link.cjs b/test-create-payment-link.cjs new file mode 100644 index 0000000..378d9ab --- /dev/null +++ b/test-create-payment-link.cjs @@ -0,0 +1,34 @@ +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(); diff --git a/test-frontend-payload.cjs b/test-frontend-payload.cjs new file mode 100644 index 0000000..e369ab7 --- /dev/null +++ b/test-frontend-payload.cjs @@ -0,0 +1,35 @@ +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(); \ No newline at end of file diff --git a/test-snap-token.cjs b/test-snap-token.cjs new file mode 100644 index 0000000..87daf13 --- /dev/null +++ b/test-snap-token.cjs @@ -0,0 +1,34 @@ +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(); \ No newline at end of file diff --git a/tmp-createtransaksi.json b/tmp-createtransaksi.json index 49d2134..c937e8d 100644 --- a/tmp-createtransaksi.json +++ b/tmp-createtransaksi.json @@ -1,12 +1,12 @@ { - "mercant_id": "REFNO-001", - "timestamp": 1731300000000, + "mercant_id": "REFNO-002", + "timestamp": 1733283600000, "deskripsi": "Bayar Internet", "nominal": 200000, - "nama": "Demo User", + "nama": "Demo User 2", "no_telepon": "081234567890", - "email": "demo@example.com", + "email": "demo2@example.com", "item": [ - { "item_id": "TKG-2511131", "nama": "Internet", "harga": 200000, "qty": 1 } + { "item_id": "TKG-2512041", "nama": "Internet Desember", "harga": 200000, "qty": 1 } ] }