Integrating Mobile Money Payments In Laravel
Step-by-step patterns for integrating M-Pesa, Tigo Pesa, and Airtel Money into Laravel applications — with real examples from the ClickPesa package.
Why payment integration in Africa is different
Unlike Stripe or PayPal markets where card payments dominate, East African commerce runs primarily on mobile money. M-Pesa alone processes over $300 billion annually across the region. If your business app does not support USSD-based mobile money payments, you are excluding 80%+ of potential paying customers.
The challenge is that each provider (Vodacom M-Pesa, Tigo Pesa, Airtel Money, Halotel) has its own API specification, callback format, timeout behavior, and reconciliation process. Building and maintaining individual integrations is expensive.
The aggregator pattern with ClickPesa
Payment aggregators like ClickPesa provide a unified API that routes transactions to the correct mobile money provider based on the customer's phone number prefix. I built and published the clickpesa/clickpesa-laravel package on Packagist to standardize this integration for Laravel developers.
The package provides a clean service class: ClickPesa::charge($phoneNumber, $amount, $reference). Internally, it handles provider detection, API authentication, request signing, and callback verification. The developer only needs to implement a webhook controller to handle payment confirmations.
Handling payment callbacks securely
Mobile money callbacks arrive asynchronously — sometimes within 5 seconds, sometimes after 30 minutes if the customer delays USSD confirmation. Your application must handle this gracefully: create the order with a "pending_payment" status, listen for the callback, verify its authenticity (signature + IP whitelist), and only then transition the order to "confirmed".
Critical rule: never trust the callback amount. Always verify that the amount received matches the amount you requested. Some integrations have been exploited by attackers sending fake callbacks with modified amounts.
Reconciliation and financial reporting
Every payment transaction must be recorded in a dedicated payments ledger table, separate from the orders table. This ledger includes: transaction_id, provider, phone_number, amount_requested, amount_received, status, callback_payload (JSON), and timestamps. This separation makes financial reconciliation possible without querying the orders table.
We run daily reconciliation jobs that compare our ledger against the provider's transaction report (downloaded via API or CSV). Discrepancies are flagged automatically and reviewed by the finance team before the day's books are closed.
Designing Secure APIs For Real Operations
Why consistent contracts, permissions, and structured failure handling matter more than flashy endpoint counts.
Read ArticleRBAC Design For Business Systems
A practical approach to authorization when your product has admins, reviewers, operators, and stakeholders with different responsibilities.
Read Article