first commit

This commit is contained in:
2026-01-17 14:17:42 +05:30
commit 0f194eb9e7
328 changed files with 73544 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { calculateCommissions } from '@/lib/commission'
export async function POST(request: NextRequest, props: { params: Promise<{ id: string }> }) {
const params = await props.params;
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const orderId = params.id
// Verify order belongs to user
const order = await prisma.order.findFirst({
where: {
id: orderId,
userId: session.user.id
}
})
if (!order) {
return NextResponse.json({ error: 'Order not found' }, { status: 404 })
}
if (order.status !== 'PENDING') {
return NextResponse.json({ error: 'Order already processed' }, { status: 400 })
}
// Update order status and calculate commissions in transaction
const result = await prisma.$transaction(async (tx) => {
// Update order status
const updatedOrder = await tx.order.update({
where: { id: orderId },
data: { status: 'PAID' }
})
// Calculate and create commissions
const commissionResult = await calculateCommissions(orderId)
return {
order: updatedOrder,
commissions: commissionResult
}
})
return NextResponse.json({
success: true,
order: result.order,
commissions: result.commissions
})
} catch (error) {
console.error('Error confirming order:', error)
return NextResponse.json(
{ error: 'Failed to confirm order' },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,110 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { id } = await params
const order = await prisma.order.findFirst({
where: {
id,
userId: session.user.id
},
include: {
orderItems: {
include: {
product: {
select: {
id: true,
name: true,
images: true,
discount: true,
sku: true
}
}
}
},
user: {
select: {
name: true,
email: true
}
}
}
})
if (!order) {
return NextResponse.json({ error: 'Order not found' }, { status: 404 })
}
return NextResponse.json(order)
} catch (error) {
console.error('Error fetching order:', error)
return NextResponse.json(
{ error: 'Failed to fetch order' },
{ status: 500 }
)
}
}
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { id } = await params
const { status } = await request.json()
// Only allow cancellation of pending orders
if (status === 'CANCELLED') {
const order = await prisma.order.findFirst({
where: {
id,
userId: session.user.id,
status: 'PENDING'
}
})
if (!order) {
return NextResponse.json(
{ error: 'Order not found or cannot be cancelled' },
{ status: 400 }
)
}
const updatedOrder = await prisma.order.update({
where: { id },
data: { status: 'CANCELLED' }
})
return NextResponse.json(updatedOrder)
}
return NextResponse.json(
{ error: 'Invalid status update' },
{ status: 400 }
)
} catch (error) {
console.error('Error updating order:', error)
return NextResponse.json(
{ error: 'Failed to update order' },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,90 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
export async function POST(request: NextRequest) {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { orderId, reason } = await request.json()
const result = await prisma.$transaction(async (tx) => {
// Get order with items
const order = await tx.order.findFirst({
where: {
id: orderId,
userId: session.user.id,
status: { in: ['PENDING', 'PAID'] } // Only allow cancellation for these statuses
},
include: {
orderItems: true
}
})
if (!order) {
throw new Error('Order not found or cannot be cancelled')
}
// Update order status
const updatedOrder = await tx.order.update({
where: { id: orderId },
data: {
status: 'CANCELLED'
}
})
// Restore stock for all items
for (const item of order.orderItems) {
await tx.product.update({
where: { id: item.productId },
data: {
stock: {
increment: item.quantity
}
}
})
}
// If order was paid, reverse commissions
if (order.status === 'PAID') {
await tx.commission.updateMany({
where: { orderId: order.id },
data: { status: 'CANCELLED' }
})
// Reverse wallet earnings (you may want to handle this differently)
const commissions = await tx.commission.findMany({
where: { orderId: order.id }
})
for (const commission of commissions) {
await tx.wallet.update({
where: { userId: commission.userId },
data: {
balance: { decrement: commission.amount },
totalEarnings: { decrement: commission.amount }
}
})
}
}
return updatedOrder
})
return NextResponse.json({
success: true,
order: result
})
} catch (error) {
console.error('Error cancelling order:', error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to cancel order' },
{ status: 500 }
)
}
}

186
app/api/orders/route.ts Normal file
View File

@@ -0,0 +1,186 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { createRazorpayInstance, getRazorpayKeyId } from '@/lib/razorpay'
export async function POST(request: NextRequest) {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ error: 'Authentication required to place orders' },
{ status: 401 }
)
}
const data = await request.json()
const { items, total, shippingAddress, shippingAddressId } = data
// Validate required fields
if (!items || !Array.isArray(items) || items.length === 0) {
return NextResponse.json(
{ error: 'Order items are required' },
{ status: 400 }
)
}
if (!total || total <= 0) {
return NextResponse.json(
{ error: 'Valid order total is required' },
{ status: 400 }
)
}
// Validate stock availability for all items
for (const item of items) {
const product = await prisma.product.findUnique({
where: { id: item.productId },
select: { stock: true, name: true, isActive: true }
})
if (!product) {
return NextResponse.json(
{ error: `Product not found: ${item.productId}` },
{ status: 400 }
)
}
if (!product.isActive) {
return NextResponse.json(
{ error: `Product is no longer available: ${product.name}` },
{ status: 400 }
)
}
if (product.stock < item.quantity) {
return NextResponse.json(
{ error: `Insufficient stock for ${product.name}. Available: ${product.stock}, Requested: ${item.quantity}` },
{ status: 400 }
)
}
}
const razorpay = createRazorpayInstance()
const razorpayKeyId = getRazorpayKeyId()
console.log('Creating Razorpay order for amount:', total)
console.log('Using Razorpay environment:', process.env.RAZORPAY_ENV || 'test')
console.log('Razorpay Key ID:', razorpayKeyId)
// Create Razorpay order
const razorpayOrder = await razorpay.orders.create({
amount: Math.round(total * 100), // Convert to paise
currency: 'INR',
receipt: `order_${Date.now()}`,
notes: {
userId: session.user.id,
itemCount: items.length,
environment: process.env.RAZORPAY_ENV || 'test'
}
})
console.log('Razorpay order created:', razorpayOrder.id)
// Use transaction to ensure atomicity
const result = await prisma.$transaction(async (tx) => {
// Create order
const order = await tx.order.create({
data: {
userId: session.user.id,
total,
status: 'PENDING',
razorpayOrderId: razorpayOrder.id,
shippingAddressId: shippingAddressId || null,
orderItems: {
create: items.map((item: any) => ({
productId: item.productId,
quantity: item.quantity,
price: item.price
}))
}
},
include: {
orderItems: {
include: {
product: true
}
},
shippingAddress: true
}
})
// Reserve stock (reduce stock for pending orders)
for (const item of items) {
await tx.product.update({
where: { id: item.productId },
data: {
stock: {
decrement: item.quantity
}
}
})
}
return order
})
return NextResponse.json({
id: result.id,
total: result.total,
status: result.status,
createdAt: result.createdAt,
razorpayOrderId: razorpayOrder.id,
razorpayKeyId: razorpayKeyId,
isProduction: process.env.RAZORPAY_ENV === 'production'
})
} catch (error) {
console.error('Error creating order:', error)
return NextResponse.json(
{ error: 'Failed to create order' },
{ status: 500 }
)
}
}
export async function GET(request: NextRequest) {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const orders = await prisma.order.findMany({
where: {
userId: session.user.id
},
include: {
orderItems: {
include: {
product: {
select: {
id: true,
name: true,
images: true,
discount: true
}
}
}
}
},
orderBy: {
createdAt: 'desc'
}
})
return NextResponse.json(orders)
} catch (error) {
console.error('Error fetching orders:', error)
return NextResponse.json(
{ error: 'Failed to fetch orders' },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,218 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { getRazorpayConfig } from '@/lib/razorpay'
import crypto from 'crypto'
import { emailService } from '@/lib/email'
import { rankSystem } from '@/lib/ranks'
import { calculateCommissions } from '@/lib/commission'
export async function POST(request: NextRequest) {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { razorpay_order_id, razorpay_payment_id, razorpay_signature, orderId } = await request.json()
const config = getRazorpayConfig()
// Verify signature
const hmac = crypto.createHmac('sha256', config.keySecret)
hmac.update(razorpay_order_id + '|' + razorpay_payment_id)
const generated_signature = hmac.digest('hex')
if (generated_signature !== razorpay_signature) {
console.error('Payment signature verification failed')
console.error('Generated:', generated_signature)
console.error('Received:', razorpay_signature)
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
}
// Process successful payment in transaction
const result = await prisma.$transaction(async (tx) => {
// Update order status
const order = await tx.order.update({
where: {
id: orderId,
userId: session.user.id
},
data: {
status: 'PAID',
razorpayPaymentId: razorpay_payment_id
},
include: {
orderItems: {
include: {
product: true
}
},
user: {
include: {
referrer: true
}
}
}
})
return order
})
// Calculate commissions after successful payment
if (result.user.referrerId) {
const commissionResult = await calculateCommissions(result.id)
console.log('Commission calculation result:', commissionResult)
}
// Send order confirmation email (don't block on failure)
setImmediate(async () => {
try {
const emailResult = await emailService.sendOrderConfirmation(
result.user.email,
result.user.name || 'Customer',
result
)
if (!emailResult.skipped) {
console.log('Order confirmation email sent successfully')
}
} catch (emailError) {
console.error('Failed to send order confirmation email:', emailError)
}
})
// Update ranks for user and referrers (don't block on failure)
setImmediate(async () => {
try {
await rankSystem.updateUserRank(result.userId)
if (result.user.referrerId) {
await rankSystem.updateUserRank(result.user.referrerId)
}
console.log('User ranks updated successfully')
} catch (rankError) {
console.error('Failed to update ranks:', rankError)
}
})
console.log('Payment verified and order processed successfully')
return NextResponse.json({
success: true,
order: result
})
} catch (error) {
console.error('Error verifying payment:', error)
return NextResponse.json(
{ error: 'Failed to verify payment' },
{ status: 500 }
)
}
}
async function generateCommissions(tx: any, order: any) {
// Check if commission settings exist, if not create default ones
const commissionSettings = await tx.commissionSettings.findMany({
where: { isActive: true },
orderBy: { level: 'asc' }
})
// If no settings exist, create default commission structure
if (commissionSettings.length === 0) {
await tx.commissionSettings.createMany({
data: [
{ level: 1, percentage: 10, isActive: true },
{ level: 2, percentage: 5, isActive: true },
{ level: 3, percentage: 3, isActive: true }
]
})
// Refetch settings
const newSettings = await tx.commissionSettings.findMany({
where: { isActive: true },
orderBy: { level: 'asc' }
})
await processCommissions(tx, order, newSettings)
} else {
await processCommissions(tx, order, commissionSettings)
}
}
async function processCommissions(tx: any, order: any, settings: any[]) {
let currentUser = order.user
let level = 1
for (const setting of settings) {
if (!currentUser.referrerId || level > setting.level) break
const referrer = await tx.user.findUnique({
where: { id: currentUser.referrerId },
include: { referrer: true, currentRank: true }
})
if (!referrer) break
// Apply rank multiplier to commission
let commissionAmount = (order.total * setting.percentage) / 100
if (referrer.currentRank) {
commissionAmount *= referrer.currentRank.commissionMultiplier
}
// Create commission record
const commission = await tx.commission.create({
data: {
userId: referrer.id,
fromUserId: order.userId,
orderId: order.id,
amount: commissionAmount,
level: setting.level,
type: 'REFERRAL',
status: 'APPROVED'
},
include: {
fromUser: {
select: {
name: true,
email: true
}
}
}
})
// Update referrer's wallet
await tx.wallet.upsert({
where: { userId: referrer.id },
update: {
balance: { increment: commissionAmount },
totalEarnings: { increment: commissionAmount }
},
create: {
userId: referrer.id,
balance: commissionAmount,
totalEarnings: commissionAmount,
totalWithdrawn: 0
}
})
// Send commission email (async, don't block transaction)
setImmediate(async () => {
try {
const emailResult = await emailService.sendCommissionAlert(
referrer.email,
referrer.name || 'User',
commission
)
if (!emailResult.skipped) {
console.log('Commission alert email sent successfully')
}
} catch (emailError) {
console.error('Failed to send commission email:', emailError)
}
})
console.log(`Generated commission of ₹${commissionAmount.toFixed(2)} for user ${referrer.id} at level ${setting.level}`)
currentUser = referrer
level++
}
}