initial commit
This commit is contained in:
237
app/api/balance/add/route.ts
Normal file
237
app/api/balance/add/route.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import crypto from 'crypto'
|
||||
|
||||
interface AddBalanceRequest {
|
||||
amount: number
|
||||
currency: 'INR' | 'USD'
|
||||
}
|
||||
|
||||
interface UserTokenPayload {
|
||||
siliconId: string
|
||||
email: string
|
||||
type: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Balance API
|
||||
* Initiates "Add Balance" transaction for user accounts
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Verify user authentication
|
||||
const token = request.cookies.get('accessToken')?.value
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Authentication required' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const secret = process.env.JWT_SECRET || 'your-secret-key'
|
||||
const user = jwt.verify(token, secret) as UserTokenPayload
|
||||
|
||||
// Parse request body
|
||||
const body: AddBalanceRequest = await request.json()
|
||||
const { amount, currency } = body
|
||||
|
||||
// Validate input
|
||||
if (!amount || amount <= 0 || !['INR', 'USD'].includes(currency)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Invalid amount or currency' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Validate amount limits
|
||||
const minAmount = currency === 'INR' ? 100 : 2 // Minimum ₹100 or $2
|
||||
const maxAmount = currency === 'INR' ? 100000 : 1500 // Maximum ₹1,00,000 or $1500
|
||||
|
||||
if (amount < minAmount || amount > maxAmount) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: `Amount must be between ${currency === 'INR' ? '₹' : '$'}${minAmount} and ${currency === 'INR' ? '₹' : '$'}${maxAmount}`,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate unique transaction ID
|
||||
const transactionId = generateTransactionId()
|
||||
|
||||
// TODO: Save transaction to database
|
||||
// In real implementation:
|
||||
// await saveAddBalanceTransaction({
|
||||
// siliconId: user.siliconId,
|
||||
// amount,
|
||||
// currency,
|
||||
// transaction_id: transactionId,
|
||||
// status: 'pending'
|
||||
// })
|
||||
|
||||
// Mock database save for demo
|
||||
await mockSaveAddBalanceTransaction(user.siliconId, amount, currency, transactionId)
|
||||
|
||||
// PayU configuration
|
||||
const merchantKey = process.env.PAYU_MERCHANT_KEY || 'test-key'
|
||||
const merchantSalt = process.env.PAYU_MERCHANT_SALT || 'test-salt'
|
||||
const payuUrl = process.env.PAYU_URL || 'https://test.payu.in/_payment'
|
||||
|
||||
// Prepare payment data
|
||||
const productinfo = 'add_balance'
|
||||
const firstname = 'Customer'
|
||||
const email = user.email
|
||||
const phone = '9876543210' // Default phone or fetch from user profile
|
||||
|
||||
// Success and failure URLs for balance transactions
|
||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:4023'
|
||||
const surl = `${baseUrl}/api/balance/success`
|
||||
const furl = `${baseUrl}/api/balance/failure`
|
||||
|
||||
// Generate PayU hash
|
||||
const hashString = `${merchantKey}|${transactionId}|${amount}|${productinfo}|${firstname}|${email}|||||||||||${merchantSalt}`
|
||||
const hash = crypto.createHash('sha512').update(hashString).digest('hex')
|
||||
|
||||
// Return payment form data for frontend submission
|
||||
const paymentData = {
|
||||
success: true,
|
||||
transaction_id: transactionId,
|
||||
currency,
|
||||
payment_url: payuUrl,
|
||||
form_data: {
|
||||
key: merchantKey,
|
||||
txnid: transactionId,
|
||||
amount: amount.toFixed(2),
|
||||
productinfo,
|
||||
firstname,
|
||||
email,
|
||||
phone,
|
||||
surl,
|
||||
furl,
|
||||
hash,
|
||||
service_provider: 'payu_paisa',
|
||||
},
|
||||
}
|
||||
|
||||
return NextResponse.json(paymentData)
|
||||
} catch (dbError) {
|
||||
console.error('Database error during add balance:', dbError)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Failed to initiate balance transaction' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Add balance error:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Add balance initiation failed' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Add Balance History
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Verify user authentication
|
||||
const token = request.cookies.get('accessToken')?.value
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Authentication required' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const secret = process.env.JWT_SECRET || 'your-secret-key'
|
||||
const user = jwt.verify(token, secret) as UserTokenPayload
|
||||
|
||||
// TODO: Fetch balance history from database
|
||||
// In real implementation:
|
||||
// const history = await getBalanceHistory(user.siliconId)
|
||||
|
||||
// Mock balance history for demo
|
||||
const mockHistory = await getMockBalanceHistory(user.siliconId)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
balance_history: mockHistory,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Get balance history error:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Failed to fetch balance history' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function generateTransactionId(): string {
|
||||
const timestamp = Date.now().toString(36)
|
||||
const random = Math.random().toString(36).substr(2, 5)
|
||||
return `bal_${timestamp}${random}`.toUpperCase()
|
||||
}
|
||||
|
||||
// Mock functions for demonstration
|
||||
async function mockSaveAddBalanceTransaction(
|
||||
siliconId: string,
|
||||
amount: number,
|
||||
currency: string,
|
||||
transactionId: string
|
||||
) {
|
||||
console.log(`Mock DB Save: Add Balance Transaction`)
|
||||
console.log(`SiliconID: ${siliconId}, Amount: ${amount} ${currency}, TxnID: ${transactionId}`)
|
||||
|
||||
// Mock database record
|
||||
const transaction = {
|
||||
id: Date.now(),
|
||||
silicon_id: siliconId,
|
||||
transaction_id: transactionId,
|
||||
amount,
|
||||
currency,
|
||||
status: 'pending',
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
}
|
||||
|
||||
// Mock delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
return transaction
|
||||
}
|
||||
|
||||
async function getMockBalanceHistory(siliconId: string) {
|
||||
// Mock balance transaction history
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
transaction_id: 'BAL_123ABC',
|
||||
amount: 1000,
|
||||
currency: 'INR',
|
||||
status: 'success',
|
||||
created_at: '2025-01-15T10:30:00Z',
|
||||
updated_at: '2025-01-15T10:31:00Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
transaction_id: 'BAL_456DEF',
|
||||
amount: 500,
|
||||
currency: 'INR',
|
||||
status: 'failed',
|
||||
created_at: '2025-01-10T14:20:00Z',
|
||||
updated_at: '2025-01-10T14:21:00Z',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
transaction_id: 'BAL_789GHI',
|
||||
amount: 2000,
|
||||
currency: 'INR',
|
||||
status: 'success',
|
||||
created_at: '2025-01-05T09:15:00Z',
|
||||
updated_at: '2025-01-05T09:16:00Z',
|
||||
},
|
||||
]
|
||||
}
|
||||
169
app/api/balance/deduct/route.ts
Normal file
169
app/api/balance/deduct/route.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import connectDB from '@/lib/mongodb'
|
||||
import { User as UserModel } from '@/models/user'
|
||||
import { Transaction } from '@/models/transaction'
|
||||
|
||||
// Schema for balance deduction request
|
||||
const DeductBalanceSchema = z.object({
|
||||
amount: z.number().positive('Amount must be positive'),
|
||||
service: z.string().min(1, 'Service name is required'),
|
||||
serviceId: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
transactionId: z.string().optional(),
|
||||
})
|
||||
|
||||
// Schema for balance deduction response
|
||||
const DeductBalanceResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z
|
||||
.object({
|
||||
transactionId: z.string(),
|
||||
previousBalance: z.number(),
|
||||
newBalance: z.number(),
|
||||
amountDeducted: z.number(),
|
||||
service: z.string(),
|
||||
timestamp: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
error: z
|
||||
.object({
|
||||
message: z.string(),
|
||||
code: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Authenticate user
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'UNAUTHORIZED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
await connectDB()
|
||||
|
||||
// Parse and validate request body
|
||||
const body = await request.json()
|
||||
const validatedData = DeductBalanceSchema.parse(body)
|
||||
|
||||
// Get user's current balance from database
|
||||
const userData = await UserModel.findOne({ email: user.email })
|
||||
if (!userData) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'User not found', code: 'USER_NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const currentBalance = userData.balance || 0
|
||||
|
||||
// Check if user has sufficient balance
|
||||
if (currentBalance < validatedData.amount) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
message: `Insufficient balance. Required: ₹${validatedData.amount}, Available: ₹${currentBalance}`,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate new balance
|
||||
const newBalance = currentBalance - validatedData.amount
|
||||
|
||||
// Update user balance in database
|
||||
await UserModel.updateOne(
|
||||
{ email: user.email },
|
||||
{
|
||||
$set: { balance: newBalance },
|
||||
$inc: { __v: 1 }, // Increment version for optimistic locking
|
||||
}
|
||||
)
|
||||
|
||||
// Generate transaction ID if not provided
|
||||
const transactionId =
|
||||
validatedData.transactionId || `txn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
// Save transaction record to database
|
||||
try {
|
||||
const newTransaction = new Transaction({
|
||||
transactionId,
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
type: 'debit',
|
||||
amount: validatedData.amount,
|
||||
service: validatedData.service,
|
||||
serviceId: validatedData.serviceId,
|
||||
description: validatedData.description || `Payment for ${validatedData.service}`,
|
||||
status: 'completed',
|
||||
previousBalance: currentBalance,
|
||||
newBalance,
|
||||
metadata: {
|
||||
userAgent: request.headers.get('user-agent'),
|
||||
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
|
||||
},
|
||||
})
|
||||
|
||||
await newTransaction.save()
|
||||
console.log('Transaction record saved:', transactionId)
|
||||
} catch (transactionError) {
|
||||
console.error('Failed to save transaction record:', transactionError)
|
||||
// Continue even if transaction record fails - balance was already deducted
|
||||
}
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
data: {
|
||||
transactionId,
|
||||
previousBalance: currentBalance,
|
||||
newBalance,
|
||||
amountDeducted: validatedData.amount,
|
||||
service: validatedData.service,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
|
||||
// Validate response format
|
||||
const validatedResponse = DeductBalanceResponseSchema.parse(responseData)
|
||||
return NextResponse.json(validatedResponse, { status: 200 })
|
||||
} catch (error) {
|
||||
console.error('Balance deduction error:', error)
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Invalid request data',
|
||||
code: 'VALIDATION_ERROR',
|
||||
details: error.issues,
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to deduct balance', code: 'INTERNAL_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
147
app/api/balance/failure/route.ts
Normal file
147
app/api/balance/failure/route.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import crypto from 'crypto'
|
||||
|
||||
interface PayUBalanceFailureResponse {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance Addition Failure Handler
|
||||
* Processes failed "Add Balance" payments from PayU gateway
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const formData = await request.formData()
|
||||
const payuResponse: Partial<PayUBalanceFailureResponse> = {}
|
||||
|
||||
// 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(`Balance addition failed - Transaction: ${txnid}, Status: ${status}, Error: ${error || error_Message || 'Unknown'}`)
|
||||
|
||||
// Validate required parameters
|
||||
if (!txnid) {
|
||||
console.error('Missing transaction ID in balance failure response')
|
||||
return NextResponse.redirect(new URL('/payment/failed?error=invalid-response&type=balance', 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 balance failure response for transaction: ${txnid}`)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Update database with failed balance transaction
|
||||
// In real implementation:
|
||||
// await updateAddBalanceStatus(txnid, 'failed', error || error_Message)
|
||||
|
||||
// Mock database update for demo
|
||||
await mockUpdateBalanceFailure(txnid as string, error || error_Message || 'Payment failed')
|
||||
|
||||
// Determine failure reason for user-friendly message
|
||||
let failureReason = 'unknown'
|
||||
let userMessage = 'Your balance addition failed. Please try again.'
|
||||
|
||||
if (error_Message?.toLowerCase().includes('insufficient')) {
|
||||
failureReason = 'insufficient-funds'
|
||||
userMessage = 'Insufficient funds in your payment method.'
|
||||
} else if (error_Message?.toLowerCase().includes('declined')) {
|
||||
failureReason = 'card-declined'
|
||||
userMessage = 'Your card was declined. Please try a different payment method.'
|
||||
} else if (error_Message?.toLowerCase().includes('timeout')) {
|
||||
failureReason = 'timeout'
|
||||
userMessage = 'Payment timed out. Please try again.'
|
||||
} else if (error_Message?.toLowerCase().includes('cancelled')) {
|
||||
failureReason = 'user-cancelled'
|
||||
userMessage = 'Payment was cancelled.'
|
||||
} else if (error_Message?.toLowerCase().includes('invalid')) {
|
||||
failureReason = 'invalid-details'
|
||||
userMessage = 'Invalid payment details. Please check and try again.'
|
||||
}
|
||||
|
||||
// Redirect to profile page with failure message
|
||||
const failureUrl = new URL('/profile', request.url)
|
||||
failureUrl.searchParams.set('payment', 'failed')
|
||||
failureUrl.searchParams.set('type', 'balance')
|
||||
failureUrl.searchParams.set('reason', failureReason)
|
||||
failureUrl.searchParams.set('txn', txnid as string)
|
||||
failureUrl.searchParams.set('message', encodeURIComponent(userMessage))
|
||||
if (amount) {
|
||||
failureUrl.searchParams.set('amount', amount as string)
|
||||
}
|
||||
|
||||
return NextResponse.redirect(failureUrl)
|
||||
|
||||
} catch (dbError) {
|
||||
console.error('Database update error during balance failure handling:', dbError)
|
||||
// Continue to failure page even if DB update fails
|
||||
const failureUrl = new URL('/profile', request.url)
|
||||
failureUrl.searchParams.set('payment', 'failed')
|
||||
failureUrl.searchParams.set('type', 'balance')
|
||||
failureUrl.searchParams.set('error', 'db-error')
|
||||
failureUrl.searchParams.set('txn', txnid as string)
|
||||
|
||||
return NextResponse.redirect(failureUrl)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Balance failure handler error:', error)
|
||||
return NextResponse.redirect(new URL('/profile?payment=failed&type=balance&error=processing-error', request.url))
|
||||
}
|
||||
}
|
||||
|
||||
// Mock function for demonstration
|
||||
async function mockUpdateBalanceFailure(txnid: string, errorMessage: string) {
|
||||
console.log(`Mock DB Update: Balance Transaction ${txnid} failed`)
|
||||
console.log(`Failure reason: ${errorMessage}`)
|
||||
|
||||
// Mock: Update add_balance_history status
|
||||
const historyUpdate = {
|
||||
transaction_id: txnid,
|
||||
status: 'failed',
|
||||
error_message: errorMessage,
|
||||
updated_at: new Date(),
|
||||
retry_count: 1, // Could track retry attempts
|
||||
payment_gateway_response: {
|
||||
status: 'failed',
|
||||
error: errorMessage,
|
||||
failed_at: new Date()
|
||||
}
|
||||
}
|
||||
|
||||
// Mock: Could also track failed attempt statistics
|
||||
const analytics = {
|
||||
failed_balance_additions: 1,
|
||||
common_failure_reasons: {
|
||||
[errorMessage]: 1
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Mock: Balance failure recorded for analysis')
|
||||
|
||||
// Mock delay
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
return { historyUpdate, analytics }
|
||||
}
|
||||
132
app/api/balance/success/route.ts
Normal file
132
app/api/balance/success/route.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import crypto from 'crypto'
|
||||
|
||||
interface PayUBalanceResponse {
|
||||
status: string
|
||||
txnid: string
|
||||
amount: string
|
||||
productinfo: string
|
||||
firstname: string
|
||||
email: string
|
||||
hash: string
|
||||
key: string
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance Addition Success Handler
|
||||
* Processes successful "Add Balance" payments from PayU gateway
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const formData = await request.formData()
|
||||
const payuResponse: Partial<PayUBalanceResponse> = {}
|
||||
|
||||
// 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 } = payuResponse
|
||||
|
||||
// Validate required parameters
|
||||
if (!status || !txnid || !amount || !hash) {
|
||||
console.error('Missing required PayU parameters in balance success')
|
||||
return NextResponse.redirect(new URL('/payment/failed?error=invalid-response', request.url))
|
||||
}
|
||||
|
||||
// Verify payment status
|
||||
if (status !== 'success') {
|
||||
console.log(`Balance payment failed for transaction: ${txnid}, status: ${status}`)
|
||||
return NextResponse.redirect(new URL('/payment/failed?txn=' + txnid + '&type=balance', request.url))
|
||||
}
|
||||
|
||||
// Verify PayU hash for security
|
||||
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 for balance transaction: ${txnid}`)
|
||||
return NextResponse.redirect(new URL('/payment/failed?error=hash-mismatch&type=balance', request.url))
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Update database with successful balance addition
|
||||
// In real implementation:
|
||||
// const siliconId = await getSiliconIdFromTransaction(txnid)
|
||||
// await updateAddBalanceStatus(txnid, 'success')
|
||||
// await incrementUserBalance(siliconId, parseFloat(amount))
|
||||
|
||||
console.log(`Balance addition successful for transaction: ${txnid}, amount: ₹${amount}`)
|
||||
|
||||
// Mock database update for demo
|
||||
const updateResult = await mockUpdateBalanceSuccess(txnid as string, parseFloat(amount as string))
|
||||
|
||||
// Redirect to profile page with success message
|
||||
const successUrl = new URL('/profile', request.url)
|
||||
successUrl.searchParams.set('payment', 'success')
|
||||
successUrl.searchParams.set('type', 'balance')
|
||||
successUrl.searchParams.set('amount', amount as string)
|
||||
successUrl.searchParams.set('txn', txnid as string)
|
||||
|
||||
return NextResponse.redirect(successUrl)
|
||||
|
||||
} catch (dbError) {
|
||||
console.error('Database update error during balance success:', dbError)
|
||||
// Log for manual reconciliation - payment was successful at gateway
|
||||
console.error(`CRITICAL: Balance payment ${txnid} succeeded at PayU but DB update failed`)
|
||||
|
||||
// Still redirect to success but with warning
|
||||
const successUrl = new URL('/profile', request.url)
|
||||
successUrl.searchParams.set('payment', 'success')
|
||||
successUrl.searchParams.set('type', 'balance')
|
||||
successUrl.searchParams.set('amount', amount as string)
|
||||
successUrl.searchParams.set('txn', txnid as string)
|
||||
successUrl.searchParams.set('warning', 'db-update-failed')
|
||||
|
||||
return NextResponse.redirect(successUrl)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Balance success handler error:', error)
|
||||
return NextResponse.redirect(new URL('/payment/failed?error=processing-error&type=balance', request.url))
|
||||
}
|
||||
}
|
||||
|
||||
// Mock function for demonstration
|
||||
async function mockUpdateBalanceSuccess(txnid: string, amount: number) {
|
||||
console.log(`Mock DB Update: Balance Transaction ${txnid} successful`)
|
||||
|
||||
// Mock: Get user Silicon ID from transaction
|
||||
const mockSiliconId = 'USR_12345' // In real implementation, fetch from add_balance_history table
|
||||
|
||||
// Mock: Update add_balance_history status
|
||||
const historyUpdate = {
|
||||
transaction_id: txnid,
|
||||
status: 'success',
|
||||
updated_at: new Date(),
|
||||
payment_gateway_response: {
|
||||
amount: amount,
|
||||
currency: 'INR',
|
||||
payment_date: new Date()
|
||||
}
|
||||
}
|
||||
|
||||
// Mock: Update user balance
|
||||
const balanceUpdate = {
|
||||
silicon_id: mockSiliconId,
|
||||
balance_increment: amount,
|
||||
previous_balance: 5000, // Mock previous balance
|
||||
new_balance: 5000 + amount,
|
||||
updated_at: new Date()
|
||||
}
|
||||
|
||||
console.log(`Mock: User ${mockSiliconId} balance increased by ₹${amount}`)
|
||||
console.log(`Mock: New balance: ₹${balanceUpdate.new_balance}`)
|
||||
|
||||
// Mock delay
|
||||
await new Promise(resolve => setTimeout(resolve, 150))
|
||||
|
||||
return { historyUpdate, balanceUpdate }
|
||||
}
|
||||
Reference in New Issue
Block a user