Payments
Setting up PayFast and SnapScan, and going live safely.
Two gateways ship, PayFast and SnapScan. A store can enable either or both; with both, the checkout asks the shopper which they would like to use.
They are different shapes. PayFast takes a signed form posted to its site. SnapScan is a link the shopper opens — on a phone it opens the SnapScan app, and on a desktop it is shown as a QR code to scan. Everything after the hand-over is the same for both.
How an order gets paid
- Checkout creates a pending order: a snapshot of the titles, options and prices, with the total computed from the catalog inside the same transaction. Stock does not move yet.
- The shopper is handed over to the gateway and pays there.
- The gateway notifies the store's callback, server to server. This is the only thing that can mark an order paid, and it is checked before anything happens.
- The order is marked paid, stock moves, the cart empties, and then the emails go out.
The page a shopper comes back to after paying proves nothing — anyone can open it — so it says the payment is being confirmed rather than that it succeeded.
Stock is taken at payment, not reserved at checkout, so an abandoned checkout holds no inventory. The cost is that two people can pay for the last item; the second order is still recorded paid and is flagged oversold in the admin and in the packing email, for a refund.
PayFast
Setting it up
- Get a merchant id and key from the PayFast dashboard. The sandbox's have no relationship to a live account's.
- Set a salt passphrase in the dashboard and the same value in
PAYFAST_PASSPHRASE. Set on one side only, every signature fails. - Leave
PAYFAST_SANDBOX=trueuntil a full sandbox payment has worked end to end. - Make sure PayFast can reach the callback. It is derived from
BASE_URL, which on a laptop is unreachable, so local testing needs a tunnel and its address goes inPAYFAST_NOTIFY_URL.
Then place an order, pay it on the sandbox, and check that the order is paid
and the stock has moved.
Going live
PAYFAST_SANDBOX defaults to true so that nobody's first afternoon with the
project charges a real card. The mirror-image mistake is a deployment that never
sets it, takes no money and looks like it works — so set it explicitly
wherever you deploy.
Going live is two changes: PAYFAST_SANDBOX=false, and your own merchant
credentials. The server refuses to start with the sandbox switched off and
PayFast's published sandbox merchant id.
Behind a proxy or a managed platform, set
CLIENT_IP_SOURCE too. Otherwise
PayFast's address check compares its ranges against your load balancer's and
rejects every genuine notification — money taken, nothing recorded.
What a notification has to pass
- The signature recomputes over the fields exactly as received.
- The source address is one of PayFast's published ranges.
- PayFast confirms it when the exact bytes are posted back to it.
- The merchant id is this store's.
Then the amount is checked against the order's own total, and a replayed notification cannot move stock twice. None of the four checks is enough alone.
SnapScan
Setting it up
- Ask SnapScan merchant support for a snap code, an API key and a
webhook authentication key, and give them the webhook address:
BASE_URL+/payments/snapscan/callback. - Set
SNAPSCAN_SNAP_CODE,SNAPSCAN_API_KEYandSNAPSCAN_WEBHOOK_AUTH_KEY. The server refuses to start with the first and not the others. - Optionally, ask them to enable Secure QR Payload and set
SNAPSCAN_VALIDATION_KEY, which signs the amount and order reference in the payment link so neither can be edited.
There is no SnapScan sandbox. Any configuration takes real money, so the first test is the smallest payment you are willing to make — and, locally, it needs a tunnel for the callback.
What a notification has to pass
The body's HMAC must match the webhook key, and then the payment is read back from SnapScan's API. The status and amount the store acts on come from that response, never from the notification itself, so a leaked webhook key is not a free order.