yii2-ecitizen-gateway ¶
- Compatibility
- Installation
- Quickstart: Laravel
- Quickstart: Yii2
- Quickstart: Standalone PHP (No Framework)
- Core Features & Usage Details
- File & Class Structure
- Security
- License
A beginner-friendly Kenya eCitizen / PesaFlow payment gateway for Laravel, Yii2, and Standalone PHP. Build signed checkout payloads, render payment buttons, and verify webhook callbacks with plain-English fields.
- Framework Agnostic: Pure PHP with zero forced dependencies (
php: >=7.4). Works seamlessly in Laravel, Yii2, Yii3, WordPress, or vanilla PHP. - Laravel Auto-Discovery: Includes native
EcitizenServiceProviderandEcitizenFacade withphp artisan vendor:publishsupport. - Yii2 Gii Tools: Includes eCitizen Client Generator and eCitizen Controller Generator in Gii with zero view-template dependencies.
- Zero Database Obligation: Accepts and verifies payments out of the box without requiring any database tables or migrations. Bring your own database models when ready.
- Instant Payment Button: Render ready-to-use, HMAC-signed payment forms in one line of code (
payButton()). - Safaricom M-Pesa STK Push: Automatic Kenyan phone normalization (
PhoneHelper) to trigger instant PIN prompts on customer phones. - Cryptographic Signature Verification: Validate server-to-server IPN notifications with HMAC-SHA256 (
verify(),isPaid()).
Compatibility ¶
- Requires PHP 7.4 or newer (fully tested on PHP 8.0, 8.1, 8.2, 8.3, and 8.4+).
- Laravel: Fully compatible with Laravel 6.x, 7.x, 8.x, 9.x, 10.x, and 11.x+.
- Yii2: Fully compatible with Yii 2.0.x and Gii code generation.
- Standalone PHP: Core classes (
EcitizenClient,EcitizenGateway,PhoneHelper) have zero third-party dependencies.
Installation ¶
Install via Composer:
composer require josemodi97/yii2-ecitizen-gateway
Quickstart: Laravel ¶
The package registers its Service Provider and Ecitizen Facade automatically via Laravel Package Discovery.
1. Publish Configuration ¶
Run the Artisan publish command to create config/ecitizen.php:
php artisan vendor:publish --tag=ecitizen-config
2. Configure Environment Variables ¶
Add your merchant credentials to your .env file:
ECITIZEN_CLIENT_ID=your_api_client_id
ECITIZEN_API_KEY=your_api_key
ECITIZEN_SECRET=your_merchant_secret
ECITIZEN_SERVICE_ID=your_service_id
ECITIZEN_GATEWAY_URL=https://payments.ecitizen.go.ke/PaymentAPI/iframev2.1.php
ECITIZEN_CURRENCY=KES
3. Create a Payment Controller ¶
Use the Ecitizen Facade in your controller:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Ecitizen; // or: use odhis\ecitizen\adapters\laravel\Facades\Ecitizen;
class PaymentController extends Controller
{
/**
* Display the payment summary card and pay button.
*/
public function pay()
{
$payButtonHtml = Ecitizen::payButton([
'amount' => 1500,
'reference' => 'INV-1002',
'description' => 'Land Rates Clearance',
'name' => 'John Doe',
'idNumber' => '28374619',
'phone' => '0712345678', // Automatically triggers Safaricom M-Pesa STK push!
'callbackUrl' => route('payment.success'),
'notifyUrl' => route('payment.notify'),
], 'Proceed to eCitizen', ['class' => 'btn btn-success btn-lg']);
return view('payment.pay', compact('payButtonHtml'));
}
/**
* Webhook endpoint: receives server-to-server IPN from eCitizen.
*/
public function notify(Request $request)
{
$result = Ecitizen::verify($request->all());
if ($result['success']) {
// Payment verified and settled!
$reference = $result['reference'];
$amountPaid = $result['amountPaid'];
// Optional: update your database record here
// Order::where('reference', $reference)->update(['status' => 'paid']);
return response()->json(['status' => 'ok']);
}
return response()->json(['status' => 'error', 'message' => 'Invalid signature'], 400);
}
/**
* Browser redirect landing page after payment.
*/
public function success()
{
return view('payment.success');
}
}
4. Configure Routes & Exempt CSRF ¶
Because eCitizen servers send webhook notifications via POST, you must exempt the notification route from CSRF verification.
Routes (routes/web.php): ¶
use App\Http\Controllers\PaymentController;
Route::get('/payment/pay', [PaymentController::class, 'pay'])->name('payment.pay');
Route::post('/payment/notify', [PaymentController::class, 'notify'])->name('payment.notify');
Route::get('/payment/success', [PaymentController::class, 'success'])->name('payment.success');
CSRF Exemption: ¶
Laravel 11+ (
bootstrap/app.php):`php ->withMiddleware(function (Middleware $middleware) {$middleware->validateCsrfTokens(except: [ 'payment/notify', ]);})
`Laravel 6.x – 10.x (
app/Http/Middleware/VerifyCsrfToken.php):`php protected $except = ['payment/notify',];
`
5. Render in Blade View (resources/views/payment/pay.blade.php) ¶
<div class="card p-4 shadow-sm text-center">
<h2>Invoice #INV-1002</h2>
<p class="lead">Amount: <strong>KES 1,500</strong></p>
<div class="mt-3">
{!! $payButtonHtml !!}
</div>
</div>
Quickstart: Yii2 ¶
Yii2 provides dedicated visual code generators in Gii with zero view-template dependencies.
Step 1: Client Generator (ecitizen-client) ¶
Open Gii in your browser (http://localhost/index.php?r=gii), select eCitizen Client Generator, fill in your credentials, and click Generate:

This writes @app/config/ecitizen.php:
use odhis\ecitizen\EcitizenClient;
$ecitizen = new EcitizenClient([
'apiClientID' => 'YOUR_API_CLIENT_ID',
'apiKey' => 'YOUR_API_KEY',
'secret' => 'YOUR_SECRET',
'serviceID' => 'YOUR_SERVICE_ID',
]);
return $ecitizen;
Step 2: Controller Generator (ecitizen-controller) ¶
Select eCitizen Controller Generator and click Generate:

This creates @app/controllers/PaymentController.php with self-contained rendering (no separate view files needed):
actionPay()— Displays payment summary and payment button.actionNotify()— Receives and cryptographically verifies server-to-server webhook notifications.actionSuccess()— Customer confirmation landing page.
Step 3: Making Payments in Yii2 ¶
Visit http://localhost/index.php?r=payment/pay:

Customize parameters on the fly via query parameters:
http://localhost/index.php?r=payment/pay&amount=1200&description=Permit+Renewal&phone=0712345678
When payment completes, eCitizen redirects to your confirmation page:

Quickstart: Standalone PHP (No Framework) ¶
You can use the gateway directly in any vanilla PHP script or microframework:
1. Initialize Client & Render Payment Button ¶
require_once __DIR__ . '/vendor/autoload.php';
use odhis\ecitizen\EcitizenClient;
$ecitizen = new EcitizenClient([
'apiClientID' => 'YOUR_API_CLIENT_ID',
'apiKey' => 'YOUR_API_KEY',
'secret' => 'YOUR_SECRET',
'serviceID' => 'YOUR_SERVICE_ID',
]);
echo $ecitizen->payButton([
'amount' => 500,
'reference' => 'INV-0001',
'description' => 'School fees',
'name' => 'Jane Doe',
'idNumber' => '12345678',
'phone' => '0712345678', // Automatically formatted for M-Pesa STK push
'callbackUrl' => 'https://example.com/payment/success',
'notifyUrl' => 'https://example.com/payment/notify',
]);
2. Verify Incoming Webhook (notify.php) ¶
require_once __DIR__ . '/vendor/autoload.php';
use odhis\ecitizen\EcitizenClient;
$ecitizen = new EcitizenClient([
'apiClientID' => 'YOUR_API_CLIENT_ID',
'apiKey' => 'YOUR_API_KEY',
'secret' => 'YOUR_SECRET',
'serviceID' => 'YOUR_SERVICE_ID',
]);
$result = $ecitizen->verify($_POST);
header('Content-Type: application/json');
if ($result['success']) {
$ref = $result['reference']; // e.g. 'INV-0001'
$paid = $result['amountPaid']; // e.g. 500.00
$status = $result['status']; // e.g. 'Settled'
// Update your database / log settlement...
echo json_encode(['status' => 'ok']);
exit;
}
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Signature verification failed']);
Core Features & Usage Details ¶
1. Safaricom M-Pesa STK Push ¶
When the customer provides a phone number in any common Kenyan format (0712345678, +254712345678, or 254712345678), the built-in PhoneHelper automatically normalizes it to international standard 2547XXXXXXXX and sets sendStkPush => true.
This causes eCitizen to immediately initiate an M-Pesa STK push PIN prompt on the customer's phone upon reaching the gateway.
2. Custom Checkout Form or Embedded iFrame (checkout()) ¶
If you want to render an embedded <iframe> on your page or submit via custom JavaScript instead of using payButton(), use checkout():
$checkout = $ecitizen->checkout([
'amount' => 2500,
'reference' => 'APP-4401',
'description' => 'Building Permit',
'name' => 'Grace Mwangi',
'idNumber' => '19283746',
'callbackUrl' => 'https://example.com/payment/success',
'notifyUrl' => 'https://example.com/payment/notify',
]);
// Returns:
// [
// 'url' => 'https://payments.ecitizen.go.ke/PaymentAPI/iframev2.1.php',
// 'payload' => [
// 'apiClientID' => '...',
// 'serviceID' => '...',
// 'billRefNumber' => 'APP-4401',
// 'amountExpected' => '2500.00',
// 'currency' => 'KES',
// 'secureHash' => '...',
// ...
// ]
// ]
Embedded iFrame HTML Example: ¶
<iframe
src="<?= htmlspecialchars($checkout['url'] . '?' . http_build_query($checkout['payload'])) ?>"
width="100%"
height="700px"
frameborder="0"
allow="payment">
</iframe>
3. URLs Explained: Browser Redirect vs Server Webhook ¶
eCitizen handles communication through two separate channels:
| Parameter | Initiated By | Purpose | Verification |
|---|---|---|---|
callbackUrl (callBackURLOnSuccess) | Customer's Browser | Redirects the user back to your site after payment to show a confirmation page. | Client-side redirect. Never use this alone to mark orders as paid. |
notifyUrl (notificationURL) | eCitizen's Server | Asynchronous server-to-server webhook (IPN) carrying transaction status and cryptographic HMAC signature. | Verified via verify() or isPaid(). Use this to confirm settlement in your database. |
4. Bring Your Own Database (BYOD) ¶
The library does not mandate any database tables, migrations, or schema definitions. You are free to persist transactions however your project requires:
// In your notification webhook handler:
if ($result['success']) {
// Example with Eloquent (Laravel):
Order::where('reference', $result['reference'])->update([
'status' => 'paid',
'amount_paid' => $result['amountPaid'],
]);
// Example with ActiveRecord (Yii2):
// $order = Order::findOne(['reference' => $result['reference']]);
// if ($order) { $order->status = 'paid'; $order->save(false); }
// Example with PDO (Vanilla PHP):
// $stmt = $pdo->prepare("UPDATE orders SET status = 'paid' WHERE reference = ?");
// $stmt->execute([$result['reference']]);
}
File & Class Structure ¶
| Path | Purpose |
|---|---|
src/EcitizenClient.php | Main public API (payButton(), checkout(), verify(), isPaid()). |
src/EcitizenGateway.php | Cryptographic HMAC-SHA256 signature generator and verifier. |
src/helpers/PhoneHelper.php | Normalizes Kenyan phone numbers for Safaricom M-Pesa STK push. |
src/adapters/laravel/EcitizenServiceProvider.php | Laravel Service Provider supporting config publishing and DI container binding. |
src/adapters/laravel/Facades/Ecitizen.php | Laravel Facade for static Ecitizen::... calls. |
src/adapters/laravel/config/ecitizen.php | Default Laravel configuration template. |
src/Bootstrap.php | Yii2 extension bootstrap registering Gii code generators. |
src/generators/client/ | Yii2 Gii Client Generator (ecitizen-client). |
src/generators/controller/ | Yii2 Gii Controller Generator (ecitizen-controller) with zero view dependencies. |
src/interfaces/EcitizenInvoiceInterface.php | Optional interface for existing invoice models. |
src/controllers/EcitizenPaymentTrait.php | Optional trait for webhook handling. |
Security ¶
- Sensitive credentials (API Keys, Secrets, Passwords) should always be stored in environment variables (
.env). - Never skip HMAC signature verification on inbound webhooks. Always use
$ecitizen->verify($postData). - Always exempt the notification webhook URL from CSRF middleware so eCitizen's server can deliver payment updates.
License ¶
MIT License. See LICENSE for details.
User Contributed Notes
Leave a comment
If you have any questions, please ask in the forum instead.
Join the conversation to share a note.