import { NextRequest, NextResponse } from 'next/server' import crypto from 'crypto' interface PayUFailureResponse { status: string txnid: string amount: string productinfo: string firstname: string email: string hash: string key: string error?: string error_Message?: string [key: string]: string | undefined } /** * PayU Payment Failure Handler * Processes failed payment responses from PayU gateway */ export async function POST(request: NextRequest) { try { const formData = await request.formData() const payuResponse: Partial = {} // Extract all PayU response parameters for (const [key, value] of formData.entries()) { payuResponse[key] = value.toString() } const { status, txnid, amount, productinfo, firstname, email, hash, key: merchantKey, error, error_Message } = payuResponse // Log failure details for debugging console.log(`Payment failed - Transaction: ${txnid}, Status: ${status}, Error: ${error || error_Message || 'Unknown'}`) // Validate required parameters if (!txnid) { console.error('Missing transaction ID in failure response') return NextResponse.redirect(new URL('/payment/failed?error=invalid-response', request.url)) } // Verify PayU hash if provided (for security) if (hash && merchantKey && status && amount) { const merchantSalt = process.env.PAYU_MERCHANT_SALT || 'test-salt' const expectedHashString = `${merchantSalt}|${status}|||||||||||${email}|${firstname}|${productinfo}|${amount}|${txnid}|${merchantKey}` const expectedHash = crypto.createHash('sha512').update(expectedHashString).digest('hex').toLowerCase() if (hash.toLowerCase() !== expectedHash) { console.error(`Hash mismatch in failure response for transaction: ${txnid}`) } } try { // TODO: Update database with failed payment status // In a real implementation: // await updateBillingStatus(txnid, 'failed', error || error_Message) // Mock database update for demo await mockUpdatePaymentStatus(txnid as string, 'failed', error || error_Message || 'Payment failed') // Determine failure reason for user-friendly message let failureReason = 'unknown' if (error_Message?.toLowerCase().includes('insufficient')) { failureReason = 'insufficient-funds' } else if (error_Message?.toLowerCase().includes('declined')) { failureReason = 'card-declined' } else if (error_Message?.toLowerCase().includes('timeout')) { failureReason = 'timeout' } else if (error_Message?.toLowerCase().includes('cancelled')) { failureReason = 'user-cancelled' } // Redirect to failure page with transaction details const failureUrl = new URL('/payment/failed', request.url) failureUrl.searchParams.set('txn', txnid as string) failureUrl.searchParams.set('reason', failureReason) if (amount) { failureUrl.searchParams.set('amount', amount as string) } return NextResponse.redirect(failureUrl) } catch (dbError) { console.error('Database update error during failure handling:', dbError) // Continue to failure page even if DB update fails return NextResponse.redirect(new URL('/payment/failed?error=db-error&txn=' + txnid, request.url)) } } catch (error) { console.error('Payment failure handler error:', error) return NextResponse.redirect(new URL('/payment/failed?error=processing-error', request.url)) } } // Mock function for demonstration async function mockUpdatePaymentStatus(txnid: string, status: string, errorMessage: string) { // In real implementation, this would update MongoDB/database console.log(`Mock DB Update: Transaction ${txnid} marked as ${status}`) console.log(`Failure reason: ${errorMessage}`) // Simulate database operations const billingUpdate = { billing_id: txnid, payment_status: status, payment_date: new Date(), error_message: errorMessage, retry_count: 1 // Could track retry attempts } // Mock delay await new Promise(resolve => setTimeout(resolve, 100)) return billingUpdate }