The product challenge is not "add M-PESA to checkout"
A SaaS company entering Kenya may already have mature subscription billing: plans, trials, invoices, upgrades and downgrades, grace periods, account access, card payments and finance reporting. The new requirement is local payment access. That should not force the product team to build a separate Kenyan subscription engine. The cleaner approach is to keep your existing billing model and add M-PESA at the payment-attempt layer.
Keep your subscription and invoice model
Suppose your product has a monthly Pro plan at KES 1,900, renewing on the 15th. Your billing system should create the November invoice using the subscription rules it already trusts:
Invoice: INV-2026-11-884
Customer: account-184
Plan: Pro
Amount: KES 1,900
Status: Due
If the customer chooses M-PESA, create a payment attempt against that invoice. The invoice should not become an M-PESA-specific object. That decision is what makes the next wallet or market easier to add.
Treat customer approval as part of the payment method
Safaricom's M-PESA Express API (STK Push) lets a business send a PayBill or Buy Goods payment request to a customer's phone, and the customer approves it with their M-PESA PIN. For your product, three things stay distinct:
- the subscription renews;
- the invoice becomes due;
- the customer approves the M-PESA payment.
The billing workflow can be fully automated even though the payment itself is approved by the customer. Do not describe the experience as silent card-style charging unless the specific payment method you use supports it.
Design the renewal experience around the wallet
A card-first product can show very little at renewal, because the charge happens in the background. A mobile-money renewal is more visible. The product should tell the customer what is renewing, the KES amount, when payment is due, which payment method they chose, what to expect on their phone, and whether payment has been confirmed.
A useful sequence of in-product states:
- Invoice due.
- M-PESA selected.
- Payment request sent.
- Waiting for approval on the phone.
- Checking payment.
- Paid.
If an attempt does not complete, show the right next action rather than a generic error page.
Do not let a missing callback suspend an account
Mobile-money results arrive asynchronously, so an account should never move straight from "payment request sent" to "active" or "suspended." The billing layer should interpret the provider evidence first. If an M-PESA Express callback is late, the M-PESA Express Query API checks the request's status using the CheckoutRequestID you received when you sent it.
A useful internal model:
- Pending: still within the expected payment flow.
- Unknown: the result needs checking.
- Failed or expired: the attempt has ended and another action may be allowed.
- Paid: the invoice is satisfied by a verified payment; provider settlement can follow later.
Your access policy should read the invoice state, not raw provider timing.
Grace periods are a product decision
After a failed renewal, a SaaS product might give three days of full access, switch to read-only mode, limit features, review the account manually or suspend immediately. That policy belongs to the product. The payment integration's job is to supply reliable state so the policy runs correctly:
Invoice: Overdue
Grace: Day 2 of 3
Latest payment: Failed
Next action: Customer can retry
That is far more useful than a raw provider error.
Retry against the same invoice
A November subscription fee is one commercial obligation. If the first M-PESA attempt expires, do not create a second November invoice; create another attempt against the existing one. If the customer later pays with another enabled method, the same invoice closes. That keeps subscription state, revenue reporting, customer history and reconciliation clean.
Keep payment recovery separate from entitlements
The recovery system decides when to remind, whether a retry is allowed, whether another wallet can be offered and whether a payment is unresolved. Your entitlement system decides whether the account is active, in grace, limited, suspended or restored. The two should communicate through stable billing events, for example:
invoice.paid
subscription.grace_started
subscription.payment_overdue
Do not wire the M-PESA callback directly to feature access.
Make M-PESA as traceable as cards
For every successful mobile-money payment, keep the subscription or account, invoice, payment attempt, method, provider, M-PESA reference, amount, payment time and reconciliation status. Finance then has one recurring-revenue model across card and mobile-money customers, with settlement tracked as a separate downstream state.
Design for the next payment method now
You may start with M-PESA and later need Airtel Money in Kenya, MTN MoMo in Uganda, a different PSP in another country or bank transfer elsewhere. Your commercial model should survive those additions. A reliable internal chain is: subscription, invoice, payment attempt, normalized payment state, invoice paid or unpaid, entitlement event. Provider-specific code sits behind the payment adapter.
What to test before launch
Do not stop at the happy path. Test that:
- A customer pays successfully.
- A customer ignores the prompt.
- A customer cancels.
- The callback is delayed.
- A transaction becomes unknown and is resolved with a status query.
- A customer retries after a final failure.
- A customer pays through another approved route.
- Two payment events arrive for one invoice.
- An account is in grace when payment succeeds.
- A payment succeeds while an old reminder job is waiting to run.
These scenarios decide whether the billing experience is reliable.
If you are also entering Kenya as a foreign business
This guide covers implementing M-PESA inside an existing SaaS billing system. Questions about entity structure, merchant onboarding, settlement currency, pricing in KES and provider eligibility are a separate commercial workstream, and are worth starting early because they affect which payment routes you can use. For the wider picture, see our guide to recurring payments in Kenya.
SaaS acceptance tests: payment outcome versus product access
These are illustrative release tests for a SaaS team implementing an enabled wallet route; they are not a description of RoamerWave's current production integrations.
| Test input | Expected billing behavior | Expected entitlement behavior |
|---|---|---|
| STK request accepted; customer has not approved | Invoice remains due with an active attempt | Keep the existing access/grace policy; do not claim payment succeeded |
| Callback delayed, result unknown | Preserve the attempt and check the provider result | Do not suspend solely because the callback has not arrived |
| Verified payment arrives twice as duplicate callback delivery | Credit the invoice once, keep event evidence | Apply the access transition only once |
| First attempt failed; customer pays through another approved route | Same invoice, new correlated attempt, one paid obligation | Reactivate according to policy once verified |
| External bank/card payment satisfies the invoice while an M-PESA reminder is queued | Record the external payment; suppress further collection | Do not block a paid customer over an old reminder |
| Late contradictory provider success after another successful payment | Quarantine the conflicting evidence for review | Do not grant duplicate credit or charge again |
The entitlement event should depend on a verified invoice/business state, not a raw wallet callback. Depending on the product contract, an account may remain active during grace and lose some features later, but those are separate service decisions.
A migration check for card-first teams
Pick one plan with upgrades, trials and cancellation rules. Add an M-PESA attempt to the existing invoice model and prove that prorations, receipts, invoice history, accounting and access still behave as expected. The payment integration should add a route—not fork the subscription contract. See the wider Kenya recurring-payments guide for merchant-side payment operations.



