# YMAU Registration and Review Desk

> Conference site, Stripe registration, and a staff review desk for Yale Model African Union's sixth conference in Addis Ababa.

Lead Developer · Aug 2026 to now

## Results

- 4,600+ delegates signed up for YMAU VI
- 116 delegations
- 3 languages on the site

## The problem

YMAU VI takes applications from thousands of young delegates, with fees, financial aid, documents, and a cap on paid places. The Secretariat needed one system to register and charge delegates, review aid fairly, and never double-sell a place or mark someone paid by mistake.

## What I built

- A rebuilt yalemodelau.org (with French and Amharic pages) and a seven-step applicant portal with autosave, document uploads, and email sign-in links.
- Stripe Checkout for single and whole-delegation payments, with webhook reconciliation, idempotency keys, refund and dispute flags, and a guard that turns payments off if Stripe's mode and the database disagree.
- A Secretariat desk with three roles, an audit log, and financial aid review where an officer sees other reviewers' scores only after saving their own, so nobody anchors on someone else's number.
- Row-level security on every table, and database traffic cuts when free-tier limits started blocking uploads.

## The hard part

Selling a capped number of places correctly across serverless instances, when one delegate can pay for a whole group and Stripe responses can get lost. Every payment, submission and aid write takes one transaction-scoped Postgres row lock first. A group checkout reserves all its places at once or fails as a whole, and a checkout only expires when Stripe confirms it, never on the app's own clock. I tested it by running the real migration in an in-memory Postgres, with two people racing for the last place and duplicate or out-of-order webhooks.

## How it fits together

- Email sign-in -> Applicant portal (sign in)
- Applicant portal -> Checkout + webhooks (reserve, pay)
- Applicant portal -> Documents (uploads)
- Checkout + webhooks -> Database + capacity lock (reconcile)
- Secretariat desk -> Database + capacity lock (decisions)

Parts David built: Applicant portal, Checkout + webhooks, Database + capacity lock, Secretariat desk.

Invariant: a paid place exists only after Stripe confirms it, and only one writer can hold the capacity lock at a time.

## Decisions

- One transaction-scoped Postgres row lock guards capacity instead of counters checked in app code, so concurrent checkouts can't oversell a place.
- A checkout expires only when Stripe confirms it, never on the app's own clock, so a slow payment can't lose its reserved place.
- Payments switch off automatically if Stripe's test or live mode disagrees with the database, so test charges can never mark a real delegate paid.

Tech: Next.js, Stripe, PostgreSQL. Links: [live](https://www.yalemodelau.org)

Page: https://davidantwi.vercel.app/work/ymau-registration-desk
