diff --git a/app/components/EventTicketPurchase.vue b/app/components/EventTicketPurchase.vue index f0eddb4..10c33ef 100644 --- a/app/components/EventTicketPurchase.vue +++ b/app/components/EventTicketPurchase.vue @@ -330,7 +330,6 @@ const handleSubmit = async () => { await initializeTicketPayment( props.eventId, form.value.email, - ticketInfo.value.price, props.eventTitle, ); diff --git a/app/composables/useHelcimPay.js b/app/composables/useHelcimPay.js index 775e86d..5e07a30 100644 --- a/app/composables/useHelcimPay.js +++ b/app/composables/useHelcimPay.js @@ -29,26 +29,14 @@ export const useHelcimPay = () => { } }; - // Initialize payment for event ticket purchase - const initializeTicketPayment = async ( - eventId, - email, - amount, - eventTitle = null, - ) => { + const _initializeTicket = async (metadata, errorPrefix) => { try { const response = await $fetch("/api/helcim/initialize-payment", { method: "POST", body: { customerId: null, - customerCode: email, // Use email as customer code for event tickets - amount, - metadata: { - type: "event_ticket", - eventId, - email, - eventTitle, - }, + customerCode: metadata.email, + metadata, }, }); @@ -62,50 +50,24 @@ export const useHelcimPay = () => { }; } - throw new Error("Failed to initialize ticket payment session"); + throw new Error(`Failed to initialize ${errorPrefix} session`); } catch (error) { - console.error("Ticket payment initialization error:", error); + console.error(`${errorPrefix} initialization error:`, error); throw error; } }; - // Initialize payment for series pass purchase - const initializeSeriesTicketPayment = async ( - seriesId, - email, - seriesTitle = null, - ) => { - try { - const response = await $fetch("/api/helcim/initialize-payment", { - method: "POST", - body: { - customerId: null, - customerCode: email, - metadata: { - type: "series_ticket", - seriesId, - email, - eventTitle: seriesTitle, - }, - }, - }); + const initializeTicketPayment = (eventId, email, eventTitle = null) => + _initializeTicket( + { type: "event_ticket", eventId, email, eventTitle }, + "ticket payment", + ); - if (response.success) { - checkoutToken = response.checkoutToken; - secretToken = response.secretToken; - return { - success: true, - checkoutToken: response.checkoutToken, - amount: response.amount, - }; - } - - throw new Error("Failed to initialize series payment session"); - } catch (error) { - console.error("Series payment initialization error:", error); - throw error; - } - }; + const initializeSeriesTicketPayment = (seriesId, email, seriesTitle = null) => + _initializeTicket( + { type: "series_ticket", seriesId, email, eventTitle: seriesTitle }, + "series payment", + ); // Show payment modal const showPaymentModal = () => { @@ -179,6 +141,7 @@ export const useHelcimPay = () => { if (typeof window.appendHelcimPayIframe === "function") { // Set up event listener for HelcimPay.js responses const helcimPayJsIdentifierKey = "helcim-pay-js-" + checkoutToken; + let observerTimer, paymentTimer; const handleHelcimPayEvent = (event) => { console.log("Received window message:", event.data); @@ -188,6 +151,8 @@ export const useHelcimPay = () => { // Remove event listener to prevent multiple responses window.removeEventListener("message", handleHelcimPayEvent); + clearTimeout(observerTimer); + clearTimeout(paymentTimer); // Close the Helcim modal if (typeof window.removeHelcimPayIframe === "function") { @@ -277,10 +242,10 @@ export const useHelcimPay = () => { ); // Clean up observer after a timeout - setTimeout(() => observer.disconnect(), 5000); + observerTimer = setTimeout(() => observer.disconnect(), 5000); // Add timeout to clean up if no response (10 minutes for manual card entry) - setTimeout(() => { + paymentTimer = setTimeout(() => { console.log("Payment timeout reached, cleaning up event listener..."); window.removeEventListener("message", handleHelcimPayEvent); reject(new Error("Payment timeout - no response received")); diff --git a/docs/LAUNCH_READINESS.md b/docs/LAUNCH_READINESS.md index feae0ca..bc54672 100644 --- a/docs/LAUNCH_READINESS.md +++ b/docs/LAUNCH_READINESS.md @@ -122,6 +122,7 @@ See `docs/TODO.md` for: - Members table NAME column clipping. - OWASP ASVS L1 Phase 4 (file-upload validation pipeline, granular RBAC, credential encryption). - `tickets/available.get.js:115` `memberSavings` block reports `$0 saved` for inactive members — cosmetic; suppress comparison block when `!hasMemberAccess(member)` if it ever surfaces in UI. +- Simplify-pass follow-ups (2026-04-25): source-grep test bloat, login/verify rate-limit gap, stringly-typed `metadata.type`, reconcile-payments sequential loop, stale `new Date()` in events list, `loadPublicSeries` helper extraction. ### Known gotchas worth addressing post-launch @@ -148,3 +149,4 @@ Context: Phase 4 audit against `docs/specs/events-visual-audit-findings.md` fixe - Update stale tier comment in `app/composables/useMemberPayment.js:59`. - Update error log message referencing "tier" in `server/api/members/update-contribution.post.js:221`. - Rename `handleUpdateTier` handler in `app/pages/member/account.vue`. + diff --git a/server/api/auth/verify.post.js b/server/api/auth/verify.post.js index 4ada236..1a0a6cd 100644 --- a/server/api/auth/verify.post.js +++ b/server/api/auth/verify.post.js @@ -3,6 +3,7 @@ import jwt from 'jsonwebtoken' import Member from '../../models/member.js' import { validateBody } from '../../utils/validateBody.js' import { verifyMagicLinkSchema } from '../../utils/schemas.js' +import { setAuthCookie } from '../../utils/auth.js' export default defineEventHandler(async (event) => { const { token } = await validateBody(event, verifyMagicLinkSchema) @@ -57,20 +58,7 @@ export default defineEventHandler(async (event) => { { runValidators: false } ) - // Issue session token with tokenVersion claim for revocation support - const sessionToken = jwt.sign( - { memberId: member._id, email: member.email, tv: member.tokenVersion }, - config.jwtSecret, - { expiresIn: '7d' }, - ) - - setCookie(event, 'auth-token', sessionToken, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - path: '/', - maxAge: 60 * 60 * 24 * 7, // 7 days - }) + setAuthCookie(event, member) const redirectUrl = member.role === 'admin' ? '/admin' : '/member/dashboard' return { success: true, redirectUrl }