ghostguild-org/server/api/helcim/initialize-payment.post.js
Jennie Robinson Faber 9b79ae6bf4 refactor(auth): rename paymentBridge → signupBridge
After commit 90acc35 issued the cookie for $0 signups too, the "payment"
framing was wrong — there's no payment in a $0 signup. The cookie is
about bridging the gap between signup-form submit and email verify, not
about payment specifically.

Changes:
- setPaymentBridgeCookie  → setSignupBridgeCookie
- getPaymentBridgeMember  → getSignupBridgeMember
- Cookie wire name        payment-bridge → signup-bridge
- JWT scope               payment_bridge → signup_bridge

Touches both /api/helcim/subscription (signup activation) and
/api/helcim/initialize-payment (paid Helcim checkout) which both consume
the cookie. In-flight signup sessions started before this lands will
need to re-submit the form (cookie name mismatch); cutover hasn't
happened yet, so the only impact is local dev sessions.
2026-04-30 15:31:54 +01:00

106 lines
3.9 KiB
JavaScript

import Member from '../../models/member.js'
import { loadPublicEvent } from '../../utils/loadEvent.js'
import { loadPublicSeries } from '../../utils/loadSeries.js'
import { calculateTicketPrice, calculateSeriesTicketPrice, hasMemberAccess } from '../../utils/tickets.js'
import { requireAuth, getOptionalMember, getSignupBridgeMember } from '../../utils/auth.js'
import { initializeHelcimPaySession } from '../../utils/helcim.js'
export default defineEventHandler(async (event) => {
try {
const body = await validateBody(event, helcimInitializePaymentSchema)
const metaType = body.metadata?.type
const isEventTicket = metaType === PAYMENT_METADATA_TYPES.EVENT_TICKET
const isSeriesTicket = metaType === PAYMENT_METADATA_TYPES.SERIES_TICKET
const isTicket = isEventTicket || isSeriesTicket
const isMembershipSignup = metaType === PAYMENT_METADATA_TYPES.MEMBERSHIP_SIGNUP
if (!isTicket) {
if (isMembershipSignup) {
const bridgeMember = await getSignupBridgeMember(event)
if (!bridgeMember) {
await requireAuth(event)
}
} else {
await requireAuth(event)
}
}
let amount = 0
let description = body.metadata?.eventTitle
if (isTicket) {
// Re-derive price server-side; never trust body.amount for ticket flows.
const caller = await getOptionalMember(event).catch(() => null)
const lookupEmail = body.metadata?.email?.toLowerCase()
const member = caller || (lookupEmail
? await Member.findOne({ email: lookupEmail })
: null)
const accessMember = hasMemberAccess(member) ? member : null
if (isEventTicket) {
const eventId = body.metadata?.eventId
if (!eventId) {
throw createError({ statusCode: 400, statusMessage: 'metadata.eventId is required for event_ticket' })
}
const eventDoc = await loadPublicEvent(event, eventId)
const ticketInfo = calculateTicketPrice(eventDoc, accessMember)
if (!ticketInfo) {
throw createError({ statusCode: 403, statusMessage: 'No tickets available for your membership status' })
}
amount = ticketInfo.price
description = description || eventDoc.title
} else {
const seriesId = body.metadata?.seriesId
if (!seriesId) {
throw createError({ statusCode: 400, statusMessage: 'metadata.seriesId is required for series_ticket' })
}
const series = await loadPublicSeries(event, seriesId)
const ticketInfo = calculateSeriesTicketPrice(series, accessMember)
if (!ticketInfo) {
throw createError({ statusCode: 403, statusMessage: 'No series passes available for your membership status' })
}
amount = ticketInfo.price
description = description || series.title
}
} else {
amount = body.amount || 0
}
const paymentType = isTicket && amount > 0 ? 'purchase' : 'verify'
const requestBody = {
paymentType,
amount: paymentType === 'purchase' ? amount : 0,
currency: 'CAD',
paymentMethod: 'cc'
}
if (body.customerCode && paymentType === 'verify') {
requestBody.customerCode = body.customerCode
}
if (description) {
requestBody.description = description
requestBody.notes = description
const orderId = body.metadata?.eventId || body.metadata?.seriesId
if (orderId) requestBody.orderNumber = `${orderId}`
}
const paymentData = await initializeHelcimPaySession(requestBody)
return {
success: true,
checkoutToken: paymentData.checkoutToken,
secretToken: paymentData.secretToken,
// Echo derived amount so the client can sanity-check before opening modal.
amount
}
} catch (error) {
if (error.statusCode) throw error
console.error('Error initializing HelcimPay:', error)
throw createError({
statusCode: 500,
statusMessage: 'An unexpected error occurred'
})
}
})