Bonaventure OgetoBy Bonaventure Ogeto|

Building an E-Commerce Checkout with MoMo and Airtel Money in Uganda

A Ugandan e-commerce checkout with mobile money needs: (1) a payment form where the customer enters their phone number and selects MoMo or Airtel Money, (2) a backend that sends the payment request to the correct provider API, (3) a waiting/polling screen because mobile money payments are asynchronous, (4) a callback handler that receives the payment result from MoMo/Airtel Money, and (5) a confirmation or failure screen. The most common mistake is designing the checkout like a Stripe flow (instant result) instead of accounting for the delay while the customer confirms on their phone.

The Full Checkout Flow for Mobile Money

Here is the complete sequence of a mobile money checkout, from the customer clicking "Pay" to the order confirmation:

  1. Customer enters phone number. Your checkout form collects the customer's mobile money phone number. This is the equivalent of entering card details in a Stripe checkout.
  2. Your app detects the provider. Based on the phone number prefix, you determine whether to use the MoMo API or the Airtel Money API. MTN and Airtel Uganda use different number ranges.
  3. Your backend sends the payment request. An API call to MoMo or Airtel Money with the phone number, amount in UGX, and a unique transaction reference.
  4. Your frontend shows a waiting state. "We have sent a payment prompt to your phone. Please enter your PIN to confirm." This is the step most developers forget or design poorly.
  5. Customer confirms on their phone. They see a USSD prompt and enter their PIN. This happens entirely outside your app.
  6. Callback arrives at your server. MoMo or Airtel Money sends a POST request with the transaction result. Your server updates the order status.
  7. Your frontend updates. Via polling or WebSocket, the frontend detects the payment result and shows a success or failure screen.

Steps 4 through 7 are what make mobile money checkout fundamentally different from card checkout. You are designing for a flow where there is a real, unpredictable delay between the payment request and the result.

Designing the Frontend: Payment Form and Waiting State

The payment form itself is simple: a phone number input and a "Pay" button. But the details matter for mobile money UX:

  • Phone number input: Format it for Ugandan numbers (256XXXXXXXXX or 07XXXXXXXX). Accept both formats and normalize in your backend. Show a validation error if the number is too short, too long, or uses an unrecognized prefix.
  • Provider indicator: Once the customer enters enough digits, show which provider you detected (MTN MoMo or Airtel Money) with the provider's brand icon. This reassures the customer that the right account will be charged.
  • Amount display: Show the total clearly in UGX. Format it with commas for readability (e.g., "UGX 150,000").

The waiting state is the most important screen in the flow:

  • Show a clear message: "A payment prompt has been sent to 07XX XXX XXX. Open your phone and enter your MoMo PIN to confirm."
  • Show a spinner or progress indicator.
  • Include a "Cancel" option in case the customer changes their mind.
  • Start polling your backend for the transaction status (every 3-5 seconds is reasonable).
  • Set a timeout. If the customer does not confirm within 2-3 minutes, show a "Payment timed out" message with an option to retry.

Do not redirect to a success page optimistically. Wait for actual confirmation from your backend (which got it from the callback or status poll) before showing the success screen.

Backend Architecture: Handling Dual Providers

Your backend needs to handle both MoMo and Airtel Money. The cleanest approach is a payment service layer with a common interface:

  1. Payment request endpoint: Your frontend sends the phone number and order details to your backend. Your backend determines the provider, calls the correct API, and returns a transaction reference to the frontend.
  2. Provider routing: A function that maps phone number prefixes to providers. Maintain this as a configuration, not hardcoded logic, because number allocations can change.
  3. Callback endpoint: A single URL path that receives callbacks from both providers. Parse the callback, determine which provider sent it, extract the transaction reference and status, and update your order database.
  4. Status polling endpoint: The frontend polls this endpoint with the transaction reference. Your backend returns the current status from your database. If the callback has not arrived yet, optionally query the provider's status API as a fallback.

Key implementation details:

  • Idempotency: Process each callback exactly once. Use the transaction reference as a deduplication key. If you receive a callback for a transaction you have already processed, acknowledge it but do not create duplicate records.
  • Transaction state machine: Each transaction moves through states: INITIATED, PENDING, SUCCESSFUL, FAILED, TIMED_OUT. Never skip states. Never go backward.
  • Logging: Log every API request and every callback you receive, with timestamps and full payloads. When something goes wrong in production (and it will), these logs are how you debug it.

Error Handling and Edge Cases

The errors you will hit in production, roughly in order of frequency:

  • Timeout: The customer never confirms. Maybe they did not see the prompt, maybe they changed their mind, maybe their phone is off. Your app needs to handle this gracefully and offer a retry option.
  • Insufficient balance: The customer does not have enough money in their MoMo or Airtel Money wallet. Show a clear message ("Your mobile money balance is insufficient for this transaction").
  • Wrong PIN: The customer enters the wrong PIN. MoMo and Airtel Money handle this on their end (the customer can retry), but you may receive a failure callback.
  • Network issues: The callback never arrives because of a network problem between the provider and your server. This is why status polling exists as a fallback.
  • Duplicate callbacks: The provider sends the same callback twice. Your idempotent handler prevents double-processing.
  • Provider downtime: MoMo or Airtel Money's API is temporarily unavailable. Show a message asking the customer to try again later. Do not retry automatically in a tight loop.

For a comprehensive list of error codes and how to handle them, see our common MoMo and Airtel Money API errors guide.

The general principle: every error should result in a clear, actionable message to the customer. "Payment failed" with no context is bad UX. "Your MoMo balance is insufficient. Please top up and try again" is useful.

Testing the Complete Checkout Flow

Test every path through the checkout in sandbox before going live:

  1. Happy path: Customer pays, confirms, callback arrives, order is marked as paid.
  2. Timeout path: Payment requested but never confirmed. Your timeout logic triggers. Customer sees the timeout message.
  3. Failure path: Payment fails (insufficient balance, wrong PIN). Failure callback arrives. Customer sees the failure message with an option to retry.
  4. Callback failure path: Simulate a callback that never arrives (stop your callback endpoint). Verify that your polling mechanism picks up the result.
  5. Duplicate callback path: Send the same callback twice. Verify that your order is not double-processed.
  6. Provider switch: Test with both MTN and Airtel phone number prefixes. Verify that the correct API is called for each.

The sandbox makes most of this easy to test. For more advanced testing strategies, see our sandbox testing guide.

For the deployment itself, McTaba's Deployment and Going Live course (KES 4,999, approximately UGX 140,000) covers the production deployment checklist, including how to handle the transition from sandbox to live with real money.

Key Takeaways

  • Mobile money checkout is asynchronous. The customer confirms payment on their phone, outside your app. Your UI must account for this delay with a clear waiting state.
  • Auto-detect the provider from the phone number prefix. Do not make customers manually select "MoMo" or "Airtel Money" if you can determine it from their number.
  • Implement both callback handling and status polling. Callbacks can fail or be delayed. Polling ensures you always know the payment result, even if the callback never arrives.
  • Handle every edge case: timeouts (customer never confirms), duplicate callbacks, network failures between your server and the provider, and partial failures in multi-item orders.
  • Test the full checkout flow in sandbox before going live. The sandbox lets you simulate success, failure, and timeout scenarios.

Frequently Asked Questions

How long does a mobile money payment take to confirm?
Typically 10-30 seconds when the customer is attentive and has good network. It can take longer if the customer is slow to confirm or if there are network delays. Set your timeout to 2-3 minutes to allow for slow confirmations without making the customer wait indefinitely.
Should I use WebSocket or polling for the frontend status updates?
Polling (every 3-5 seconds) is simpler and works for most use cases. WebSocket gives a better user experience (instant update when the callback arrives) but adds complexity. Start with polling. Move to WebSocket if the latency bothers your users.
Can I refund a mobile money payment?
Yes, using the Disbursements API (for direct integration) or the aggregator's refund feature. You send money back to the customer's mobile money wallet. This is a separate API call, not an automatic reversal of the original payment.
How do I handle a checkout with multiple items?
Charge the total amount in a single mobile money transaction, not one transaction per item. Customers should only have to confirm once. Calculate the total on your backend, send one Request to Pay for the full amount, and associate the transaction with all items in the order.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon