286 lines
8.3 KiB
TypeScript
286 lines
8.3 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 { 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 }
|
|
)
|
|
}
|
|
}
|