Search by

josemodi97 / yii2-safaricom-daraja

JoseModi97

Beginner-friendly Safaricom Daraja M-Pesa gateway for Laravel, Yii2, and standalone PHP: configure credentials via Gii, initiate STK Push, query statuses, and handle webhook callbacks with plain-English fields.

Package info

github.com/JoseModi97/yii2-safaricom-daraja

Documentation

Type:yii2-extension

pkg:composer/josemodi97/yii2-safaricom-daraja

Statistics

Installs: 21

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.1.4 2026-09-06 11:01 UTC

This package is auto-updated.

Last update: 2026-09-06 11:10:57 UTC


README

A beginner-friendly Safaricom Daraja M-Pesa payment gateway for Yii2, Laravel, and Standalone PHP. Configure credentials directly via Gii, trigger STK Push PIN prompts on customer phones, and verify webhook callbacks with plain-English fields.

  • Direct Gii Credentials Setup: Configure your Safaricom Consumer Key, Secret, Passkey, and ShortCode directly through Gii (daraja-client) — zero manual config file editing required.
  • Instant Payment Controller in Gii: Generate a ready-to-use MpesaController (daraja-controller) with a built-in checkout card, webhook receiver with automatic CSRF exemption, and status querying.
  • Zero Database Obligation: Trigger STK push and receive verified callbacks out of the box without requiring database migrations or tables. Bring your own database models when ready.
  • Automatic Phone Normalization: Automatically converts Kenyan phone numbers (07..., 01..., +254..., 7...) into standard 254... format via PhoneHelper.
  • Laravel Auto-Discovery: Includes native DarajaServiceProvider and Daraja Facade with php artisan vendor:publish support.
  • Framework Agnostic & Standalone Core: Pure PHP (php: >=7.4) with zero forced framework dependencies. Works in Yii2, Laravel, WordPress, or vanilla PHP.
  • Complete Safaricom API Coverage: Full support for STK Push, STK Query, C2B, B2C, B2B, Ratiba, Lipa na Bonga, Pull Transactions, IMSI/SWAP, and IoT SIM Portal APIs.

Compatibility

  • Requires PHP 7.4 or newer (fully tested on PHP 7.4, 8.0, 8.1, 8.2, 8.3, and 8.4+).
  • Yii2: Fully compatible with Yii 2.0.x and Gii code generation.
  • Laravel: Fully compatible with Laravel 6.x, 7.x, 8.x, 9.x, 10.x, and 11.x+.
  • Standalone PHP: Core classes (DarajaClient, Daraja, PhoneHelper) have zero mandatory third-party dependencies.

Installation

Install via Composer:

composer require josemodi97/yii2-safaricom-daraja

Quickstart: Yii2 (Dedicated Modular Gii Tools)

Instead of a cluttered monolithic configuration, each Safaricom Daraja feature has its own dedicated Gii generator with its bare minimum required credentials.

Unified File Architecture: All Gii tools write into the same configuration file (@app/config/daraja.php by default). When you run any tool, it automatically pre-populates existing credentials and merges newly entered settings so previously configured keys are never overwritten or lost!

Gii Generator Link ID Bare Minimum Required Credentials Target File
1. Core API Credentials daraja-client consumerKey, consumerSecret, environment @app/config/daraja.php
2. M-Pesa Express (STK Push) daraja-stk Core Auth + shortCode, passkey @app/config/daraja.php
3. Customer to Business (C2B) daraja-c2b Core Auth + shortCode, responseType @app/config/daraja.php
4. Business to Customer (B2C) daraja-b2c Core Auth + shortCode, initiatorName, securityCredential @app/config/daraja.php
5. Transactions & Balance daraja-transaction Core Auth + shortCode, initiatorName, securityCredential @app/config/daraja.php
6. IoT SIM Management daraja-iot Core Auth + iotApiKey @app/config/daraja.php
7. Payment Controller daraja-controller Controller class name, base class, component ID MpesaController.php

Live Gii Setup Demonstration with Blurred Credentials

Here is a step-by-step walkthrough demonstrating how to configure the extension in Gii using your credentials (visually blurred in the screenshots for security):

DARAJA_CONSUMER_KEY=GKMPa04A74Wy****************************pseu
DARAJA_CONSUMER_SECRET=aUEAoECfvtKW****************************iRn
DARAJA_SHORTCODE=174379
DARAJA_PASSKEY=bfb279f9aa9b****************************c919

Step 1: Access Gii Code Generator

Open http://localhost:8080/index.php?r=gii (or http://localhost/index.php?r=gii). You will see the 7 dedicated Safaricom Daraja generators ready to use:

Safaricom Daraja Gii Overview

Step 2: Configure Core API Credentials (daraja-client)

Click 1. Safaricom Daraja Core API Credentials (daraja-client). Enter your Consumer Key and Consumer Secret, select your environment (Sandbox or Production), preview the generated file, and click Generate:

Safaricom Daraja Core Credentials Setup

Your credentials are saved to @app/config/daraja.php.

Step 3: Configure M-Pesa STK Push (daraja-stk)

Click 2. Safaricom M-Pesa STK Push (Express) (daraja-stk). Notice that your Core API Credentials (consumerKey and consumerSecret) are automatically pre-populated from @app/config/daraja.php!

Enter your ShortCode (174379) and Passkey, check Generate STK Payment Controller, and click Preview:

Safaricom M-Pesa STK Push Setup

Scrolling down reveals the optional controller generator settings and the code file preview table:

Safaricom M-Pesa STK Push Scrolled Preview

Step 4: Live Generation Result & Automatic Route Setup

Upon clicking Generate, Gii creates the files and displays the success alert, controller endpoints, and ready-to-use snippet:

Safaricom Daraja Generation Success

The STK Push credentials are automatically merged into @app/config/daraja.php, and a ready-to-use controller (app\controllers\MpesaStkController) is created with checkout (/mpesa-stk/pay), webhook receiver (/mpesa-stk/callback), and query (/mpesa-stk/query) routes.

Step 5: Unified Configuration Output (@app/config/daraja.php)

Both Core authentication and STK Push settings now live cleanly together in @app/config/daraja.php:

Unified Configuration Output

Yii::$app->daraja is automatically loaded and ready to trigger payments anywhere in your application!

Additional Feature Generators (Transactions & Balance, C2B, B2C)

Why were Steps 1–5 presented first? The quickstart above deliberately spotlights the fundamental onboarding path (Authentication + STK Push) so developers can accept their first mobile money payment in under 5 minutes without wading through a massive wall of images.

However, Safaricom Daraja is modular. Each of the following specialized Gii tools automatically inherits your core credentials and writes cleanly to your unified @app/config/daraja.php:

1. Safaricom M-Pesa Transactions & Balance (daraja-transaction)

Click 5. Safaricom M-Pesa Transactions & Balance to manage account balance inquiries, transaction status queries, and payment reversals. Supply your shortcode, initiator name, security credential, and optional result/timeout webhooks:

Safaricom M-Pesa Transactions & Balance Setup

Code Usage:
// Query Account Balance
$balance = Yii::$app->daraja->checkBalance([
    'remarks' => 'Daily balance check',
]);

// Query Status of Any Transaction
$status = Yii::$app->daraja->queryTransaction([
    'transactionId' => 'NLJ7RT61SV',
    'remarks'       => 'Check transaction status',
]);

// Reverse a Transaction
$reversal = Yii::$app->daraja->reverseTransaction([
    'transactionId' => 'NLJ7RT61SV',
    'amount'        => 500,
    'remarks'       => 'Refund customer overpayment',
]);

2. Safaricom M-Pesa C2B Paybill / Buy Goods Till (daraja-c2b)

Click 3. Safaricom M-Pesa C2B (Paybill / Till) to register your validation and confirmation URLs, configure fallback behavior (Completed vs Cancelled), and optionally generate a dedicated MpesaC2bController:

Safaricom M-Pesa C2B Paybill Setup

Code Usage:
// Register Validation & Confirmation URLs with Safaricom
$response = Yii::$app->daraja->c2bRegister([
    'shortCode'       => '600981',
    'responseType'    => 'Completed', // or 'Cancelled'
    'confirmationUrl' => 'https://yourdomain.com/mpesa-c2b/confirmation',
    'validationUrl'   => 'https://yourdomain.com/mpesa-c2b/validation',
]);

// Simulate C2B Payment (Sandbox only)
$sim = Yii::$app->daraja->c2bSimulate([
    'phone'     => '0708374149',
    'amount'    => 100,
    'reference' => 'INV-001',
]);

3. Safaricom M-Pesa B2C Disbursements (daraja-b2c)

Click 4. Safaricom M-Pesa B2C (Disbursements) to configure automated business payouts (salaries, merchant payments, and promotional disbursements):

Safaricom M-Pesa B2C Disbursements Setup

Code Usage:
$response = Yii::$app->daraja->b2c([
    'phone'     => '0712345678',
    'amount'    => 1500,
    'commandId' => 'SalaryPayment', // Options: SalaryPayment, BusinessPayment, PromotionPayment
    'remarks'   => 'Monthly salary payout',
]);

Using Daraja in Your Code

1. M-Pesa STK Push (Express)

use Safaricom\Daraja\DarajaClient;

// Through the auto-registered application component:
$daraja = Yii::$app->daraja;
// or directly loading the unified config:
$daraja = require Yii::getAlias('@app/config/daraja.php');

// Trigger STK Push (Lipa Na M-Pesa Online):
$response = $daraja->stkPush([
    'phone'       => '0712345678', // Auto-normalized to 254712345678!
    'amount'      => 100,
    'reference'   => 'INV-001',
    'description' => 'Service payment',
]);

2. Verify Webhook Callbacks

In your controller action (e.g. MpesaStkController::actionCallback):

public function actionCallback()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    $daraja = Yii::$app->daraja;

    $result = $daraja->verifyCallback(Yii::$app->request->bodyParams);

    if ($result['success']) {
        $receipt = $result['mpesaReceiptNumber']; // e.g. NLJ7RT61SV
        $amount  = $result['amount'];             // e.g. 100.00
        $phone   = $result['phone'];              // e.g. 254712345678
        // Update your order/database here...
    }

    return ['ResultCode' => 0, 'ResultDesc' => 'Accepted'];
}

3. B2C Disbursements

$response = Yii::$app->daraja->b2c([
    'phone'     => '0712345678',
    'amount'    => 500,
    'commandId' => 'SalaryPayment', // or BusinessPayment, PromotionPayment
    'remarks'   => 'September salary',
]);

4. Query STK Push Transaction Status

$status = Yii::$app->daraja->stkQuery('ws_CO_060920261234567890');

Quickstart: Laravel

The package automatically registers its Service Provider and Daraja Facade via Laravel Package Discovery.

1. Publish Configuration

php artisan vendor:publish --tag=daraja-config

2. Configure .env

DARAJA_ENV=sandbox
DARAJA_CONSUMER_KEY=your_consumer_key
DARAJA_CONSUMER_SECRET=your_consumer_secret
DARAJA_PASSKEY=bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919
DARAJA_SHORTCODE=174379
DARAJA_CALLBACK_BASE_URL=https://yourdomain.com

3. Trigger STK Push & Handle Callbacks

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Daraja; // or use Safaricom\Daraja\adapters\laravel\Facades\Daraja;

class PaymentController extends Controller
{
    public function pay()
    {
        $response = Daraja::stkPush([
            'phone'       => '0712345678',
            'amount'      => 100,
            'reference'   => 'INV-001',
            'description' => 'Service payment',
            'callbackUrl' => route('mpesa.callback'),
        ]);

        return response()->json($response);
    }

    public function callback(Request $request)
    {
        $result = Daraja::verifyCallback($request->all());

        if ($result['success']) {
            $receipt = $result['mpesaReceiptNumber'];
            $amount  = $result['amount'];
            // Update order status in your database...
        }

        return response()->json(['ResultCode' => 0, 'ResultDesc' => 'Accepted']);
    }
}

CSRF Exemption in Laravel: Exclude your webhook route from CSRF protection:

  • Laravel 11+: Add ->validateCsrfTokens(except: ['mpesa/callback']) in bootstrap/app.php.
  • Laravel 6–10: Add 'mpesa/callback' to $except in app/Http/Middleware/VerifyCsrfToken.php.

Quickstart: Standalone PHP

require_once __DIR__ . '/vendor/autoload.php';

use Safaricom\Daraja\DarajaClient;

$daraja = new DarajaClient([
    'consumerKey'    => 'your_consumer_key',
    'consumerSecret' => 'your_consumer_secret',
    'passkey'        => 'your_passkey',
    'shortCode'      => '174379',
    'environment'    => 'sandbox',
]);

// 1. Trigger STK Push:
$response = $daraja->stkPush([
    'phone'       => '0712345678',
    'amount'      => 100,
    'reference'   => 'INV-001',
    'description' => 'Service payment',
    'callbackUrl' => 'https://example.com/callback.php',
]);

// 2. In callback.php:
$data = json_decode(file_get_contents('php://input'), true);
$result = $daraja->verifyCallback($data);

if ($result['success']) {
    echo "Paid! Receipt: " . $result['mpesaReceiptNumber'];
}

Environment Variables

Create a .env file in the Yii2 application root if your app uses dotenv-style environment loading.

Path from Yii2 basic app root: .env

Path from Yii2 advanced project root: .env

DARAJA_ENVIRONMENT=sandbox
DARAJA_CONSUMER_KEY=your_consumer_key
DARAJA_CONSUMER_SECRET=your_consumer_secret
DARAJA_SHORT_CODE=174379
DARAJA_PASSKEY=your_lipa_na_mpesa_passkey
DARAJA_INITIATOR_NAME=your_initiator_name
DARAJA_INITIATOR_PASSWORD=your_initiator_password
DARAJA_CALLBACK_BASE_URL=https://your-domain.example
DARAJA_IOT_API_KEY=your_iot_api_key
DARAJA_IOT_MSISDN=254700000000

Yii2 does not load .env files by default in every template. If your application already loads .env, getenv('DARAJA_CONSUMER_KEY') will work as shown above. If it does not, install and bootstrap a dotenv loader in the Yii2 application, or set these variables in your server environment.

If you choose the dotenv approach, install the loader in the Yii2 application:

composer require vlucas/phpdotenv

Example using vlucas/phpdotenv in a Yii2 basic app:

Path from Yii2 app root: web/index.php

require __DIR__ . '/../vendor/autoload.php';

if (class_exists('Dotenv\\Dotenv')) {
    $dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
    $dotenv->safeLoad();
}

require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';

Example using vlucas/phpdotenv in a Yii2 advanced app:

Common entry files from project root:

  • Frontend: frontend/web/index.php
  • Backend: backend/web/index.php
  • Console: yii

Load .env before requiring common/config/bootstrap.php or before reading config files:

require __DIR__ . '/../../vendor/autoload.php';

if (class_exists('Dotenv\\Dotenv')) {
    $dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2));
    $dotenv->safeLoad();
}

Do not hard-code real consumer keys, secrets, passkeys, initiator passwords, or API keys in code. The Postman collection may contain sample values; move all secrets to environment variables.

Basic Usage

Place these calls inside your own controller action, service class, console command, or model method. The MVC example below uses these paths:

  • Form model: models/StkPushForm.php
  • Controller: controllers/DarajaController.php
  • Optional payment view: views/daraja/stk-push.php

Generate an OAuth access token:

$tokenResponse = Yii::$app->daraja->generateAccessToken();
$accessToken = $tokenResponse['access_token'];

Most API calls do not need you to pass the token manually. The component automatically generates and refreshes the bearer token when consumerKey and consumerSecret are configured.

Use named helper methods where available:

$response = Yii::$app->daraja->stkPush($payload);
$response = Yii::$app->daraja->c2bRegisterUrl($payload);
$response = Yii::$app->daraja->accountBalance($payload);

Use the generic endpoint catalog for any endpoint:

use Safaricom\Daraja\EndpointCatalog;

$response = Yii::$app->daraja->request(EndpointCatalog::PULL_QUERY, [
    'ShortCode' => Yii::$app->params['daraja.shortCode'],
    'StartDate' => '2026-07-01 00:00:00',
    'EndDate' => '2026-07-16 23:59:59',
    'OffSetValue' => '0',
]);

Yii2 MVC Pattern

A clean Yii2 integration usually looks like this:

  • Model or form: validates phone numbers, amount, account reference, date ranges, and required business fields.
  • Controller: receives the user request, builds the Daraja payload, calls the component, and returns a Yii response.
  • Callback controller action: receives Safaricom result/confirmation/validation callbacks and stores them.
  • Service or ActiveRecord layer: saves payment requests, checkout request IDs, transaction IDs, and callback result codes.

Example Model: STK Push Form

Create the form model.

Path from Yii2 app root: models/StkPushForm.php

<?php

namespace app\models;

use Yii;
use yii\base\Model;

class StkPushForm extends Model
{
    public $phoneNumber;
    public $amount;
    public $accountReference;
    public $transactionDesc;

    public function rules()
    {
        return [
            [['phoneNumber', 'amount', 'accountReference'], 'required'],
            ['amount', 'number', 'min' => 1],
            [['accountReference', 'transactionDesc'], 'string', 'max' => 100],
            ['phoneNumber', 'match', 'pattern' => '/^2547[0-9]{8}$/', 'message' => 'Use format 2547XXXXXXXX.'],
        ];
    }

    public function send()
    {
        if (!$this->validate()) {
            return false;
        }

        $shortCode = Yii::$app->params['daraja.shortCode'];
        $passkey = Yii::$app->params['daraja.passkey'];
        $timestamp = date('YmdHis');

        return Yii::$app->daraja->stkPush([
            'BusinessShortCode' => $shortCode,
            'Password' => Yii::$app->daraja->generateStkPassword($shortCode, $passkey, $timestamp),
            'Timestamp' => $timestamp,
            'TransactionType' => 'CustomerPayBillOnline',
            'Amount' => $this->amount,
            'PartyA' => $this->phoneNumber,
            'PartyB' => $shortCode,
            'PhoneNumber' => $this->phoneNumber,
            'CallBackURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/stk-callback'),
            'AccountReference' => $this->accountReference,
            'TransactionDesc' => $this->transactionDesc ? $this->transactionDesc : 'Payment',
        ]);
    }
}

Example Controller

Create the controller.

Path from Yii2 app root: controllers/DarajaController.php

<?php

namespace app\controllers;

use Yii;
use yii\web\Controller;
use yii\web\Response;
use app\models\StkPushForm;

class DarajaController extends Controller
{
    public $enableCsrfValidation = false;

    public function actionStkPush()
    {
        Yii::$app->response->format = Response::FORMAT_JSON;

        $model = new StkPushForm();
        $model->load(Yii::$app->request->post(), '');

        if (!$model->validate()) {
            return ['ok' => false, 'errors' => $model->getErrors()];
        }

        try {
            return ['ok' => true, 'data' => $model->send()];
        } catch (\Exception $e) {
            Yii::error($e->getMessage(), __METHOD__);
            return ['ok' => false, 'message' => $e->getMessage()];
        }
    }

    public function actionStkCallback()
    {
        Yii::$app->response->format = Response::FORMAT_JSON;

        $raw = Yii::$app->request->getRawBody();
        $payload = json_decode($raw, true);

        Yii::info($payload, 'daraja.stk.callback');

        /*
         * Save the callback to your database here.
         * Common fields:
         * $payload['Body']['stkCallback']['MerchantRequestID']
         * $payload['Body']['stkCallback']['CheckoutRequestID']
         * $payload['Body']['stkCallback']['ResultCode']
         * $payload['Body']['stkCallback']['ResultDesc']
         */

        return ['ResultCode' => 0, 'ResultDesc' => 'Accepted'];
    }
}

STK Push and Query

Start a Lipa na M-Pesa Online payment:

$timestamp = date('YmdHis');
$shortCode = Yii::$app->params['daraja.shortCode'];
$password = Yii::$app->daraja->generateStkPassword($shortCode, Yii::$app->params['daraja.passkey'], $timestamp);

$response = Yii::$app->daraja->stkPush([
    'BusinessShortCode' => $shortCode,
    'Password' => $password,
    'Timestamp' => $timestamp,
    'TransactionType' => 'CustomerPayBillOnline',
    'Amount' => 1,
    'PartyA' => '254700000000',
    'PartyB' => $shortCode,
    'PhoneNumber' => '254700000000',
    'CallBackURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/stk-callback'),
    'AccountReference' => 'INV-1001',
    'TransactionDesc' => 'Invoice payment',
]);

Query an STK payment using the CheckoutRequestID returned by Safaricom:

$response = Yii::$app->daraja->stkQuery([
    'BusinessShortCode' => $shortCode,
    'Password' => $password,
    'Timestamp' => $timestamp,
    'CheckoutRequestID' => 'ws_CO_...',
]);

C2B URL Registration and Simulation

Put the registration/simulation calls in a controller action, console command, or service class. For example:

  • Controller path from Yii2 app root: controllers/DarajaController.php
  • Console command path from Yii2 app root: commands/DarajaController.php

Register confirmation and validation URLs:

$response = Yii::$app->daraja->c2bRegisterUrl([
    'ShortCode' => Yii::$app->params['daraja.shortCode'],
    'ResponseType' => 'Completed',
    'ConfirmationURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/c2b-confirmation'),
    'ValidationURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/c2b-validation'),
]);

Sandbox C2B simulation:

$response = Yii::$app->daraja->c2bSimulate([
    'ShortCode' => Yii::$app->params['daraja.shortCode'],
    'CommandID' => 'CustomerPayBillOnline',
    'Amount' => '10',
    'Msisdn' => '254700000000',
    'BillRefNumber' => 'INV-1001',
]);

Callback examples can be added as methods inside the same web controller.

Path from Yii2 app root: controllers/DarajaController.php

public function actionC2bValidation()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    Yii::info(json_decode(Yii::$app->request->getRawBody(), true), 'daraja.c2b.validation');

    return ['ResultCode' => 0, 'ResultDesc' => 'Accepted'];
}

public function actionC2bConfirmation()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    Yii::info(json_decode(Yii::$app->request->getRawBody(), true), 'daraja.c2b.confirmation');

    return ['ResultCode' => 0, 'ResultDesc' => 'Accepted'];
}

B2C, B2B, and B2Pochi

Put these payout/request examples in your own service class, console command, or controller action.

Suggested paths from Yii2 app root:

  • Service class: components/DarajaService.php
  • Console command: commands/DarajaController.php
  • Web controller: controllers/DarajaController.php

Generate a security credential from your initiator password and Safaricom public certificate:

$credential = Yii::$app->daraja->generateSecurityCredential(
    Yii::$app->params['daraja.initiatorPassword'],
    Yii::getAlias(Yii::$app->params['daraja.certificatePath'])
);

B2C payment request:

$response = Yii::$app->daraja->b2cPayment([
    'InitiatorName' => Yii::$app->params['daraja.initiatorName'],
    'SecurityCredential' => $credential,
    'CommandID' => 'BusinessPayment',
    'Amount' => '100',
    'PartyA' => Yii::$app->params['daraja.shortCode'],
    'PartyB' => '254700000000',
    'Remarks' => 'Payout',
    'QueueTimeOutURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/timeout'),
    'ResultURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/result'),
    'Occasion' => 'Refund',
]);

B2B payment request:

$response = Yii::$app->daraja->b2bPayment([
    'Initiator' => Yii::$app->params['daraja.initiatorName'],
    'SecurityCredential' => $credential,
    'CommandID' => 'BusinessPayBill',
    'SenderIdentifierType' => '4',
    'RecieverIdentifierType' => '4',
    'Amount' => '100',
    'PartyA' => Yii::$app->params['daraja.shortCode'],
    'PartyB' => '600000',
    'AccountReference' => 'INV-1001',
    'Remarks' => 'Supplier payment',
    'QueueTimeOutURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/timeout'),
    'ResultURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/result'),
]);

B2Pochi payment request:

$response = Yii::$app->daraja->b2PochiPayment([
    'OriginatorConversationID' => uniqid('b2pochi-', true),
    'InitiatorName' => Yii::$app->params['daraja.initiatorName'],
    'SecurityCredential' => $credential,
    'CommandID' => 'BusinessPayment',
    'Amount' => '100',
    'PartyA' => Yii::$app->params['daraja.shortCode'],
    'PartyB' => '254700000000',
    'Remarks' => 'B2Pochi payment',
    'QueueTimeOutURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/timeout'),
    'ResultURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/result'),
    'Occasion' => 'Payment',
]);

Reversal, Transaction Status, and Account Balance

Put these examples in your own service class, console command, or controller action.

Suggested paths from Yii2 app root:

  • Service class: components/DarajaService.php
  • Console command: commands/DarajaController.php
  • Web controller: controllers/DarajaController.php

Reverse a transaction:

$response = Yii::$app->daraja->reversal([
    'Initiator' => Yii::$app->params['daraja.initiatorName'],
    'SecurityCredential' => $credential,
    'CommandID' => 'TransactionReversal',
    'TransactionID' => 'ABC123XYZ',
    'Amount' => '100',
    'ReceiverParty' => Yii::$app->params['daraja.shortCode'],
    'RecieverIdentifierType' => '4',
    'ResultURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/result'),
    'QueueTimeOutURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/timeout'),
    'Remarks' => 'Customer refund',
    'Occasion' => 'Refund',
]);

Query transaction status:

$response = Yii::$app->daraja->transactionStatus([
    'Initiator' => Yii::$app->params['daraja.initiatorName'],
    'SecurityCredential' => $credential,
    'CommandID' => 'TransactionStatusQuery',
    'TransactionID' => 'ABC123XYZ',
    'PartyA' => Yii::$app->params['daraja.shortCode'],
    'IdentifierType' => '4',
    'ResultURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/result'),
    'QueueTimeOutURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/timeout'),
    'Remarks' => 'Status query',
    'Occasion' => 'Status',
]);

Query account balance:

$response = Yii::$app->daraja->accountBalance([
    'Initiator' => Yii::$app->params['daraja.initiatorName'],
    'SecurityCredential' => $credential,
    'CommandID' => 'AccountBalance',
    'PartyA' => Yii::$app->params['daraja.shortCode'],
    'IdentifierType' => '4',
    'Remarks' => 'Balance query',
    'QueueTimeOutURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/timeout'),
    'ResultURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/result'),
]);

M-Pesa Ratiba Standing Orders

Put these examples in your own service class, console command, or controller action.

Suggested paths from Yii2 app root:

  • Service class: components/DarajaService.php
  • Console command: commands/DarajaController.php
  • Web controller: controllers/DarajaController.php

Create a standing order for Paybill:

$response = Yii::$app->daraja->ratibaCreatePaybill([
    'StandingOrderName' => 'Monthly fee',
    'BusinessShortCode' => '174379',
    'TransactionType' => 'Standing Order Customer Pay Bill',
    'Amount' => '100',
    'PartyA' => '254700000000',
    'ReceiverPartyIdentifierType' => '4',
    'CallBackURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/ratiba-callback'),
    'AccountReference' => 'ACC-1001',
    'TransactionDesc' => 'Monthly payment',
    'Frequency' => '1',
    'StartDate' => '20260716',
    'EndDate' => '20270716',
]);

Create a standing order for Buy Goods:

$response = Yii::$app->daraja->ratibaCreateBuyGoods([
    'StandingOrderName' => 'Merchant subscription',
    'BusinessShortCode' => '300584',
    'TransactionType' => 'Standing Order Customer Pay Merchant',
    'Amount' => '100',
    'PartyA' => '254700000000',
    'ReceiverPartyIdentifierType' => '2',
    'CallBackURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/ratiba-callback'),
    'AccountReference' => 'ACC-1001',
    'TransactionDesc' => 'Merchant payment',
    'Frequency' => '1',
    'StartDate' => '20260716',
    'EndDate' => '20270716',
]);

Lipa na Bonga

Put these examples in your own service class, console command, or controller action.

Suggested paths from Yii2 app root:

  • Service class: components/DarajaService.php
  • Console command: commands/DarajaController.php
  • Web controller: controllers/DarajaController.php

Redeem Bonga points to Paybill:

$response = Yii::$app->daraja->lipaNaBongaRedeemPaybill([
    'msisdn' => '254700000000',
    'amount' => 100,
    'bongaPoints' => 500,
    'conversionRate' => 0.2,
    'shortCode' => Yii::$app->params['daraja.shortCode'],
    'accountNumber' => 'ACC-1001',
]);

Calculate points:

$response = Yii::$app->daraja->lipaNaBongaCalculatePoints([
    'points' => '500',
]);

IMSI and SWAP CheckATI

Put this example in your own service class, console command, or controller action.

Suggested paths from Yii2 app root:

  • Service class: components/DarajaService.php
  • Console command: commands/DarajaController.php
  • Web controller: controllers/DarajaController.php
$response = Yii::$app->daraja->imsiCheckAti([
    'customerNumber' => '254700000000',
]);

$response = Yii::$app->daraja->swapCheckAti([
    'customerNumber' => '254700000000',
]);

Pull Transactions API

Put these examples in your own service class, console command, or controller action.

Suggested paths from Yii2 app root:

  • Service class: components/DarajaService.php
  • Console command: commands/DarajaController.php
  • Web controller: controllers/DarajaController.php

Register a callback URL:

$response = Yii::$app->daraja->pullRegister([
    'ShortCode' => Yii::$app->params['daraja.shortCode'],
    'RequestType' => 'Pull',
    'NominatedNumber' => '254700000000',
    'CallBackURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/pull-callback'),
]);

Query transactions:

$response = Yii::$app->daraja->pullQuery([
    'ShortCode' => Yii::$app->params['daraja.shortCode'],
    'StartDate' => '2026-07-01 00:00:00',
    'EndDate' => '2026-07-16 23:59:59',
    'OffSetValue' => '0',
]);

IoT SIM Portal APIs

Put these examples in your own service class, console command, or controller action.

Suggested paths from Yii2 app root:

  • Service class: components/DarajaService.php
  • Console command: commands/DarajaController.php
  • Web controller: controllers/DarajaController.php

The IoT SIM portal endpoints from the collection use the same request() engine, but they commonly need additional headers such as x-api-key, x-source-system, X-MSISDN, X-App, and X-MessageID. Use Daraja::iot($endpointKey, $data, $headers, $query).

use Safaricom\Daraja\EndpointCatalog;

$headers = [
    'x-correlation-conversationid' => uniqid('', true),
    'x-source-system' => 'web-portal',
    'x-api-key' => getenv('DARAJA_IOT_API_KEY'),
    'Accept-Language' => 'EN',
    'X-MSISDN' => getenv('DARAJA_IOT_MSISDN'),
    'X-App' => 'web-portal',
    'X-MessageID' => uniqid('msg-', true),
];

$response = Yii::$app->daraja->iot(
    EndpointCatalog::IOT_GET_ALL_MESSAGES,
    ['vpnGroup' => 'MY-GROUP'],
    $headers,
    ['pageNo' => 1, 'pageSize' => 10]
);

The package also exposes named IoT helpers such as iotSearchMessages(), iotSendSingleMessage(), iotAllSims(), and iotSuspendUnsuspendSub(). These helpers call the same endpoints as iot() and accept the same payload/header/query style where paging is needed.

Search messages:

$response = Yii::$app->daraja->iot(
    EndpointCatalog::IOT_SEARCH_MESSAGES,
    ['searchValue' => 'hello', 'vpnGroup' => 'MY-GROUP', 'username' => 'admin'],
    $headers,
    ['pageNo' => 1, 'pageSize' => 5]
);

Send one message:

$response = Yii::$app->daraja->iot(
    EndpointCatalog::IOT_SEND_SINGLE_MESSAGE,
    [
        'msisdn' => '254700000000',
        'message' => 'Test message',
        'vpnGroup' => 'MY-GROUP',
        'username' => 'admin',
    ],
    $headers
);

SIM activation:

$response = Yii::$app->daraja->iot(
    EndpointCatalog::IOT_SIM_ACTIVATION,
    ['msisdn' => '254700000000', 'vpnGroup' => 'MY-GROUP', 'username' => 'admin'],
    $headers
);

All Tools and Endpoints from the Collection

Import the constant class where you need generic access:

use Safaricom\Daraja\EndpointCatalog;
Tool / API from Postman Helper method Endpoint constant
OAuth access token generateAccessToken() EndpointCatalog::OAUTH_TOKEN
M-Pesa Ratiba Paybill standing order ratibaCreatePaybill($data) EndpointCatalog::RATIBA_CREATE_PAYBILL
M-Pesa Ratiba Buy Goods standing order ratibaCreateBuyGoods($data) EndpointCatalog::RATIBA_CREATE_BUY_GOODS
B2B payment request b2bPayment($data) EndpointCatalog::B2B_PAYMENT
B2C payment request b2cPayment($data) EndpointCatalog::B2C_PAYMENT
B2Pochi payment request b2PochiPayment($data) EndpointCatalog::B2POCHI_PAYMENT
C2B URL registration c2bRegisterUrl($data) EndpointCatalog::C2B_REGISTER_URL
C2B simulation c2bSimulate($data) EndpointCatalog::C2B_SIMULATE
STK Push process request stkPush($data) EndpointCatalog::STK_PUSH
STK Push query stkQuery($data) EndpointCatalog::STK_QUERY
Transaction reversal reversal($data) EndpointCatalog::REVERSAL
Transaction status query transactionStatus($data) EndpointCatalog::TRANSACTION_STATUS
Account balance query accountBalance($data) EndpointCatalog::ACCOUNT_BALANCE
Lipa na Bonga redeem Paybill lipaNaBongaRedeemPaybill($data) EndpointCatalog::LIPA_NA_BONGA_REDEEM_PAYBILL
Lipa na Bonga calculate points lipaNaBongaCalculatePoints($data) EndpointCatalog::LIPA_NA_BONGA_CALCULATE_POINTS
IMSI CheckATI imsiCheckAti($data) EndpointCatalog::IMSI_CHECK_ATI
SWAP CheckATI swapCheckAti($data) EndpointCatalog::SWAP_CHECK_ATI
Pull Transactions register URL pullRegister($data) EndpointCatalog::PULL_REGISTER
Pull Transactions query pullQuery($data) EndpointCatalog::PULL_QUERY
IoT search messages iotSearchMessages($data, $headers, $query) or iot() EndpointCatalog::IOT_SEARCH_MESSAGES
IoT filter messages iotFilterMessages($data, $headers, $query) or iot() EndpointCatalog::IOT_FILTER_MESSAGES
IoT delete message thread iotDeleteMessageThread($data, $headers) or iot() EndpointCatalog::IOT_DELETE_MESSAGE_THREAD
IoT get all messages iotGetAllMessages($data, $headers, $query) or iot() EndpointCatalog::IOT_GET_ALL_MESSAGES
IoT send single message iotSendSingleMessage($data, $headers) or iot() EndpointCatalog::IOT_SEND_SINGLE_MESSAGE
IoT delete message iotDeleteMessage($data, $headers) or iot() EndpointCatalog::IOT_DELETE_MESSAGE
IoT all SIMs iotAllSims($data, $headers, $query) or iot() EndpointCatalog::IOT_ALL_SIMS
IoT query lifecycle status iotQueryLifecycleStatus($data, $headers) or iot() EndpointCatalog::IOT_QUERY_LIFECYCLE_STATUS
IoT query customer info iotQueryCustomerInfo($data, $headers) or iot() EndpointCatalog::IOT_QUERY_CUSTOMER_INFO
IoT SIM activation iotSimActivation($data, $headers) or iot() EndpointCatalog::IOT_SIM_ACTIVATION
IoT get activation trends iotGetActivationTrends($data, $headers) or iot() EndpointCatalog::IOT_GET_ACTIVATION_TRENDS
IoT rename asset iotRenameAsset($data, $headers) or iot() EndpointCatalog::IOT_RENAME_ASSET
IoT get location info iotGetLocationInfo($data, $headers) or iot() EndpointCatalog::IOT_GET_LOCATION_INFO
IoT suspend / unsuspend subscriber iotSuspendUnsuspendSub($data, $headers) or iot() EndpointCatalog::IOT_SUSPEND_UNSUSPEND_SUB

Endpoint Paths

All paths use the configured base URL:

  • Sandbox: https://sandbox.safaricom.co.ke
  • Production: https://api.safaricom.co.ke
Constant Method Path
OAUTH_TOKEN GET /oauth/v1/generate?grant_type=client_credentials
RATIBA_CREATE_PAYBILL POST /standingorder/v1/createStandingOrderExternal
RATIBA_CREATE_BUY_GOODS POST /standingorder/v1/createStandingOrderExternal
B2B_PAYMENT POST /mpesa/b2b/v1/paymentrequest
B2C_PAYMENT POST /mpesa/b2c/v1/paymentrequest
B2POCHI_PAYMENT POST /mpesa/b2c/v1/paymentrequest
C2B_REGISTER_URL POST /mpesa/c2b/v1/registerurl
C2B_SIMULATE POST /mpesa/c2b/v1/simulate
STK_PUSH POST /mpesa/stkpush/v1/processrequest
STK_QUERY POST /mpesa/stkpushquery/v1/query
REVERSAL POST /mpesa/reversal/v1/request
TRANSACTION_STATUS POST /mpesa/transactionstatus/v1/query
ACCOUNT_BALANCE POST /mpesa/accountbalance/v1/query
LIPA_NA_BONGA_REDEEM_PAYBILL POST /v1/lipa/na/bonga/redeem-paybill
LIPA_NA_BONGA_CALCULATE_POINTS POST /v1/lipa/na/bonga/calculator-points
IMSI_CHECK_ATI POST /imsi/v1/checkATI
SWAP_CHECK_ATI POST /imsi/v2/checkATI
PULL_REGISTER POST /pulltransactions/v1/register
PULL_QUERY POST /pulltransactions/v1/query
IOT_SEARCH_MESSAGES POST /simportal/v1/searchmessages
IOT_FILTER_MESSAGES POST /simportal/v1/filtermessages
IOT_DELETE_MESSAGE_THREAD POST /simportal/v1/deleteMessageThread
IOT_GET_ALL_MESSAGES POST /simportal/v1/getallmessages
IOT_SEND_SINGLE_MESSAGE POST /simportal/v1/sendsinglemessage
IOT_DELETE_MESSAGE POST /simportal/v1/deletemessage
IOT_ALL_SIMS POST /simportal/v1/allsims
IOT_QUERY_LIFECYCLE_STATUS POST /simportal/v1/queryLifeCycleStatus
IOT_QUERY_CUSTOMER_INFO POST /simportal/v1/querycustomerinfo
IOT_SIM_ACTIVATION POST /simportal/v1/simactivation
IOT_GET_ACTIVATION_TRENDS POST /simportal/v1/getactivationtrends
IOT_RENAME_ASSET POST /simportal/v1/renameasset
IOT_GET_LOCATION_INFO POST /simportal/v1/getlocationinfo
IOT_SUSPEND_UNSUSPEND_SUB POST /simportal/v1/suspend_unsuspend_sub

Callback and Result URL Notes

Callback actions belong in a web controller because Safaricom calls them over HTTPS.

Suggested path from Yii2 app root: controllers/DarajaController.php

Safaricom sends many responses asynchronously. Any payload with ResultURL, QueueTimeOutURL, CallBackURL, ConfirmationURL, or ValidationURL must point to a publicly reachable HTTPS URL.

Use the component helper when building callback payload fields:

'CallBackURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/stk-callback'),
'ResultURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/result'),
'QueueTimeOutURL' => Yii::$app->daraja->buildCallbackUrl('/daraja/timeout'),

For a deployed housing application, you can omit callbackBaseUrl when Yii sees the correct public host. If the app runs behind a proxy or a queue/console command builds the payload, configure it explicitly:

DARAJA_CALLBACK_BASE_URL=https://your-public-url.example

localhost cannot receive Safaricom callbacks directly because it is only reachable from your own machine. For local development, expose the Yii app through a secure public HTTPS tunnel, then use that tunnel URL as DARAJA_CALLBACK_BASE_URL.

Always store the raw callback JSON before transforming it. This makes reconciliation much easier when Safaricom sends unexpected fields.

Error Handling

Failed HTTP responses throw Safaricom\Daraja\DarajaException.

try {
    $response = Yii::$app->daraja->accountBalance($payload);
} catch (\Safaricom\Daraja\DarajaException $e) {
    Yii::error($e->getMessage(), 'daraja');
    throw $e;
}

Testing

Run the package tests:

vendor/bin/phpunit

The included tests check the endpoint catalog and component behavior. Real API calls require valid Safaricom credentials and publicly reachable callback URLs.

Notes

  • The extension does not hard-code credentials from the Postman collection.
  • Put credentials in environment variables or Yii application params.
  • Keep callback actions CSRF-exempt because Safaricom will not send a Yii CSRF token.
  • Keep your production and sandbox credentials separate.
  • Store IDs returned by Safaricom, especially MerchantRequestID, CheckoutRequestID, ConversationID, OriginatorConversationID, and transaction IDs.