monnify/monnify-laravel
A Laravel wrapper for the Monnify payment gateway, making it easy to accept collections, initiate single and bulk transfers, verify transactions, and handle webhooks. Includes configurable credentials, a fluent API, and support for sandbox and live environments.
Install the package and publish its config:
composer require monnify/monnify-laravel
php artisan vendor:publish --tag=monnify-config
Add your Monnify credentials to .env (MONNIFY_API_KEY, MONNIFY_SECRET_KEY, MONNIFY_CONTRACT_CODE, and the sandbox/base URL). Start by reading the new Quick Start section — it now contains a complete payment-initialisation and callback-verification example, which is the fastest path to a working integration.
Your first use case should be a simple card/transfer checkout: initialise a transaction, redirect the customer to the returned checkout URL, then verify the transaction on the callback route. Once that works end-to-end in sandbox, move on to webhooks (the docs now cover all 12 event types with a full Laravel controller example) so you are not relying solely on browser redirects.
For anything beyond collections, the 1.1.0 Bills Payment support is the next area to explore: browse categories and billers, fetch products, validate a customer, then vend. Use the newly documented sandbox test cards (no-OTP, OTP, 3DS, and failure scenarios) to exercise the unhappy paths before going live.
Payment initialisation and callback verification. Keep initialisation in a dedicated service or action class rather than a controller. Generate your own paymentReference (or persist Monnify's) before redirecting, so you can reconcile callbacks even if the user never returns. On the callback route, verify the transaction server-side with the package before marking an order paid — never trust query-string status alone.
Webhook-first fulfilment. Add a dedicated webhook controller, exclude it from CSRF in bootstrap/app.php (Laravel 11+) or VerifyCsrfToken (Laravel 10), and verify the signature against the raw request body before parsing. The 1.1.0 docs cover signature verification and best practices; follow them exactly. Dispatch a queued job from the webhook and return 200 quickly, so slow order processing does not cause Monnify retries. Make handlers idempotent keyed on paymentReference / transactionReference.
Bills Payment flow. Treat it as a multi-step workflow: categories → billers → products → validate customer → vend → requery. Cache categories and billers (they change infrequently) but always validate the customer immediately before vend, since validation is product- and customer-specific. Persist the vend reference before calling out, and use the requery endpoint for any transaction left in a pending/in-progress state rather than blindly retrying vend.
Bulk transfers. With destinationAccountName now optional in 1.1.0, you can send transaction lists that match the Monnify spec without fabricating names. Still validate the overall payload shape locally (amounts, account numbers, references) before submitting — a partially rejected bulk batch is more painful to reconcile than a pre-flight validation failure.
Response handling. The docs now explain the responseBody nesting convention. Standardise on a small helper or DTO that unwraps responseBody once, so your controllers and jobs do not repeatedly reach through nested arrays. Return your own consistent API responses rather than leaking Monnify's envelope to your frontend.
Integration tips. Bind the package client behind an interface if you plan to fake it in tests; use Http::fake() for feature tests around your own endpoints. Keep all Monnify calls inside jobs or service classes so retries and timeouts are explicit. Log the request reference, response code, and response body on every call — reconciliation during support incidents is dramatically easier.
responseBody nesting. Most confusion for new integrators comes from reading fields at the wrong level. Unwrap once at the boundary and pass clean data inward.vend is not idempotent by default. Always persist your reference before calling, and requery rather than retrying a vend whose response you did not receive.destinationAccountName is optional for the API, not for your UI. If your product requires a beneficiary name for user confirmation, keep that validation in your form request layer.composer.lock platform constraints aligned if you deploy across mixed PHP versions.How can I help you explore Laravel packages today?