PHPackages                             myckhel/laravel-paystack - 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. [Payment Processing](/categories/payments)
4. /
5. myckhel/laravel-paystack

Abandoned → [binkode/laravel-paystack](/?search=binkode%2Flaravel-paystack)Package[Payment Processing](/categories/payments)

myckhel/laravel-paystack
========================

A description for laravel-paystack.

v1.6.0(1mo ago)12842↓85.7%1[1 issues](https://github.com/binkode/laravel-paystack/issues)[1 PRs](https://github.com/binkode/laravel-paystack/pulls)MITPHPPHP ^8.1CI passing

Since May 14Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/binkode/laravel-paystack)[ Packagist](https://packagist.org/packages/myckhel/laravel-paystack)[ Fund](https://ko-fi.com/myckhel)[ RSS](/packages/myckhel-laravel-paystack/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (7)Versions (21)Used By (0)

Laravel Paystack
================

[](#laravel-paystack)

Laravel wrapper for the [Paystack API](https://paystack.com/docs/), built for direct use in controllers, services, and queued jobs.

[![Latest Version on Packagist](https://camo.githubusercontent.com/bcb4247f04342435697d2511e8dde6d5007044fe1e88f768d9c44616c9bab02d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f62696e6b6f64652f6c61726176656c2d706179737461636b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/binkode/laravel-paystack)[![Total Downloads](https://camo.githubusercontent.com/4879665f745fc751e5e5252906d225d9c6e00cf740f45ecdb35e97b61a2f3410/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f62696e6b6f64652f6c61726176656c2d706179737461636b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/binkode/laravel-paystack)[![Tests](https://camo.githubusercontent.com/241d1a39264253236fbdd926b6cac32f412d2584ed587a8798b0eea2b642b479/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f62696e6b6f64652f6c61726176656c2d706179737461636b2f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/binkode/laravel-paystack/actions/workflows/tests.yml)[![PHPStan](https://camo.githubusercontent.com/fa63e0381a93ba9755a46ec197198ef973137dca1643836d06b3d6263c9aa7c8/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c2532306d61782d627269676874677265656e3f7374796c653d666c61742d737175617265)](https://phpstan.org/)[![License](https://camo.githubusercontent.com/a24e8dbbe3a4af3bbad34b6588513c55316b4289208e26f43c33231d7e2fd85b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f62696e6b6f64652f6c61726176656c2d706179737461636b2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![Laravel](https://camo.githubusercontent.com/b21bc2003aa619c22eebfa5bcd69dca1cc7effe70502ebde76cd4801f3c84f9e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d3130253230253743253230313125323025374325323031322d4646324432303f7374796c653d666c61742d737175617265266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465)](https://packagist.org/packages/binkode/laravel-paystack)

Features
--------

[](#features)

- Covers a broad set of Paystack endpoints (transactions, customers, transfers, plans, subscriptions, disputes, refunds, and more).
- Optional built-in HTTP routes for quick API proxying from your Laravel app.
- Built-in webhook route with signature validation and event dispatching.
- Compatible with Laravel `10`, `11`, `12`, and `13`.

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

[](#installation)

```
composer require binkode/laravel-paystack
```

Laravel package auto-discovery will register the service provider and facade alias automatically.

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

[](#configuration)

Publish config:

```
php artisan vendor:publish --provider="Binkode\Paystack\PaystackServiceProvider"
```

Set your credentials in `.env`:

```
PAYSTACK_PUBLIC_KEY=pk_test_xxx
PAYSTACK_SECRET_KEY=sk_test_xxx
PAYSTACK_URL=https://api.paystack.co
PAYSTACK_MERCHANT_EMAIL=merchant@example.com
```

Default config (`config/paystack.php`):

```
return [
    "public_key" => env("PAYSTACK_PUBLIC_KEY"),
    "secret_key" => env("PAYSTACK_SECRET_KEY"),
    "url" => env("PAYSTACK_URL", "https://api.paystack.co"),
    "merchant_email" => env("PAYSTACK_MERCHANT_EMAIL"),
    "route" => [
        "middleware" => ["paystack_route_disabled", "api"],
        "prefix" => "api",
        "hook_middleware" => ["validate_paystack_hook", "api"],
    ],
];
```

Quick Usage
-----------

[](#quick-usage)

Call support classes directly:

```
use Binkode\Paystack\Support\Transaction;
use Binkode\Paystack\Support\Customer;

$init = Transaction::initialize([
    "email" => "customer@example.com",
    "amount" => 500000, // amount in kobo
]);

$verify = Transaction::verify("reference_here");

$customer = Customer::create([
    "email" => "customer@example.com",
    "first_name" => "Jane",
    "last_name" => "Doe",
]);
```

Developer Integration Scenarios
-------------------------------

[](#developer-integration-scenarios)

Here are common design patterns for using this package across different parts of a Laravel application.

### 1. Controllers (Checkout &amp; Verification)

[](#1-controllers-checkout--verification)

Controllers should initiate payments and handle callback verification. When an API call fails, the package automatically calls Laravel's `abort()`, throwing a `Symfony\Component\HttpKernel\Exception\HttpException` which is caught by Laravel's global exception handler.

```
namespace App\Http\Controllers;

use App\Models\Order;
use Binkode\Paystack\Support\Transaction;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
    /**
     * Step 1: Initialize checkout and redirect to Paystack
     */
    public function checkout(Order $order)
    {
        // Paystack amount is in kobo (e.g. 5,000 NGN = 500000 kobo)
        $amountInKobo = $order->total_amount * 100;

        $response = Transaction::initialize([
            'email' => auth()->user()->email,
            'amount' => $amountInKobo,
            'reference' => 'ORD-' . $order->id . '-' . time(),
            'callback_url' => route('payment.callback'),
            'metadata' => [
                'order_id' => $order->id,
            ],
        ]);

        if (isset($response['status']) && $response['status'] === true) {
            // Save reference to the order
            $order->update([
                'payment_reference' => $response['data']['reference'],
                'status' => 'pending',
            ]);

            // Redirect user to the Paystack checkout page
            return redirect($response['data']['authorization_url']);
        }

        return back()->with('error', 'Unable to initialize transaction with Paystack.');
    }

    /**
     * Step 2: Handle user redirection back from Paystack (Callback)
     */
    public function callback(Request $request)
    {
        $reference = $request->query('reference');

        if (!$reference) {
            return redirect()->route('dashboard')->with('error', 'No reference returned.');
        }

        $response = Transaction::verify($reference);

        if (isset($response['data']['status']) && $response['data']['status'] === 'success') {
            $order = Order::where('payment_reference', $reference)->firstOrFail();

            // Avoid double processing (idempotency check)
            if ($order->status !== 'completed') {
                $order->update(['status' => 'completed']);
                // Trigger any order success events / mailers here
            }

            return redirect()->route('orders.show', $order)->with('success', 'Payment successful!');
        }

        return redirect()->route('dashboard')->with('error', 'Payment verification failed.');
    }
}
```

### 2. Service Classes (Business Logic Isolation)

[](#2-service-classes-business-logic-isolation)

For larger applications, abstract Paystack calls into a service layer to keep controllers clean. This is especially useful for managing complex customer profiles, plans, or subscriptions.

```
namespace App\Services;

use App\Models\User;
use Binkode\Paystack\Support\Customer;
use Binkode\Paystack\Support\Subscription;

class BillingService
{
    /**
     * Ensure a user has a Paystack customer account, then subscribe them to a plan.
     */
    public function subscribeUserToPlan(User $user, string $planCode): array
    {
        // 1. Ensure user has a Paystack customer code
        if (!$user->paystack_customer_code) {
            $customerRes = Customer::create([
                'email' => $user->email,
                'first_name' => $user->first_name,
                'last_name' => $user->last_name,
                'phone' => $user->phone,
            ]);

            if (isset($customerRes['data']['customer_code'])) {
                $user->update([
                    'paystack_customer_code' => $customerRes['data']['customer_code'],
                ]);
            }
        }

        // 2. Create the subscription on Paystack
        $subscriptionRes = Subscription::create([
            'customer' => $user->paystack_customer_code,
            'plan' => $planCode,
        ]);

        if (isset($subscriptionRes['status']) && $subscriptionRes['status'] === true) {
            $user->update([
                'subscription_code' => $subscriptionRes['data']['subscription_code'],
                'subscription_status' => 'active',
                'subscribed_at' => now(),
            ]);
        }

        return $subscriptionRes;
    }
}
```

### 3. Queued Jobs (Background Processing)

[](#3-queued-jobs-background-processing)

When interacting with the Paystack API inside queued jobs (e.g. processing bulk transfers or validating statuses in the background), network errors or rate limits (`429 Too Many Requests`) can occur.

You should design your jobs to handle these failures gracefully and support retries:

```
namespace App\Jobs;

use App\Models\TransferRequest;
use Binkode\Paystack\Support\Transfer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\HttpException;

class ProcessPayoutJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * The number of times the job may be attempted.
     */
    public int $tries = 3;

    /**
     * The number of seconds to wait before retrying the job.
     */
    public int $backoff = 60;

    protected TransferRequest $payout;

    public function __construct(TransferRequest $payout)
    {
        $this->payout = $payout;
    }

    public function handle(): void
    {
        // Don't re-process completed payouts
        if ($this->payout->status === 'processed') {
            return;
        }

        try {
            $response = Transfer::initiate([
                'source' => 'balance',
                'amount' => $this->payout->amount * 100, // in kobo
                'recipient' => $this->payout->recipient_code,
                'reason' => "Payout for Request #{$this->payout->id}",
                'reference' => 'PAY-' . $this->payout->id . '-' . time(),
            ]);

            if (isset($response['status']) && $response['status'] === true) {
                $this->payout->update([
                    'transfer_code' => $response['data']['transfer_code'],
                    'status' => 'processing',
                ]);
            }
        } catch (HttpException $e) {
            // Log the API failure
            Log::error("Paystack API Payout Failure: " . $e->getMessage(), [
                'payout_id' => $this->payout->id,
                'status_code' => $e->getStatusCode()
            ]);

            // If it's a server error (5xx) or rate limit (429), retry the job
            if ($e->getStatusCode() >= 500 || $e->getStatusCode() === 429) {
                $this->release($this->backoff);
                return;
            }

            // For client errors (400, 401, 403, 404), fail the job as retries won't help
            $this->payout->update(['status' => 'failed', 'error_log' => $e->getMessage()]);
            $this->fail($e);
        }
    }
}
```

### 4. Error Handling

[](#4-error-handling)

Because the package uses Laravel's HTTP client under the hood, any failed response (status codes `4xx` and `5xx`) automatically throws a `Symfony\Component\HttpKernel\Exception\HttpException` via `abort($res->status(), ...)`.

You can catch this in your application code for fine-grained error handling:

```
use Binkode\Paystack\Support\Transaction;
use Symfony\Component\HttpKernel\Exception\HttpException;

try {
    $verify = Transaction::verify("non_existent_ref");
} catch (HttpException $e) {
    $statusCode = $e->getStatusCode(); // e.g. 404
    $errorMessage = $e->getMessage(); // Message returned from Paystack

    // Handle error accordingly
}
```

Available Support Classes
-------------------------

[](#available-support-classes)

- `ApplePay`
- `BulkCharge`
- `Charge`
- `ControlPanel`
- `Customer`
- `DedicatedVirtualAccount`
- `Dispute`
- `Invoice`
- `Miscellaneous`
- `Order`
- `Page`
- `Plan`
- `Product`
- `Recipient`
- `Refund`
- `Settlement`
- `Split`
- `SubAccount`
- `Subscription`
- `Terminal`
- `Transaction`
- `Transfer`
- `TransferControl`
- `Verification`
- `VirtualTerminal`

See class methods in `src/Support/*`.

Built-In Routes
---------------

[](#built-in-routes)

The package registers route definitions from `src/routes.php`. By default, API routes are disabled through the `paystack_route_disabled` middleware.

To enable built-in routes, remove `paystack_route_disabled` from `paystack.route.middleware` in `config/paystack.php`.

Default route prefix is `api`, so endpoints resolve like:

- `POST /api/transaction/initialize`
- `GET /api/transaction/verify/{reference}`
- `POST /api/customer`

Webhooks
--------

[](#webhooks)

Webhook endpoint:

- `POST /api/hooks` (route is registered as `Route::any`, but Paystack should call it with `POST`)

Incoming webhook requests are validated by the `validate_paystack_hook` middleware using your `PAYSTACK_SECRET_KEY`.

Each valid incoming webhook dispatches the `Binkode\Paystack\Events\Hook` event.

Create a listener:

```
php artisan make:listener PaystackWebhookListener --event=Binkode\\Paystack\\Events\\Hook
```

Example listener:

```
use Binkode\Paystack\Events\Hook;
use App\Models\Order;
use App\Models\TransferRequest;
use Illuminate\Support\Facades\Log;

class PaystackWebhookListener
{
    public function handle(Hook $event): void
    {
        $payload = $event->event;
        $eventType = $payload['event'] ?? null;
        $data = $payload['data'] ?? [];

        Log::info("Paystack webhook received: {$eventType}");

        switch ($eventType) {
            case 'charge.success':
                $reference = $data['reference'] ?? null;
                if ($reference) {
                    $order = Order::where('payment_reference', $reference)->first();
                    if ($order && $order->status !== 'completed') {
                        $order->update(['status' => 'completed']);
                    }
                }
                break;

            case 'transfer.success':
                $transferCode = $data['transfer_code'] ?? null;
                if ($transferCode) {
                    $payout = TransferRequest::where('transfer_code', $transferCode)->first();
                    if ($payout) {
                        $payout->update(['status' => 'processed']);
                    }
                }
                break;

            case 'transfer.failed':
            case 'transfer.reversed':
                $transferCode = $data['transfer_code'] ?? null;
                if ($transferCode) {
                    $payout = TransferRequest::where('transfer_code', $transferCode)->first();
                    if ($payout) {
                        $payout->update([
                            'status' => 'failed',
                            'error_log' => $data['reason'] ?? 'Transfer failed or was reversed.',
                        ]);
                    }
                }
                break;

            default:
                Log::warning("Unhandled Paystack event: {$eventType}");
                break;
        }
    }
}
```

Testing
-------

[](#testing)

```
composer test
```

Useful Links
------------

[](#useful-links)

- [Paystack API Docs](https://paystack.com/docs/)
- [Postman Collection](https://www.postman.com/myckhel/workspace/myckhel/collection/9558301-024596ae-713a-4890-b12b-6842195ef802?action=share&creator=9558301)
- [Package Demo App](https://github.com/binkode/paystack-demo)

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

[](#contributing)

Please read [CONTRIBUTING.md](CONTRIBUTING.md).

Security
--------

[](#security)

If you discover any security-related issues, email `binkode1@hotmail.com` instead of opening a public issue.

License
-------

[](#license)

Released under the [MIT License](LICENSE.md).

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance90

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity67

Established project with proven stability

 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 ~94 days

Recently: every ~218 days

Total

17

Last Release

42d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/58f0d24f84c6d2007b34e6b49f62315c6ac103ce5b29817a9053535f10a8e7d6?d=identicon)[myckhel](/maintainers/myckhel)

---

Top Contributors

[![myckhel](https://avatars.githubusercontent.com/u/34090541?v=4)](https://github.com/myckhel "myckhel (70 commits)")

---

Tags

laravel-paystacklaravel-paystack-subscriptionpaystackpaystack-apipaystack-payment-gatewaylaravel

###  Code Quality

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/myckhel-laravel-paystack/health.svg)

```
[![Health](https://phpackages.com/badges/myckhel-laravel-paystack/health.svg)](https://phpackages.com/packages/myckhel-laravel-paystack)
```

###  Alternatives

[laravel/socialite

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

5.7k113.1M997](/packages/laravel-socialite)[craftcms/cms

Craft CMS

3.6k3.7M3.4k](/packages/craftcms-cms)[spatie/laravel-health

Monitor the health of a Laravel application

88212.7M188](/packages/spatie-laravel-health)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5226.7k](/packages/simplestats-io-laravel-client)[sebdesign/laravel-viva-payments

A Laravel package for integrating the Viva Payments gateway

4952.7k](/packages/sebdesign-laravel-viva-payments)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

353.6k](/packages/eslazarev-wildberries-sdk)

PHPackages © 2026

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