Case study: building PureTax's full system
How we designed and built the digital front door for PureTax, an Alberton-based tax and accounting practice — fast, mobile-first and built to turn visitors into clients.
A payment notification that's rejected, delivered twice or arrives late shouldn't cost you money or customers. Here's the layered approach we use for PayFast integrations.
Payments are where "it works on my machine" goes to die. Networks drop, users double-click, browsers retry, and payment gateways deliver the same notification more than once. Framework upgrades can break payments silently, too: a change in how CSRF protection is configured can start rejecting every gateway notification, leaving successful payments stuck at pending.
Here's the approach we now use on every PayFast integration.
PayFast's Instant Transaction Notification (ITN) is a server-to-server POST, so it can't carry a CSRF token. It must be excluded from CSRF checks — in Laravel 11 that's validateCsrfTokens(except: [...]) in bootstrap/app.php, not the old HTTP kernel.
Excluding CSRF is safe only because we verify everything else, in PayFast's recommended order:
VALID.A transaction moves pending → complete | failed | cancelled | expired, and complete is terminal. A duplicate COMPLETE is a no-op; a late CANCELLED can't undo a payment. A COMPLETE arriving after we've expired an abandoned checkout still completes it — money received always wins.
All changes run inside a database transaction with a row lock, and each one is written to an audit timeline.
The browser generates an Idempotency-Key per checkout attempt and reuses it on retries. The server scopes the key to the user, serialises checkouts per user with a lock, and backs it all with a unique database index. A retried request gets the same payment session back instead of creating a second one.
We also write the transaction row before calling PayFast, so a notification can never arrive for a payment we have no record of.
Unlocking what the customer bought goes through a unique (user, product) constraint, so repeating the grant is harmless. If someone genuinely pays twice — two open tabs — the second payment is flagged for a refund instead of being silently swallowed.
Receipts are sent by a queued job that claims a receipt_sent_at column atomically, so retries never email twice.
Even with all of this, you want a safety net. A reconciliation command pulls PayFast's transaction history, matches it to unconfirmed payments by reference, and completes them through the same state machine — dry run first, apply second.
Integrating payments and want them to be boring? We can help.
How we designed and built the digital front door for PureTax, an Alberton-based tax and accounting practice — fast, mobile-first and built to turn visitors into clients.
We work with PyCentric, an engineering-led technology consultancy, on a contract basis — contributing software engineering to the systems they deliver for South African businesses.
Many online CV builders let you spend an hour on your CV and then ask for a recurring subscription at download. We built ours differently — here's why.
Tell us what you need and we will come back to you with honest next steps — no hard sell.