import Series from "../../../../models/series.js"; import Event from "../../../../models/event.js"; import Member from "../../../../models/member.js"; import { validateSeriesTicketPurchase, calculateSeriesTicketPrice, reserveSeriesTicket, releaseSeriesTicket, completeSeriesTicketPurchase, registerForAllSeriesEvents, } from "../../../../utils/tickets.js"; import { sendSeriesPassConfirmation } from "../../../../utils/resend.js"; export default defineEventHandler(async (event) => { try { const seriesId = getRouterParam(event, "id"); const body = await validateBody(event, seriesTicketPurchaseSchema); const { name, email, paymentId } = body; // Fetch series // Build query conditions based on whether seriesId looks like ObjectId or string const isObjectId = /^[0-9a-fA-F]{24}$/.test(seriesId); const seriesQuery = isObjectId ? { $or: [{ _id: seriesId }, { id: seriesId }, { slug: seriesId }] } : { $or: [{ id: seriesId }, { slug: seriesId }] }; const series = await Series.findOne(seriesQuery); if (!series) { throw createError({ statusCode: 404, statusMessage: "Series not found", }); } // Check membership — prefer JWT auth for accurate member pricing let member = null; try { member = await requireAuth(event); } catch { // Not authenticated — fall through to email lookup } if (!member) { member = await Member.findOne({ email: email.toLowerCase() }); } // Resolve canonical email: use authenticated member's email if available const canonicalEmail = member ? member.email : email.toLowerCase(); // Validate purchase const validation = validateSeriesTicketPurchase(series, { email: canonicalEmail, name, member, }); if (!validation.valid) { throw createError({ statusCode: 400, statusMessage: validation.reason, }); } const { ticketInfo } = validation; // Validate submitted ticket type matches entitlement (prevents price mismatch) if (body.ticketType && body.ticketType !== ticketInfo.ticketType) { throw createError({ statusCode: 422, statusMessage: `Ticket type mismatch: you are entitled to "${ticketInfo.ticketType}" but submitted "${body.ticketType}"`, }) } // For paid tickets, require payment ID if (!ticketInfo.isFree && !paymentId) { throw createError({ statusCode: 400, statusMessage: "Payment is required for this series pass", }); } // Create series registration const registration = { memberId: member?._id, name, email: canonicalEmail, membershipLevel: member?.circle || "non-member", isMember: !!member, ticketType: ticketInfo.ticketType, ticketPrice: ticketInfo.price, paymentStatus: ticketInfo.isFree ? "not_required" : "completed", paymentId: paymentId || null, amountPaid: ticketInfo.price, registeredAt: new Date(), eventRegistrations: [], }; series.registrations.push(registration); await completeSeriesTicketPurchase(series, ticketInfo.ticketType); // Get the newly created registration const newRegistration = series.registrations[series.registrations.length - 1]; // Fetch all events in this series const seriesEvents = await Event.find({ "series.id": series.id, isCancelled: false, }).sort({ "series.position": 1, startDate: 1 }); // Register user for all events const eventRegistrations = await registerForAllSeriesEvents( series, seriesEvents, newRegistration, ); // Send confirmation email try { await sendSeriesPassConfirmation({ to: canonicalEmail, name, series: { title: series.title, description: series.description, type: series.type, }, ticket: { type: ticketInfo.ticketType, price: ticketInfo.price, currency: ticketInfo.currency, isFree: ticketInfo.isFree, }, events: seriesEvents.map((e) => ({ title: e.title, startDate: e.startDate, endDate: e.endDate, location: e.location, })), paymentId, }); if (member) { logActivity(member._id, 'email_sent', { emailType: 'series_pass', subject: `Series pass: ${series.title}` }) } } catch (emailError) { console.error( "Failed to send series pass confirmation email:", emailError, ); // Don't fail the registration if email fails } return { success: true, message: "Series pass purchased successfully", registration: { id: newRegistration._id, ticketType: newRegistration.ticketType, amountPaid: newRegistration.amountPaid, eventsRegistered: eventRegistrations.filter((r) => r.success).length, totalEvents: seriesEvents.length, }, events: eventRegistrations.map((r) => ({ eventId: r.eventId, success: r.success, reason: r.reason, })), }; } catch (error) { console.error("Error purchasing series pass:", error); throw createError({ statusCode: error.statusCode || 500, statusMessage: error.statusMessage || "Failed to purchase series pass", }); } });