TC-EVT-008: Event Check-in — Staff Access-Code Portal
🆕 New on stage — EPIC #412, #413–#428 (Jun 25 release); #524 (Jul 1 release). A brand-new on-site check-in surface: staff redeem a time-boxed, event-scoped access code at a public no-login page and get a scoped session valid for one event only. This is a new, separate model from the original logged-in-admin check-in path (TC-EVT-005 / the legacy
POST /api/events/checkin flow) — both coexist.
Module: Events & Registrations — Check-in (staff access-code portal)
Primary Test Users: events@demo.membervu.com / Events123! (generates codes, admin side); staff redemption needs no account.
Priority: P1 (flagship new feature — cross-event isolation is a security gate)
Objectives
- Admin generates a time-boxed, event-scoped access code from the Access Codes panel.
- Staff redeem
code + nameat the public page/:tenantSlug/events/:eventId/check-in/staff(no login) → scoped session token. - Confirm the scoped session is isolated to its own event — reused against a different event's check-in API is denied.
- Attendee search + member/guest pill in the portal; check someone in.
- Walk-in tab: free walk-in, then a paid walk-in that produces a real Invoice + Payment record (gateway is inert — records only) plus a best-effort welcome email.
- Audit trail is immutable from the portal and exportable to CSV.
- Reconcile — consolidated stats surface on the attendance report.
- An ended-event / expired / revoked code is rejected with the correct distinct error, including the newer
EVENT_ENDEDcode (#524).
Scenario summary
| ID | Scenario | Key data assertion |
|---|---|---|
| CHK8-01 | Generate access code | POST /events/:eventId/checkin/codes succeeds; code appears in the Access Codes panel with expiry; the staff check-in URL is shown and copyable (#524 follow-up). |
| CHK8-02 | Staff redeem (valid code) | POST /api/events/:eventId/checkin/session with {code, staffName} returns a scoped JWT; portal loads for that event only. |
| CHK8-03 | Cross-event isolation (security gate) | Reusing the scoped token against a different event's check-in-by-code call returns 403 OUT_OF_SCOPE — never a successful check-in. |
| CHK8-04 | Attendee search + member/guest pill | GET /events/:eventId/checkin/search returns matches; each result is labeled Member or Guest. |
| CHK8-05 | Free walk-in | POST /events/:eventId/checkin/walk-in (no payment payload, free event) creates a CONFIRMED, auto-CHECKED_IN registration — no Invoice. |
| CHK8-06 | Paid walk-in → real records | Paid walk-in with paymentCollectedOffline:true creates a shell guest Member + a real Invoice (status PAID) + a real Payment (channel MANUAL_OTHER, status SUCCEEDED) — no live gateway charge (gateway inert on staging). A welcome email lands in webmail. |
| CHK8-07 | Paid walk-in, no payment payload | 402 PAYMENT_REQUIRED with {amount, currency}; no registration row is created. |
| CHK8-08 | Audit trail immutable + export | Every check-in / walk-in / undo appears in the audit panel with actor + timestamp; no edit/delete UI exists; CSV export matches the panel. |
| CHK8-09 | Reconcile / consolidated stats | Attendance report shows checked-in vs no-show vs registered counts in one place, reflecting portal activity. |
| CHK8-10 | Expired / revoked / event-ended code | Expired code → 401 ACCESS_CODE_EXPIRED; revoked → 401 ACCESS_CODE_REVOKED; event already over → 401 EVENT_ENDED (#524, replaces the old expired message for this case). |
Rate limit note: the code-redemption endpoint (
POST /api/events/:eventId/checkin/session) is limited to 10 attempts / IP / 15 min. A 429 during repeated testing means you hit the limiter, not a bad code — restart the backend (docker restart membervu-backend) to reset it.
➡️ Full click-by-click steps: TC-EVT-008 (Detailed).