Payfast
PayFast (SA) integration — generate form-post MD5 signatures in the documented field order with URL-encoded values, and verify ITN POST signatures using the same algorithm.From its SKILL.md
npx -y skills add tzone85/sa-fintech-skills --skill payfastAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.6 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
PayFast — South African payment integration
Use when the user is wiring a frontend or backend to PayFast for South African card / EFT / Instant EFT / wallet payments. PayFast is form-post + ITN (Instant Transaction Notification) callback, not REST. The signing algorithm is the #1 source of integration failure — get it exactly right.
Triggers
- "payfast"
- "ITN"
- "instant transaction notification"
- "signature mismatch"
- "merchant_id"
- "m_payment_id"
- "passphrase"
- "sandbox.payfast.co.za"
- "process.payfast"
Examples
examples/form-signature.ts— generate the hiddensignaturefield for the checkout formexamples/itn-verify.ts— verify an incoming ITN POST signature + recommended four-step ITN validationexamples/anti-pattern.ts— the four classic signature-mismatch causes with explanationsfixtures/itn-valid.txtandfixtures/itn-invalid-sig.txt— sample ITN POST bodies
Canonical signature algorithm
PayFast's signature algorithm is documented in their PHP reference. Replicating it precisely is the only way the signatures match. The algorithm in TypeScript:
for each (key, value) in fields_in_documented_order:
if value === "" then skip
output += key + "=" + phpUrlEncode(value.trim()) + "&"
output = output.slice(0, -1) # drop trailing &
if passphrase:
output += "&passphrase=" + phpUrlEncode(passphrase.trim())
md5(output).hex()
Three rules that trip everyone up:
- Field order matters and it is NOT alphabetical. The "attributes description" order from the docs is canonical:
merchant_id, merchant_key, return_url, cancel_url, notify_url, name_first, name_last, email_address, cell_number, m_payment_id, amount, item_name, item_description, custom_int1..5, custom_str1..5, email_confirmation, confirmation_address, payment_method, subscription_type, billing_date, recurring_amount, frequency, cycles, subscription_notify_email, subscription_notify_webhook, subscription_notify_buyer. PayFast explicitly warns: do not use the alphabetical API-signature ordering for form posts and ITNs. - URL-encode with PHP semantics — spaces become
+, not%20.encodeURIComponentin JavaScript uses%20and will mismatch. Implement a smallphpUrlEncodehelper that callsencodeURIComponentthen replaces%20with+. - Empty fields are skipped, not included as
key=. Andvalue.trim()strips leading/trailing whitespace before encoding.
ITN four-step validation (do all four)
- Verify signature — same algorithm as form-post, using the fields exactly as PayFast posted them (preserving the order in which they arrived).
- Source-IP check — PayFast publishes ITN IP ranges (
https://www.payfast.co.za/notify_method/host). Reject ITNs from any other source. - Data integrity round-trip — POST the entire received body back to
https://sandbox.payfast.co.za/eng/query/validate(sandbox) orhttps://www.payfast.co.za/eng/query/validate(live) and expectVALIDin the response. - Internal sanity check —
amount_grossmatches the order,m_payment_idmatches an open order in your DB, payment isn't already marked complete (idempotency).
Skip any one of the four and you can be replay-attacked or spoofed.
URLs
| Environment | Form POST URL | Validate URL |
|---|---|---|
| Sandbox | https://sandbox.payfast.co.za/eng/process | https://sandbox.payfast.co.za/eng/query/validate |
| Live | https://www.payfast.co.za/eng/process | https://www.payfast.co.za/eng/query/validate |
Use m_payment_id (your own UUID) as the idempotency key everywhere you persist ITN data.
Common mistakes
- URL-encoding spaces as
%20instead of+— signature mismatches every time. Use a PHP-compatible encoder. - Sorting fields alphabetically — that is the API-signature format, not the form-post format. PayFast warns against this in the docs.
- Including empty fields — the algorithm skips them. Including
cell_number=&when the buyer didn't provide one will break the hash. - Not trimming values before encoding — leading/trailing whitespace from a textarea can silently break the signature.
- Trusting an ITN that passes signature only — without IP + validate-endpoint + DB sanity check, an attacker who can guess your passphrase or grab one valid ITN can replay it.
- Using
encodeURIComponentwithout the+substitution — equivalent to mistake #1 above.
Configuration
PAYFAST_MERCHANT_ID,PAYFAST_MERCHANT_KEY,PAYFAST_PASSPHRASEfrom the Merchant Dashboard. Sandbox values fromhttps://sandbox.payfast.co.za.- The passphrase is optional but every PayFast support page recommends setting one.
- The ITN URL (
notify_url) must be HTTPS and reachable from PayFast's IPs.
See also
shared/za-primitives/vat.ts—amountis the gross (VAT-inclusive) rand value in0.00format.skills/popia/SKILL.md— ITN bodies contain PII; the same logging-scrub + consent rules apply.
What ships with it: 5 files
12.1 KB alongside SKILL.md, 3 of them executable
examples/
- anti-pattern.tsruns4.3 KB
- form-signature.tsruns4.0 KB
- itn-verify.tsruns3.2 KB
fixtures/
- itn-invalid-sig.txt320 B
- itn-valid.txt320 B