Project dossier
Occasio
Event booking and management platform with payments, QR ticketing, and organizer workflows.
What it solves
Overview
Occasio helps organizers publish events, sell tickets, manage attendees, process payments, and verify QR tickets from an operational dashboard. Interview focus: explain the organizer/attendee split, Prisma relational model, registration-to-order-to-ticket lifecycle, Razorpay and PhonePe verification, QR payload safety, waitlists, discount codes, ticket tiers, polls, team members, certificate generation, uploads, queues, and why payment webhooks are the authority.
Target audience
System design
Architecture
The product uses a React and Vite frontend with a Node backend. The backend owns event data, bookings, uploads, payments, and webhooks, while the frontend gives organizers and attendees separate flows. The source schema models users, events, forms, registrations, orders, tickets, waitlists, discounts, reviews, polls, team members, ticket tiers, speakers, and reminders. The backend also includes payment services, ticket services, queue/reminder services, QR utilities, upload middleware, Cloudinary/R2/S3 helpers, and scanner routes.
Architecture diagram
Attendee experience
Event discovery, booking flow, payment initiation, ticket retrieval, and QR access.
Organizer console
Event creation, capacity management, media uploads, and attendee reporting.
Backend API
RESTful endpoints for events, users, bookings, uploads, payments, and ticket verification.
Commerce and verification
Payment gateway integration, webhook reconciliation, and QR-based entry validation.
Registration lifecycle layer
A registration starts as pending form data, creates an order, moves through payment status, then receives a ticket and check-in state.
Organizer operations layer
Admins manage event teams, polls, reviews, ticket tiers, reminders, speakers, discounts, and certificate templates from operational screens.
Asset and document layer
Poster, ticket PDF, and certificate assets are separated from event metadata and routed through upload/storage utilities.
Implementation surface
Tech stack
Attendee and organizer user interfaces.
Fast local development and SPA bundling.
Backend runtime for REST APIs and payment callbacks.
Routing, middleware, and controller organization.
Payment initiation, status checks, and webhook confirmation.
Event poster and media upload handling.
Typed ORM for PostgreSQL models including events, registrations, orders, tickets, discounts, waitlists, and polls.
Relational source of truth for users, events, orders, tickets, and organizer operations.
Background reminder, notification, and asynchronous job path for operational workflows.
Media and document storage options for posters, tickets, certificates, and downloadable assets.
Operational flow
How it works
Organizers create events and attendees book tickets. Payments are reconciled through webhooks, then QR tickets become valid for entry scanning.
Create event
An organizer enters event details, capacity, ticket rules, and media assets.
Publish listing
The event appears in the attendee-facing catalog with date, venue, price, and availability.
Book and pay
The attendee chooses quantity, starts payment, and the backend creates a pending booking.
Reconcile webhook
The payment provider calls the backend, which verifies the payload and marks the booking paid.
Issue and scan ticket
The system generates a QR ticket that can be verified at check-in against booking state.
Apply discount and ticket-tier rules
Before order creation, the backend computes pricing from ticket tiers, discount code constraints, max uses, and registration quantity.
This keeps pricing authority on the server instead of trusting the browser's displayed total.
Generate provider metadata
Payment requests include order, registration, and event identifiers in provider notes or metadata so callbacks can be reconciled safely.
Razorpay notes and PhonePe merchant transaction IDs are essential when users leave and return from hosted payment pages.
Update attendance state
Scanner routes mark checkedInAt, checkedOutAt, checkedInBy, and legacy scannedAt fields while rejecting revoked or already-used tickets.
The QR code is not the source of truth; it is a lookup payload for server-side validation.
Run post-event operations
Organizers can manage reviews, polls, certificates, reminders, and team access after the core ticket purchase flow.
Sequence diagram
Concept depth
Key concepts
Payments and ticket state are naturally event-driven because external gateways confirm payment asynchronously after a user leaves the app flow.
In Occasio: Occasio treats payment callbacks as events that transition bookings from pending to paid.
Confidence
Implementation evidence
Code highlights
Payment webhook reconciliation
The backend should verify provider callbacks before marking a booking as paid.
The browser redirect is not trusted as payment proof.
Ticket issuance happens after server-side payment confirmation.
QR ticket verification
Ticket scanning checks booking state and prevents duplicate entry.
The encoded code is only an identifier; the backend decides validity.
Duplicate scan prevention is part of the verification path.
Idempotent ticket issuance
A payment completion handler should detect already-paid orders before generating a ticket.
The existing paid order is returned instead of issuing another ticket.
The order update and ticket creation happen in one transaction.
Server-side pricing authority
The backend computes the amount from ticket tiers and discount codes before creating a provider order.
Client totals are display-only; the server recomputes final amount.
Discount use limits must be checked before provider order creation.
Contracts
API design
Base URL: http://localhost:5000/api
/eventsLists published events with dates, venue, capacity, and ticket status.
/bookingsCreates a pending booking and payment order for selected tickets.
{ "eventId": "evt_42", "quantity": 2 }{ "bookingId": "book_91", "paymentOrderId": "order_77" }/payments/webhookVerifies gateway callback and updates booking payment state.
/tickets/verifyValidates a QR ticket and records check-in.
{ "ticketCode": "TICKET-9F42" }/registrationsStores form response, selected ticket tier, attendee identity, and pending registration state before payment.
{ "eventId": "evt_42", "tierId": "tier_student", "formResponse": { "college": "ABC" } }{ "registrationId": "reg_81", "status": "PENDING" }/orders/{orderId}/phonepe/statusChecks PhonePe transaction status using merchant transaction ID and X-VERIFY checksum.
/waitlistAdds an attendee to an event waitlist when capacity or ticket availability is exhausted.
/certificates/generateGenerates participation or prize certificates from event certificate templates and registration data.
/polls/{pollId}/voteRecords attendee poll responses while respecting single or multiple choice poll configuration.
State model
Database design
Data relationship diagram
events
Event listing, schedule, venue, capacity, and organizer ownership.
bookings
Attendee booking intent and status transitions from pending to paid.
tickets
QR ticket codes and check-in timestamps linked to paid bookings.
registrations
Attendee form response and lifecycle state before and after payment.
orders
Payment order linked to a registration, provider, amount, status, and raw provider metadata.
discount_codes
Event-scoped discount rules with type, amount, max uses, active window, and used count.
waitlists
Attendees waiting for capacity, keyed by event and email.
polls
Event poll questions, options, response settings, and end time.
Architecture decisions
Trade-offs
Frontend framework
React with Vite over Next.js
The app is an operational SPA where authenticated workflows matter more than SEO or server rendering.
Payment truth source
Gateway webhook over Frontend success redirect
The redirect can be interrupted or forged. The provider webhook gives server-side confirmation.
Ticket format
QR code identifier over Printable free-form receipt
A QR identifier can be scanned quickly and verified against backend state during entry.
Data model
Explicit Registration, Order, and Ticket tables over One booking table with many nullable fields
The lifecycle has distinct responsibilities: user form data, payment reconciliation, and entry validation. Separate tables make state transitions and debugging clearer.
Payment providers
Razorpay and PhonePe support over Single gateway dependency
Indian event flows often need multiple payment options. Supporting both creates integration complexity but improves operational flexibility.
Asset storage
External object storage helpers over Storing files in the database
Posters, ticket PDFs, and certificates are large binary assets; databases should keep metadata and URLs while object storage handles file delivery.
Scanner validation
Server-verified QR payload over QR code as standalone proof
A QR payload can be copied. The scanner must check payment status, event, revocation, validity window, and scan history on the backend.
Lessons learned
Challenges and solutions
Problem
Payment state can become inconsistent if the user closes the browser after payment.
Solution: Use webhooks as the authority and let the booking page poll or refresh state.
Lesson: Commerce flows should treat the server-side payment event as the source of truth.
Problem
Event posters and uploaded assets need consistent handling across routes.
Solution: Centralize upload middleware and keep event metadata separate from file storage concerns.
Lesson: Media upload is a backend boundary, not just a form field.
Problem
Payment providers can retry callbacks or return the user through different redirect paths.
Solution: Use provider metadata to map callbacks to orders and make order-paid transitions idempotent.
Lesson: Payment code must be designed around retries, not just the happy path.
Problem
Capacity can be oversold if two attendees reserve the last ticket at the same time.
Solution: Compute availability server-side and update registration/order state in a transaction or through a locking strategy.
Lesson: Inventory correctness is a backend concurrency problem.
Problem
Certificate and ticket templates can become tightly coupled to one event design.
Solution: Store certificate mappings and ticket styling as JSON configuration attached to the event.
Lesson: Template systems should keep presentation configuration out of business logic.
Runbook
Requirements and future work
Requirements
- Node.js and npm for backend and frontend development.
- Payment gateway credentials for Razorpay or PhonePe flows.
- File upload storage configuration for event media.
- Organizer and attendee role handling.
- DATABASE_URL must point to the PostgreSQL/Neon database used by Prisma.
- Razorpay and PhonePe credentials are required for live payment flows; sandbox credentials are only for testing.
- Ticket scanning requires backend access so QR payloads can be validated against order and ticket state.
- Object storage credentials are needed for production posters, certificates, and downloadable ticket assets.
- Queue or reminder configuration is needed before relying on automated reminders or background notifications.
Future improvements
- Add live organizer analytics for sales, capacity, and check-ins.
- Support seat maps and tiered ticket inventory.
- Add automated refund and cancellation workflows.
- Add transaction-level capacity locking or reservation expiry for high-demand events.
- Add offline scanner mode with signed short-lived manifests and later reconciliation.
- Add organizer audit logs for event edits, check-ins, refund actions, and certificate generation.
- Add webhook replay tooling so failed payment callbacks can be inspected and retried safely.
Active recall
Interview Q&A
Why should webhooks decide payment success?
What does QR verification need beyond decoding the QR code?
What would you add for high-volume events?
Why separate Registration, Order, and Ticket instead of using one booking table?
How do you make payment callbacks idempotent?
What should the scanner validate before allowing entry?
What is risky about trusting the frontend price?
How would you prevent overselling capacity?
Why store ticketStyle and certificate configs as JSON?
What does PhonePe checksum verification protect against?
What background jobs belong in an event platform?