How to Integrate MTN Mobile Money in PHP – Complete Guide
How to Integrate MTN Mobile Money in PHP – Complete Guide
Mobile Money is the dominant payment method in Africa, with MTN Mobile Money (MoMo) leading the market. If you’re building a PHP application that needs to accept payments or send money in Africa, integrating MTN MoMo is essential.
In this guide, I’ll show you how to integrate MTN Mobile Money using a modern PHP library that makes it simple and type-safe.
What We’ll Build
By the end of this tutorial, you’ll be able to:
- Accept payments from customers (Collection)
- Send money to users (Disbursement)
- Check transaction status
- Handle callbacks
- Test everything in sandbox mode
Installation
First, install the library via Composer:
composer require lepresk/momo-api
Requirements: PHP 7.4+ or PHP 8.x
Quick Start: Accepting Payments
Let’s start with the most common use case – accepting a payment from a customer.
1. Setup Collection API
<?php
use LepreskMomoApiMomoApi;
$collection = MomoApi::collection([
'environment' => 'sandbox', // Use 'mtncongo', 'mtnuganda', etc. for production
'subscription_key' => 'YOUR_SUBSCRIPTION_KEY',
'api_user' => 'YOUR_API_USER',
'api_key' => 'YOUR_API_KEY',
'callback_url' => 'https://yourdomain.com/callback'
]);
2. Request Payment (Quick Method)
The simplest way to request a payment:
// Request 1000 XAF from phone number 242068511358
$paymentId = $collection->quickPay('1000', '242068511358', 'ORDER-123');
echo "Payment ID: $paymentId"; // UUID v4
3. Check Payment Status
$transaction = $collection->getPaymentStatus($paymentId);
if ($transaction->isSuccessful()) {
echo "Payment of {$transaction->getAmount()} {$transaction->getCurrency()} received!";
echo "Payer: {$transaction->getPayer()}";
// Update your database, fulfill order, etc.
} elseif ($transaction->isFailed()) {
$reason = $transaction->getReason();
if ($reason->isNotEnoughFunds()) {
echo "Customer has insufficient funds";
} elseif ($reason->isPayerLimitReached()) {
echo "Customer reached their transaction limit";
}
}
Advanced: Sending Money (Disbursement)
Need to pay out to users? Use the Disbursement API:
use LepreskMomoApiMomoApi;
use LepreskMomoApiModelsTransferRequest;
$disbursement = MomoApi::disbursement([
'environment' => 'sandbox',
'subscription_key' => 'YOUR_SUBSCRIPTION_KEY',
'api_user' => 'YOUR_API_USER',
'api_key' => 'YOUR_API_KEY'
]);
// Send money to a user
$transfer = TransferRequest::make('5000', '242068511358', 'SALARY-001');
$transferId = $disbursement->transfer($transfer);
// Check transfer status
$result = $disbursement->getTransferStatus($transferId);
if ($result->isSuccessful()) {
echo "Transfer of {$result->getAmount()} sent successfully!";
}
Handling Callbacks
MTN MoMo sends asynchronous callbacks when transactions complete. Here’s how to handle them:
<?php
// webhook.php
use LepreskMomoApiModelsTransaction;
// Parse the callback data
$transaction = Transaction::parse($_GET);
if ($transaction->isSuccessful()) {
// Update order status in database
$orderId = $transaction->getExternalId();
$amount = $transaction->getAmount();
DB::table('orders')
->where('id', $orderId)
->update(['status' => 'paid', 'amount' => $amount]);
// Send confirmation email, etc.
} elseif ($transaction->isFailed()) {
$reason = $transaction->getReason();
// Log the failure
error_log("Payment failed: {$reason->getCode()} - {$reason->getMessage()}");
}
http_response_code(200); // Always return 200 to MTN
Testing in Sandbox Mode
Before going to production, test everything in sandbox:
1. Create Sandbox API User
use LepreskMomoApiMomoApi;
use LepreskMomoApiSupportUuid;
$momo = MomoApi::create(MomoApi::ENVIRONMENT_SANDBOX);
// Generate UUID for API user
$uuid = Uuid::v4();
// Create API user
$apiUser = $momo->sandbox('YOUR_SANDBOX_SUBSCRIPTION_KEY')
->createApiUser($uuid, 'https://yourdomain.com/callback');
// Create API key
$apiKey = $momo->sandbox('YOUR_SANDBOX_SUBSCRIPTION_KEY')
->createApiKey($apiUser);
echo "API User: $apiUsern";
echo "API Key: $apiKeyn";
2. Use These Credentials
Now use the generated credentials for testing in sandbox mode.
Production Deployment
When ready for production:
$collection = MomoApi::collection([
'environment' => 'mtncongo', // or 'mtnuganda', 'mtnghana', etc.
'subscription_key' => env('MOMO_SUBSCRIPTION_KEY'),
'api_user' => env('MOMO_API_USER'),
'api_key' => env('MOMO_API_KEY'),
'callback_url' => env('MOMO_CALLBACK_URL')
]);
Supported Countries
The library supports all MTN MoMo markets:
- Congo
- Uganda
- Ghana
- Cameroon
- Zambia
- Ivory Coast
- Benin
- South Africa
- And more…
Best Practices
1. Always Use Environment Variables
// .env
MOMO_SUBSCRIPTION_KEY=your_key_here
MOMO_API_USER=your_user_here
MOMO_API_KEY=your_key_here
MOMO_CALLBACK_URL=https://yourdomain.com/webhook
2. Validate Callbacks via API
Never trust callback data alone. Always verify:
// In your webhook handler
$callbackData = Transaction::parse($_GET);
$paymentId = $callbackData->getExternalId();
// Verify by calling the API
$verified = $collection->getPaymentStatus($paymentId);
if ($verified->isSuccessful()) {
// NOW you can trust it and process the order
}
3. Handle Errors Gracefully
use LepreskMomoApiExceptionsResourceNotFoundException;
use LepreskMomoApiExceptionsInternalServerErrorException;
try {
$paymentId = $collection->quickPay('1000', '242068511358', 'ORDER-123');
} catch (ResourceNotFoundException $e) {
// Payment not found
log_error("Payment not found: " . $e->getMessage());
} catch (InternalServerErrorException $e) {
// MTN server error - retry later
queue_retry($paymentRequest);
}
4. Check Account Balance
Monitor your account balance regularly:
$balance = $collection->getBalance();
if ((int)$balance->getAvailableBalance() < 10000) {
notify_admin("Low MoMo balance: {$balance->getAvailableBalance()} {$balance->getCurrency()}");
}
Security Tips
-
Never commit credentials – Use
.envfiles - Validate webhook IPs – Only accept callbacks from MTN IPs
- Use HTTPS – For all callback URLs
- Log everything – Keep audit trails
-
Implement idempotency – Use unique
externalIdfor each transaction
Error Handling Reference
Common error reasons you’ll encounter:
$transaction = $collection->getPaymentStatus($paymentId);
if ($transaction->isFailed()) {
$reason = $transaction->getReason();
// Check specific errors
if ($reason->isNotEnoughFunds()) {
// Customer balance too low
} elseif ($reason->isPayerLimitReached()) {
// Daily/monthly limit exceeded
} elseif ($reason->isPayeeNotFound()) {
// Invalid phone number
}
// Get raw error data
echo $reason->getCode(); // e.g., "NOT_ENOUGH_FUNDS"
echo $reason->getMessage(); // Human-readable message
}
Advanced Features
Refunds
use LepreskMomoApiModelsRefundRequest;
// Refund a previous transaction
$refund = RefundRequest::make(
'1000', // Amount
'07a461a4-e721-462b-81c6-b9aa2f8abf06', // Original transaction ID
'REFUND-001' // Your refund reference
);
$refundId = $disbursement->refund($refund);
// Check refund status
$status = $disbursement->getRefundStatus($refundId);
Custom Payment Request
For more control over payment requests:
use LepreskMomoApiModelsPaymentRequest;
$request = new PaymentRequest(
amount: '2500',
currency: 'XAF',
externalId: 'ORDER-456',
payer: '242068511358',
payerMessage: 'Payment for premium subscription',
payeeNote: 'Thank you for your purchase'
);
$paymentId = $collection->requestToPay($request);
Real-World Example: E-commerce Checkout
Here’s a complete checkout flow:
<?php
// checkout.php
use LepreskMomoApiMomoApi;
// 1. Initialize collection
$collection = MomoApi::collection([...]);
// 2. Get cart total from session
$cartTotal = $_SESSION['cart_total'];
$userPhone = $_SESSION['user_phone'];
$orderId = generate_order_id();
// 3. Request payment
try {
$paymentId = $collection->quickPay(
(string)$cartTotal,
$userPhone,
$orderId
);
// 4. Save payment ID to database
DB::table('orders')->insert([
'id' => $orderId,
'payment_id' => $paymentId,
'amount' => $cartTotal,
'status' => 'pending',
'created_at' => now()
]);
// 5. Show waiting page
redirect("/payment/waiting/$paymentId");
} catch (Exception $e) {
show_error("Payment failed: " . $e->getMessage());
}
<?php
// webhook.php - Handle MTN callback
$transaction = Transaction::parse($_GET);
$orderId = $transaction->getExternalId();
if ($transaction->isSuccessful()) {
DB::table('orders')
->where('id', $orderId)
->update(['status' => 'paid']);
// Trigger order fulfillment
dispatch(new FulfillOrderJob($orderId));
// Send confirmation email
Mail::to($customer)->send(new OrderConfirmation($orderId));
}
http_response_code(200);
Resources
- GitHub Repository: lepresk/momo-api
- Packagist: lepresk/momo-api
- MTN Developer Portal: momodeveloper.mtn.com
- API Documentation: GitHub README
Conclusion
Integrating MTN Mobile Money in PHP is now straightforward with this library. You get:
- Type-safe – Full PHPStan level 5 compliance
- Modern – Fluent API with sensible defaults
- Tested – 57 tests with 211 assertions
- Production-ready – Used in real applications
- Well-documented – Examples for every method
The library handles all the complexity of OAuth tokens, HTTP headers, error handling, and status codes – so you can focus on building your application.
Questions? Drop a comment below or open an issue on GitHub!
Found this helpful? Star the repo on GitHub!
Tags: #php #payment #momo #mobilemoney #africa #api #tutorial #laravel #symfony #backend