Retour aux articles
PHP

MTN Mobile Money Integration in PHP: Complete Guide

17 novembre 2025
5 min read
MTN Mobile Money Integration in PHP: Complete Guide

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:

  1. Visit MTN Developer Portal
  2. Create an application
  3. Get your API key and subscription key
  4. 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

  1. Always use HTTPS for production
  2. Validate webhook signatures to prevent fraud
  3. Handle timeouts gracefully - mobile payments can be slow
  4. Store transaction records for reconciliation
  5. Test thoroughly in sandbox before production
  6. Implement retry logic for failed API calls
  7. 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