ai-wpa/app/api/balance/deduct/route.ts

170 lines
4.7 KiB
TypeScript

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 }
)
}
}