Day-of-launch deep-dive audit and remediation. 11 issues fixed across security, correctness, and reliability. Tests: 698 → 758 passing (+60), 0 failing, 2 skipped. CRITICAL (security) Fix #1 — HELCIM_API_TOKEN removed from runtimeConfig.public; dead useHelcim.js deleted. Production token MUST BE ROTATED post-deploy (was previously exposed in window.__NUXT__ payload). Fix #2 — /api/helcim/customer gated with origin check + per-IP/email rate limit + magic-link email verification (replaces unauthenticated setAuthCookie). Adds payment-bridge token for paid-tier signup so users can complete Helcim checkout before email verify. New utils: server/utils/{magicLink,rateLimit}.js. UX: signup success copy now prompts user to check email. Fix #3 — /api/events/[id]/payment deleted (dead code with unauth member-spoof bypass — processHelcimPayment was a permanent stub). Removes processHelcimPayment export and eventPaymentSchema. Fix #4 — /api/helcim/initialize-payment re-derives ticket amount server-side via calculateTicketPrice and calculateSeriesTicketPrice. Adds new series_ticket metadata type (was being shoved through event_ticket with seriesId in metadata.eventId). Fix #5 — /api/helcim/customer upgrades existing status:guest members in place rather than rejecting with 409. Lowercases email at lookup; preserves _id so prior event registrations stay linked. HIGH (correctness / reliability) Fix #6 — Daily reconciliation cron via Netlify scheduled function (@daily). New: netlify.toml, netlify/functions/reconcile-payments.mjs, server/api/internal/reconcile-payments.post.js. Shared-secret auth via NUXT_RECONCILE_TOKEN env var. Inline 3-retry exponential backoff on Helcim transactions API. Fix #7 — validateBeforeSave: false on event subdoc saves (waitlist endpoints) to dodge legacy location validators. Fix #8 — /api/series/[id]/tickets/purchase always upserts a guest Member when caller is unauthenticated, mirrors event-ticket flow byte-for-byte. SeriesPassPurchase.vue adds guest-account hint and client auth refresh on signedIn:true response. Fix #9 — /api/members/cancel-subscription leaves status active per ratified bylaws (was pending_payment). Adds lastCancelledAt audit field on Member model. Indirectly fixes false-positive detectStuckPendingPayment admin alert for cancelled members. Fix #10 — /api/auth/verify uses validateBody with strict() Zod schema (verifyMagicLinkSchema, max 2000 chars). Fix #11 — 8 vitest cases for cancel-subscription handler (was uncovered). Specs and audit at docs/superpowers/specs/2026-04-25-fix-*.md and docs/superpowers/plans/2026-04-25-launch-readiness-fixes.md. LAUNCH_READINESS.md updated with new test count, 3 deploy-time tasks (rotate Helcim token, set NUXT_RECONCILE_TOKEN, verify Netlify scheduled function), and Fixed-2026-04-25 fix log.
40 lines
1.2 KiB
JavaScript
40 lines
1.2 KiB
JavaScript
/**
|
|
* Netlify scheduled function — daily reconciliation of Helcim payments.
|
|
*
|
|
* Calls the protected Nitro route `/api/internal/reconcile-payments` with the
|
|
* shared-secret header. Heavy lifting (Mongo queries, Helcim API calls, retry
|
|
* logic) lives in the Nitro handler so it can use auto-imported utils.
|
|
*
|
|
* Required env (set in Netlify dashboard):
|
|
* - URL (set automatically by Netlify)
|
|
* - RECONCILE_TOKEN (must match NUXT_RECONCILE_TOKEN in Nitro runtime config)
|
|
*
|
|
* Schedule: @daily (00:00 UTC). Also pinned in netlify.toml.
|
|
*/
|
|
|
|
export default async () => {
|
|
const url = `${process.env.URL}/api/internal/reconcile-payments`
|
|
const token = process.env.RECONCILE_TOKEN
|
|
|
|
if (!token) {
|
|
const msg = '[reconcile] RECONCILE_TOKEN not configured; aborting'
|
|
console.error(msg)
|
|
return new Response(msg, { status: 500 })
|
|
}
|
|
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'X-Reconcile-Token': token }
|
|
})
|
|
const body = await res.text()
|
|
if (!res.ok) {
|
|
console.error('[reconcile] route failed', res.status, body)
|
|
return new Response(body, { status: res.status })
|
|
}
|
|
console.log('[reconcile] ok', body)
|
|
return new Response(body, { status: 200 })
|
|
}
|
|
|
|
export const config = {
|
|
schedule: '@daily'
|
|
}
|