import { Billing, IBilling, BillingSchema } from '@/models/billing' import { Transaction } from '@/models/transaction' import { User as UserModel } from '@/models/user' import connectDB from '@/lib/mongodb' // Utility function to generate unique billing ID function toBase62(num: number): string { const base62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' let encoded = '' let n = num while (n > 0) { encoded = base62[n % 62] + encoded n = Math.floor(n / 62) } return encoded } export function generateBillingId(): string { const epochStart = new Date('2020-01-01').getTime() const now = Date.now() const timestamp = (now - epochStart) % 1000000000000 const randomNum = Math.floor(Math.random() * 100) const finalNum = timestamp * 100 + randomNum return `bill_${toBase62(finalNum).padStart(8, '0')}` } export function generateTransactionId(serviceType: string): string { const timestamp = Date.now() const randomStr = Math.random().toString(36).substr(2, 9) return `txn_${serviceType}_${timestamp}_${randomStr}` } // Interface for creating billing records export interface CreateBillingParams { user: { id: string email: string siliconId?: string } service: { name: string type: | 'vps' | 'kubernetes' | 'developer_hire' | 'vpn' | 'hosting' | 'storage' | 'database' | 'ai_service' | 'custom' id?: string clusterId?: string instanceId?: string config?: Record } billing: { amount: number currency?: 'INR' | 'USD' cycle: 'onetime' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly' discountApplied?: number taxAmount?: number autoRenew?: boolean billingPeriodStart?: Date billingPeriodEnd?: Date nextBillingDate?: Date } payment: { transactionId?: string status: 'pending' | 'paid' | 'failed' | 'refunded' } status: { service: 'pending' | 'active' | 'completed' | 'cancelled' | 'failed' | 'refunded' serviceStatus: 0 | 1 | 2 // 0: inactive, 1: active, 2: suspended } metadata?: { remarks?: string deploymentSuccess?: boolean deploymentResponse?: any userAgent?: string ip?: string [key: string]: any } } export class BillingService { /** * Create a comprehensive billing record for any service */ static async createBillingRecord(params: CreateBillingParams): Promise { await connectDB() const billingId = generateBillingId() const totalAmount = params.billing.amount + (params.billing.taxAmount || 0) - (params.billing.discountApplied || 0) // Validate the billing data const billingData = { billing_id: billingId, service: params.service.name, service_type: params.service.type, amount: params.billing.amount, currency: params.billing.currency || 'INR', user_email: params.user.email, silicon_id: params.user.siliconId || params.user.id, user_id: params.user.id, status: params.status.service, payment_status: params.payment.status, cycle: params.billing.cycle, service_id: params.service.id, cluster_id: params.service.clusterId, instance_id: params.service.instanceId, service_name: params.service.name, service_config: params.service.config, billing_period_start: params.billing.billingPeriodStart, billing_period_end: params.billing.billingPeriodEnd, next_billing_date: params.billing.nextBillingDate, auto_renew: params.billing.autoRenew || false, discount_applied: params.billing.discountApplied || 0, tax_amount: params.billing.taxAmount || 0, total_amount: totalAmount, transaction_id: params.payment.transactionId, remarks: params.metadata?.remarks, metadata: params.metadata, service_status: params.status.serviceStatus, created_by: params.user.email, } // Create billing record directly without Zod validation to avoid field conflicts // The Mongoose schema will handle validation const newBilling = new Billing(billingData) return await newBilling.save() } /** * Update billing record status */ static async updateBillingStatus( billingId: string, updates: { status?: 'pending' | 'active' | 'completed' | 'cancelled' | 'failed' | 'refunded' paymentStatus?: 'pending' | 'paid' | 'failed' | 'refunded' serviceStatus?: 0 | 1 | 2 remarks?: string updatedBy?: string } ): Promise { await connectDB() const updateData: any = {} if (updates.status) updateData.status = updates.status if (updates.paymentStatus) updateData.payment_status = updates.paymentStatus if (updates.serviceStatus !== undefined) updateData.service_status = updates.serviceStatus if (updates.remarks) updateData.remarks = updates.remarks if (updates.updatedBy) updateData.updated_by = updates.updatedBy return await Billing.findOneAndUpdate( { billing_id: billingId }, { $set: updateData }, { new: true } ) } /** * Get billing records for a user */ static async getUserBillings( userEmail: string, siliconId: string, options?: { serviceType?: string status?: string limit?: number offset?: number } ): Promise { await connectDB() const query: any = { $or: [{ user_email: userEmail }, { silicon_id: siliconId }], } if (options?.serviceType) { query.service_type = options.serviceType } if (options?.status) { query.status = options.status } let billingQuery = Billing.find(query).sort({ createdAt: -1 }) if (options?.limit) { billingQuery = billingQuery.limit(options.limit) } if (options?.offset) { billingQuery = billingQuery.skip(options.offset) } return await billingQuery.exec() } /** * Get active services for a user */ static async getActiveServices(userEmail: string, siliconId: string): Promise { await connectDB() return await (Billing as any).findActiveServices(userEmail, siliconId) } /** * Process service deployment billing (deduct balance + create billing record) */ static async processServiceDeployment(params: { user: { id: string; email: string; siliconId?: string } service: { name: string type: | 'vps' | 'kubernetes' | 'developer_hire' | 'vpn' | 'hosting' | 'storage' | 'database' | 'ai_service' | 'custom' id?: string clusterId?: string instanceId?: string config?: Record } amount: number currency?: 'INR' | 'USD' cycle?: 'onetime' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly' deploymentSuccess: boolean deploymentResponse?: any metadata?: Record }): Promise<{ billing: IBilling transaction?: any balanceUpdated: boolean }> { await connectDB() // Get user data and check balance const userData = await UserModel.findOne({ email: params.user.email }) if (!userData) { throw new Error('User not found') } const currentBalance = userData.balance || 0 let balanceUpdated = false let transactionRecord = null // Deduct balance if deployment was successful and amount > 0 if (params.deploymentSuccess && params.amount > 0) { if (currentBalance < params.amount) { throw new Error( `Insufficient balance. Required: ₹${params.amount}, Available: ₹${currentBalance}` ) } const newBalance = currentBalance - params.amount await UserModel.updateOne({ email: params.user.email }, { $set: { balance: newBalance } }) balanceUpdated = true // Create transaction record const transactionId = generateTransactionId(params.service.type) try { const newTransaction = new Transaction({ transactionId, userId: params.user.id, email: params.user.email, type: 'debit', amount: params.amount, service: params.service.name, serviceId: params.service.id || params.service.clusterId || params.service.instanceId, description: `Payment for ${params.service.name} - ${params.service.type}`, status: 'completed', previousBalance: currentBalance, newBalance, metadata: { serviceType: params.service.type, deploymentSuccess: params.deploymentSuccess, ...params.metadata, }, }) transactionRecord = await newTransaction.save() } catch (transactionError) { console.error('Failed to create transaction record:', transactionError) // Continue with billing creation even if transaction fails } } // Create billing record const billing = await this.createBillingRecord({ user: params.user, service: params.service, billing: { amount: params.amount, currency: params.currency || 'INR', cycle: params.cycle || 'onetime', }, payment: { transactionId: transactionRecord?.transactionId, status: params.deploymentSuccess && params.amount > 0 ? 'paid' : 'pending', }, status: { service: params.deploymentSuccess ? 'active' : 'failed', serviceStatus: params.deploymentSuccess ? 1 : 0, }, metadata: { remarks: params.deploymentSuccess ? 'Deployment Success' : 'Deployment Failed', deploymentSuccess: params.deploymentSuccess, deploymentResponse: params.deploymentResponse, ...params.metadata, }, }) return { billing, transaction: transactionRecord, balanceUpdated, } } /** * Get billing statistics for a user */ static async getBillingStats( userEmail: string, siliconId: string ): Promise<{ totalSpent: number activeServices: number totalServices: number serviceBreakdown: Record }> { await connectDB() const billings = await this.getUserBillings(userEmail, siliconId) const stats = { totalSpent: 0, activeServices: 0, totalServices: billings.length, serviceBreakdown: {} as Record, } billings.forEach((billing) => { stats.totalSpent += billing.total_amount || billing.amount if (billing.status === 'active' && billing.service_status === 1) { stats.activeServices++ } const serviceType = billing.service_type if (!stats.serviceBreakdown[serviceType]) { stats.serviceBreakdown[serviceType] = { count: 0, amount: 0 } } stats.serviceBreakdown[serviceType].count++ stats.serviceBreakdown[serviceType].amount += billing.total_amount || billing.amount }) return stats } } export default BillingService