Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Payfast Php Sdk Laravel Package

payfast/payfast-php-sdk

A PHP SDK for integrating the PayFast payment gateway into Laravel or PHP applications. It simplifies payment form generation, signature creation, and ITN validation with a clean, expressive API, making it easy to accept secure online payments from South African customers.

View on GitHub
Deep Wiki
Context7

Getting Started

The Payfast PHP SDK is a framework-agnostic wrapper around Payfast's payment APIs (custom/once-off form payments, onsite, subscriptions, refunds, and ITN validation). It is not a Laravel package — there is no service provider, config file, or facade — so "getting started" means wiring it into Laravel yourself.

1. Install and pin the version.

composer require payfast/payfast-php-sdk:^1.1.6

Pin to ^1.1.6 explicitly. v1.1.5 shipped namespace/PSR-4 changes that v1.1.6 reverts, so a loose constraint combined with an existing lockfile can put you on either side of that revert. Run composer update payfast/payfast-php-sdk deliberately, then check your use statements.

2. Look at the samples/ directory first. This is the single highest-value place to start, and it's why v1.1.6 matters: the samples now include form inputs for merchant ID, merchant key, and passphrase, instead of hardcoded values. That turns the samples into a working sandbox harness — you can clone the repo, drop in your sandbox credentials, and see a full payment round-trip plus ITN callback before touching your app.

3. Centralise credentials. Put them in config/services.php and .env, not inline:

// config/services.php
'payfast' => [
    'merchant_id' => env('PAYFAST_MERCHANT_ID'),
    'merchant_key' => env('PAYFAST_MERCHANT_KEY'),
    'passphrase' => env('PAYFAST_PASSPHRASE'),
    'sandbox' => env('PAYFAST_SANDBOX', true),
    'return_url' => env('APP_URL').'/checkout/return',
    'cancel_url' => env('APP_URL').'/checkout/cancel',
    'notify_url' => env('APP_URL').'/webhooks/payfast',
],

Note that Payfast sandbox and live accounts have separate merchant IDs, keys, and passphrases — keep two full credential sets and switch by environment, not just by base URL.

4. First use case: a once-off payment. Build the payment data array, generate the signature via the SDK, render an auto-submitting form to Payfast, and register an ITN route. That flow alone covers the majority of real integrations. Validate it end-to-end in sandbox before adding subscriptions or refunds.

Implementation Patterns

Wrap the SDK in one application service. Because there's no Laravel integration, create a thin PayfastService (or a few focused classes) that reads from config('services.payfast') and exposes intent-level methods:

final class PayfastGateway
{
    public function __construct(private array $config) {}

    public function paymentData(Order $order): array { /* builds + signs */ }
    public function validateItn(array $payload): bool { /* signature + server confirm */ }
}

Bind it as a singleton in a service provider and inject it. This isolates every direct SDK call behind one seam, which is what saves you when namespaces shift between minor releases (as they did in 1.1.5 → 1.1.6).

Initiation: server-side form POST, not a redirect URL. Payfast's custom form flow expects a POSTed form. Build the data server-side (never in a Livewire/Alpine payload — the signature and passphrase must not leak to the browser), then render a Blade view with an auto-submitting form, or return a small HTML response from the controller.

Use your own m_payment_id. Set it to your order reference (or a UUID), keep it well under Payfast's length limits and free of characters that break signature encoding, and store it on the order. This is your idempotency key for the ITN.

ITN handling is the whole ballgame. Treat the webhook as the source of truth, not the return URL.

Route::post('/webhooks/payfast', PayfastItnController::class)
    ->withoutMiddleware([VerifyCsrfToken::class]);

The controller should, in order:

  1. Capture the raw payload and persist it (a payfast_itns table, or at minimum a log line) before any logic — debugging without the raw body is painful.
  2. Verify the signature (including passphrase) using the SDK's ITN/notify helper.
  3. Perform the server-side confirmation POST back to Payfast — signature validity alone does not prove the request came from Payfast.
  4. Match m_payment_id to your order, and compare amount_gross against the expected order total before marking paid. This is the classic omission; without it a valid-signature ITN for R1 can settle a R10 000 order.
  5. Check payment_status is COMPLETE and act idempotently — Payfast retries ITNs, so guard with a status transition check, not an if (! $order->paid) alone if you have concurrent workers.
  6. Respond 200 quickly, dispatching a queued job for fulfilment (emails, provisioning, invoices). Long-running work in the webhook leads to timeouts and retries.

Pattern for fulfilment:

// controller
ProcessPayfastItn::dispatch($itnPayload)->onQueue('webhooks');
return response('OK', 200);

Do the signature/amount validation in the controller (fast, synchronous, decides 200 vs 400) and the side effects in the job.

Subscriptions and tokenisation. For recurring billing, use the SDK's subscription/update helpers to create the tokenised agreement, then handle the recurring ITNs through the same webhook — recurring notifications carry the same fields plus token. Don't build a second handler.

Refunds. Refunds go through the API (not ITN). Wrap the call, log the request/response payload, and record the refund reference on your order. Treat the API response as authoritative but reconcile against ITN/portal, since partial refunds and reversals can lag.

Testing. v1.1.6 fixed unit tests, so you can pin a version you're reasonably confident in, but don't rely on the package's own test suite as a contract. Instead, test your seam: fake PayfastGateway in feature tests, and unit-test signature generation against fixed fixture payloads so a future SDK bump that changes encoding gets caught immediately. Use Laravel's Http::fake() if your wrapper makes the confirmation POST itself.

Gotchas and Tips

v1.1.6 reverted namespace and PSR-4 changes from v1.1.5. This is the release's main compatibility story and a genuine trap. If anyone on your team upgraded to 1.1.5 and updated use statements (or if a composer.json constraint resolved to 1.1.5 in a lockfile), those imports are now wrong again. Check git log for SDK-related use changes when merging this upgrade, and run a full composer update rather than a partial one. If you pin ^1.1.6 today you avoid the mess, but any branch still on 1.1.5 needs attention.

PHP 8.2 support is now official. If you're on 8.2+, this release is the first one you can run without deprecation noise from dynamic properties and similar. If you're still on 7.4/8.0, note the 1.1.6 changes assume a modern runtime path — verify on your lowest supported PHP version in CI, since "update for PHP 8.2" releases sometimes quietly drop older-version polish.

Signature generation is the #1 source of "it just doesn't work." The classic pitfalls: parameters must be ordered as Payfast expects, values URL-encoded the way Payfast encodes them (spaces as + vs %20 matters), empty fields excluded, and the passphrase appended with a trailing &passphrase=... before hashing. The SDK handles this — so do not hand-roll the signature and do not "clean up" the data array between signing and posting. Any mutation (trimming, reformatting) after signing invalidates it.

Format amounts as strings with exactly two decimals. number_format($total, 2, '.', ''). Floats passed raw produce 150 instead of 150.00 and signatures that fail, or mismatched totals on the ITN side. Store order totals as integer cents and convert at the boundary.

The sandbox is not fully reliable. ITNs sometimes don't arrive for sandbox transactions, and sandbox credentials are easy to mix up with live ones. Build a "replay this ITN payload against my local endpoint" artisan command early — you will use it constantly.

Never trust the return URL for fulfilment. Users can hit /checkout/return directly. The return page should only show "we're confirming your payment" and poll your own order status; the ITN sets that status. Some integrations add a short grace period plus a Payfast API query before showing a hard failure.

No config publishing, no facade, no Payfast:: accessor. Everything is constructor injection of credentials. Budget for this in your service provider; don't scatter credential handling across controllers.

Watch for the library reading $_POST directly. Some SDK ITN helpers expect superglobals. If so, either pass the payload explicitly where the API allows it, or ensure your route is a genuine form-encoded POST — a JSON webhook body will silently produce an empty payload and a failed validation you'll misdiagnose as a signature problem. Log the raw body first, always.

Extension point: since the SDK is thin, the meaningful place to add behaviour (retries, structured logging, metrics on ITN latency, alerting on payment_status = FAILED) is your wrapper and your queued job — not by subclassing SDK internals, which have churned across patch releases.

Log the request/response pair for every refund and API call. Payfast's API error messages are terse; without the payload you can't tell a bad credential from a bad amount from a permissions issue on the account.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle