303 lines
10 KiB
TypeScript
303 lines
10 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState, Suspense } from 'react'
|
|
import { useSearchParams, useRouter } from 'next/navigation'
|
|
import { XCircle, RotateCcw, Home, AlertTriangle, HelpCircle, CreditCard } from 'lucide-react'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardContent } from '@/components/ui/card'
|
|
|
|
interface FailureDetails {
|
|
txn?: string
|
|
amount?: string
|
|
type?: string
|
|
reason?: string
|
|
error?: string
|
|
message?: string
|
|
}
|
|
|
|
interface FailureInfo {
|
|
title: string
|
|
description: string
|
|
suggestions: string[]
|
|
icon: typeof XCircle
|
|
color: string
|
|
}
|
|
|
|
const FAILURE_REASONS: Record<string, FailureInfo> = {
|
|
'insufficient-funds': {
|
|
title: 'Insufficient Funds',
|
|
description: 'Your payment method does not have enough balance to complete this transaction.',
|
|
suggestions: [
|
|
'Add money to your payment account',
|
|
'Try a different payment method',
|
|
'Contact your bank if you believe this is an error'
|
|
],
|
|
icon: CreditCard,
|
|
color: 'text-red-500'
|
|
},
|
|
'card-declined': {
|
|
title: 'Card Declined',
|
|
description: 'Your card was declined by the bank or payment processor.',
|
|
suggestions: [
|
|
'Check your card details are correct',
|
|
'Ensure your card is not expired',
|
|
'Try a different card',
|
|
'Contact your bank for more information'
|
|
],
|
|
icon: XCircle,
|
|
color: 'text-red-500'
|
|
},
|
|
'timeout': {
|
|
title: 'Payment Timeout',
|
|
description: 'The payment took too long to process and timed out.',
|
|
suggestions: [
|
|
'Check your internet connection',
|
|
'Try again with a stable connection',
|
|
'Clear your browser cache and cookies'
|
|
],
|
|
icon: AlertTriangle,
|
|
color: 'text-orange-500'
|
|
},
|
|
'user-cancelled': {
|
|
title: 'Payment Cancelled',
|
|
description: 'The payment was cancelled by you during the process.',
|
|
suggestions: [
|
|
'You can try the payment again',
|
|
'Make sure to complete the payment process',
|
|
'Contact support if you need assistance'
|
|
],
|
|
icon: XCircle,
|
|
color: 'text-gray-500'
|
|
},
|
|
'invalid-details': {
|
|
title: 'Invalid Payment Details',
|
|
description: 'The payment details provided were invalid or incorrect.',
|
|
suggestions: [
|
|
'Double-check your card number and CVV',
|
|
'Verify the expiry date is correct',
|
|
'Ensure billing address matches your card'
|
|
],
|
|
icon: AlertTriangle,
|
|
color: 'text-yellow-500'
|
|
},
|
|
'unknown': {
|
|
title: 'Payment Failed',
|
|
description: 'An unexpected error occurred while processing your payment.',
|
|
suggestions: [
|
|
'Try again in a few minutes',
|
|
'Use a different payment method',
|
|
'Contact support if the problem persists'
|
|
],
|
|
icon: HelpCircle,
|
|
color: 'text-gray-500'
|
|
}
|
|
}
|
|
|
|
function PaymentFailedContent() {
|
|
const searchParams = useSearchParams()
|
|
const router = useRouter()
|
|
const [details, setDetails] = useState<FailureDetails>({})
|
|
const [failureInfo, setFailureInfo] = useState<FailureInfo>(FAILURE_REASONS.unknown)
|
|
const [countdown, setCountdown] = useState(15)
|
|
|
|
useEffect(() => {
|
|
// Extract failure details from URL parameters
|
|
const txn = searchParams.get('txn')
|
|
const amount = searchParams.get('amount')
|
|
const type = searchParams.get('type') || 'billing'
|
|
const reason = searchParams.get('reason') || 'unknown'
|
|
const error = searchParams.get('error')
|
|
const message = searchParams.get('message')
|
|
|
|
const failureDetails: FailureDetails = {
|
|
txn: txn || undefined,
|
|
amount: amount || undefined,
|
|
type,
|
|
reason,
|
|
error: error || undefined,
|
|
message: message ? decodeURIComponent(message) : undefined
|
|
}
|
|
|
|
setDetails(failureDetails)
|
|
setFailureInfo(FAILURE_REASONS[reason] || FAILURE_REASONS.unknown)
|
|
|
|
// Auto-redirect countdown
|
|
const timer = setInterval(() => {
|
|
setCountdown((prev) => {
|
|
if (prev <= 1) {
|
|
router.push('/profile')
|
|
return 0
|
|
}
|
|
return prev - 1
|
|
})
|
|
}, 1000)
|
|
|
|
return () => clearInterval(timer)
|
|
}, [searchParams, router])
|
|
|
|
const handleRetryPayment = () => {
|
|
if (details.type === 'balance') {
|
|
router.push('/profile?tab=balance')
|
|
} else {
|
|
router.push('/services')
|
|
}
|
|
}
|
|
|
|
const handleDashboardRedirect = () => {
|
|
router.push('/profile')
|
|
}
|
|
|
|
const handleContactSupport = () => {
|
|
// TODO: Implement support contact functionality
|
|
router.push('/contact')
|
|
}
|
|
|
|
const IconComponent = failureInfo.icon
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-red-50 to-orange-50 dark:from-red-900/10 dark:to-orange-900/10 flex items-center justify-center p-4">
|
|
<Card className="w-full max-w-lg">
|
|
<CardContent className="p-8 text-center">
|
|
{/* Failure Icon */}
|
|
<div className="mb-6">
|
|
<div className="mx-auto w-16 h-16 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center">
|
|
<IconComponent className={`w-10 h-10 ${failureInfo.color}`} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Failure Message */}
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
|
{failureInfo.title}
|
|
</h1>
|
|
|
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
|
{details.message || failureInfo.description}
|
|
</p>
|
|
|
|
{/* Payment Details (if available) */}
|
|
{details.txn && (
|
|
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 mb-6 space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">Transaction ID</span>
|
|
<code className="text-sm font-mono text-gray-900 dark:text-gray-100">{details.txn}</code>
|
|
</div>
|
|
{details.amount && (
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">Amount</span>
|
|
<span className="text-lg font-bold text-gray-900 dark:text-gray-100">
|
|
₹{parseFloat(details.amount).toLocaleString('en-IN', { minimumFractionDigits: 2 })}
|
|
</span>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">Type</span>
|
|
<span className="text-sm capitalize text-gray-900 dark:text-gray-100">
|
|
{details.type === 'balance' ? 'Balance Addition' : 'Service Payment'}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">Date</span>
|
|
<span className="text-sm text-gray-900 dark:text-gray-100">
|
|
{new Date().toLocaleDateString('en-IN', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex flex-col sm:flex-row gap-3 mb-6">
|
|
<Button
|
|
onClick={handleRetryPayment}
|
|
className="flex-1"
|
|
>
|
|
<RotateCcw className="w-4 h-4 mr-2" />
|
|
Try Again
|
|
</Button>
|
|
<Button
|
|
onClick={handleDashboardRedirect}
|
|
variant="outline"
|
|
className="flex-1"
|
|
>
|
|
<Home className="w-4 h-4 mr-2" />
|
|
Dashboard
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Suggestions */}
|
|
<div className="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
|
<h3 className="text-sm font-medium text-blue-900 dark:text-blue-100 mb-2">
|
|
What can you do?
|
|
</h3>
|
|
<ul className="text-sm text-blue-700 dark:text-blue-300 space-y-1 text-left">
|
|
{failureInfo.suggestions.map((suggestion, index) => (
|
|
<li key={index} className="flex items-start">
|
|
<span className="mr-2 mt-0.5">•</span>
|
|
<span>{suggestion}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
{/* Support Section */}
|
|
<div className="mb-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
|
|
Need Help?
|
|
</h3>
|
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
|
If you continue to experience issues, our support team is here to help.
|
|
</p>
|
|
<Button
|
|
onClick={handleContactSupport}
|
|
variant="outline"
|
|
size="sm"
|
|
>
|
|
Contact Support
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Auto-redirect Notice */}
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
Redirecting to dashboard in {countdown} seconds
|
|
</p>
|
|
|
|
{/* Error Details (for debugging, only show in development) */}
|
|
{details.error && process.env.NODE_ENV === 'development' && (
|
|
<details className="mt-4 text-left">
|
|
<summary className="text-sm text-gray-500 cursor-pointer">
|
|
Debug Information
|
|
</summary>
|
|
<pre className="mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto">
|
|
{JSON.stringify({ details, failureInfo }, null, 2)}
|
|
</pre>
|
|
</details>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function PaymentFailedPage() {
|
|
return (
|
|
<Suspense fallback={
|
|
<div className="min-h-screen bg-gradient-to-br from-red-50 to-red-100 dark:from-red-950 dark:to-red-900 flex items-center justify-center p-4">
|
|
<Card className="w-full max-w-md mx-auto">
|
|
<CardContent className="p-8">
|
|
<div className="text-center space-y-4">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-red-600 mx-auto"></div>
|
|
<p className="text-gray-600">Loading...</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
}>
|
|
<PaymentFailedContent />
|
|
</Suspense>
|
|
)
|
|
} |