Africa processes over $1 trillion in mobile money transactions annually. More than 500 million people use mobile money as their primary financial instrument -- not as an alternative to cards, but as a replacement for an entire banking system that never reached them. Yet if you try to accept a payment from a customer in Ivory Coast, Senegal, or Cameroon using Stripe, you will discover that the world's most popular payment platform simply does not work for most of the continent.
This is the Africa payment problem. And it is the single biggest reason we built 0fee.dev.
The Numbers That Change Everything
Consider these figures from the GSMA's State of the Industry Report:
| Metric | Value |
|---|---|
| Registered mobile money accounts (Africa) | 850+ million |
| Active mobile money accounts (monthly) | 400+ million |
| Annual transaction value | $1 trillion+ |
| Mobile money agents across Africa | 10+ million |
| Card penetration (Sub-Saharan Africa) | < 5% |
| Bank account penetration (Sub-Saharan Africa) | ~30% |
| Mobile phone penetration (Sub-Saharan Africa) | ~85% |
The contrast is stark. In the United States, 80%+ of adults have a credit or debit card. In Sub-Saharan Africa, fewer than 5% do. But 85% have a mobile phone, and nearly half of all adults have an active mobile money account. The payment infrastructure is not absent -- it is just fundamentally different.
How Mobile Money Works
For readers unfamiliar with mobile money, here is the flow. It has nothing in common with card payments.
The Customer Experience
- The customer selects "Pay with Orange Money" (or MTN MoMo, Wave, M-Pesa, etc.).
- They enter their phone number.
- They receive a USSD push or STK push notification on their phone -- a simple menu on their screen, not an app notification.
- They enter their PIN to confirm the payment.
- The transaction settles. Both parties receive an SMS confirmation.
There is no card number, no CVV, no 3D Secure, no billing address. The entire authentication is phone number + PIN. The "account" is the SIM card itself.
The Technical Flow
From an integration perspective, mobile money is asynchronous by nature:
1. Merchant -> Provider API: initiate payment (phone, amount)
2. Provider -> Telco: send USSD/STK push to customer
3. Customer -> Phone: enter PIN to confirm
4. Telco -> Provider: confirmation callback
5. Provider -> Merchant: webhook notification (success/failure)This means your integration must handle:
- Asynchronous confirmation: the customer might take 30 seconds or 5 minutes to confirm.
- Timeout handling: if the customer does not respond, the transaction expires (typically 60-120 seconds for USSD).
- Phone number validation: each country has specific formats (+225 07XXXXXXXX for Ivory Coast, +233 XX XXX XXXX for Ghana).
- USSD session limits: some telcos limit concurrent USSD sessions per phone number.
The Provider Landscape
No single provider covers all of Africa. Each provider has its own geographic footprint, supported payment methods, and technical integration:
East Africa
| Provider | Countries | Methods | Integration |
|---|---|---|---|
| PawaPay | Kenya, Tanzania, Uganda, Rwanda, DRC, Mozambique, Zambia, Malawi | M-Pesa, MTN MoMo, Airtel Money, Tigo Pesa | REST API, webhooks |
| Safaricom (direct) | Kenya | M-Pesa only | Daraja API, STK Push |
| Flutterwave | Kenya, Tanzania, Uganda, Rwanda | M-Pesa, cards, bank transfer | REST API |
West Africa (Francophone)
| Provider | Countries | Methods | Integration |
|---|---|---|---|
| Hub2 | Ivory Coast, Senegal, Mali, Burkina Faso, Togo, Benin, Guinea, Cameroon | Orange Money, MTN MoMo, Moov Money, Wave | REST API, callbacks |
| PaiementPro | Ivory Coast, Senegal, Burkina Faso, Togo, Benin | Orange Money, MTN MoMo, Moov Money, Visa/Mastercard | REST API |
| BUI | Ivory Coast, Senegal, Mali, Burkina Faso | Mobile money, cards | REST API |
West Africa (Anglophone)
| Provider | Countries | Methods | Integration |
|---|---|---|---|
| Paystack | Nigeria, Ghana, South Africa, Kenya | Cards, bank transfer, USSD, mobile money | REST API |
| Flutterwave | Nigeria, Ghana + 28 others | Cards, bank transfer, mobile money, USSD | REST API |
Pan-African
| Provider | Countries | Methods | Integration |
|---|---|---|---|
| PawaPay | 21+ African countries | Mobile money (all major wallets) | REST API |
| Flutterwave | 30+ African countries | Cards, mobile money, bank transfers | REST API |
| Cellulant (Tingg) | 35+ African countries | Mobile money, cards, bank transfers | REST API |
To accept payments across Africa, you need at minimum three to four provider integrations -- and likely more if you want optimal coverage and redundancy.
The 0fee Solution
0fee abstracts all of this complexity behind a unified API. Every mobile money payment method in every country follows the same request/response format:
typescriptimport { ZeroFee } from 'zerofee';
const zf = new ZeroFee({ apiKey: 'zf_live_...' });
// Orange Money in Ivory Coast
const paymentCI = await zf.payments.create({
amount: 5000, // 5,000 XOF
currency: 'XOF',
country: 'CI',
method: 'PAYIN_ORANGE_CI',
customer: { phone: '+2250700112233' },
returnUrl: 'https://yourapp.com/callback'
});
// MTN MoMo in Ghana
const paymentGH = await zf.payments.create({
amount: 50_00, // 50.00 GHS in cents
currency: 'GHS',
country: 'GH',
method: 'PAYIN_MTN_GH',
customer: { phone: '+233241234567' },
returnUrl: 'https://yourapp.com/callback'
});
// M-Pesa in Kenya
const paymentKE = await zf.payments.create({
amount: 1000_00, // 1,000.00 KES in cents
currency: 'KES',
country: 'KE',
method: 'PAYIN_MPESA_KE',
customer: { phone: '+254712345678' },
returnUrl: 'https://yourapp.com/callback'
});Three different countries, three different mobile money providers, three different telcos -- but identical code structure. The routing engine determines which underlying provider to use based on country, method, and configured credentials.
Payment Method Naming Convention
We designed a systematic naming convention for every payment method in the system:
{DIRECTION}_{METHOD}_{COUNTRY}
PAYIN_ORANGE_CI = Pay in via Orange Money in Ivory Coast
PAYIN_MTN_GH = Pay in via MTN MoMo in Ghana
PAYIN_MPESA_KE = Pay in via M-Pesa in Kenya
PAYIN_WAVE_SN = Pay in via Wave in Senegal
PAYIN_CARD_US = Pay in via card in the United States
PAYOUT_ORANGE_CI = Pay out to Orange Money in Ivory Coast
PAYOUT_BANK_NG = Pay out to bank account in NigeriaThis convention makes the API self-documenting. A developer can immediately understand what PAYIN_AIRTEL_UG means without reading documentation: it is a pay-in via Airtel Money in Uganda.
The complete list of African mobile money methods in 0fee:
| Method Code | Provider | Country |
|---|---|---|
| PAYIN_ORANGE_CI | Orange Money | Ivory Coast |
| PAYIN_ORANGE_SN | Orange Money | Senegal |
| PAYIN_ORANGE_ML | Orange Money | Mali |
| PAYIN_ORANGE_BF | Orange Money | Burkina Faso |
| PAYIN_ORANGE_CM | Orange Money | Cameroon |
| PAYIN_MTN_GH | MTN MoMo | Ghana |
| PAYIN_MTN_CM | MTN MoMo | Cameroon |
| PAYIN_MTN_UG | MTN MoMo | Uganda |
| PAYIN_MTN_CI | MTN MoMo | Ivory Coast |
| PAYIN_MTN_BJ | MTN MoMo | Benin |
| PAYIN_MPESA_KE | M-Pesa | Kenya |
| PAYIN_MPESA_TZ | M-Pesa | Tanzania |
| PAYIN_WAVE_SN | Wave | Senegal |
| PAYIN_WAVE_CI | Wave | Ivory Coast |
| PAYIN_MOOV_CI | Moov Money | Ivory Coast |
| PAYIN_MOOV_BJ | Moov Money | Benin |
| PAYIN_MOOV_TG | Moov Money | Togo |
| PAYIN_AIRTEL_UG | Airtel Money | Uganda |
| PAYIN_AIRTEL_TZ | Airtel Money | Tanzania |
| PAYIN_AIRTEL_KE | Airtel Money | Kenya |
| PAYIN_TIGO_TZ | Tigo Pesa | Tanzania |
| PAYIN_VODACOM_TZ | Vodacom M-Pesa | Tanzania |
| PAYIN_VODACOM_MZ | Vodacom M-Pesa | Mozambique |
And this is just a subset. The full catalog includes 117 payment methods across Africa, Europe, North America, South America, and Asia.
The Webhook Problem
One of the most painful aspects of multi-provider integration is webhook handling. Each provider sends callbacks in a different format:
PawaPay webhook:
``json
{
"depositId": "dep_abc123",
"status": "COMPLETED",
"amount": "1000",
"currency": "KES",
"correspondent": "MPESA_KEN"
}
``
Hub2 callback:
``json
{
"transaction_id": "txn_xyz789",
"statut": "SUCCESS",
"montant": 5000,
"devise": "XOF",
"operateur": "ORANGE_MONEY"
}
``
Paystack webhook:
``json
{
"event": "charge.success",
"data": {
"id": 123456,
"status": "success",
"amount": 50000,
"currency": "NGN",
"channel": "mobile_money"
}
}
``
Three different providers, three different field names, three different status values, three different amount formats. In a direct integration, you would write three separate webhook handlers with three separate validation and normalization pipelines.
0fee normalizes all of this. Your application receives one standardized webhook format regardless of which provider processed the payment:
json{
"event": "payment.completed",
"data": {
"id": "pay_0fee_abc123",
"status": "completed",
"amount": 5000,
"currency": "XOF",
"country": "CI",
"method": "PAYIN_ORANGE_CI",
"provider": "hub2",
"customer": {
"phone": "+2250700112233"
},
"metadata": {},
"created_at": "2026-03-27T10:30:00Z",
"completed_at": "2026-03-27T10:30:45Z"
}
}One webhook handler. One status enum. One amount format. Every provider normalized to the same contract.
OTP and USSD: Authentication Without Cards
Card payments use a well-understood authentication model: card number, expiry, CVV, and optionally 3D Secure. Mobile money authentication is entirely different and varies by telco and country.
USSD Push (most common in West Africa): The provider sends a USSD prompt to the customer's phone. The customer sees a text-based menu: "Pay 5,000 XOF to [Merchant]? Enter PIN:" -- this happens at the SIM/telco level, not within an app.
STK Push (M-Pesa, East Africa): The SIM Toolkit triggers a payment prompt directly on the phone. The customer sees a pop-up asking them to confirm the amount and enter their M-Pesa PIN.
OTP via SMS (some providers): The customer receives an SMS with a one-time code, which they enter on the merchant's checkout page. This is closer to 3D Secure but uses SMS instead of a bank's authentication page.
Each of these flows has different timeout characteristics, different retry semantics, and different failure modes. 0fee handles all of them behind the same asynchronous payment flow: create the payment, wait for the webhook, process the result.
Why This Matters for African Developers
If you are building a product for African users, payments should not be the hardest part of your stack. But today, a developer in Abidjan who wants to accept Orange Money, MTN MoMo, and Wave payments in Ivory Coast alone needs to integrate at least two providers. Add Nigeria and Kenya, and you are looking at four or five integrations.
0fee reduces that to one. Install the SDK, configure your credentials in the dashboard, and your checkout page works across the continent.
bashnpm install zerofeeThat is the entire integration prerequisite. One package. One API key. Fifty-three providers. Two hundred countries. Africa included from day one -- not as an afterthought.
This article is part of the "How We Built 0fee.dev" series. 0fee.dev is a payment orchestrator covering 53+ providers across 200+ countries, built by Juste A. GNIMAVO and Claude from Abidjan with zero human engineers. Follow the series for the complete build story.