ghostguild-org/server/api/events/[id]/tickets/purchase.post.js

162 lines
4.6 KiB
JavaScript

import Event from "../../../../models/event.js";
import Member from "../../../../models/member.js";
import { connectDB } from "../../../../utils/mongoose.js";
import {
validateTicketPurchase,
calculateTicketPrice,
completeTicketPurchase,
} from "../../../../utils/tickets.js";
import { sendEventRegistrationEmail } from "../../../../utils/resend.js";
import mongoose from "mongoose";
/**
* POST /api/events/[id]/tickets/purchase
* Purchase a ticket for an event
* Body: { name, email, paymentToken? }
*/
export default defineEventHandler(async (event) => {
try {
await connectDB();
const identifier = getRouterParam(event, "id");
const body = await readBody(event);
if (!identifier) {
throw createError({
statusCode: 400,
statusMessage: "Event identifier is required",
});
}
// Validate required fields
if (!body.name || !body.email) {
throw createError({
statusCode: 400,
statusMessage: "Name and email are required",
});
}
// Fetch the event
let eventData;
if (mongoose.Types.ObjectId.isValid(identifier)) {
eventData = await Event.findById(identifier);
}
if (!eventData) {
eventData = await Event.findOne({ slug: identifier });
}
if (!eventData) {
throw createError({
statusCode: 404,
statusMessage: "Event not found",
});
}
// Check if user is a member
const member = await Member.findOne({ email: body.email.toLowerCase() });
// Validate ticket purchase
const validation = validateTicketPurchase(eventData, {
email: body.email,
name: body.name,
member,
});
if (!validation.valid) {
throw createError({
statusCode: 400,
statusMessage: validation.reason,
data: {
waitlistAvailable: validation.waitlistAvailable,
},
});
}
const { ticketInfo } = validation;
const requiresPayment = ticketInfo.price > 0;
// Handle payment if required
let transactionId = null;
if (requiresPayment) {
// For HelcimPay.js with purchase type, the transaction is already completed
// We just need to verify we received the transaction ID
if (!body.transactionId) {
throw createError({
statusCode: 400,
statusMessage:
"Transaction ID is required. Payment must be completed first.",
});
}
transactionId = body.transactionId;
// Optional: Verify the transaction with Helcim API
// This adds extra security to ensure the transaction is legitimate
// For now, we trust the transaction ID from HelcimPay.js
console.log("Payment completed with transaction ID:", transactionId);
}
// Create registration
const registration = {
memberId: member ? member._id : null,
name: body.name,
email: body.email.toLowerCase(),
membershipLevel: member
? `${member.circle}-${member.contributionTier}`
: "non-member",
isMember: !!member,
ticketType: ticketInfo.ticketType,
ticketPrice: ticketInfo.price,
paymentStatus: requiresPayment ? "completed" : "not_required",
paymentId: transactionId,
amountPaid: ticketInfo.price,
registeredAt: new Date(),
};
// Add registration to event
eventData.registrations.push(registration);
// Complete ticket purchase (updates sold/reserved counts)
await completeTicketPurchase(eventData, ticketInfo.ticketType);
// Save event with registration
await eventData.save();
// Send confirmation email
try {
await sendEventRegistrationEmail(registration, eventData);
} catch (emailError) {
console.error("Failed to send confirmation email:", emailError);
// Don't fail the registration if email fails
}
return {
success: true,
message: "Ticket purchased successfully!",
registration: {
id: eventData.registrations[eventData.registrations.length - 1]._id,
name: registration.name,
email: registration.email,
ticketType: registration.ticketType,
amountPaid: registration.amountPaid,
},
payment: transactionId
? {
transactionId: transactionId,
amount: ticketInfo.price,
currency: ticketInfo.currency,
}
: null,
};
} catch (error) {
console.error("Error purchasing ticket:", error);
if (error.statusCode) {
throw error;
}
throw createError({
statusCode: 500,
statusMessage: "Failed to purchase ticket. Please try again.",
});
}
});