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.
106 lines
3.9 KiB
JavaScript
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'
|
|
})
|
|
}
|
|
})
|