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.
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.
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.
CareFirst Healthcare Solutions — internal stakeholder for whom this system is the front desk of the operation: where the customer journey begins.
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.
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.
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.
System admins, unit managers, and users — each role with a distinct boundary that must be enforced in code and in the database.
Card payments via PayFast — signed initiates, ITN webhook validation, and a pull-based reconciliation fallback when ITN doesn't arrive.
The handoff to the downstream consultation product can't double-register a patient if an operator clicks Start Consult twice.
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.
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.
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.
Patient search → details → PayFast payment → vitals + T&Cs handoff. Each step persists so refresh or close doesn't lose state.
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.
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.
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.
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.
Four decisions that turn a payment-taking booking app into something safe to actually take payment with.
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.
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.
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.
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.
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.
The whole system — schema, RLS, payment, auth, deploys — is structured so the on-call playbook is short.
| Concern | Mechanism |
|---|---|
| Schema evolution | Forward-only numbered SQL migrations 001 → 037, single-purpose |
| Authorisation | App role checks + Postgres RLS (defence in depth) |
| Payment integrity | PayFast ITN signature + reconciliation poller + Postgres status trigger |
| Auth resilience | 5/15-min throttle, salted PIN reset, PIN re-verify on destructive actions |
| Audit trail | Full audit log with filters, pagination, CSV export, dedicated /audit-log page |
| Deploy | Multi-stage Docker, Traefik, Hostinger VPS, OPERATIONS.md |
| Compliance | POPIA consent capture, right-to-erasure endpoint (migration 018) |
| Testing | Playwright end-to-end suite |
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.