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

Laravel Paystack Laravel Package

myckhel/laravel-paystack

A Laravel package that wraps the Paystack payment API, letting you accept payments, verify transactions, manage customers and plans, and handle webhooks through a clean, fluent interface. Supports cards, bank transfers, USSD, and subscriptions.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure

composer require binkode/laravel-paystack
php artisan vendor:publish --tag=paystack-config

Add your keys to .env (test keys first — Paystack test and live keys are not interchangeable):

PAYSTACK_PUBLIC_KEY=pk_test_xxx
PAYSTACK_SECRET_KEY=sk_test_xxx
PAYSTACK_PAYMENT_URL=https://api.paystack.co
[email protected]

Where to look first

  1. config/paystack.php — the only file you should need to touch for a standard integration.
  2. The facade / service class in src/ — skim the public methods once; most of what you need day-to-day is initialize, verify, and the transaction helpers.
  3. The package's README usage block for the exact return shape (the package returns decoded responses, usually as arrays or Collections — check which, because it changes how you read data.authorization_url).

First use case: a redirect checkout

The canonical flow is initialize → redirect → verify on callback. Put the reference in the database before redirecting, so the callback has something to reconcile against:

$response = Paystack::initialize([
    'email' => $user->email,
    'amount' => $order->total_kobo, // subunits, see Gotchas
    'reference' => $order->reference,
    'callback_url' => route('paystack.callback'),
]);

return redirect($response['data']['authorization_url']);

Then in the callback route, verify server-side and only then mark the order paid.

Requirements note (v1.6.0): CI and dependency constraints now cover PHP 8.4 and Laravel 13, so the package is safe on modern stacks. If you are pinned to an older PHP (8.0 or below) or Laravel 9, stay on v1.5.x.

Implementation Patterns

Wrap the facade, don't spray it everywhere. Bind a thin PaystackGateway service (or an interface) in your container that delegates to the facade. It gives you one seam for tests, one place to add logging/retries, and one place to switch keys per tenant.

Persist the reference, treat the callback as a hint. The browser callback only tells you the user came back. The source of truth is verify($reference) or the webhook:

$result = Paystack::verify($reference);
if (($result['data']['status'] ?? null) === 'success'
    && $result['data']['amount'] === $order->total_kobo) {
    $order->markPaid($result['data']);
}

Note the amount check — verifying status alone is not enough.

Webhook handler. Register a route outside auth/csrf, read the raw body, and compare x-paystack-signature against hash_hmac('sha512', $rawBody, config('paystack.secretKey')). Then dispatch a job so the HTTP response returns 200 fast:

Route::post('/paystack/webhook', PaystackWebhookController::class)
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);

Store the event id/reference in a unique-indexed table for idempotency — Paystack retries.

Queues and jobs. Initialization is a network call; inside a request it's fine, but anything that fans out (bulk refunds, subscriptions sweeps) belongs in a queued job with $tries and backoff set. Treat non-2xx responses as retryable and 4xx validation errors as terminal.

Per-tenant keys. If you serve multiple merchants, resolve keys at call time from the tenant model rather than the global config. The cleanest approach is a scoped binding: register your gateway with the tenant's secret key in a request-scoped container binding, or pass an explicit key argument where the package methods allow it.

New in v1.6.0 — Terminal, Virtual Terminal, and Order APIs. These surfaces are now available in the package, which means three new workflows:

  • Terminal (physical POS): use it when you want to push a transaction to a merchant's assigned terminal and poll/list for the outcome instead of redirecting a browser. Model it as an outbound job with polling, not a synchronous request — terminal payment completion is asynchronous and can take tens of seconds.
  • Virtual Terminal: create a persistent virtual terminal per branch/cashier and drive card-present-style flows from your app. Treat the terminal identifier as a configurable model attribute, not a hardcoded string.
  • Order API: useful when you want Paystack to host the checkout object (line items, totals) rather than sending a bare amount. This is the better fit for multi-item carts and gives you a stable order reference for reconciliation.

For all three: check the method signatures in src/ against Paystack's docs before wiring them in. Feature PRs like these typically map one-to-one onto the API but can lag on optional parameters, and the package will not always validate payload shapes for you.

Testing. Fake at the HTTP layer (Http::fake([...])) rather than mocking the facade, so you also exercise the package's request construction — particularly the amount and reference fields, which are the two things you most want regression coverage on. For webhooks, build a signed payload with hash_hmac in the test rather than disabling signature checks.

Gotchas and Tips

Amounts are subunits. 1000 is ₦10.00, not ₦1000. Store money in the smallest unit in your DB (or a dedicated accessor) and never mix the two representations. This is the single most common production bug with any Paystack wrapper.

Currency matters. The default currency typically comes from config or the merchant account. If you sell in multiple currencies, pass currency explicitly on initialize and confirm the account supports it — a mismatched currency fails at the API, not in your code.

Verify signature against the raw body. $request->all() re-serialises and will not match the HMAC. Use $request->getContent(). If you have middleware that parses or rewrites JSON, exclude the webhook route.

References must be unique. Reusing a reference returns an error, and re-verifying a reference that is already consumed is fine (verify is idempotent) but re-initializing is not. Generate references server-side with a prefix, e.g. order_2024_000123.

Config caching. php artisan config:cache will freeze your keys. If you do per-tenant key switching, do it at runtime in your gateway, not via config() mutation + cache.

Never trust the client-side callback payload. Anyone can hit your callback URL with a forged reference. Always verify() and always check amount and currency and status.

Log responses, not keys. Add a debug log of the response body and the request reference in your gateway wrapper, but scrub authorization, keys, and card metadata. Paystack responses can include authorization objects you do not want in your logs.

Extension point: your own gateway class. Rather than patching the vendor package, wrap it. When a new API surface lands (as Terminal/Order did in v1.6.0) you can expose your own typed methods and normalise the array responses into DTOs, so a future package change only touches one file.

Watch the release cadence for API coverage. v1.6.0 is a feature-plus-support release with no documented breaking changes, so it is a safe minor upgrade from v1.5.x. Because the package tracks Paystack's API surface directly, pin a minor range (^1.6) and read release notes before jumping minors — new endpoints rarely break, but response normalisation sometimes shifts.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style