Skip to content

EP Ecommerce M-Pesa

EP Ecommerce M-Pesa takes M-Pesa payments in Kenya for EP Ecommerce. Your customer enters their number at the checkout, a prompt appears on their phone, and they approve it with their PIN.

Published by ElmsPark Studio.

  • STK push through Safaricom Daraja. No card details, no redirect, no app to install.
  • The number is checked before anything is sent. A mistyped number is the commonest reason an M-Pesa payment fails, and its failure mode is the worst kind: no prompt arrives at all, so the customer stares at a phone that never buzzes and decides your shop is broken. The checkout validates the number, shows it back masked, and refuses to send until it looks like a real Safaricom number.
  • Waiting is a proper screen, not a spinner. The customer is told what to do, on which number, and how long they have.
  • The tab can close. The order settles on your server regardless, and the receipt email is the real confirmation.
  • Nothing sits pending forever. An approval that never comes closes cleanly as expired, with a reason, instead of leaving an order stuck.
  • Reasons the customer can act on: not enough money, wrong PIN, timed out, cancelled on the handset.
  • Every success is confirmed with Safaricom before the order completes. See Security below, because this one matters.
  • PageMotor 0.11 or later
  • EP Ecommerce (base plugin)
  • EP Suite base class
  • A Safaricom Daraja account with a short code, consumer key, consumer secret and pass key
  • Prices in KES. M-Pesa settles in Kenyan shillings only.
  1. Install EP Ecommerce first.
  2. ep-ecommerce-mpesa.zip comes with an EP Suite licence, supplied directly by ElmsPark (see EP Suite plugins); after install it updates through your site’s Updates screen.
  3. Upload via Plugins → Manage Plugins. Activate.

In the Daraja portal, create an app and take the consumer key and consumer secret. From your Lipa na M-Pesa setup you also need the business short code and the pass key.

Open Plugins → EP Ecommerce M-Pesa → Settings.

  • Accept M-Pesa payments: leave this on No until the rest is filled in.
  • Mode: start on Sandbox, which uses Safaricom’s test environment and takes no real money.
  • Fill in the short code, consumer key, consumer secret and pass key. The three secrets are stored encrypted.
  • Seconds to wait for approval: 120 is a sensible default. Below 60 will fail people who are simply slow to find their phone.

The settings screen shows your callback URL:

https://yoursite.com/ep-payment-webhook.json?provider=mpesa

Register it in the Daraja portal.

Keep the .json on the end. Without it, PageMotor redirects the request to a trailing slash and the payment notification body is lost on the way.

Safaricom callback IP addresses, one per line. The M-Pesa callback carries no signature, so Safaricom identify themselves by address alone. Leaving this empty is still safe, because every success is confirmed with Safaricom before an order completes, but filling it in turns a forged callback away before your site even asks. Take the current list from the Daraja portal rather than from a blog post.

  1. Your customer enters their M-Pesa number at the checkout and presses Purchase.
  2. The number is validated and shown back masked, so a wrong one is caught here rather than by silence.
  3. The plugin asks Daraja to push a prompt to that handset. The amount comes from the product record on your site.
  4. The checkout switches to the waiting screen: what to do, the masked number, and a countdown.
  5. Your customer approves on their phone.
  6. Safaricom calls your site back. The plugin asks Safaricom to confirm it, and only then completes the order.
  7. The receipt email arrives.

If the approval never comes, the order closes as expired when the window passes and says why.

Daraja accepts whole shillings only. A price of KES 1,500.50 is sent as 1,501. Prices are easiest to keep in whole shillings.

M-Pesa callbacks go missing routinely. EP Ecommerce ships a reconciliation action that asks Safaricom what really happened to any order still waiting past its window, then completes or closes it.

Run it on a schedule through EP Cron, or call it yourself:

EP_Ecommerce / reconcile-orders

It is safe to run twice, and safe to run while a callback is arriving. It cannot complete an order that is already complete.

If it cannot reach Daraja at all, it leaves the order alone and tries again later rather than closing something that may well have been paid.

The M-Pesa callback is not signed. Every other payment provider signs its notifications with a shared secret, so your site can prove the message came from them. Safaricom do not. They identify themselves by the address the request came from, and nothing else.

So this plugin treats the callback as a hint rather than as proof. The callback says which order to look at, and the plugin then asks Safaricom directly what actually happened, through the STK Push Query API. Only an answer from Safaricom completes an order.

That means a forged callback claiming a payment succeeded costs your site one query and nothing else. It also means that if Safaricom cannot be reached, nothing completes: the plugin fails closed rather than guessing.

The optional IP list in the settings adds a second layer by turning away anything that did not come from Safaricom before the query is even made.

Alongside that:

  • Secrets are encrypted at rest and read back as __saved__ over the API.
  • The amount charged always comes from the product record on your site.
  • The number is re-validated on the server. Nothing the browser sends is trusted.

Almost always the number. The checkout should have caught it, so check the number shown on the waiting screen against the customer’s actual M-Pesa number. Safaricom mobile numbers start 07 or 01.

”The prompt arrives but the order stays waiting”

Section titled “”The prompt arrives but the order stays waiting””

The callback did not reach your site. Check System Status for the last callback received and any rejections, then run reconcile-orders, which will settle it from Safaricom’s own answer.

”Orders expire even though customers say they paid”

Section titled “”Orders expire even though customers say they paid””

Check the approval window. Anything under 60 seconds is tight for someone who has to find their phone, unlock it and type a PIN.

If you filled in the IP list, Safaricom may be calling from an address not on it. The current list comes from the Daraja portal. Clearing the field is safe: confirmation with Safaricom still protects the order.

Sandbox and live have entirely separate credentials and short codes. Check the Mode setting matches the credentials you pasted.

  • Settings language menu. The language menu in this plugin’s settings now lists only the languages it is actually translated into, plus English, so you can no longer pick a language that changes nothing.
  • Once an M-Pesa payment is approved, the page moves on as it is set up to. Where the checkout says where to go after payment (an invoice’s pay link goes back to the invoice, which then shows it paid), the page now goes there a moment after “Payment received” appears. It used to stay on the waiting screen.
  • “Send it again” only reuses your own order. Someone else could previously point another customer’s open M-Pesa order at their own phone, so the real customer’s payment no longer matched and their order expired even though they had paid.
  • A repeated payment confirmation can no longer look like a new payment.
  • If Paystack takes a Kenyan shilling product, the M-Pesa checkout now sends the buyer to Paystack instead of reloading the page.
  • M-Pesa donations now ask for the amount the donor chose. A pay-what-you-like donation paid with M-Pesa used to send the product’s list price to the donor’s phone. It now sends the amount they typed. “Send it again” asks for the same amount as the first request. Needs EP Ecommerce 0.1.43.
  • A discount code typed at checkout is now refused with a message when M-Pesa takes the payment, instead of being ignored and charging the full price.

First release. STK push through Safaricom Daraja, with number validation before sending, a real waiting state, confirmation with Safaricom before any order completes, reconciliation for missed callbacks, and clean expiry with a reason.

For a quick question about this plugin, EP Support inside your admin is the fastest option. The chat widget sits on every EP plugin settings page and knows which one you’re on, with starter questions and links preloaded for that exact screen.

For anything bigger, a bug report, a feature request, or a “how do I…” that needs a real reply, open a ticket at help.elmspark.com. A real person, helped by AI, writes the reply. Usually within a few hours. Tickets don’t disappear into the void.