initial commit
This commit is contained in:
183
app/api/admin/billing/route.ts
Normal file
183
app/api/admin/billing/route.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
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 })
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user