initial commit

This commit is contained in:
Kar k1
2025-08-30 18:18:57 +05:30
commit 7219108342
270 changed files with 70221 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
import { NextRequest, NextResponse } from 'next/server'
import { authMiddleware } from '@/lib/auth-middleware'
import connectDB from '@/lib/mongodb'
import { User as UserModel } from '@/models/user'
import BillingService from '@/lib/billing-service'
import { checkServiceAvailability } from '@/lib/system-settings'
// VPC subnet mapping based on datacenter
const VPC_SUBNET_MAP: { [key: string]: string } = {
inmumbaizone2: '3889f7ca-ca19-4851-abc7-5c6b4798b3fe', // Mumbai public subnet
inbangalore: 'c17032c9-3cfd-4028-8f2a-f3f5aa8c2976', // Bangalore public subnet
innoida: '95c60b59-4925-4b7e-bd05-afbf940d8000', // Delhi/Noida public subnet
defra1: '72ea201d-e1e7-4d30-a186-bc709efafad8', // Frankfurt public subnet
uslosangeles: '0e253cd1-8ecc-4b65-ae0f-6acbc53b0fb6', // Los Angeles public subnet
inbangalore3: 'f687a58b-04f0-4ebe-b583-65788a1d18bf', // Bangalore DC3 public subnet
}
export async function POST(request: NextRequest) {
try {
// Check if VPS deployment service is enabled
if (!(await checkServiceAvailability('vps'))) {
return NextResponse.json(
{
status: 'error',
message: 'VPS deployment service is currently disabled by administrator',
},
{ status: 503 }
)
}
// Check authentication using your auth middleware
const user = await authMiddleware(request)
if (!user) {
return NextResponse.json(
{ status: 'error', message: 'Unauthorized: Please login to continue' },
{ status: 401 }
)
}
// Get input data from request
const input = await request.json()
if (!input) {
return NextResponse.json({ status: 'error', message: 'Invalid JSON' }, { status: 400 })
}
// Get API key from environment
const UTHO_API_KEY = process.env.UTHO_API_KEY
if (!UTHO_API_KEY) {
return NextResponse.json(
{ status: 'error', message: 'API configuration missing' },
{ status: 500 }
)
}
// Connect to MongoDB
await connectDB()
// Get user data for billing
const userData = await UserModel.findOne({ email: user.email })
if (!userData) {
return NextResponse.json({ status: 'error', message: 'User not found' }, { status: 404 })
}
const requiredAmount = input.amount || 0
// Check balance only if amount > 0 (some services might be free)
if (requiredAmount > 0) {
const currentBalance = userData.balance || 0
if (currentBalance < requiredAmount) {
return NextResponse.json(
{
status: 'error',
message: `Insufficient balance. Required: ₹${requiredAmount}, Available: ₹${currentBalance}`,
code: 'INSUFFICIENT_BALANCE',
},
{ status: 400 }
)
}
}
// Get VPC subnet based on datacenter
const vpcSubnet = VPC_SUBNET_MAP[input.dclocation] || VPC_SUBNET_MAP['inbangalore']
// Prepare Utho payload - use the exact format from your PHP code
const uthoPayload = {
dcslug: input.dclocation,
planid: input.planid,
billingcycle: 'hourly',
auth: 'option2',
enable_publicip: input.publicip !== false,
subnetRequired: false,
firewall: '23434645',
cpumodel: 'intel',
enablebackup: input.backup || false,
root_password: input.password,
support: 'unmanaged',
vpc: vpcSubnet,
cloud: [
{
hostname: input.hostname,
},
],
image: input.image,
sshkeys: '',
}
console.log('Sending to Utho API:', uthoPayload)
// Make API request to Utho
const UTHO_API_URL = 'https://api.utho.com/v2/cloud/deploy'
const response = await fetch(UTHO_API_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${UTHO_API_KEY}`,
'Content-Type': 'application/json',
Accept: '*/*',
},
body: JSON.stringify(uthoPayload),
})
const httpCode = response.status
const deploymentSuccess = httpCode >= 200 && httpCode < 300
const responseData = await response.json()
// Add status field like PHP does
responseData.status = deploymentSuccess ? 'success' : 'error'
console.log('Utho API response:', { httpCode, responseData })
// Process billing using the new comprehensive billing service
try {
const billingResult = await BillingService.processServiceDeployment({
user: {
id: user.id,
email: user.email,
siliconId: userData.siliconId,
},
service: {
name: 'VPS Server',
type: 'vps',
id: responseData.server_id || responseData.id,
instanceId: responseData.server_id || responseData.id,
config: {
hostname: input.hostname,
planid: input.planid,
dclocation: input.dclocation,
image: input.image,
backup: input.backup,
publicip: input.publicip,
},
},
amount: requiredAmount,
currency: 'INR',
cycle: (input.cycle as any) || 'onetime',
deploymentSuccess,
deploymentResponse: responseData,
metadata: {
userAgent: request.headers.get('user-agent'),
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
uthoPayload,
httpCode,
},
})
console.log('Billing processed:', {
billingId: billingResult.billing.billing_id,
transactionId: billingResult.transaction?.transactionId,
balanceUpdated: billingResult.balanceUpdated,
})
} catch (billingError) {
console.error('Billing processing failed:', billingError)
// Continue even if billing fails, but return error if it's balance-related
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
return NextResponse.json(
{
status: 'error',
message: billingError.message,
code: 'INSUFFICIENT_BALANCE',
},
{ status: 400 }
)
}
}
// Return the exact same response format as PHP
return NextResponse.json(responseData, { status: httpCode })
} catch (error) {
console.error('Cloud deployment error:', error)
return NextResponse.json(
{
status: 'error',
message: 'Internal server error',
details: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,247 @@
import { NextRequest, NextResponse } from 'next/server'
import { authMiddleware } from '@/lib/auth-middleware'
import connectDB from '@/lib/mongodb'
import { User as UserModel } from '@/models/user'
import BillingService from '@/lib/billing-service'
// Hardcoded VPC as requested
const K8S_VPC = '81b2bd94-61dc-424b-a1ca-ca4c810ed4c4'
export async function POST(request: NextRequest) {
try {
// Check authentication
const user = await authMiddleware(request)
if (!user) {
return NextResponse.json(
{ status: 'error', message: 'Unauthorized: Please login to continue' },
{ status: 401 }
)
}
// Get input data from request
const input = await request.json()
if (!input) {
return NextResponse.json({ status: 'error', message: 'Invalid JSON' }, { status: 400 })
}
// Validate required fields
if (!input.cluster_label || !input.nodepools) {
return NextResponse.json(
{ status: 'error', message: 'Cluster label and nodepools are required' },
{ status: 400 }
)
}
// Get API key from environment
const UTHO_API_KEY = process.env.UTHO_API_KEY
if (!UTHO_API_KEY) {
return NextResponse.json(
{ status: 'error', message: 'API configuration missing' },
{ status: 500 }
)
}
// Connect to MongoDB
await connectDB()
// Get user data for billing
const userData = await UserModel.findOne({ email: user.email })
if (!userData) {
return NextResponse.json({ status: 'error', message: 'User not found' }, { status: 404 })
}
const requiredAmount = input.amount || 0
// Check balance only if amount > 0
if (requiredAmount > 0) {
const currentBalance = userData.balance || 0
if (currentBalance < requiredAmount) {
return NextResponse.json(
{
status: 'error',
message: `Insufficient balance. Required: ₹${requiredAmount}, Available: ₹${currentBalance}`,
code: 'INSUFFICIENT_BALANCE',
},
{ status: 400 }
)
}
}
// Prepare Utho payload for Kubernetes
const uthoPayload = {
dcslug: 'inmumbaizone2', // Hardcoded as requested
cluster_label: input.cluster_label,
cluster_version: input.cluster_version || '1.30.0-utho',
nodepools: input.nodepools,
vpc: K8S_VPC, // Hardcoded VPC
network_type: 'publicprivate',
cpumodel: 'amd',
}
console.log('Sending to Utho Kubernetes API:', uthoPayload)
// Make API request to Utho Kubernetes
const UTHO_API_URL = 'https://api.utho.com/v2/kubernetes/deploy'
const response = await fetch(UTHO_API_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${UTHO_API_KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(uthoPayload),
})
const httpCode = response.status
const deploymentSuccess = httpCode >= 200 && httpCode < 300
const responseData = await response.json()
// Add status field and clusterId for consistency
responseData.status = deploymentSuccess ? 'success' : 'error'
if (deploymentSuccess && responseData.id) {
responseData.clusterId = responseData.id
}
console.log('Utho Kubernetes API response:', { httpCode, responseData })
// Process billing using the new comprehensive billing service
try {
const billingResult = await BillingService.processServiceDeployment({
user: {
id: user.id,
email: user.email,
siliconId: userData.siliconId,
},
service: {
name: `Kubernetes Cluster - ${input.cluster_label}`,
type: 'kubernetes',
id: responseData.clusterId || responseData.id,
clusterId: responseData.clusterId || responseData.id,
config: {
cluster_label: input.cluster_label,
cluster_version: input.cluster_version,
nodepools: input.nodepools,
dcslug: 'inmumbaizone2',
vpc: K8S_VPC,
network_type: 'publicprivate',
cpumodel: 'amd',
},
},
amount: requiredAmount,
currency: 'INR',
cycle: (input.cycle as any) || 'onetime',
deploymentSuccess,
deploymentResponse: responseData,
metadata: {
userAgent: request.headers.get('user-agent'),
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
uthoPayload,
httpCode,
},
})
console.log('Billing processed:', {
billingId: billingResult.billing.billing_id,
transactionId: billingResult.transaction?.transactionId,
balanceUpdated: billingResult.balanceUpdated,
})
} catch (billingError) {
console.error('Billing processing failed:', billingError)
// Continue even if billing fails, but return error if it's balance-related
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
return NextResponse.json(
{
status: 'error',
message: billingError.message,
code: 'INSUFFICIENT_BALANCE',
},
{ status: 400 }
)
}
}
return NextResponse.json(responseData, { status: httpCode })
} catch (error) {
console.error('Kubernetes deployment error:', error)
return NextResponse.json(
{
status: 'error',
message: 'Internal server error',
details: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
)
}
}
// GET endpoint for downloading kubeconfig
export async function GET(request: NextRequest) {
try {
// Check authentication
const user = await authMiddleware(request)
if (!user) {
return NextResponse.json(
{ status: 'error', message: 'Unauthorized: Please login to continue' },
{ status: 401 }
)
}
const { searchParams } = new URL(request.url)
const clusterId = searchParams.get('clusterId')
if (!clusterId) {
return NextResponse.json(
{ status: 'error', message: 'Cluster ID is required' },
{ status: 400 }
)
}
// Get API key from environment
const UTHO_API_KEY = process.env.UTHO_API_KEY
if (!UTHO_API_KEY) {
return NextResponse.json(
{ status: 'error', message: 'API configuration missing' },
{ status: 500 }
)
}
// Download kubeconfig
const KUBECONFIG_URL = `https://api.utho.com/v2/kubernetes/${clusterId}/download`
const response = await fetch(KUBECONFIG_URL, {
headers: {
Authorization: `Bearer ${UTHO_API_KEY}`,
Accept: 'application/yaml',
},
})
if (response.ok) {
const kubeconfig = await response.text()
// Return as downloadable file
return new NextResponse(kubeconfig, {
status: 200,
headers: {
'Content-Type': 'application/yaml',
'Content-Disposition': `attachment; filename="kubeconfig-${clusterId}.yaml"`,
},
})
} else {
const errorText = await response.text()
console.error('Kubeconfig download failed:', response.status, errorText)
return NextResponse.json(
{ status: 'error', message: 'Failed to download kubeconfig' },
{ status: response.status }
)
}
} catch (error) {
console.error('Kubeconfig download error:', error)
return NextResponse.json(
{
status: 'error',
message: 'Internal server error',
details: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,162 @@
import { NextRequest, NextResponse } from 'next/server'
import { authMiddleware } from '@/lib/auth-middleware'
import connectDB from '@/lib/mongodb'
import { User as UserModel } from '@/models/user'
import BillingService from '@/lib/billing-service'
// Define your VPN endpoints
const VPN_ENDPOINTS = {
america: 'https://wireguard-vpn.3027622.siliconpin.com/vpn',
europe: 'https://wireguard.vps20.siliconpin.com/vpn',
}
export async function POST(request: NextRequest) {
try {
// Check authentication
const user = await authMiddleware(request)
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Get data from request body
const { orderId, location, plan, amount } = await request.json()
if (!orderId) {
return NextResponse.json({ error: 'Order ID is required' }, { status: 400 })
}
if (!location || !VPN_ENDPOINTS[location as keyof typeof VPN_ENDPOINTS]) {
return NextResponse.json({ error: 'Valid VPN location is required' }, { status: 400 })
}
// Connect to MongoDB
await connectDB()
// Get user data for billing
const userData = await UserModel.findOne({ email: user.email })
if (!userData) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
const requiredAmount = amount || 0
// Check balance only if amount > 0
if (requiredAmount > 0) {
const currentBalance = userData.balance || 0
if (currentBalance < requiredAmount) {
return NextResponse.json(
{
error: `Insufficient balance. Required: ₹${requiredAmount}, Available: ₹${currentBalance}`,
code: 'INSUFFICIENT_BALANCE',
},
{ status: 400 }
)
}
}
// Get environment variables
const VPN_API_KEY = process.env.VPN_API_KEY
if (!VPN_API_KEY) {
return NextResponse.json({ error: 'VPN API configuration missing' }, { status: 500 })
}
// Get the endpoint for the selected location
const endpoint = VPN_ENDPOINTS[location as keyof typeof VPN_ENDPOINTS]
// Make API request to the selected VPN endpoint
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'X-API-Key': VPN_API_KEY,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
new: orderId.toString(),
userId: user.id,
plan,
location,
}),
})
console.log('VPN_API_KEY', VPN_API_KEY)
// Handle non-200 responses
if (!response.ok) {
const errorData = await response.text()
throw new Error(`VPN API returned HTTP ${response.status}: ${errorData}`)
}
// Parse the response
const vpnData = await response.json()
const deploymentSuccess = response.ok && vpnData?.config
if (!deploymentSuccess) {
throw new Error('Invalid response from VPN API: Missing config')
}
// Process billing using the new comprehensive billing service
try {
const billingResult = await BillingService.processServiceDeployment({
user: {
id: user.id,
email: user.email,
siliconId: userData.siliconId,
},
service: {
name: `VPN Service - ${location}`,
type: 'vpn',
id: orderId.toString(),
config: {
orderId,
location,
plan,
endpoint: endpoint,
},
},
amount: requiredAmount,
currency: 'INR',
cycle: 'monthly',
deploymentSuccess,
deploymentResponse: vpnData,
metadata: {
userAgent: request.headers.get('user-agent'),
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
vpnLocation: location,
vpnPlan: plan,
},
})
console.log('VPN billing processed:', {
billingId: billingResult.billing.billing_id,
transactionId: billingResult.transaction?.transactionId,
balanceUpdated: billingResult.balanceUpdated,
})
} catch (billingError) {
console.error('Billing processing failed:', billingError)
// Continue even if billing fails, but return error if it's balance-related
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
return NextResponse.json(
{
error: billingError.message,
code: 'INSUFFICIENT_BALANCE',
},
{ status: 400 }
)
}
}
// Return the VPN configuration
return NextResponse.json({
success: true,
data: vpnData,
})
} catch (error) {
console.error('VPN deployment error:', error)
return NextResponse.json(
{
error: 'VPN deployment failed',
details: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,108 @@
import { NextRequest, NextResponse } from 'next/server'
import { authMiddleware } from '@/lib/auth-middleware'
import connectDB from '@/lib/mongodb'
import { User as UserModel } from '@/models/user'
import BillingService from '@/lib/billing-service'
export async function GET(request: NextRequest) {
try {
// Check authentication
const user = await authMiddleware(request)
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Get billing ID from query parameters
const { searchParams } = new URL(request.url)
const billingId = searchParams.get('billing_id')
const amount = parseFloat(searchParams.get('amount') || '0')
if (!billingId) {
return NextResponse.json({ error: 'Billing ID is required' }, { status: 400 })
}
// Connect to MongoDB
await connectDB()
// Get user data for billing
const userData = await UserModel.findOne({ email: user.email })
if (!userData) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
// Mock hosting configuration (in production, this would come from database)
const hostingConfig = {
billing_id: billingId,
siliconId: user.id,
domain: `demo-${billingId}.siliconpin.com`,
cp_url: `https://cp-${billingId}.siliconpin.com:2083`,
panel: 'cPanel',
user_id: `user_${billingId}`,
password: `secure_password_${Date.now()}`,
server_ip: '192.168.1.100',
nameservers: ['ns1.siliconpin.com', 'ns2.siliconpin.com'],
ftp_settings: {
host: `ftp.demo-${billingId}.siliconpin.com`,
username: `user_${billingId}`,
port: 21,
},
database_settings: {
host: 'localhost',
prefix: `db_${billingId}_`,
},
ssl_certificate: {
enabled: true,
type: "Let's Encrypt",
auto_renew: true,
},
}
const configJson = JSON.stringify(hostingConfig, null, 2)
// Process billing for hosting configuration download
try {
await BillingService.processServiceDeployment({
user: {
id: user.id,
email: user.email,
siliconId: userData.siliconId,
},
service: {
name: `Hosting Configuration - ${hostingConfig.domain}`,
type: 'hosting',
id: billingId,
config: hostingConfig,
},
amount: amount,
currency: 'INR',
cycle: 'onetime',
deploymentSuccess: true,
deploymentResponse: { configDownloaded: true },
metadata: {
userAgent: request.headers.get('user-agent'),
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
downloadType: 'hosting_config',
domain: hostingConfig.domain,
},
})
console.log('Hosting config billing processed for:', billingId)
} catch (billingError) {
console.error('Billing processing failed:', billingError)
// Continue with download even if billing fails
}
// Return the config as a downloadable JSON file
return new NextResponse(configJson, {
status: 200,
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="sp_credential_${billingId}.json"`,
'Cache-Control': 'no-cache',
},
})
} catch (error) {
console.error('Download error:', error)
return NextResponse.json({ error: 'Download failed' }, { status: 500 })
}
}

View File

@@ -0,0 +1,54 @@
import { NextRequest, NextResponse } from 'next/server'
import { authMiddleware } from '@/lib/auth-middleware'
export async function GET(request: NextRequest) {
try {
// Check authentication
const user = await authMiddleware(request)
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Get cluster ID from query parameters
const { searchParams } = new URL(request.url)
const clusterId = searchParams.get('cluster_id')
if (!clusterId) {
return NextResponse.json({ error: 'Cluster ID is required' }, { status: 400 })
}
// Mock Kubernetes configuration
const kubeConfig = `apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJRWRtTFUzZUNCUXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRBeE1EUXhOekF6TXpCYUZ3MHpOREF4TURFeE56QXpNekJhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLLQEKQVFJREFRQUI=
server: https://k8s-api.siliconpin.com:6443
name: ${clusterId}
contexts:
- context:
cluster: ${clusterId}
user: ${clusterId}-admin
name: ${clusterId}
current-context: ${clusterId}
kind: Config
preferences: {}
users:
- name: ${clusterId}-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ==`
// Return the config as a downloadable file
return new NextResponse(kubeConfig, {
status: 200,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="kubeconfig-${clusterId}.yaml"`,
'Cache-Control': 'no-cache',
},
})
} catch (error) {
console.error('Download error:', error)
return NextResponse.json({ error: 'Download failed' }, { status: 500 })
}
}

View File

@@ -0,0 +1,285 @@
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 { DeveloperRequest, IDeveloperRequest } from '@/models/developer-request'
import { Transaction } from '@/models/transaction'
import BillingService from '@/lib/billing-service'
import { checkServiceAvailability } from '@/lib/system-settings'
// Schema for developer hire request
const HireDeveloperSchema = z.object({
planId: z.enum(['hourly', 'daily', 'monthly']),
planName: z.string().min(1),
planPrice: z.number().positive(),
requirements: z.string().min(10, 'Requirements must be at least 10 characters').max(5000),
contactInfo: z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Valid email is required'),
phone: z.string().optional(),
}),
})
// Schema for response
const HireDeveloperResponseSchema = z.object({
success: z.boolean(),
data: z
.object({
requestId: z.string(),
transactionId: z.string(),
status: z.string(),
message: z.string(),
estimatedResponse: z.string(),
})
.optional(),
error: z
.object({
message: z.string(),
code: z.string(),
})
.optional(),
})
export async function POST(request: NextRequest) {
try {
// Check if developer hire service is enabled
if (!(await checkServiceAvailability('developer'))) {
return NextResponse.json(
{
success: false,
error: {
message: 'Developer hire service is currently disabled by administrator',
code: 'SERVICE_DISABLED',
},
},
{ status: 503 }
)
}
// 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 = HireDeveloperSchema.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 (minimum deposit required)
const minimumDeposit = Math.min(validatedData.planPrice * 0.5, 10000) // 50% or max ₹10,000
if (currentBalance < minimumDeposit) {
return NextResponse.json(
{
success: false,
error: {
message: `Insufficient balance. Minimum deposit required: ₹${minimumDeposit}, Available: ₹${currentBalance}`,
code: 'INSUFFICIENT_BALANCE',
},
},
{ status: 400 }
)
}
// Create developer request first
const developerRequest = new DeveloperRequest({
userId: userData._id,
planId: validatedData.planId,
planName: validatedData.planName,
planPrice: validatedData.planPrice,
requirements: validatedData.requirements,
contactInfo: validatedData.contactInfo,
status: 'pending',
paymentStatus: 'pending', // Will be updated after billing
transactionId: null, // Will be set after transaction is created
})
// Save developer request first
const savedRequest = await developerRequest.save()
// Process billing using the new comprehensive billing service
try {
const billingResult = await BillingService.processServiceDeployment({
user: {
id: user.id,
email: user.email,
siliconId: userData.siliconId,
},
service: {
name: `Developer Hire - ${validatedData.planName}`,
type: 'developer_hire',
id: savedRequest._id.toString(),
config: {
planId: validatedData.planId,
planName: validatedData.planName,
planPrice: validatedData.planPrice,
requirements: validatedData.requirements,
contactInfo: validatedData.contactInfo,
},
},
amount: minimumDeposit,
currency: 'INR',
cycle: 'onetime',
deploymentSuccess: true, // Developer hire request is always successful
deploymentResponse: { requestId: savedRequest._id.toString() },
metadata: {
userAgent: request.headers.get('user-agent'),
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
depositAmount: minimumDeposit,
fullPlanPrice: validatedData.planPrice,
},
})
// Update developer request with billing info
await (DeveloperRequest as any).updateOne(
{ _id: savedRequest._id },
{
$set: {
paymentStatus: 'paid',
transactionId: billingResult.transaction?._id,
},
}
)
console.log('Developer hire billing processed:', {
requestId: savedRequest._id,
billingId: billingResult.billing.billing_id,
transactionId: billingResult.transaction?.transactionId,
})
} catch (billingError) {
console.error('Billing processing failed:', billingError)
// Rollback developer request if billing fails
await (DeveloperRequest as any).deleteOne({ _id: savedRequest._id })
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
return NextResponse.json(
{
success: false,
error: {
message: billingError.message,
code: 'INSUFFICIENT_BALANCE',
},
},
{ status: 400 }
)
}
throw new Error('Failed to process payment')
}
const responseData = {
success: true,
data: {
requestId: savedRequest._id.toString(),
transactionId: 'processed_via_billing',
status: 'pending',
message:
'Your developer hire request has been submitted successfully! Our team will review your requirements and contact you within 24 hours.',
estimatedResponse: '24 hours',
},
}
const validatedResponse = HireDeveloperResponseSchema.parse(responseData)
return NextResponse.json(validatedResponse, { status: 200 })
} catch (error) {
console.error('Developer hire 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 process developer hire request', code: 'INTERNAL_ERROR' },
},
{ status: 500 }
)
}
}
// GET endpoint to fetch user's developer requests
export async function GET(request: NextRequest) {
try {
const user = await authMiddleware(request)
if (!user) {
return NextResponse.json(
{
success: false,
error: { message: 'Authentication required', code: 'UNAUTHORIZED' },
},
{ status: 401 }
)
}
await connectDB()
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 }
)
}
// Get user's developer requests
const requests = await (DeveloperRequest as any)
.find({ userId: userData._id })
.sort({ createdAt: -1 })
.populate('transactionId')
.lean()
return NextResponse.json({
success: true,
data: {
requests,
total: requests.length,
},
})
} catch (error) {
console.error('Failed to fetch developer requests:', error)
return NextResponse.json(
{
success: false,
error: { message: 'Failed to fetch requests', code: 'INTERNAL_ERROR' },
},
{ status: 500 }
)
}
}