Retour aux articles
PHP
MTN Mobile Money Integration in PHP: Complete Guide
17 novembre 2025
5 min read
Mobile money is essential for applications in Africa. This guide shows you how to integrate MTN Mobile Money into your PHP application.
Prerequisites
- PHP 8.1+
- Composer
- MTN Mobile Money API credentials
- HTTPS endpoint for callbacks
Getting Started
1. API Credentials
Sign up for MTN Mobile Money API access:
- Visit MTN Developer Portal
- Create an application
- Get your API key and subscription key
- Configure callback URLs
2. Install Dependencies
composer require guzzlehttp/guzzle
Implementation
MTN MoMo Service Class
<?php
namespace App\Services\Payment;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class MTNMoMoService
{
protected Client $client;
protected string $apiKey;
protected string $subscriptionKey;
protected string $baseUrl;
public function __construct()
{
$this->apiKey = config('services.mtn.api_key');
$this->subscriptionKey = config('services.mtn.subscription_key');
$this->baseUrl = config('services.mtn.base_url');
$this->client = new Client([
'base_uri' => $this->baseUrl,
'timeout' => 30,
]);
}
/**
* Request payment from customer
*/
public function requestPayment(
string $phoneNumber,
float $amount,
string $currency = 'XAF',
string $externalId = null
): array {
$referenceId = $externalId ?? $this->generateReferenceId();
try {
$response = $this->client->post('/collection/v1_0/requesttopay', [
'headers' => [
'Authorization' => 'Bearer ' . $this->getAccessToken(),
'X-Reference-Id' => $referenceId,
'X-Target-Environment' => config('services.mtn.environment'),
'Ocp-Apim-Subscription-Key' => $this->subscriptionKey,
'Content-Type' => 'application/json',
],
'json' => [
'amount' => (string) $amount,
'currency' => $currency,
'externalId' => $referenceId,
'payer' => [
'partyIdType' => 'MSISDN',
'partyId' => $this->formatPhoneNumber($phoneNumber),
],
'payerMessage' => 'Payment for order',
'payeeNote' => 'Payment received',
],
]);
return [
'success' => true,
'reference_id' => $referenceId,
'status' => 'PENDING',
];
} catch (GuzzleException $e) {
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
/**
* Check payment status
*/
public function checkPaymentStatus(string $referenceId): array
{
try {
$response = $this->client->get(
"/collection/v1_0/requesttopay/{$referenceId}",
[
'headers' => [
'Authorization' => 'Bearer ' . $this->getAccessToken(),
'X-Target-Environment' => config('services.mtn.environment'),
'Ocp-Apim-Subscription-Key' => $this->subscriptionKey,
],
]
);
$data = json_decode($response->getBody()->getContents(), true);
return [
'success' => true,
'status' => $data['status'],
'amount' => $data['amount'],
'currency' => $data['currency'],
'financial_transaction_id' => $data['financialTransactionId'] ?? null,
];
} catch (GuzzleException $e) {
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
/**
* Get OAuth access token
*/
protected function getAccessToken(): string
{
$cacheKey = 'mtn_momo_access_token';
return cache()->remember($cacheKey, now()->addMinutes(50), function () {
$response = $this->client->post('/collection/token/', [
'auth' => [$this->apiKey, config('services.mtn.api_secret')],
'headers' => [
'Ocp-Apim-Subscription-Key' => $this->subscriptionKey,
],
]);
$data = json_decode($response->getBody()->getContents(), true);
return $data['access_token'];
});
}
protected function generateReferenceId(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
protected function formatPhoneNumber(string $phoneNumber): string
{
// Remove any non-numeric characters
$phoneNumber = preg_replace('/[^0-9]/', '', $phoneNumber);
// Add country code if missing (example: 237 for Cameroon)
if (strlen($phoneNumber) === 9) {
$phoneNumber = '237' . $phoneNumber;
}
return $phoneNumber;
}
}
Configuration
Add to config/services.php:
'mtn' => [
'api_key' => env('MTN_API_KEY'),
'api_secret' => env('MTN_API_SECRET'),
'subscription_key' => env('MTN_SUBSCRIPTION_KEY'),
'base_url' => env('MTN_BASE_URL', 'https://sandbox.momodeveloper.mtn.com'),
'environment' => env('MTN_ENVIRONMENT', 'sandbox'),
],
Usage Example
In a Controller
<?php
namespace App\Http\Controllers;
use App\Services\Payment\MTNMoMoService;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
public function __construct(
protected MTNMoMoService $mtnMoMo
) {}
public function initiatePayment(Request $request)
{
$validated = $request->validate([
'phone_number' => 'required|string',
'amount' => 'required|numeric|min:100',
]);
$result = $this->mtnMoMo->requestPayment(
$validated['phone_number'],
$validated['amount']
);
if ($result['success']) {
// Store transaction in database
$transaction = Transaction::create([
'reference_id' => $result['reference_id'],
'phone_number' => $validated['phone_number'],
'amount' => $validated['amount'],
'status' => 'pending',
]);
return response()->json([
'message' => 'Payment initiated',
'transaction_id' => $transaction->id,
'reference_id' => $result['reference_id'],
]);
}
return response()->json([
'message' => 'Payment failed',
'error' => $result['error'],
], 400);
}
public function checkStatus(string $referenceId)
{
$result = $this->mtnMoMo->checkPaymentStatus($referenceId);
if ($result['success']) {
// Update transaction status
Transaction::where('reference_id', $referenceId)
->update(['status' => strtolower($result['status'])]);
}
return response()->json($result);
}
/**
* Webhook for payment notifications
*/
public function webhook(Request $request)
{
$data = $request->all();
$transaction = Transaction::where('reference_id', $data['referenceId'])
->first();
if ($transaction) {
$transaction->update([
'status' => strtolower($data['status']),
'financial_transaction_id' => $data['financialTransactionId'] ?? null,
]);
// Trigger any post-payment actions
if ($data['status'] === 'SUCCESSFUL') {
event(new PaymentSuccessful($transaction));
}
}
return response()->json(['status' => 'OK']);
}
}
Best Practices
- Always use HTTPS for production
- Validate webhook signatures to prevent fraud
- Handle timeouts gracefully - mobile payments can be slow
- Store transaction records for reconciliation
- Test thoroughly in sandbox before production
- Implement retry logic for failed API calls
- Monitor transaction status asynchronously
Testing
Use MTN's sandbox environment for testing:
// .env.testing
MTN_BASE_URL=https://sandbox.momodeveloper.mtn.com
MTN_ENVIRONMENT=sandbox
Conclusion
MTN Mobile Money integration is straightforward with proper error handling and testing. This implementation provides a solid foundation for accepting mobile payments in your PHP application.
For production use, ensure you've completed MTN's KYC process and moved to their production environment.
Tags
API
PHP
Tutorial