M-Pesa Gateway for Paid Memberships Pro
Mpesa gateway for PMPRO
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/urandu/pmpro-mpesa-gateway/archive/refs/heads/master.zipAccept M-Pesa payments on your WordPress membership site powered by Paid Memberships Pro using the Safaricom Daraja STK Push (Lipa Na M-Pesa Online / M-Pesa Express) API.
Live page: urandu.github.io/pmpro-mpesa-gateway
Features
- 🚀 Daraja STK Push – customers receive an M-Pesa PIN prompt directly on their phone; no manual "go to M-Pesa and pay" instructions
- 🔄 Automatic status polling –
mpesa_stk_query()checks Daraja if the async callback hasn't arrived yet, so checkout completes on the second form submission - 🔒 Secure callback endpoint – HMAC-safe secret UID validates every Safaricom server-to-server call
- 🛡️ SQL-injection free – all database operations use
wpdb->prepare()/wpdb->insert/update/delete() - ♻️ WordPress HTTP API – no raw cURL; respects WordPress proxy settings
- 🧪 25 PHPUnit tests included
Requirements
| Requirement | Minimum version |
|---|---|
| PHP | 7.4 or higher |
| WordPress | 5.6 or higher |
| Paid Memberships Pro | 2.9 or higher |
| Safaricom Daraja API | v1 (sandbox + production) |
Installation
From a GitHub Release (recommended)
- Go to the Releases page and download the latest
pmpro-mpesa-gateway-x.x.x.zip. - In your WordPress admin, go to Plugins → Add New → Upload Plugin.
- Upload the zip file and click Install Now, then Activate.
Manual / Git
cd wp-content/plugins
git clone https://github.com/urandu/pmpro-mpesa-gateway.git
Then activate the plugin from Plugins → Installed Plugins.
Configuration
1 – Get your Daraja API credentials
- Log in to the Safaricom Developer Portal.
- Create an app (or use an existing one) and note down:
- Consumer Key
- Consumer Secret
- Under Lipa Na M-Pesa Online, retrieve your Passkey for your shortcode.
2 – Configure the plugin
Go to WordPress Admin → Memberships → Payment Settings and select M-Pesa (Daraja) as the gateway, then fill in:
| Field | Description |
|---|---|
| Business Short Code | Your Safaricom paybill or till number |
| Consumer Key | From the Daraja developer portal |
| Consumer Secret | From the Daraja developer portal |
| Lipa Na M-Pesa Passkey | Online passkey for your shortcode |
The settings page also shows your auto-generated STK Push Callback URL – copy this value.
3 – Configure your Daraja app
In the Safaricom Developer Portal:
- Open your app's settings.
- Paste the Callback URL from step 2 into the CallbackURL field.
- Save.
4 – Sandbox vs Production
In WordPress Admin → Memberships → Payment Settings, set Gateway Environment to:
sandbox– use the Safaricom sandbox (test credentials, test phone254708374149)live– use the production Safaricom API
How It Works
Customer enters phone number and clicks "Submit"
│
▼
Plugin calls Daraja STK Push API
│
▼
Customer receives PIN prompt on their phone
│
├─ Customer enters PIN
│ │
│ ├─ Safaricom POSTs callback → result_code=0 saved in DB
│ │
│ └─ Customer clicks "Submit" again
│ │
│ └─ Plugin finds confirmed row → order marked SUCCESS ✅
│
└─ Callback not received yet?
│
└─ Customer clicks "Submit" again
│
└─ Plugin calls mpesa_stk_query() → polls Daraja directly
│
├─ Confirmed → order marked SUCCESS ✅
├─ Cancelled/expired → new STK Push initiated
└─ Still pending → "Please check your phone"
API Reference
mpesa_get_access_token(): string|false
Obtains an OAuth 2.0 access token from the Daraja API using the saved Consumer Key and Consumer Secret.
Returns the token string on success, false on failure.
mpesa_stk_push( string $phone, float $amount, string $reference, string $description = '' ): object|false
Initiates a Daraja STK Push request (M-Pesa Express / Lipa Na M-Pesa Online).
| Parameter | Type | Description |
|---|---|---|
$phone |
string |
Phone number in international format e.g. 254712345678 |
$amount |
float |
Amount to request (will be rounded up to the nearest integer) |
$reference |
string |
Account reference – typically the order code (max 12 chars) |
$description |
string |
Short transaction description (max 13 chars) |
Returns the decoded Daraja API response object, or false on connection failure.
Important: a pending DB row is inserted before the API call so the async callback can be matched by checkout_request_id. If the API rejects the request the row is cleaned up automatically.
mpesa_stk_query( string $checkout_request_id ): object|false
Queries the Daraja STK Push Query API for the status of a previously initiated push.
| Parameter | Type | Description |
|---|---|---|
$checkout_request_id |
string |
The CheckoutRequestID returned by mpesa_stk_push() |
Returns the decoded Daraja API response. Key field: ResultCode.
| ResultCode | Meaning |
|---|---|
0 |
Transaction completed successfully |
1032 |
Request cancelled by the user |
1037 |
DS timeout – user did not respond |
17 |
Request already in process |
mpesa_process_stk_callback( object $data, string $raw_payload ): bool
Persists a Daraja STK Push callback to the wp_pmpro_mpesa table.
Called automatically by pmpro_mpesa_ipn_listener(). Can also be called programmatically if you handle the callback yourself.
Database Schema
The plugin creates and manages a wp_pmpro_mpesa table (prefix may vary):
| Column | Type | Description |
|---|---|---|
id |
bigint |
Primary key, auto-increment |
msisdn |
varchar(20) |
Phone number in international format |
time |
datetime |
Row creation timestamp |
user_id |
varchar(255) |
WordPress user ID (optional) |
amount |
float |
Amount paid / requested |
order_id |
varchar(255) |
PMPro order code, -1 until confirmed |
payload |
longtext |
Raw Daraja callback JSON |
mpesa_transaction_id |
varchar(50) |
M-Pesa receipt number |
checkout_request_id |
varchar(100) |
Daraja CheckoutRequestID for status queries |
result_code |
int |
-1 pending · 0 success · >0 failed |
Tests
The plugin ships with a PHPUnit test suite. No extra packages are required – the globally installed PHPUnit 8.5+ is used.
cd pmpro-mpesa-gateway
phpunit --testdox
Expected output:
Mpesa Gateway
✔ Normalize phone (5 formats)
✔ Get access token – success / connection error / missing token / live endpoint
✔ STK Push – no token / pending row / checkout_request_id stored / API rejection cleanup
✔ STK Query – no token / success / cancelled / connection error / live endpoint
✔ Callback – invalid payload / new row / update row / failed payment / no existing row
✔ IPN listener – no query var / invalid UID / missing UID
Changelog
1.0.0
- Complete rewrite using the Daraja STK Push (Lipa Na M-Pesa Online) API
- Replaced raw cURL with the WordPress HTTP API
- Fixed PHP 4-style constructor
- Fixed SQL injection vulnerabilities
- Added
mpesa_stk_query()for live payment status polling - Added
checkout_request_idandresult_codeDB columns - Added 25-test PHPUnit test suite
- Renamed settings:
mpesa_api_key→mpesa_consumer_key,mpesa_secret_key→mpesa_consumer_secret - Added
mpesa_passkeysetting
Contributing
Pull requests and issues are welcome! Please open an issue first to discuss what you'd like to change.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Run the tests (
phpunit --testdox) - Push and open a Pull Request
License
GPL-2.0-or-later – see LICENSE.