PHPackages                             mb-development/payfast-core - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. mb-development/payfast-core

ActiveLibrary

mb-development/payfast-core
===========================

A Laravel package for PayFast payment gateway integration

1.0.1(1mo ago)02↑2900%proprietaryPHPPHP ^8.1CI failing

Since Mar 27Pushed 1mo agoCompare

[ Source](https://github.com/MBDevelopment-pty-ltd/payfast-core)[ Packagist](https://packagist.org/packages/mb-development/payfast-core)[ RSS](/packages/mb-development-payfast-core/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (8)Versions (3)Used By (0)

PayFast Core
============

[](#payfast-core)

[![Tests](https://github.com/mb-development/payfast-core/actions/workflows/tests.yml/badge.svg)](https://github.com/mb-development/payfast-core/actions)[![Latest Version on Packagist](https://camo.githubusercontent.com/e3e142edbdd3a5c18234a66edd20d0b16a4866f49b78b14a4389595bb022664d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d622d646576656c6f706d656e742f706179666173742d636f72652e737667)](https://packagist.org/packages/mb-development/payfast-core)[![Total Downloads](https://camo.githubusercontent.com/8e8d063e30ae65cdfff5ed468bd56a24fec036882aa949391cd98700d3b0da27/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d622d646576656c6f706d656e742f706179666173742d636f72652e737667)](https://packagist.org/packages/mb-development/payfast-core)[![License](https://camo.githubusercontent.com/380eec7486d3860c75e76a1d3d0ead27c0ac7e13637249e69168d63c9ddee9c8/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d622d646576656c6f706d656e742f706179666173742d636f72652e737667)](LICENSE.md)[![PHP](https://camo.githubusercontent.com/0bcc30ed3a32e348c3655f72c1c716807270f94237ae9b3d14e34f716cd65db2/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6d622d646576656c6f706d656e742f706179666173742d636f72652e737667)](composer.json)

A first-class Laravel package for integrating the [PayFast](https://www.payfast.co.za) payment gateway. Handles once-off payments, recurring subscriptions, ITN (Instant Transaction Notification) verification, transaction logging, and webhook security — so you can focus on your application logic instead of boilerplate.

Developed and maintained by **[MB Development Pty Ltd](https://mbdevelopment.co.za)**.

---

Contents
--------

[](#contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Once-Off Payments](#once-off-payments)
- [Handling ITN Callbacks](#handling-itn-callbacks)
- [The PayfastPaymentEvent](#the-payfastpaymentevent)
- [Transaction Logging](#transaction-logging)
- [Recurring Subscriptions](#recurring-subscriptions)
- [Webhook Middleware](#webhook-middleware)
- [Facade Reference](#facade-reference)
- [Events Reference](#events-reference)
- [Testing](#testing)
- [Contributing](#contributing)
- [Security](#security)
- [License](#license)

---

Requirements
------------

[](#requirements)

RequirementVersionPHP`^8.1` and newerLaravel`10.x` and newer---

Installation
------------

[](#installation)

Install the package via Composer:

```
composer require mb-development/payfast-core
```

The service provider and `Payfast` facade are registered automatically via Laravel's package discovery. No manual registration is required.

---

Configuration
-------------

[](#configuration)

### 1. Publish the config file

[](#1-publish-the-config-file)

```
php artisan vendor:publish --tag=payfast-config
```

This creates `config/payfast.php` in your application.

### 2. Add your credentials to `.env`

[](#2-add-your-credentials-to-env)

```
# Your PayFast merchant credentials
PAYFAST_MERCHANT_ID=10000100
PAYFAST_MERCHANT_KEY=46f0cd694581a

# Optional — only required if you set a passphrase in your PayFast account
PAYFAST_PASSPHRASE=your_passphrase_here

# Set to false when going live
PAYFAST_SANDBOX=true

# URLs PayFast will redirect to or POST to after payment
PAYFAST_RETURN_URL=https://yourapp.com/payment/return
PAYFAST_CANCEL_URL=https://yourapp.com/payment/cancel
PAYFAST_NOTIFY_URL=https://yourapp.com/payfast/notify

# Validates that ITN requests come from known PayFast IPs — recommended in production
PAYFAST_VALIDATE_IP=true
```

### 3. Run migrations

[](#3-run-migrations)

```
php artisan vendor:publish --tag=payfast-migrations
php artisan migrate
```

This creates two tables: `payfast_transactions` and `payfast_subscriptions`.

---

Once-Off Payments
-----------------

[](#once-off-payments)

### Redirect the user to PayFast (GET)

[](#redirect-the-user-to-payfast-get)

The simplest integration — build a signed URL and redirect:

```
use MbDevelopment\PayfastCore\Facades\Payfast;

public function checkout(Request $request)
{
    $url = Payfast::buildPaymentUrl([
        'amount'        => '349.99',
        'item_name'     => 'Order #1042',
        'custom_str1'   => (string) $order->id,   // passed back in the ITN
        'name_first'    => $request->user()->first_name,
        'email_address' => $request->user()->email,
    ]);

    return redirect($url);
}
```

### POST form submission

[](#post-form-submission)

For a seamless checkout experience, render a self-submitting form:

```
use MbDevelopment\PayfastCore\Facades\Payfast;
use MbDevelopment\PayfastCore\Services\PayfastService;

public function checkout()
{
    $paymentData = Payfast::generatePaymentData([
        'amount'      => '349.99',
        'item_name'   => 'Order #1042',
        'custom_str1' => (string) $order->id,
    ]);

    $endpoint = app(PayfastService::class)->getPaymentEndpoint();

    return view('checkout', compact('paymentData', 'endpoint'));
}
```

```
{{-- resources/views/checkout.blade.php --}}
@include('payfast::payment-form', [
    'paymentData' => $paymentData,
    'endpoint'    => $endpoint,
    'buttonText'  => 'Pay R349.99',
    'autoSubmit'  => false,   // set true to skip the button and auto-redirect
])
```

### Using the `HasPayfastPayments` trait

[](#using-the-haspayfastpayments-trait)

Add the trait to any Eloquent model to generate payment URLs directly from the model:

```
use MbDevelopment\PayfastCore\Traits\HasPayfastPayments;

class Order extends Model
{
    use HasPayfastPayments;

    public function getPayfastPaymentData(): array
    {
        return [
            'amount'      => $this->total,
            'item_name'   => "Order #{$this->id}",
            'custom_str1' => (string) $this->id,
        ];
    }
}

// In a controller:
return redirect($order->getPayfastPaymentUrl());
```

### Dependency injection

[](#dependency-injection)

You can also resolve `PayfastInterface` directly from the container:

```
use MbDevelopment\PayfastCore\Contracts\PayfastInterface;

class CheckoutController extends Controller
{
    public function __construct(protected PayfastInterface $payfast) {}

    public function pay(Request $request)
    {
        $url = $this->payfast->buildPaymentUrl([
            'amount'    => $request->amount,
            'item_name' => $request->item,
        ]);

        return redirect($url);
    }
}
```

---

Handling ITN Callbacks
----------------------

[](#handling-itn-callbacks)

PayFast posts payment notifications to your `notify_url`. This package registers the route **automatically** — you do not need to add it yourself:

```
POST /payfast/notify

```

> **Important:** You must exclude this route from CSRF protection. Add it to the `$except` array in `app/Http/Middleware/VerifyCsrfToken.php`:
>
> ```
> protected $except = [
>     'payfast/notify',
> ];
> ```

The built-in handler validates the ITN signature, optionally checks the source IP, persists the transaction to the database, and fires events for you to listen to.

---

The PayfastPaymentEvent
-----------------------

[](#the-payfastpaymentevent)

`PayfastPaymentEvent` is the **single event you need to listen to** in order to react to any PayFast payment — once-off or subscription, successful or failed. It is fired on every valid ITN after the transaction has been persisted.

### Register your listener

[](#register-your-listener)

Publish the ready-made stub listener:

```
php artisan vendor:publish --tag=payfast-stubs
```

This copies `HandlePayfastPayment.php` into `app/Listeners/`. Then register it in your `EventServiceProvider`:

```
use MbDevelopment\PayfastCore\Events\PayfastPaymentEvent;
use App\Listeners\HandlePayfastPayment;

protected $listen = [
    PayfastPaymentEvent::class => [
        HandlePayfastPayment::class,
    ],
];
```

### Writing your listener

[](#writing-your-listener)

```
use MbDevelopment\PayfastCore\Events\PayfastPaymentEvent;

class HandlePayfastPayment
{
    public function handle(PayfastPaymentEvent $event): void
    {
        // Activate an order after a successful once-off payment
        if ($event->isOnceOff() && $event->isComplete()) {
            $orderId = $event->customStr(1); // value you passed in custom_str1
            Order::find($orderId)?->markPaid();
        }

        // Grant access after a successful subscription payment
        if ($event->isSubscription() && $event->isComplete()) {
            $userId = $event->customStr(1);
            User::find($userId)?->grantSubscriptionAccess();
        }

        // Notify the customer when a payment fails
        if ($event->isFailed()) {
            Notification::route('mail', $event->buyerEmail())
                ->notify(new PaymentFailedNotification($event->transaction));
        }
    }
}
```

### PayfastPaymentEvent API reference

[](#payfastpaymentevent-api-reference)

MemberTypeDescription`$event->paymentType``string``'once_off'` or `'subscription'``$event->paymentStatus``string``'COMPLETE'`, `'FAILED'`, `'PENDING'`, `'CANCELLED'``$event->transaction``PayfastTransaction`The persisted transaction record (always present)`$event->subscription``PayfastSubscription|null`Subscription record (subscriptions only, else `null`)`$event->payload``array`The full raw ITN payload from PayFast`$event->isOnceOff()``bool``true` for standard once-off payments`$event->isSubscription()``bool``true` for recurring subscription payments`$event->isComplete()``bool``true` when PayFast confirms payment success`$event->isFailed()``bool``true` when the payment failed`$event->isPending()``bool``true` when the payment is still pending`$event->isCancelled()``bool``true` when the payment was cancelled`$event->amountGross()``float`Gross amount before PayFast fees`$event->amountNet()``float`Net amount after PayFast fees`$event->pfPaymentId()``string|null`The PayFast payment ID (`pf_payment_id`)`$event->subscriptionToken()``string|null`Subscription token (subscriptions only)`$event->customStr(int $n)``string|null`Value of `custom_str1`, `custom_str2`, or `custom_str3``$event->customInt(int $n)``int|null`Value of `custom_int1` or `custom_int2``$event->buyerFirstName()``string|null`Buyer's first name`$event->buyerLastName()``string|null`Buyer's last name`$event->buyerEmail()``string|null`Buyer's email address`$event->itemName()``string|null`The `item_name` from the payment`$event->summary()``string`Human-readable log string e.g. `COMPLETE once_off — R349.99 — Order #1042`---

Transaction Logging
-------------------

[](#transaction-logging)

Every valid ITN is automatically persisted to the `payfast_transactions` table via the `PayfastTransaction` model. No extra setup is needed.

```
use MbDevelopment\PayfastCore\Models\PayfastTransaction;

// All completed transactions
PayfastTransaction::complete()->get();

// All failed transactions
PayfastTransaction::failed()->get();

// Find transactions for a specific order (stored in custom_str1)
PayfastTransaction::forReference($order->id)->get();

// Find transactions by buyer email
PayfastTransaction::forEmail('jane@example.com')->get();

// Status helpers on a single record
$tx = PayfastTransaction::find(1);

$tx->isComplete();   // bool
$tx->isFailed();     // bool
$tx->isPending();    // bool
$tx->raw_payload;    // array — the full ITN payload as received
```

---

Recurring Subscriptions
-----------------------

[](#recurring-subscriptions)

### Creating a subscription payment

[](#creating-a-subscription-payment)

```
use MbDevelopment\PayfastCore\Services\SubscriptionService;
use MbDevelopment\PayfastCore\Models\PayfastSubscription;

$service = app(SubscriptionService::class);

$paymentData = $service->generateSubscriptionPaymentData([
    'amount'      => 299.00,
    'item_name'   => 'Pro Plan — Monthly',
    'frequency'   => PayfastSubscription::FREQUENCY_MONTHLY,
    'cycles'      => 0,             // 0 = indefinite, or set a fixed number
    'custom_str1' => (string) $user->id,
]);

// Pass $paymentData and $endpoint to your Blade checkout view
```

### Frequency options

[](#frequency-options)

ConstantValueBilling interval`FREQUENCY_MONTHLY``3`Every month`FREQUENCY_QUARTERLY``4`Every 3 months`FREQUENCY_BIANNUALLY``5`Every 6 months`FREQUENCY_ANNUALLY``6`Every year### Trial subscriptions

[](#trial-subscriptions)

To offer a free or discounted first billing:

```
$paymentData = $service->generateTrialSubscriptionPaymentData([
    'amount'    => 299.00,
    'item_name' => 'Pro Plan',
], trialAmount: 0.00); // first billing is free
```

### Using the `HasPayfastSubscriptions` trait

[](#using-the-haspayfastsubscriptions-trait)

Add the trait to your `User` model (or any subscribable model):

```
use MbDevelopment\PayfastCore\Traits\HasPayfastSubscriptions;

class User extends Authenticatable
{
    use HasPayfastSubscriptions;

    // Override to provide default subscription parameters for this model
    public function getPayfastSubscriptionDefaults(): array
    {
        return [
            'custom_str1' => (string) $this->id,
        ];
    }
}
```

Then in your controllers:

```
// Redirect the user to PayFast to start their subscription
return redirect($user->newPayfastSubscriptionUrl([
    'amount'    => 299.00,
    'item_name' => 'Pro Plan',
    'frequency' => PayfastSubscription::FREQUENCY_MONTHLY,
]));

// Check subscription status
$user->subscribedToPayfast();         // bool — has an active subscription
$user->activePayfastSubscription();   // PayfastSubscription|null
$user->onPayfastTrial();              // bool — currently within trial period
$user->payfastSubscriptions();        // Eloquent relation — all subscriptions

// Cancel the active subscription via the PayFast API
$user->cancelPayfastSubscription();
```

### Managing subscriptions via the API

[](#managing-subscriptions-via-the-api)

```
$service = app(\MbDevelopment\PayfastCore\Services\SubscriptionService::class);

$service->fetchSubscription($token);      // fetch current status from PayFast
$service->pause($token);                  // pause billing
$service->unpause($token);               // resume billing
$service->cancel($token);                // cancel permanently
$service->updateAmount($token, 349.00);  // change the billing amount
```

### Subscription model helpers

[](#subscription-model-helpers)

```
$subscription = PayfastSubscription::forToken($token)->firstOrFail();

$subscription->isActive();       // bool
$subscription->isPaused();       // bool
$subscription->isCancelled();    // bool
$subscription->onTrial();        // bool

$subscription->cancel();         // marks cancelled, sets cancelled_at timestamp
$subscription->pause();          // sets status to 'paused'
$subscription->resume();         // sets status back to 'active'

$subscription->frequencyLabel(); // 'Monthly', 'Quarterly', 'Bi-Annually', 'Annually'
$subscription->cycles_complete;  // int — number of billing cycles completed
$subscription->transactions;     // HasMany — related PayfastTransaction records
```

### Subscription events

[](#subscription-events)

EventWhen it firesPayload`PayfastSubscriptionCreated`First successful billing for a new token`$subscription`, `$payload``PayfastSubscriptionRenewed`A recurring billing cycle completes`$subscription`, `$transaction`, `$payload``PayfastSubscriptionCancelled``cancel()` is called on a subscription`$subscription`, `$payload````
use MbDevelopment\PayfastCore\Events\PayfastSubscriptionCreated;
use MbDevelopment\PayfastCore\Events\PayfastSubscriptionRenewed;
use MbDevelopment\PayfastCore\Events\PayfastSubscriptionCancelled;

// EventServiceProvider
protected $listen = [
    PayfastSubscriptionCreated::class   => [ActivateSubscription::class],
    PayfastSubscriptionRenewed::class   => [ExtendSubscription::class],
    PayfastSubscriptionCancelled::class => [NotifySubscriptionCancelled::class],
];
```

---

Webhook Middleware
------------------

[](#webhook-middleware)

The `VerifyPayfastWebhook` middleware is registered automatically as the `payfast.webhook` alias. It independently verifies the MD5 signature and (optionally) the source IP address on any route you apply it to.

```
// routes/api.php
Route::post('/my-payfast-endpoint', [MyController::class, 'handle'])
    ->middleware('payfast.webhook');
```

On a failed signature check it returns `400`. On an unrecognised source IP it returns `403`. Both cases report the exception to your application's exception handler.

> The built-in `POST /payfast/notify` route already handles ITN validation internally. Apply `payfast.webhook` to **additional custom routes** that need to accept signed PayFast requests.

---

Facade Reference
----------------

[](#facade-reference)

```
use MbDevelopment\PayfastCore\Facades\Payfast;

// Build a signed redirect URL for a GET-based checkout
Payfast::buildPaymentUrl(array $params): string;

// Generate the full payment data array (for a POST form submission)
Payfast::generatePaymentData(array $params): array;

// Generate an MD5 signature for an arbitrary data array
Payfast::generateSignature(array $data, ?string $passPhrase = null): string;

// Validate an ITN payload — throws PayfastException or InvalidSignatureException on failure
Payfast::validateItn(array $data): bool;
```

---

Events Reference
----------------

[](#events-reference)

EventFired whenKey data`PayfastItnReceived`Every valid ITN, before any processing`array $payload``PayfastPaymentEvent`Every valid ITN, after transaction is persisted`$transaction`, `$subscription`, type &amp; status helpers`PayfastPaymentComplete`ITN with `payment_status = COMPLETE``array $payload``PayfastPaymentFailed`ITN with any status other than `COMPLETE``array $payload``PayfastSubscriptionCreated`First billing for a new subscription token`$subscription`, `array $payload``PayfastSubscriptionRenewed`Recurring billing cycle completes`$subscription`, `$transaction`, `array $payload``PayfastSubscriptionCancelled``cancel()` called on a subscription`$subscription`, `array $payload`> **Recommendation:** Use `PayfastPaymentEvent` for all your core application logic. The granular events (`PayfastPaymentComplete`, `PayfastSubscriptionCreated`, etc.) are available for targeted use-cases and third-party integrations.

---

Testing
-------

[](#testing)

Run the test suite:

```
composer test
```

Run with code coverage (requires Xdebug or PCOV):

```
composer test-coverage
```

Format code with Laravel Pint:

```
composer format
```

The package ships with both unit and feature tests covering signature generation, ITN validation, transaction logging, subscription lifecycle management, the webhook middleware, and `PayfastPaymentEvent`.

---

Contributing
------------

[](#contributing)

Contributions, issues, and feature requests are welcome.

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Commit your changes: `git commit -m 'Add my feature'`
4. Push to the branch: `git push origin feature/my-feature`
5. Open a Pull Request against `main`

Please ensure all tests pass and code is formatted with Pint before submitting a PR.

---

Security
--------

[](#security)

If you discover a security vulnerability, please **do not** open a public issue. Email us directly at  and we will address it promptly.

---

Built with ❤️ by [MB Development Pty Ltd](https://mbdevelopment.co.za)

\#

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

46d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/255903309?v=4)[MB Development](/maintainers/mbdevelopment-pty-ltd)[@MBDevelopment-pty-ltd](https://github.com/MBDevelopment-pty-ltd)

---

Top Contributors

[![ManieBotha](https://avatars.githubusercontent.com/u/250661682?v=4)](https://github.com/ManieBotha "ManieBotha (10 commits)")

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/mb-development-payfast-core/health.svg)

```
[![Health](https://phpackages.com/badges/mb-development-payfast-core/health.svg)](https://phpackages.com/packages/mb-development-payfast-core)
```

###  Alternatives

[laravel/socialite

Laravel wrapper around OAuth 1 &amp; OAuth 2 libraries.

5.7k96.9M674](/packages/laravel-socialite)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[laravel/cashier-paddle

Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.

264778.4k3](/packages/laravel-cashier-paddle)[spatie/laravel-export

Create a static site bundle from a Laravel app

646127.9k5](/packages/spatie-laravel-export)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)[spatie/interactive-slack-notification-channel

Send interactive Slack notifications in Laravel apps

6571.7k1](/packages/spatie-interactive-slack-notification-channel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
