opsofts/laravel-monnify

A minimal Monnify integration for Laravel, built directly on Laravel's own HTTP client.

Maintainers

Package info

github.com/tomibady/laravel-monnify

pkg:composer/opsofts/laravel-monnify

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-06 14:56 UTC

This package is auto-updated.

Last update: 2026-08-06 14:58:23 UTC


README

A minimal Monnify integration for Laravel, built directly on Laravel's own HTTP client (Illuminate\Support\Facades\Http). No external HTTP SDK dependency — just illuminate/support/illuminate/http, which ship with every Laravel app and are maintained on the same cadence as the framework itself.

What it does

Implements the operations most apps need against Monnify's API:

  • Initialize a transaction — returns a checkoutUrl you redirect the payer to
  • Verify a transaction — by your own paymentReference or Monnify's transactionReference
  • Refund a transaction — full or partial (requires refunds to be activated on your Monnify account first — see below)
  • Check refund status — by refundReference
  • Verify a webhook signature — HMAC-SHA512 the raw body with your Secret Key and compare against the monnify-signature header

Bearer token fetching and caching (1-hour tokens, refreshed automatically) is handled for you. Anything beyond the above (reserved/virtual accounts, disbursements, sub-accounts, bulk transfers, BVN/NIN verification, etc.) isn't included — the package stays intentionally small. Feel free to extend src/Monnify.php if you need more of the API surface.

Requirements

  • PHP 8.2+
  • Laravel 11, 12, or 13

Install

composer require opsofts/laravel-monnify
php artisan vendor:publish --tag=monnify-config

Set your credentials in .env. Get these from your Monnify dashboard under Developers > API Keys & Contracts:

MONNIFY_API_KEY=MK_...
MONNIFY_SECRET_KEY=...
MONNIFY_CONTRACT_CODE=...
MONNIFY_ENVIRONMENT=sandbox

MONNIFY_CONTRACT_CODE comes from Settings > Contracts and is required on every transaction initialize call — this package fills it in automatically unless you override it in the payload.

MONNIFY_ENVIRONMENT selects the base URL (sandbox.monnify.com for sandbox, api.monnify.com for production). Set MONNIFY_BASE_URL to override directly if needed.

Usage

use Opsofts\LaravelMonnify\Facades\Monnify;

// Initialize a transaction
$transaction = Monnify::initializeTransaction([
    'amount' => 5000,
    'customerName' => $customer->name,
    'customerEmail' => $customer->email,
    'paymentReference' => $orderReference, // must be unique per attempt
    'paymentDescription' => 'Order #1234',
    'redirectUrl' => route('payment.callback'),
    'paymentMethods' => ['CARD', 'ACCOUNT_TRANSFER'],
]);
// redirect the payer to $transaction['responseBody']['checkoutUrl']

// Verify -- by your own paymentReference (default) or Monnify's transactionReference
$result = Monnify::verifyTransaction($orderReference);
// or: Monnify::verifyTransaction($transactionReference, type: 'transactionReference');

if ($result['responseBody']['paymentStatus'] === 'PAID'
    && $result['responseBody']['amountPaid'] >= $expectedAmount) {
    // mark the order/payment as paid
}
// PARTIALLY_PAID / OVERPAID / PENDING / FAILED / REVERSED / EXPIRED are the
// other possible statuses -- see Monnify's docs for how to handle each.

// Refund (full or partial) -- requires refunds to be activated on your account
Monnify::refund(
    transactionReference: $transactionReference,
    refundReference: $refundReference, // must be unique per refund
    refundAmount: $partialAmount, // between ₦100 and the original amount
    refundReason: 'Customer requested refund', // max 64 chars, your records
    customerNote: 'Order refund', // max 16 chars, shown on their bank alert
);

// Check refund status (prefer webhooks in production -- see below)
Monnify::getRefundStatus($refundReference);

// Webhook signature verification -- do this before trusting ANY webhook payload.
// Pass the RAW request body, not the parsed array.
$isValid = Monnify::verifyWebhookSignature(
    rawPayload: $request->getContent(),
    signatureHeader: $request->header('monnify-signature'),
);

if (! $isValid) {
    abort(401);
}

Refunds require activation

Refunds are not enabled by default on a Monnify account. You have to email Monnify support to activate the refund API (separately for sandbox and live), stating your use case — see Monnify's Refunds documentation for details. refund() will fail with an API error until this is done.

Also note: Monnify cannot refund to a virtual/reserved account number. If the original payment came in via a virtual account, you must collect a regular bank account number from the customer and pass it as destinationAccountNumber/destinationAccountBankCode.

A note on the webhook secret

Unlike Paystack and Flutterwave, Monnify does not use a separately configured webhook secret. The HMAC key is your Secret Key — the same one used to authenticate API calls. There's nothing extra to set up on the dashboard beyond pointing your webhook URL at your endpoint under Settings > API Keys & Webhooks (or Developers > Webhook URLs, depending on your dashboard version).

A note on getRefundStatus()

Every other endpoint in this package (init-transaction, transactions/query, refunds/initiate-refund, the auth login endpoint) was confirmed against Monnify's current documentation and cross-checked against a community reference implementation. The exact path for checking a single refund's status by reference (GET /api/v1/refunds/{refundReference}) was inferred from Monnify's "Get All Refunds" endpoint convention (GET /api/v1/refunds) rather than independently confirmed — verify it against your own Monnify API reference/Postman collection before relying on it in production. Monnify's own docs recommend webhooks over polling for refund status anyway.

What this package deliberately does not do

No card storage, no saved payment methods. It only ever talks to Monnify's API and hands back what Monnify returns — persisting references/statuses on your own models is the calling application's responsibility, same as with any SDK.

Testing

Http::fake() works exactly as it does for any Laravel HTTP client usage — fake your configured base URL (sandbox or production) for both the login call and API calls, rather than hitting the real API. No custom test helpers are provided; standard Laravel HTTP testing covers this package fully.

License

MIT.