# YAIS Platform

> The website and ticketing platform I built for the Yale Africa Innovation Symposium: verified registration, Stripe checkout, one QR pass per attendee, and a check-in desk for staff.

Chief Technology Officer · Sep 2026 to now · in private testing before launch

Scale: In private testing

## Results

- 25 merged pull requests, all mine
- 26 Postgres tables behind deny-all row-level security
- 105 unit tests, plus database race checks
- 1 ticket and email per payment, even on replayed webhooks

## The problem

YAIS V needed a new website and its own ticketing: verified registration, paid tickets for the conference and the gala, a pass people can show at the door, and tools the board can run on event day. Payments had to stay correct when a buyer closes the tab mid-checkout or Stripe sends the same notice twice.

## What I built

- The new public website: home, an archive of past editions with an Africa map of Innovation Lab cases, and the team page.
- Registration with an emailed code, pricing by attendee type with capacity limits, and Stripe Checkout confirmed by a signed webhook.
- One reusable signed QR pass per attendee across every purchase, with a PDF download and confirmation emails sent through a database outbox.
- Partial refunds: refunding the gala keeps conference access on the same pass.
- A staff workspace: a registration desk with attendees and payments, role-based access for Director, Finance, Registration and Check-in with an audit log, and a QR scanner that runs in a phone browser.

## The hard part

Making every payment become exactly one ticket. Stripe can deliver the same webhook twice or out of order, and a buyer can close the tab before the redirect. Fulfillment is idempotent in Postgres, so a replayed webhook finds the order it already filled instead of creating a second ticket or email, and a scheduled job recovers purchases whose webhook never arrived. Check-in has the same problem from the other side: a staff phone can lose a response and retry, and the retry must return the original receipt, not a false 'already scanned'. A row lock rechecks admission when the scan commits, and concurrent transactions in the tests prove it.

## How it fits together

- Registration -> API routes (code)
- API routes -> Database (reserve)
- API routes -> Checkout (pay)
- Checkout -> Fulfillment (signed event)
- Fulfillment -> Pass + email (once)
- Fulfillment -> Database (ticket)
- Database -> Staff workspace (scan)

Parts David built: Registration, API routes, Database, Fulfillment, Pass + email, Staff workspace.

Invariant: however many times Stripe reports a payment, it becomes exactly one ticket and one email.

## Decisions

- Deny-all row-level security, with database functions as the only way in, so a missed check in an API route can't expose attendee data.
- One pass per person, not per ticket: the QR code stays the same when someone adds the gala or gets a refund, so nobody arrives at the door with the wrong code.
- Access comes from an assigned role, never from a job title or an email domain, and payment amounts are visible only to the Finance role.

Tech: Next.js, TypeScript, Supabase, PostgreSQL, Stripe

Page: https://davidantwi.vercel.app/work/yais-platform
