StackMoth/Work/Reservation platform

Booking & payments

A booking platform that refuses to sell the same court twice

A reservation, billing and front-desk platform for a Colombo basketball facility operator, live and taking money daily. The interesting engineering is not the booking form. It is everything that makes a double booking impossible.

Client
A Colombo basketball facility operator
Period
Dec 2024 to Aug 2026, 21 months
Scale
About 48,000 lines, 215 commits
In production
1,558 registered users, 3 locations, 9 courts
Stack
Express 4.21, TypeScript, Next.js 15, React 18, PostgreSQL
Payments
Dialog Genie IPG, multi-merchant
Tests
274 backend tests
Access
Live and in daily use. Not linked publicly.

All case studies

The problem is physical, not clerical

A court sold twice is not a scheduling annoyance. It is two paying customers standing on the same floor at the same time, and a front desk with no good answer.

Half-court and full-court reservations overlap. Two bookings that look completely different in the interface can be the same physical space, so the rule the system has to hold is not "no duplicate rows" but "no overlapping claim on the same court".

Double booking is refused three times over

The platform does not rely on a single check. Three independent layers have to fail before a conflicting reservation can exist:

  • A transaction-scoped advisory lock. pg_advisory_xact_lock serialises requests that could collide, so two simultaneous bookings for the same slot are considered one after the other rather than at once.
  • Two overlapping partial unique indexes. Half-court and full-court exclusion is encoded as a database invariant. The rule holds even if the application is bypassed entirely, and it survives any future code that forgets it exists.
  • Clean failure at the boundary. A 23505 unique violation is translated into an HTTP 400, so the last line of defence still produces a comprehensible error rather than a 500.

There is no Redis and no external lock service. The concurrency primitives are PostgreSQL's own, which is a deliberate trade recorded in the repository's documentation under its known production risks rather than left implicit.

Money settles to the right company

The facilities are operated by different legal entities, so payment routing is a correctness problem, not a configuration detail. Payments run through Dialog Genie with merchant selection driven by location, so funds settle to the company that actually owns the booking.

  • The server re-queries the payment provider for the authoritative result. Webhooks are treated as a hint to go and look, never as the source of truth.
  • Webhook verification is HMAC-SHA256 with a five-minute replay window, compared using timingSafeEqual.

What a real money incident changed

In July 2026 a payment incident put a set of transactions into a state the system could not describe. The response was a subsystem, not a patch:

  • A classification taxonomy, so an incident has a type rather than a comment thread.
  • An idempotent incident ledger, so replaying a reconciliation cannot double-count it.
  • A received_under_review state, so the system never tells a customer that a payment failed while their money may in fact have moved.
  • A cancellation_source provenance column, so every cancellation records who or what caused it.

The reconciliation that followed covered 46 transactions and is documented in the repository rather than in someone's memory.

One deadline, one definition

A single shared SQL fragment defines the payment deadline. The deadline shown to the customer and the deadline enforced by the system are the same expression, so they cannot drift apart through an edit to one and not the other.

Tested where it would actually break

274 backend tests, including tests that assert on generated SQL text. That is an unusual thing to test, and it exists for a specific reason: a refactor that quietly drops an advisory lock or a partial index would otherwise still pass a suite that only checks behaviour under low concurrency.

AI-assisted, human-accountable. AI tools contributed significantly to the implementation described here. The specification, the review of every change, the production operation and the accountability are mine.

Next case study

CourtRes