import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { db } from '@/lib/db'; import { z } from 'zod'; import { Decimal } from '@prisma/client/runtime/library'; interface RouteContext { params: Promise<{ id: string }>; } // Validation schema for payment const paymentSchema = z.object({ paymentType: z.enum(['CASH', 'CARD', 'TRANSFER', 'MEMBERSHIP', 'FREE']), amount: z.number().positive('El monto debe ser mayor a 0').optional(), reference: z.string().max(100).optional(), notes: z.string().max(500).optional(), cashRegisterId: z.string().uuid().optional(), }); // POST /api/bookings/[id]/pay - Mark booking as paid export async function POST( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'No autorizado' }, { status: 401 } ); } const { id } = await context.params; // Verify booking exists and belongs to user's organization const existingBooking = await db.booking.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, include: { client: true, payments: true, }, }); if (!existingBooking) { return NextResponse.json( { error: 'Reserva no encontrada' }, { status: 404 } ); } // If user is SITE_ADMIN, verify they have access to this site if (session.user.role === 'SITE_ADMIN' && session.user.siteId !== existingBooking.siteId) { return NextResponse.json( { error: 'No tiene permiso para procesar pagos en esta reserva' }, { status: 403 } ); } // Check if booking is already cancelled if (existingBooking.status === 'CANCELLED') { return NextResponse.json( { error: 'No se puede procesar el pago de una reserva cancelada' }, { status: 400 } ); } // Check if booking is already fully paid const totalPaid = existingBooking.payments.reduce( (sum, p) => sum + Number(p.amount), 0 ); const totalPrice = Number(existingBooking.totalPrice); if (totalPaid >= totalPrice) { return NextResponse.json( { error: 'La reserva ya está completamente pagada' }, { status: 400 } ); } const body = await request.json(); // Validate input const validationResult = paymentSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Datos de pago inválidos', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { paymentType, amount, reference, notes, cashRegisterId } = validationResult.data; // Calculate payment amount const remainingAmount = totalPrice - totalPaid; const paymentAmount = amount !== undefined ? Math.min(amount, remainingAmount) : remainingAmount; if (paymentAmount <= 0) { return NextResponse.json( { error: 'El monto del pago debe ser mayor a 0' }, { status: 400 } ); } // If cash register is provided, verify it exists and is open if (cashRegisterId) { const cashRegister = await db.cashRegister.findFirst({ where: { id: cashRegisterId, siteId: existingBooking.siteId, closedAt: null, // Only open registers }, }); if (!cashRegister) { return NextResponse.json( { error: 'Caja registradora no encontrada o no está abierta' }, { status: 400 } ); } } // Use transaction for atomicity const result = await db.$transaction(async (tx) => { // Create payment record const payment = await tx.payment.create({ data: { bookingId: id, clientId: existingBooking.clientId, amount: new Decimal(paymentAmount), paymentType, reference: reference || null, notes: notes || null, cashRegisterId: cashRegisterId || null, }, }); // Calculate new total paid const newTotalPaid = totalPaid + paymentAmount; const isFullyPaid = newTotalPaid >= totalPrice; // Update booking const updatedBooking = await tx.booking.update({ where: { id }, data: { paidAmount: new Decimal(newTotalPaid), paymentType, // Set status to CONFIRMED if fully paid and was pending ...(isFullyPaid && existingBooking.status === 'PENDING' && { status: 'CONFIRMED', }), }, include: { court: { select: { id: true, name: true, type: true, pricePerHour: true, }, }, client: { select: { id: true, firstName: true, lastName: true, email: true, phone: true, }, }, site: { select: { id: true, name: true, timezone: true, }, }, payments: { select: { id: true, amount: true, paymentType: true, reference: true, notes: true, createdAt: true, }, orderBy: { createdAt: 'desc', }, }, }, }); return { booking: updatedBooking, payment, isFullyPaid, }; }); return NextResponse.json({ message: result.isFullyPaid ? 'Pago completado. La reserva ha sido confirmada.' : 'Pago parcial registrado exitosamente.', booking: result.booking, payment: result.payment, remainingAmount: Math.max(0, totalPrice - (totalPaid + paymentAmount)), }); } catch (error) { console.error('Error processing payment:', error); return NextResponse.json( { error: 'Error al procesar el pago' }, { status: 500 } ); } }