MTN MoMo Payment Gateway

Accept mobile money payments from MTN Mobile Money (MoMo) users across Africa. Integrates with the MTN MoMo Collections API to create “Request to Pay” transactions, enabling customers to approve payments directly from their phones.

Request to Pay

Initiate payment requests that customers approve via USSD prompt on their MTN phone.

Sandbox Provisioning

One-click generation of sandbox API credentials directly from the admin panel.

Real-time Polling

Automatic payment status polling with progress bar. Webhook support for production environments.

Encrypted Credentials

All API credentials are encrypted at rest using Laravel’s Crypt facade before database storage.

Use Cases

E-Commerce in Africa

You run an online shop on Larapen targeting customers in MTN-served countries (Uganda, Ghana, Cameroon, Ivory Coast, Benin, Congo, Liberia). Customers pay for orders using their MTN MoMo wallet: no credit card required.

  • Install the Shop and MoMo add-ons.
  • Configure MTN MoMo credentials in the admin panel.
  • Customers select “MTN Mobile Money” at checkout, enter their phone number, and approve the payment on their phone.

Digital Product Sales

You sell digital products (e-books, software, templates) to mobile-first users who prefer mobile money over card payments.

  • Pair with the Shop add-on’s digital product delivery.
  • After MoMo payment confirmation, the download link is automatically provided.

Service Bookings

Any payable entity implementing the Payable contract can use MoMo for payment, making it extensible beyond the Shop add-on.

Requirements

  • Larapen CMS v1.0.0 or later
  • PHP 8.3+
  • MySQL 8.0+
  • The Shop add-on (required dependency)
  • An MTN MoMo developer account at momodeveloper.mtn.com
  • A subscription to the Collections product on the MTN MoMo developer portal
Note: For sandbox testing, only the Subscription Key is required: the API User ID and API Key can be generated automatically via the admin panel’s provisioning feature.

Installation

Step 1: Place the Add-on

Copy or symlink the momo folder into your Larapen "extensions/addons" directory:

Step 2: Activate the Add-on

Go to Admin → Add-ons → Installed Add-ons and activate MTN MoMo Payment Gateway.

Dependency: The Shop add-on must be installed and active before activating MoMo.

Step 3: Run Migrations

This creates the momo_transactions table for tracking payment references and statuses.

Step 4: Set Permissions

The add-on registers 2 permissions (see Permissions). Assign them to admin roles via Admin → Users → Roles & Permissions.

Step 5: Configure

Navigate to Admin → MTN MoMo → Settings and configure your API credentials. For sandbox testing, use the Provisioning feature to auto-generate credentials. See Configuration.

Configuration

All settings are managed in Admin → MTN MoMo → Settings (stored in the settings table, group momo). Configuration defaults are defined in config/momo.php.

Setting Description Default
momo_subscription_key Ocp-Apim-Subscription-Key from the MTN MoMo developer portal. Stored encrypted. (empty)
momo_api_user_id UUID v4 of the API User. Generated during provisioning or obtained from the MTN Partner Portal. Stored encrypted. (empty)
momo_api_key API Key generated for the API User. Stored encrypted. (empty)
momo_environment Deployment environment: sandbox or production. sandbox
momo_target_environment Target environment header value. sandbox for testing, or a country-specific code for production (see Payment Options). sandbox
momo_currency Currency code for payments. Must be EUR in sandbox. Country-specific in production. EUR
momo_callback_host Your domain for receiving webhook callbacks (production only). Must support HTTPS. (empty)

Environment Variables

Note: Environment variables are used as defaults. Settings saved in the admin panel override them. API credentials saved via the admin panel are encrypted before storage.

MTN Developer Portal Setup

  1. Create an account at momodeveloper.mtn.com.
  2. Subscribe to the Collections product.
  3. Navigate to your Profile page and copy the Primary Key (or Secondary Key): this is your Subscription Key.
  4. For sandbox: use the admin panel’s Provisioning feature to auto-generate the API User ID and API Key.
  5. For production: obtain credentials from the MTN MoMo Partner Portal and enter them in API Credentials.
Important: Copy the Subscription Key from your Profile page, not from the product page. The key on the Profile page is the one that works with the API.

Admin: Settings

The settings page (MTN MoMo → Settings) is organized into four sections:

Sandbox Provisioning

This section automates the sandbox credential generation workflow:

  1. Enter your Subscription Key (from the MTN developer portal Profile page).
  2. Optionally enter a Callback Host (defaults to webhook.site for sandbox).
  3. Click Generate API Credentials.
  4. The system automatically:
    • Generates a UUID v4 for the API User ID
    • Creates the API User via POST /v1_0/apiuser
    • Generates the API Key via POST /v1_0/apiuser/{id}/apikey
    • Encrypts and stores all three credentials in the database
    • Sets the environment to sandbox and currency to EUR
  5. The generated credentials are displayed once in a success alert with copy buttons. Save them externally: the API Key is not retrievable again from MTN.

Test Key

The Test Key button performs a diagnostic API call (creates a temporary API User) to verify the Subscription Key is valid. Returns HTTP 201 on success, with request/response details for debugging.

API Credentials

Three password-style fields with toggle-visibility buttons:

  • Subscription Key: Ocp-Apim-Subscription-Key from the MTN portal.
  • API User ID: UUID v4 created during API User provisioning.
  • API Key: Generated from the API User.
Security: All three credentials are encrypted with Laravel’s Crypt::encryptString() before being stored in the database. They are decrypted only when needed for API calls. Leave a field blank to keep the current stored value.

Payment Options

  • Environment: sandbox or production. Determines which MTN API base URL is used.
  • Target Environment: Sent as the X-Target-Environment header. Use sandbox for testing, or one of the country-specific values for production:
Country Target Environment Value Currency
Uganda mtnuganda UGX
Ghana mtnghana GHS
Cameroon mtncameroon XAF
Ivory Coast mtnivorycoast XOF
Benin mtnbenin XOF
Congo mtncongo XAF
Liberia mtnliberia LRD
Sandbox sandbox EUR
  • Currency: Must match the target environment (see table above). Must be EUR in sandbox.
  • Callback Host: Your domain for receiving webhook callbacks (production only). HTTPS is required.

Integration Info

The bottom of the settings page displays:

  • The Callback URL (https://your-domain.com/momo/callback) for copying into the MTN portal.
  • A reference list of all valid target environment values by country.

Payment Flow

The complete payment journey from checkout to confirmation:

1. Checkout Selection

The customer selects “MTN Mobile Money” as their payment method on the checkout page. A phone number input field is displayed (the momo::payment-form Blade view).

2. Phone Number Entry

The customer enters their MTN MoMo phone number (e.g., +233XXXXXXXXX). The number is normalized (spaces, dashes, and parentheses removed) before submission.

3. Order Creation

The form is submitted via AJAX. The Shop add-on creates the Order record, then calls MomoGateway::createPaymentIntent().

4. Request to Pay

The gateway:

  1. Obtains an OAuth2 Bearer token from MTN (POST /collection/token/).
  2. Generates a UUID reference ID for the transaction.
  3. Sends a “Request to Pay” to MTN (POST /collection/v1_0/requesttopay).
  4. Stores a MomoTransaction record with PENDING status.
  5. Returns the reference ID to the frontend.

5. USSD Approval

The customer receives a USSD prompt on their MTN phone and enters their PIN to approve the payment.

6. Frontend Polling

While the customer approves, the frontend JavaScript polls GET /momo/status/{referenceId} every 5 seconds (configurable), displaying a progress bar. See Polling Mechanism.

7. Confirmation

When polling returns succeeded, the frontend redirects to GET /momo/confirm?reference_id={id}. The MomoController::confirm() method verifies the final status, marks the order as paid, and redirects to the order success page.

Sandbox vs. Production: In sandbox, polling is the primary mechanism (no real USSD prompts or callbacks). In production, both polling and webhooks work simultaneously: whichever confirms first triggers the order update.

Webhooks

In production, MTN MoMo sends callbacks to your server when a payment status changes. The callback URL is https://your-domain.com/momo/callback.

PUT /momo/callback
Description

Receives MTN MoMo payment status callbacks. CSRF protection is disabled for this endpoint. Also accepts POST requests for flexibility.

Processing

The WebhookController delegates to MomoGateway::handleWebhook(), which:

  1. Extracts the reference_id from the X-Reference-Id header or payload.
  2. Finds the matching MomoTransaction record.
  3. Updates the transaction status and financial transaction ID.
  4. If SUCCESSFUL: marks the order as paid and creates a Shop Transaction record.
  5. If FAILED: marks the payment as failed and creates a failed Transaction record.
Response (JSON)

Refunds

Not supported: The MTN MoMo Collections API does not provide a refund endpoint. The refund() method returns a failure with an explanatory message. Refunds must be processed manually through the MTN MoMo Partner Portal or via the separate Disbursements API.

Shop Add-on Integration

The MoMo add-on integrates with the Shop add-on through the PaymentGatewayInterface contract:

Gateway Discovery

The MomoGateway class is tagged as payment.gateways in the service container. The Shop add-on discovers it automatically alongside other payment gateways (e.g., Stripe).

Payable Interface

The Shop’s Order model implements App\Contracts\Payable, which provides:

  • getPayableAmount(): the order total
  • getPayableIdentifier(): the order number
  • getPaymentSuccessUrl(): redirect URL after successful payment
  • getPaymentCancelUrl(): redirect URL after failed/cancelled payment
  • markAsPaid(): updates order status to COMPLETED and payment status to PAID

Transaction Recording

Two types of transaction records are created:

  • MomoTransaction: tracks the MTN API reference ID, status, and payer phone number.
  • Shop Transaction: created by the webhook/confirm handler for the order’s payment history.

Polymorphic Support

The MomoTransaction model uses a morphTo relationship (payable_type / payable_id), allowing any model that implements Payable to use MoMo for payments: not just Shop orders.

Updating

Step 1: Replace Files

Replace the add-on directory with the new version.

Step 2: Run Migrations

Step 3: Clear Caches

Step 4: Verify

Visit MTN MoMo → Settings and use Test Key to confirm your credentials still work.

Backup first: Always back up your database before running migrations on a production system.

Troubleshooting

“Failed to obtain MTN MoMo access token”

Check that:

  • All three API credentials (Subscription Key, API User ID, API Key) are configured correctly.
  • The Subscription Key has not expired or been revoked.
  • Your server can reach sandbox.momodeveloper.mtn.com (sandbox) or proxy.momoapi.mtn.com (production).
  • The API User ID and API Key match: they must be from the same provisioning session.

Provisioning fails: HTTP 401

  • Verify you have subscribed to the Collections product on momodeveloper.mtn.com.
  • Copy the Primary Key from your Profile page, not from the product page.
  • Ensure the subscription is still active (not expired).
  • Check for leading/trailing whitespace when pasting the key.

Provisioning fails: HTTP 409

An API User with the generated UUID already exists. Simply click Generate API Credentials again: a new UUID is generated each time.

“A phone number is required for MTN MoMo payments”

The customer did not enter their phone number in the checkout form. Ensure the MoMo payment form view (momo::payment-form) is being loaded correctly and the phone input is visible when MoMo is selected as the payment method.

Payment stays “pending” indefinitely

  • In sandbox: use one of the test phone numbers (see Sandbox Testing).
  • In production: the customer may not have approved the USSD prompt on their phone.
  • After the polling timeout (~5 minutes), the user is shown a timeout message.
  • The payment can still be confirmed later via webhook if the customer approves after the timeout.

“Payment was declined or cancelled”

  • The customer declined the USSD prompt or entered an incorrect PIN.
  • The customer’s MoMo account has insufficient funds.
  • The MTN reason code is appended to the error message for debugging (e.g., PAYER_NOT_FOUND, NOT_ENOUGH_FUNDS).

“Request was blocked” (non-JSON response)

A WAF (Web Application Firewall) or proxy blocked the API request. This can happen when the MTN API gateway rejects the request at the infrastructure level. Check server logs for the full response body and verify your Subscription Key and callback host configuration.

Webhook not working in production

  • Ensure momo_callback_host is set to your domain (without https:// prefix).
  • The callback host in your settings must match the providerCallbackHost set during API User creation.
  • Your server must accept PUT and POST requests at /momo/callback without CSRF verification.
  • Verify HTTPS is working correctly on your domain.

Currency mismatch errors

  • Sandbox requires EUR: any other currency will fail.
  • Production requires the country-specific currency matching the target environment (see Payment Options).

Sandbox Test Phone Numbers

Use these phone numbers in sandbox mode to test different payment outcomes:

Phone Number Result
46733123453 Successful payment
46733123454 Payment declined
46733123455 Payment declined
46733123456 Payment declined

Was this article helpful?

Thank you for your feedback!

Still need help? Create a support ticket

Create a Ticket