yamoon / mtn-momo-api
Laravel client for the MTN Mobile Money (MoMo) Open API — Collection, Disbursement and Remittance products.
Requires
- php: ^8.2
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- ramsey/uuid: ^4.7
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
This package is auto-updated.
Last update: 2026-07-30 13:01:02 UTC
README
Unofficial Laravel client for the MTN Mobile Money (MoMo) Open API, covering the three sandbox products:
- Collection — request money from a customer (
requesttopay,requesttowithdraw) - Disbursement — pay money out to a customer (
transfer,refund) - Remittance — cross-border transfer to a customer (
transfer)
Each product is provisioned independently on the MoMo Developer portal (its own subscription key, API user and API key), but is reachable from a single facade.
This is not an official MTN package. MTN occasionally changes scopes/fields per market — always sanity-check payloads against your own subscription's Postman collection on momodeveloper.mtn.com before going to production.
Installation
composer require yamoon/mtn-momo-api
Laravel's package auto-discovery registers the service provider and the Momo facade automatically.
Publish the config file:
php artisan vendor:publish --tag=momo-config
Configuration
Copy the relevant variables from .env.example into your app's .env. You get subscription keys from the "Products" tab on the MoMo Developer portal; API user/key are generated per product (see Sandbox provisioning below).
MOMO_ENVIRONMENT=sandbox MOMO_COLLECTION_SUBSCRIPTION_KEY= MOMO_COLLECTION_API_USER= MOMO_COLLECTION_API_KEY= MOMO_COLLECTION_TARGET_ENVIRONMENT=sandbox MOMO_DISBURSEMENT_SUBSCRIPTION_KEY= MOMO_DISBURSEMENT_API_USER= MOMO_DISBURSEMENT_API_KEY= MOMO_DISBURSEMENT_TARGET_ENVIRONMENT=sandbox MOMO_REMITTANCE_SUBSCRIPTION_KEY= MOMO_REMITTANCE_API_USER= MOMO_REMITTANCE_API_KEY= MOMO_REMITTANCE_TARGET_ENVIRONMENT=sandbox
Only configure the products you actually use — each is lazily instantiated on first access.
Sandbox provisioning
Before you have API user/key credentials, you only need a subscription key (copied from the portal) to provision them:
use Mtn\MomoApi\Facades\Momo; $apiUser = Momo::collection()->createApiUser('https://example.com/momo/callback'); $apiKey = Momo::collection()->createApiKey($apiUser); // Store these as MOMO_COLLECTION_API_USER / MOMO_COLLECTION_API_KEY
Repeat per product (Momo::disbursement(), Momo::remittance()) — each has its own sandbox user/key even though the provisioning endpoint shape is identical.
Usage
Collection — request a payment
use Mtn\MomoApi\DTO\Party; use Mtn\MomoApi\Facades\Momo; $referenceId = Momo::collection()->requestToPay( amount: '500', payer: Party::msisdn('46733123454'), externalId: 'order-1234', payerMessage: 'Invoice #1234', payeeNote: 'Thanks for your order', ); // Poll for the result (or listen on your callback URL for the async notification) $status = Momo::collection()->requestToPayStatus($referenceId); // ['amount' => '500', 'currency' => 'EUR', 'status' => 'SUCCESSFUL', ...]
Disbursement — pay a customer
use Mtn\MomoApi\DTO\Party; use Mtn\MomoApi\Facades\Momo; $referenceId = Momo::disbursement()->transfer( amount: '1000', payee: Party::msisdn('46733123454'), externalId: 'payout-5678', ); $status = Momo::disbursement()->transferStatus($referenceId);
Remittance — cross-border transfer
use Mtn\MomoApi\DTO\Party; use Mtn\MomoApi\Facades\Momo; $referenceId = Momo::remittance()->transfer( amount: '2500', payee: Party::msisdn('46733123454'), externalId: 'remit-91011', ); $status = Momo::remittance()->transferStatus($referenceId);
Common to all three products
Momo::collection()->getBalance(); // ['availableBalance' => '100', 'currency' => 'EUR'] Momo::collection()->isAccountHolderActive(\Mtn\MomoApi\Enums\PartyIdType::MSISDN, '46733123454'); // true|false Momo::collection()->basicUserInfo('46733123454'); // requires the "basic userinfo" scope on your subscription
Party also has ::email() and ::partyCode() constructors for the other partyIdType values MTN supports.
Without the facade
use Mtn\MomoApi\Momo; $momo = app(Momo::class); $momo->collection()->requestToPay(/* ... */);
Error handling
Failed HTTP calls throw Mtn\MomoApi\Exceptions\MomoRequestException, which exposes MTN's error payload:
use Mtn\MomoApi\Exceptions\MomoRequestException; try { Momo::collection()->requestToPay('500', Party::msisdn('46733123454'), 'order-1'); } catch (MomoRequestException $e) { $e->momoErrorCode(); // e.g. "PAYER_NOT_FOUND", "NOT_ENOUGH_FUNDS" $e->momoErrorMessage(); // MTN's human-readable message $e->response(); // the underlying Illuminate\Http\Client\Response }
Failures to obtain an OAuth2 token throw Mtn\MomoApi\Exceptions\MomoAuthenticationException.
Token caching
Access tokens are cached automatically (per product, via your app's default cache store) until shortly before they expire. Configure the store and safety buffer in config/momo.php:
'cache' => [ 'store' => env('MOMO_CACHE_STORE'), // null = default app store 'prefix' => env('MOMO_CACHE_PREFIX', 'mtn_momo_api'), 'expiry_buffer' => env('MOMO_CACHE_EXPIRY_BUFFER', 60), // seconds ],
Testing your integration
The package uses Laravel's HTTP client under the hood, so you can fake it in your own tests:
use Illuminate\Support\Facades\Http; Http::fake([ 'https://sandbox.momodeveloper.mtn.com/collection/token/' => Http::response([ 'access_token' => 'fake-token', 'expires_in' => 3600, ]), 'https://sandbox.momodeveloper.mtn.com/collection/v1_0/requesttopay' => Http::response('', 202), ]);
Running the package's own test suite
composer install
composer test
License
MIT — see LICENSE.md.