184 lines
5.6 KiB
TypeScript
184 lines
5.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { withAdminAuth } from '@/lib/admin-middleware'
|
|
import { connectDB } from '@/lib/mongodb'
|
|
import { Billing } from '@/models/billing'
|
|
import { Transaction } from '@/models/transaction'
|
|
import { z } from 'zod'
|
|
|
|
const BillingUpdateSchema = z.object({
|
|
payment_status: z.enum(['pending', 'completed', 'failed', 'refunded']).optional(),
|
|
service_status: z.enum(['active', 'inactive', 'suspended', 'cancelled']).optional(),
|
|
amount: z.number().min(0).optional(),
|
|
notes: z.string().optional(),
|
|
})
|
|
|
|
export async function GET(request: NextRequest) {
|
|
return withAdminAuth(request, async (req, admin) => {
|
|
try {
|
|
await connectDB()
|
|
|
|
const { searchParams } = new URL(request.url)
|
|
const page = parseInt(searchParams.get('page') || '1')
|
|
const limit = parseInt(searchParams.get('limit') || '20')
|
|
const search = searchParams.get('search') || ''
|
|
const serviceType = searchParams.get('serviceType') || ''
|
|
const paymentStatus = searchParams.get('paymentStatus') || ''
|
|
const dateFrom = searchParams.get('dateFrom')
|
|
const dateTo = searchParams.get('dateTo')
|
|
|
|
const skip = (page - 1) * limit
|
|
|
|
// Build filter query
|
|
const filter: any = {}
|
|
|
|
if (search) {
|
|
filter.$or = [
|
|
{ user_email: { $regex: search, $options: 'i' } },
|
|
{ silicon_id: { $regex: search, $options: 'i' } },
|
|
{ service_name: { $regex: search, $options: 'i' } },
|
|
]
|
|
}
|
|
|
|
if (serviceType && serviceType !== 'all') {
|
|
filter.service_type = serviceType
|
|
}
|
|
|
|
if (paymentStatus && paymentStatus !== 'all') {
|
|
filter.payment_status = paymentStatus
|
|
}
|
|
|
|
if (dateFrom || dateTo) {
|
|
filter.created_at = {}
|
|
if (dateFrom) filter.created_at.$gte = new Date(dateFrom)
|
|
if (dateTo) filter.created_at.$lte = new Date(dateTo)
|
|
}
|
|
|
|
const [billings, totalBillings] = await Promise.all([
|
|
Billing.find(filter).sort({ created_at: -1 }).skip(skip).limit(limit),
|
|
Billing.countDocuments(filter),
|
|
])
|
|
|
|
const totalPages = Math.ceil(totalBillings / limit)
|
|
|
|
// Calculate summary statistics for current filter
|
|
const summaryStats = await Billing.aggregate([
|
|
{ $match: filter },
|
|
{
|
|
$group: {
|
|
_id: null,
|
|
totalAmount: { $sum: '$amount' },
|
|
totalTax: { $sum: '$tax_amount' },
|
|
totalDiscount: { $sum: '$discount_applied' },
|
|
completedCount: {
|
|
$sum: { $cond: [{ $eq: ['$payment_status', 'completed'] }, 1, 0] },
|
|
},
|
|
pendingCount: {
|
|
$sum: { $cond: [{ $eq: ['$payment_status', 'pending'] }, 1, 0] },
|
|
},
|
|
failedCount: {
|
|
$sum: { $cond: [{ $eq: ['$payment_status', 'failed'] }, 1, 0] },
|
|
},
|
|
},
|
|
},
|
|
])
|
|
|
|
return NextResponse.json({
|
|
billings,
|
|
pagination: {
|
|
currentPage: page,
|
|
totalPages,
|
|
totalBillings,
|
|
hasNext: page < totalPages,
|
|
hasPrev: page > 1,
|
|
},
|
|
summary: summaryStats[0] || {
|
|
totalAmount: 0,
|
|
totalTax: 0,
|
|
totalDiscount: 0,
|
|
completedCount: 0,
|
|
pendingCount: 0,
|
|
failedCount: 0,
|
|
},
|
|
})
|
|
} catch (error) {
|
|
console.error('Admin billing fetch error:', error)
|
|
return NextResponse.json({ error: 'Failed to fetch billing data' }, { status: 500 })
|
|
}
|
|
})
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
return withAdminAuth(request, async (req, admin) => {
|
|
try {
|
|
await connectDB()
|
|
|
|
const body = await request.json()
|
|
const { action, billingId, data } = body
|
|
|
|
if (action === 'update') {
|
|
const validatedData = BillingUpdateSchema.parse(data)
|
|
|
|
const billing = await Billing.findByIdAndUpdate(
|
|
billingId,
|
|
{
|
|
...validatedData,
|
|
updated_at: new Date(),
|
|
},
|
|
{ new: true, runValidators: true }
|
|
)
|
|
|
|
if (!billing) {
|
|
return NextResponse.json({ error: 'Billing record not found' }, { status: 404 })
|
|
}
|
|
|
|
return NextResponse.json({ billing })
|
|
}
|
|
|
|
if (action === 'refund') {
|
|
const billing = await Billing.findById(billingId)
|
|
|
|
if (!billing) {
|
|
return NextResponse.json({ error: 'Billing record not found' }, { status: 404 })
|
|
}
|
|
|
|
if (billing.payment_status !== 'paid') {
|
|
return NextResponse.json({ error: 'Can only refund paid payments' }, { status: 400 })
|
|
}
|
|
|
|
// Update billing status
|
|
billing.payment_status = 'refunded'
|
|
billing.status = 'cancelled'
|
|
await billing.save()
|
|
|
|
// Create refund transaction
|
|
const refundTransaction = new Transaction({
|
|
userId: billing.user_id,
|
|
type: 'credit',
|
|
amount: billing.amount,
|
|
description: `Refund for ${billing.service_name}`,
|
|
status: 'completed',
|
|
reference: `refund_${billing._id}`,
|
|
metadata: {
|
|
originalBillingId: billing._id,
|
|
refundedBy: admin.id,
|
|
refundReason: data.refundReason || 'Admin refund',
|
|
},
|
|
})
|
|
|
|
await refundTransaction.save()
|
|
|
|
return NextResponse.json({
|
|
billing,
|
|
transaction: refundTransaction,
|
|
message: 'Refund processed successfully',
|
|
})
|
|
}
|
|
|
|
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
|
} catch (error) {
|
|
console.error('Admin billing action error:', error)
|
|
return NextResponse.json({ error: 'Failed to perform billing action' }, { status: 500 })
|
|
}
|
|
})
|
|
}
|