Accepting Mobile Money Payments Through Paystack
To accept mobile money through Paystack, initialize a GHS transaction with the mobile_money channel enabled. Via hosted checkout, the customer selects mobile money and enters their phone number. Via the Charge API, POST to /charge with a mobile_money object containing the phone and provider. The customer receives an approval prompt on their phone. Once approved, Paystack fires a charge.success webhook.
How Mobile Money Works on Paystack
Mobile money payments follow an asynchronous approval pattern:
- The customer provides their mobile money phone number and selects their provider (MTN, AirtelTigo, or Vodafone)
- Paystack sends a payment request to the mobile money provider
- The provider sends an authorization prompt to the customer's phone (an STK push or USSD prompt)
- The customer enters their mobile money PIN on their phone to approve
- The provider deducts the funds and notifies Paystack
- Paystack fires a
charge.successwebhook to your server
The key difference from card payments: between step 2 and step 5, you are waiting for the customer to act on a separate device. This can take seconds or minutes. Your checkout UI needs to show a waiting state during this period.
Mobile Money via Hosted Checkout
The simplest approach. Initialize a GHS transaction with the mobile_money channel:
var response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'customer@example.com',
amount: 5000, // 50 GHS in pesewas
currency: 'GHS',
channels: ['mobile_money'], // or ['card', 'mobile_money'] for both
callback_url: 'https://yoursite.com/payment/callback',
}),
});
var data = await response.json();
// Redirect to data.data.authorization_url or use the access_code
Paystack's checkout page handles the phone number input, provider selection, and the waiting screen while the customer approves on their phone. You do not need to build any of this UI.
Mobile Money via the Charge API
For a custom checkout where you collect the phone number and provider in your own UI:
async function chargeMobileMoney(email, amount, phone, provider) {
var response = await fetch('https://api.paystack.co/charge', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
amount: amount,
currency: 'GHS',
mobile_money: {
phone: phone, // e.g., '0241234567'
provider: provider, // 'mtn', 'atl', or 'vod'
},
}),
});
var data = await response.json();
if (data.data.status === 'send_otp') {
// Customer needs to approve on their phone
return { status: 'pending_approval', reference: data.data.reference };
}
if (data.data.status === 'success') {
return { status: 'success', reference: data.data.reference };
}
return { status: 'failed', message: data.data.gateway_response };
}
// Usage
var result = await chargeMobileMoney(
'customer@example.com',
5000, // 50 GHS
'0241234567',
'mtn'
);
if (result.status === 'pending_approval') {
// Show: "Check your phone and approve the payment"
// Wait for webhook to confirm
}
Provider codes: mtn (MTN MoMo), atl (AirtelTigo), vod (Vodafone Cash).
Handling the Approval Wait
After the charge is initiated, the customer needs to approve on their phone. This creates a user experience challenge: how long do you wait, and what do you show?
// Frontend: show waiting state and poll for completion
async function waitForApproval(reference, maxWaitSeconds) {
var startTime = Date.now();
var maxWait = (maxWaitSeconds || 120) * 1000; // Default 2 minutes
while (Date.now() - startTime < maxWait) {
var response = await fetch('/api/check-payment?reference=' + reference);
var data = await response.json();
if (data.status === 'success') {
return { paid: true };
}
if (data.status === 'failed') {
return { paid: false, reason: data.reason };
}
// Wait 5 seconds before checking again
await new Promise(function(resolve) { setTimeout(resolve, 5000); });
}
return { paid: false, reason: 'timeout' };
}
Better approach: instead of polling, use a webhook to update your database, and have the frontend poll your database. This way you get the definitive confirmation from Paystack via webhook, not from a verify call that might miss timing.
Show a clear message while waiting: "A payment prompt has been sent to 024****567. Open your phone and enter your mobile money PIN to approve." Include a countdown timer or a loading indicator so the customer knows something is happening.
Timeout and Failure Handling
Mobile money prompts expire after a timeout period set by the provider (typically 1-2 minutes). If the customer does not approve within that window, the charge fails.
Common failure reasons:
- Insufficient funds: The customer does not have enough money in their mobile money wallet
- Wrong PIN: The customer entered the wrong PIN when approving
- Timeout: The customer did not respond to the prompt in time
- Phone off: The customer's phone was off or unreachable when the prompt was sent
- Wrong number: The phone number provided does not have a mobile money account
When a mobile money charge fails, give the customer the option to try again or switch to a different payment method (card). Do not force them to restart the entire checkout process.
Amount Handling for Mobile Money
Mobile money transactions use GHS (Ghanaian Cedi) with amounts in pesewas. The conversion is the same as other currencies: multiply the display amount by 100.
// 50 GHS = 5000 pesewas
var amountInPesewas = Math.round(50 * 100); // 5000
// Mobile money providers may have minimum and maximum transaction limits
// These limits vary by provider and account type
// Check Paystack documentation for current limits
Mobile money wallets have balance limits that are lower than bank account limits. A customer might have enough for a 500 GHS purchase on their debit card but not in their mobile money wallet. For high-value transactions, offering card as an alternative is important.
For more on amount handling, see kobo, pesewa, and cents explained.
Key Takeaways
- ✓Mobile money on Paystack is available for GHS (Ghanaian Cedi) transactions. You need a Ghana-registered Paystack account.
- ✓Supported providers: MTN Mobile Money (MoMo), AirtelTigo Money, and Vodafone Cash.
- ✓The customer enters their mobile money number and receives a prompt on their phone to authorize the payment.
- ✓Mobile money is asynchronous. There is a gap between the prompt being sent and the customer approving. Handle this wait time in your UI.
- ✓Via hosted checkout, Paystack manages the entire flow. Via the Charge API, you send the phone and provider and handle the pending status.
- ✓Always confirm via webhook, not by polling. The charge.success event is the reliable signal that payment was received.
Frequently Asked Questions
- Can I accept mobile money payments in Nigeria?
- Mobile money on Paystack is currently available for GHS (Ghana) transactions. For Nigerian payments, the equivalent channels are bank transfer, USSD, and card. The mobile money landscape in Nigeria is evolving, so check Paystack for current availability.
- What phone number format does Paystack expect?
- Paystack accepts Ghanaian phone numbers in local format (e.g., 0241234567) or international format (e.g., 233241234567). Validate the phone number on your end before sending to Paystack to provide better error messages to customers.
- Can the customer approve mobile money on a different phone?
- No. The approval prompt is sent to the phone number associated with the mobile money account. The customer must approve on that specific phone. They cannot approve on a different device.
- Do mobile money refunds work like card refunds?
- Yes. You can refund mobile money transactions via the Paystack refund endpoint. The refund goes back to the customer mobile money wallet. Processing time may vary by provider.
- Is mobile money available for recurring payments?
- Mobile money does not support saved authorization codes the way cards do. Each payment requires the customer to approve on their phone. For recurring mobile money collections, you would need to send a new charge each billing period and have the customer approve each time.
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