import jwt from 'jsonwebtoken' import PreRegistration from '../../models/preRegistration.js' import Member from '../../models/member.js' import { connectDB } from '../../utils/mongoose.js' import { setAuthCookie } from '../../utils/auth.js' import { assignMemberNumber } from '../../utils/memberNumber.js' import { createHelcimCustomer } from '../../utils/helcim.js' export default defineEventHandler(async (event) => { const body = await validateBody(event, inviteAcceptSchema) const config = useRuntimeConfig(event) await connectDB() // Re-verify the token is still valid (not expired) let decoded try { decoded = jwt.verify(body.token, config.jwtSecret) } catch { throw createError({ statusCode: 401, statusMessage: 'Invalid or expired invitation link' }) } if (decoded.type !== 'prereg-invite' || decoded.preRegistrationId !== body.preRegistrationId) { throw createError({ statusCode: 401, statusMessage: 'Invalid or expired invitation link' }) } const preReg = await PreRegistration.findById(body.preRegistrationId) if (!preReg) { throw createError({ statusCode: 404, statusMessage: 'Pre-registration not found' }) } if (preReg.status === 'accepted') { throw createError({ statusCode: 400, statusMessage: 'This invitation has already been accepted' }) } // Check no existing member with this email const existingMember = await Member.findOne({ email: preReg.email }) if (existingMember) { throw createError({ statusCode: 409, statusMessage: 'A member with this email already exists' }) } // For paid invites, create the Helcim customer up front so we can store the ID // on the Member at creation time. Done before Member.create so a Helcim failure // doesn't leave us with an orphan Member doc. let helcimCustomer = null if (body.contributionAmount > 0) { helcimCustomer = await createHelcimCustomer({ customerType: 'PERSON', contactName: body.name, email: preReg.email, }) } // Create the member const member = await Member.create({ email: preReg.email, name: body.name, pronouns: body.pronouns || undefined, location: body.location || undefined, circle: body.circle, contributionAmount: body.contributionAmount, bio: body.motivation || undefined, status: body.contributionAmount === 0 ? 'active' : 'pending_payment', helcimCustomerId: helcimCustomer?.id, agreement: { acceptedAt: new Date() }, }) await assignMemberNumber(member._id) // Update pre-registration await PreRegistration.findByIdAndUpdate(preReg._id, { $set: { status: 'accepted', acceptedAt: new Date(), memberId: member._id, } }) logActivity(member._id, 'member_joined', { source: 'pre-registration', preRegistrationId: preReg._id, }) // Issue session cookie so the member is authenticated for any follow-up calls // (Helcim initialize-payment for paid flow, dashboard fetches for free flow). setAuthCookie(event, member) // For free tier, redirect to welcome if (body.contributionAmount === 0) { return { success: true, requiresPayment: false, redirectUrl: '/member/dashboard', member: { id: member._id, email: member.email, name: member.name, circle: member.circle, contributionAmount: member.contributionAmount, status: member.status, } } } // For paid tiers, return member + Helcim customer so frontend can proceed to payment return { success: true, requiresPayment: true, customerId: helcimCustomer.id, customerCode: helcimCustomer.customerCode, member: { id: member._id, email: member.email, name: member.name, circle: member.circle, contributionAmount: member.contributionAmount, status: member.status, } } })