ghostguild-org/docs/LAUNCH_READINESS.md
Jennie Robinson Faber 6924758f99 docs(launch): check off change-card, magic-link, ticket manual tests
Event ticket purchase, magic-link login, and in-app change-card
verified 2026-04-19. Pre-registrant invite flow deferred pending
no-tiers refactor on parallel worktree.
2026-04-19 18:32:25 +01:00

11 KiB
Raw Blame History

Launch Readiness

Status as of 2026-04-18. Target launch: before 2026-05-01.

Single source of truth for work that must happen before cutover. P0 blocks launch. P1 is strongly preferred but survivable. Completed items have been archived — see ~/.claude/projects/-Users-jennie-Sites-ghostguild-org/memory/project_launch_readiness_archive.md. Post-launch backlog lives in docs/TODO.md.


Current state

  • Vitest: 577/577 passing.
  • Helcim plan consolidation migration ran against prod 2026-04-18 (Monthly plan id 50302, Annual plan id 50303). All six paid-flow manual tests pass via tunnel.
  • Remaining launch blockers: see lists below.

P0 — Must fix before launch

None outstanding. Privacy/Terms pages shipped, duplicate-customer bug fixed, pre-deploy migrations run.


P1 — Strongly preferred before launch

In-app billing management; demote Helcim portal to escape hatch

  • Helcim's hosted portal requires a separate password the member never set during /join. First-touch flow is "click link → see Helcim login → click Forgot password → wait for email → set password → sign in." Reads as broken.
  • Ship in-app equivalents for the 80% case:
    • Past invoices / receipts list on /member/account — new server route pulls from Helcim invoice API by helcimCustomerId; render a simple list with date, amount, download/view link.
    • Change card — reuse useHelcimPay composable to get a new cardToken, then new server route updates the customer's default payment method and the active subscription's payment method.
  • Keep the existing "Manage billing in Helcim →" link but relabel to something like "Advanced billing in Helcim →" so it reads as an escape hatch for disputes/edge cases, not the primary surface.
  • Rough scope: 12 days. Two new server/api/helcim/* routes + two new sections on /member/account.
  • Status (2026-04-19): Implemented on feature/helcim-plan-consolidationserver/api/helcim/payment-history.get.js, server/api/helcim/update-card.post.js, plus the two new sections on app/pages/member/account.vue. Manual browser tests for both flows are in the "Manual browser tests still needed" section below.

Charitable receipts — Phase 1 (docs/specs/receipts-launch-spec.md)

Spec exists but none of it is implemented. Phase 2 (the actual receipt generation) is post-launch (live Jan 2027), but Phase 1 must land at launch so Phase 2 has the data + compliance posture it needs. Skipping Phase 1 means: payments made between launch and Phase 2 may be missing fields needed to receipt them, and the Helcim default confirmation email may make CRA-noncompliant claims.

What needs to ship:

  • Payment logging. Every successful Helcim payment writes a record (member id, amount CAD, payment date, Helcim transaction id, payment type monthly/annual, receiptIssued: false, receiptId: null). Failed payments logged separately. No Payment model exists today; needs new model + webhook wiring in server/api/helcim/.
  • Helcim confirmation email review. Customize so it does NOT use "tax receipt," "donation receipt," or "for tax purposes," and includes the registered charity name "Baby Ghosts Studio Development Fund" plus the disclaimer wording from the spec (section 2). If Helcim's template editor can't accomplish this, suppress the Helcim confirmation and send our own via Resend.
  • Join page copy. Add the factual charity / tax-deductible note near the contribution tiers per spec section 3 — app/pages/join.vue. No form fields, just the line.
  • Member schema field. Add taxReceiptPreferences: { filesCanadianTaxes: Boolean, middleInitial: String|null, confirmedAddress: {...}|null, setupCompletedAt: Date|null } to server/models/member.js. Default null/false. Not exposed in UI yet.

Rough scope: 1 day if Helcim's email template is editable; +0.5 day if we have to route confirmations through Resend instead. Decide which path during implementation.


Deploy checklist

Pre-deploy migrations have all been run. What's left:

  • Merge feature/helcim-plan-consolidation into main.
  • Set NUXT_HELCIM_MONTHLY_PLAN_ID=50302 in Netlify production env.
  • Set NUXT_HELCIM_ANNUAL_PLAN_ID=50303 in Netlify production env.

Env vars required in production (reference):

  • MONGODB_URI
  • JWT_SECRET (or NUXT_JWT_SECRET — the NUXT_ variant wins)
  • RESEND_API_KEY
  • HELCIM_API_TOKEN
  • NUXT_HELCIM_MONTHLY_PLAN_ID
  • NUXT_HELCIM_ANNUAL_PLAN_ID
  • SLACK_BOT_TOKEN
  • BASE_URL
  • OIDC_COOKIE_SECRET
  • NUXT_PUBLIC_HELCIM_PORTAL_URL

Manual browser tests still needed

Cannot be verified by Vitest. All require a real browser + real Helcim test card + real email.

  • Event ticket purchase with payment (HelcimPay.js iframe; use cloudflared tunnel or ngrok HTTPS). (Verified 2026-04-19 via tunnel: guest purchase on "Cooperative Game Dev Masterclass" succeeded — registration recorded with paymentStatus: completed, paymentId: 47230660, tickets.public.sold incremented, guest Member created. Member-ticket path not exercised because this event's member price is $0; no paid-member-ticket event currently seeded.)
  • Pre-registrant invite → accept flow with paid tier (exercises Helcim customer creation during acceptance). (Deferred 2026-04-19 — no-tiers refactor landing on a parallel worktree will replace the accept-invite payment flow; retest once that lands.)
  • Magic-link login including 15-min expiry and jti burn on reuse. (Verified 2026-04-19 against local dev + local Mongo. Target: alex.rivera@pixelcollective.coop (active, member role). Happy path: POST /api/auth/login → 200, magicLinkJti set, magicLinkJtiUsed:false; reconstructed token from stored jti + NUXT_JWT_SECRET and POST /api/auth/verify → 200 with redirectUrl:/member/dashboard, auth-token cookie set (httpOnly, Max-Age=604800, SameSite=Lax), magicLinkJtiUsed:true, lastLogin updated. Replay: same token re-POSTed → 401 at verify.post.js:53 (jti-burn branch), Mongo state unchanged. Expiry: jwt.sign({...},{expiresIn:'-1s'}) with fresh unburned jti on the member → 401 at verify.post.js:22 (jwt.verify catch, before jti check), no mutation.)
  • Guest event signup — four branches: new email + consent, new email without consent, existing guest, existing active member. Confirms cookie only sets for new/guest, and confirmation email appends /login link for real members. (Verified 2026-04-19 via tunnel with throwaway event + timestamped test emails; cleanup done.)
  • Mobile responsive layout — main chrome sidebar hides ≤768px (not ≤1024px as previously noted); in-page two-column layouts collapse at ≤1024px. Mobile header/drawer works on phone widths. (Verified 2026-04-19.)
  • --text-dim / --text-faint WCAG AA contrast check. (Verified 2026-04-19; only live failure was .circle-desc on selected/hover tiles — fixed in e7ad076 by promoting from --text-faint to --text-dim.)
  • In-app payment history (/member/account → Past payments section). Verified: active monthly subscriber sees past charges with dates/amounts; annual subscriber sees their single upfront charge; member with no payments shows empty state; cancelled member still sees historical charges. Per-row download/view link NOT implemented — Helcim's /card-transactions/ API doesn't expose per-transaction receipt URLs. Accepted as satisfied by the existing "Advanced billing in Helcim →" escape hatch, which lands members in the Helcim portal where receipts are downloadable. (Verified 2026-04-19.)
  • In-app change card (/member/account → Change card). Verify: HelcimPay.js modal opens, new card tokenizes, customer's default payment method updates in Helcim dashboard, active subscription's payment method updates, and billing_card_updated activity log entry is written. Force a failure path (e.g. invalid card or Helcim 4xx after default updates) to confirm the rollback in server/api/helcim/update-card.post.js actually restores the prior default. (Verified 2026-04-19.)

Bylaws decoupling — follow-ups (added 2026-04-18)

Context: bylaws are being amended to remove automatic termination for nonpayment. Membership status will be fully decoupled from payment status; failed payments trigger committee outreach, not status change. Copy + UI access gates already aligned in useMemberStatus.js and account.vue (2026-04-18). Server-side status gating shipped as B2 (see archive). The behavioral changes below remain.

Not blocking launch — the amendment hasn't passed yet, and the user-visible copy/UI is already consistent. Pick up once the amendment is ratified.

B1. cancel-subscription flips status to pending_payment

  • server/api/members/cancel-subscription.post.js:31,48
  • When a member cancels their paid subscription, status is set to pending_payment and tier to '0'. Under the new model, cancelling a payment plan moves the member to the $0 tier — status should stay active.
  • Fix: change status: 'pending_payment'status: 'active' in both the findByIdAndUpdate payload (line 31) and the response (line 48). Comment at line 26 also needs updating ("(not cancelled) so member can re-subscribe" → reflect new framing).
  • Add coverage in tests/server/api/cancel-subscription.test.js if it doesn't already exist.

B3. Vestigial pending_payment status

  • Once payment is fully decoupled, pending_payment no longer gates anything and is functionally equivalent to active. Consider removing it from the enum (server/models/member.js:38, server/utils/schemas.js:299) and treating new signups as active from the moment of account creation.
  • Touches: signup flow (helcim/customer.post.js:34, invite/accept.post.js:48), admin filter UI (app/pages/admin/members/index.vue:45,382,499,1145, [id].vue:69,286), admin alerts (server/utils/adminAlerts.js:22,100-116, server/models/adminAlertDismissal.js:6), and a data migration to flip existing pending_payment rows to active.
  • Larger refactor — break out into its own ticket once B1 lands.

B4. Admin "Pending Payment" filter label (cosmetic)

  • app/pages/admin/members/index.vue:45,499, [id].vue:69 show pending_payment as "Pending Payment". If B3 removes the status entirely, this disappears too. If we keep pending_payment for now, rename in admin UI to "Payment setup incomplete" so admins also stop conflating it with membership state.

Post-launch backlog

See docs/TODO.md for:

  • Button minimum target size (WCAG AAA 2.5.5).
  • /oidc/interaction/[uid] routing quirk.
  • Admin layout migration from guild-* tokens to zine spec.
  • Admin dashboard quick-action button contrast.
  • 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.