Back to Work Case Study · Web App

CareFirst Booking System

A live operator-facing booking and payment intake system for CareFirst — Next.js 16, Supabase with real Row-Level Security, PayFast card payments, full audit and security dashboards, and an idempotent SSO handoff to the downstream clinical consultation app. Live in production at bookings.carefirst.co.za.

Next.js 16 React 19 TypeScript Supabase RLS PayFast Playwright Docker Traefik POPIA-aware
CareFirst Booking System — operator-facing booking and payment intake app, live at bookings.carefirst.co.za

About the System

An operator-facing booking and payment intake for CareFirst — a third-party portal where operators search for patients, capture booking details, take card payment via PayFast, and hand off to a separate clinical consultation product. Every step is designed to be safe to fail, idempotent on re-click, and auditable end-to-end. Live at bookings.carefirst.co.za.

Client

CareFirst Healthcare Solutions — internal stakeholder for whom this system is the front desk of the operation: where the customer journey begins.

Tech Stack

Next.js 16 (App Router, RSC, output: standalone), React 19, TypeScript, Tailwind 4 with @theme inline tokens, Supabase (Postgres + Auth + Storage) with real RLS, PayFast, Nodemailer/SMTP, Playwright E2E, MDX + Mermaid for embedded reports.

Status

Live in production at bookings.carefirst.co.za over HTTPS, served by Docker behind Traefik on a Hostinger VPS. 37 numbered SQL migrations shipped. PIN-based operator sign-in active.

Money, Medical Data, and Two Apps That Must Stay in Sync

A booking system that takes real card payments and hands a patient to a separate clinical app has to get authorisation, payment integrity, and handoff idempotency right — all three, all the time.

Three-Role RBAC

System admins, unit managers, and users — each role with a distinct boundary that must be enforced in code and in the database.

PayFast Integrity

Card payments via PayFast — signed initiates, ITN webhook validation, and a pull-based reconciliation fallback when ITN doesn't arrive.

Idempotent Clinical Handoff

The handoff to the downstream consultation product can't double-register a patient if an operator clicks Start Consult twice.

Defensible Auth Posture

6-digit PIN auth has to be brute-force-resistant, lockout-aware, and observable — failed attempts, suspicious activity, and trusted IPs all surfaced to admins.

Defence in Depth, Everywhere

Every guard exists in at least two places — app and database, app and webhook signature, app and Postgres trigger. The booking flow itself is short. The safety net under it is dense.

  1. 01

    RBAC, Twice

    Three roles (system_admin, unit_manager, user) enforced in app code and Postgres RLS — a broken server-side guard cannot leak another unit's data.

  2. 02

    4-Step Booking Flow

    Patient search → details → PayFast payment → vitals + T&Cs handoff. Each step persists so refresh or close doesn't lose state.

  3. 03

    PayFast With a Fallback

    Signed initiate, ITN validation, and a pull-based reconciliation poller against PayFast's Transaction History API — if the ITN webhook never arrives, the poller catches it.

  4. 04

    Idempotent Handoff

    The SSO handoff POSTs to the consultation app's auto-register endpoint, stores the returned redirect URL, flips status to Successful, and opens the consult — re-clicking just reopens the stored URL.

  5. 05

    Postgres Triggers as Guard Rails

    A Postgres trigger prevents UPDATEs that move a booking to Payment Complete outside the payment flow. Application bugs can't quietly mark bookings as paid.

  6. 06

    Operate Like a Product

    OPERATIONS.md in the repo covers manual refund, terminology rules, deploy, and rollback. A /system-audit.html page tracks the security backlog publicly within the team.

What's Inside

Booking & Payment

  • 4-step booking flow with per-step persistence100%
  • PayFast initiate + ITN + reconciliation poller100%
  • Abandoned-booking recovery (auto + manual)100%
  • Coupon system (client-scoped, release-on-abandon)100%

Auth & Security

  • 6-digit PIN auth · brute-force throttle100%
  • Self-service "Forgot PIN?" reset (salted, token)100%
  • Security dashboard: failed/active/suspicious/history100%
  • Trusted IP allowlist + new-IP flagging100%

Admin & Reporting

  • Full audit log + CSV export100%
  • User / Client / Unit CRUD with audit trail100%
  • MDX + Mermaid embedded reports100%
  • POPIA consent + right-to-erasure endpoint100%

Engineering Choices Worth Calling Out

Four decisions that turn a payment-taking booking app into something safe to actually take payment with.

Authorisation in Two Places

App-layer role checks are mirrored by Postgres RLS policies. The same query a buggy admin endpoint might issue is silently filtered by the database — defence in depth, not just defence on paper.

ITN Signature That Actually Validates

PayFast's PHP reference disagreed with naïve implementations on empty custom_* fields and trimmed values. A dedicated computeItnSignature() mirrors the PHP sample exactly — ITN now validates against both sandbox and production.

Idempotent Handoff by Design

Re-clicking Start Consult on a Successful booking reopens the stored redirect URL instead of making a fresh downstream call. handed_off_at, external_reference_id, handoff_status, retry count, and error reason all live in the DB.

Trigger-Backed Payment Integrity

A Postgres trigger blocks UPDATEs that flip a booking status to Payment Complete outside the payment flow. No app bug — present or future — can move a booking into a paid state by accident.

40 Findings, Tracked to Closure

A self-authored audit produced both confirmations and a backlog. Most have already shipped across migrations 001 → 037, including the HTTPS migration and the move from pilot to live production.

Live in production on HTTPS. The system is now serving real operators at bookings.carefirst.co.za — moved off the original HTTP pilot, behind Traefik on a Hostinger VPS, with the production PIN sign-in flow live.

RLS is real, not theatrical. Row-Level Security policies back every app role check — the audit verified that broken server-side guards do not leak data across units.

Forward-only numbered migrations. 001 → 037 in the repo, each single-purpose (e.g. 011_lock_booking_payment_fields.sql, 016_lock_booking_insert_status.sql). Schema evolution is auditable, not a folder of mystery SQL.

Brute-force protection in place. 5 failures per 15 minutes throttles PIN entry; PIN re-verification is required for destructive admin actions (delete, toggle, role change).

Operations runbook committed. OPERATIONS.md covers manual refund procedure (with example audit-log SQL), terminology rules, deploy, and rollback. Not "documented when it ships" — documented before.

Single-command deploys. Multi-stage Dockerfile (deps → builder → runner) with Traefik labels — production deploy is a git pull && docker compose up -d --build on the VPS; rollback is the previous commit plus the same command.

How It's Run

The whole system — schema, RLS, payment, auth, deploys — is structured so the on-call playbook is short.

ConcernMechanism
Schema evolutionForward-only numbered SQL migrations 001 → 037, single-purpose
AuthorisationApp role checks + Postgres RLS (defence in depth)
Payment integrityPayFast ITN signature + reconciliation poller + Postgres status trigger
Auth resilience5/15-min throttle, salted PIN reset, PIN re-verify on destructive actions
Audit trailFull audit log with filters, pagination, CSV export, dedicated /audit-log page
DeployMulti-stage Docker, Traefik, Hostinger VPS, OPERATIONS.md
CompliancePOPIA consent capture, right-to-erasure endpoint (migration 018)
TestingPlaywright end-to-end suite

Building Something That Takes Money and Touches Patient Data?

Software that takes payment and touches health information needs more than a tidy UI. It needs defence in depth at every layer, audit trails that hold up, and an operations posture that survives the on-call page at 2 AM. That's what this kind of system is for.

Builds like this sit under Web Development services — Next.js, Supabase, PayFast integration, Postgres Row-Level Security — paired with AI & Automation on the workflow side.