← Selected work

Platform Engineering · Ticketing · Payments

Ticket Trove Africa

An East African event platform spanning public discovery, checkout-time ticket reservation, KCB Buni / M-PESA payments, organizer operations, ticket issuance, transfer, and entry validation.

Role
Product / Platform Engineer
Frontend
Next.js 16 · React 19 · MUI 7
Backend
Fastify 5 · PostgreSQL · Redis
Delivery
Docker · GitHub Actions · BullMQ
Ticket Trove Africa live product visual.

Product surface

One product, two operating surfaces.

The attendee side needs fast event discovery and a trustworthy purchase path. The organizer side needs control over events, inventory, promos, holds, and operational state. Both depend on the same transaction model underneath.

  • Attendeediscover · cart · checkout · tickets
  • Organizercreate · publish · tickets · promo · holds
  • Platformreservation · payment · issuance · validation

System model

Ticketing is a concurrency problem disguised as checkout.

The system has to preserve buyer intent without overselling organizer inventory. That means the cart, reservation, order, payment callback, issued ticket, and entry scan cannot be treated as unrelated screens or CRUD records.

Three connected lanes

Intent, inventory, and operations move together.

Each lane has different UX concerns, but the platform has to keep all three aligned around the same event and sale state.

Attendee lane

From interest to a usable ticket

Browsing and cart state remain lightweight until checkout, when inventory becomes a transactional concern.

BrowseCartCheckoutTicket
Platform lane

Protect the authoritative sale

Redis locks, PostgreSQL transactions, payment state, and ticket validation boundaries protect the same inventory from races.

ReservePayCompleteValidate
Organizer lane

Control the event after publish

Organizer tooling covers event lifecycle, ticket types, pricing, promotional rules, holds, and event-level operational metrics.

CreatePublishControlOperate

Checkout & inventory

Reserve late. Finalize once. Release safely.

The platform deliberately does not reserve inventory when a buyer adds a ticket to the cart. Reservation begins at checkout, where the intent is strong enough to justify temporarily removing capacity from other buyers.

Successful path

Checkout becomes a controlled state transition.

The order and payment path is guarded with Redis locks while authoritative inventory changes are completed through the database.

01

Cart

Buyer intent only; inventory is not reserved yet.

02

Reserve

Checkout reserves ticket capacity for the pending order.

03

Order

Create the pending sale with customer and ticket context.

04

KCB Buni

Initiate the mobile-money request and persist payment state.

05

Callback

Lock callback processing and atomically complete the order.

06

Delivery

Queue ticket PDFs and buyer confirmations after completion.

Failed / expired payment → cancel order → release inventory → notify buyer

Payment state

M-PESA success is only the midpoint of the transaction.

A successful STK response does not make a ticket sale authoritative. The backend still has to correlate the callback to the pending order, reject duplicate processing, record the receipt, complete the sale, generate tickets, and dispatch background delivery work.

KCB Buni callback path

External payment state becomes internal business state.

Checkout request identifiers are cached for callback correlation, and callback handling acquires a Redis lock before changing the payment and order state.

OAuth + STK

Initiate

Cache Buni access tokens and send the payment request for a pending order.

Redis correlation

Match

Map the checkout request back to its order while the payment is in flight.

Callback lock

Process once

Prevent duplicate callback workers from completing the same payment concurrently.

Atomic order

Finalize

Persist the payment result, complete or cancel the order, and move inventory safely.

Frontend architecture

The browser surface is built for regional product constraints.

The frontend uses Next.js 16 with localized routes, explicit server-state and client-state boundaries, strict form validation, performance work, and an installable PWA foundation.

Frontend stack

Product concerns have dedicated boundaries.

React Hook Form and Zod handle form contracts alongside these broader application boundaries.

Next.js 16

Application shell

App Router, localized routes, public product surfaces, protected organizer routes, and SEO.

next-intl

Regional language

English and Kiswahili routing are part of the application model rather than a later translation layer.

Query + Zustand

State separation

TanStack Query owns server state while Zustand handles focused client-side state.

Serwist

PWA readiness

Manifest and service-worker tooling establish an installable, offline-aware browser foundation.

Backend architecture

A modular monolith with explicit transactional boundaries.

The Fastify backend is intentionally a single deployable application, but its domain modules keep authentication, event management, organizations, payments, and ticketing from collapsing into one undifferentiated service layer.

01

Authentication

JWT access/refresh flows, verification paths, permission rules, rate limiting, and security-oriented Fastify plugins.

02

Events

Event lifecycle, public discovery, organizer editing, ticket types, pricing, promo codes, holds, and event-level metrics.

03

Organizations

Organizer ownership and membership boundaries keep operational access scoped to the correct organization.

04

Payments

KCB Buni STK initiation, callback correlation, payment records, failure handling, and authoritative order completion.

05

Ticketing

Cart, checkout reservation, orders, inventory, ticket records, dynamic QR validation, entry OTPs, scan logs, and transfers.

06

Delivery & jobs

BullMQ-backed work queues separate ticket PDF generation and buyer notifications from the payment callback request path.

Ticket integrity

The ticket stays stateful after purchase.

Issuing a QR image is not the end of the security model. Entry validation combines short-lived QR data, a separate entry factor, concurrency protection, and scan history so the system can distinguish a valid ticket from an expired, duplicated, or wrong-event attempt.

Entry controls

Validation is treated as another transactional boundary.

The same ticket can also move between people through an explicit transfer lifecycle rather than silently editing ownership data.

Dynamic QR

30-second TOTP window

Ticket UUID, short-lived TOTP data, and timestamp rotate the scannable payload.

Entry OTP

Separate expiring factor

A hashed entry OTP has its own expiry and must validate alongside the ticket QR state.

Scan control

Duplicate-aware validation

Rate limits, Redis locking, ticket state checks, and scan logs protect concurrent entry attempts.

Transfer lifecycle

Ownership changes are explicit

Claim tokens, transfer limits, cancellation, history, and guest/authenticated claiming preserve provenance.

Delivery & operations

Deployment is part of the system design.

The backend repository carries its operational path with the code: integration-service dependencies in CI, container builds, staged deployment, migration preflight, health checks, and a production backup gate.

Delivery path

Build, verify, migrate, deploy, observe.

Security scans are present as visibility controls; the current npm audit and optional Snyk steps are non-blocking rather than being described as hard release gates.

CI

Test against dependencies

PostgreSQL 16 and Redis 7 service containers support lint, type checks, tests, and build verification.

Docker + GHCR

Package once

GitHub Actions builds the application image and publishes deployment artifacts to GitHub Container Registry.

Staging

Migrate before deploy

Migration preflight, database migration, container recreation, and local/public health checks run in deployment.

Production

Backup + manual approval

The production path is manually dispatched and creates a database backup before migration and deployment.

Engineering evidence

The implementation boundary is explicit.

The implementation repositories are private. This case study exposes the architecture, transaction model, security boundaries, and delivery controls without publishing proprietary source code.

Frontend

Product application

Next.js 16 App Router, English/Kiswahili routes, public event/cart/ checkout/ticket surfaces, organizer dashboard routes, PWA tooling, and the live Ticket Trove product.

Backend

Transactional ticketing code

Fastify modules, checkout-time reservation, Redis locks, atomic order completion/cancellation, KCB Buni callback handling, ticket validation, transfer workflows, and PostgreSQL migrations.

Delivery

CI/CD and operational controls

Test dependencies, Docker builds, GHCR delivery, deployment migrations, health checks, failure alerts, Sentry integration, and the production backup step are represented in repository workflows.

Evidence boundary

Implemented code is not the same as every roadmap target being achieved.

The case study intentionally avoids presenting planning targets or incomplete surfaces as shipped proof.

  • The dedicated organizer analytics tab is still marked “Coming Soon”; event-level metrics already exist elsewhere in the dashboard.
  • Roadmap load and scale targets are goals, not claimed benchmark results.
  • Payment-provider support in code is not presented as proof that every rail has been production-validated.
  • The implementation repositories are private; this page exposes architecture and engineering decisions without publishing proprietary source.

Ticket Trove Africa

Regional product constraints become platform constraints.

Payment rails, inventory concurrency, mobile entry, language, connectivity, and organizer operations all influence the architecture.