PHPackages                             j-muiruri/daraja-php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. j-muiruri/daraja-php-sdk

ActiveLibrary[Payment Processing](/categories/payments)

j-muiruri/daraja-php-sdk
========================

Enterprise-grade PHP SDK for the Safaricom Daraja M-Pesa API. Full coverage: OAuth, STK Push, C2B, B2C, B2B, Transaction Status, Account Balance, Reversal, Dynamic QR, Tax Remittance, and Bill Manager. Includes a Laravel integration layer with auto-discovery, events, middleware, and webhook routing.

00PHPCI failing

Since Jul 1Pushed yesterdayCompare

[ Source](https://github.com/j-muiruri/daraja-php-sdk)[ Packagist](https://packagist.org/packages/j-muiruri/daraja-php-sdk)[ RSS](/packages/j-muiruri-daraja-php-sdk/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

j-muiruri/daraja-php-sdk
========================

[](#j-muiruridaraja-php-sdk)

A modern, fully-typed PHP 8.2 SDK for the **Safaricom Daraja 3.0 M-Pesa API**.

[![PHP](https://camo.githubusercontent.com/187240af044d09d5b14a1d9d9ebdf3f7a993e4c7bc09bdb46b4ba661a891bf5b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d626c7565)](https://www.php.net)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

---

Features
--------

[](#features)

APIServiceMethod(s)OAuth 2.0`AccessTokenManager`Auto-managed (transparent)STK Push`stk()``push()`, `pushBuyGoods()`, `query()`C2B`c2b()``registerUrls()`, `simulate()`B2C`b2c()``sendSalary()`, `sendBusinessPayment()`, `sendPromotion()`, `pay()`B2B`b2b()``payBill()`, `buyGoods()`, `pay()`Transaction Status`transactionStatus()``query()`Account Balance`accountBalance()``query()`Reversal`reversal()``reverse()`Dynamic QR`qr()``generate()`, `extractImage()`, `saveImage()`---

Requirements
------------

[](#requirements)

- PHP 8.2+
- Composer
- `ext-openssl`, `ext-json`
- Guzzle 7.x

---

Installation
------------

[](#installation)

```
composer require j-muiruri/daraja-php-sdk
```

---

Quick Start
-----------

[](#quick-start)

### 1. Create the client

[](#1-create-the-client)

```
use Daraja\DarajaClient;
use Daraja\Enums\Environment;

$mpesa = DarajaClient::make(
    consumerKey:    $_ENV['MPESA_CONSUMER_KEY'],
    consumerSecret: $_ENV['MPESA_CONSUMER_SECRET'],
    shortcode:      $_ENV['MPESA_SHORTCODE'],
    passkey:        $_ENV['MPESA_PASSKEY'],
    environment:    Environment::Sandbox,
    callbackUrl:    'https://yourapp.co.ke/mpesa/callback',
    resultUrl:      'https://yourapp.co.ke/mpesa/result',
    timeoutUrl:     'https://yourapp.co.ke/mpesa/timeout',
);
```

Or read directly from environment variables:

```
$mpesa = DarajaClient::fromEnv();
```

---

API Reference
-------------

[](#api-reference)

### STK Push (Lipa na M-Pesa Online)

[](#stk-push-lipa-na-m-pesa-online)

Initiates a payment prompt on the customer's phone. The customer enters their M-Pesa PIN to confirm.

```
// Initiate an STK Push to a Paybill
$response = $mpesa->stk()->push(
    phone:            '0712345678',   // or '+254712345678' or '254712345678'
    amount:           1500,           // KES, minimum 1
    accountReference: 'INV-0042',    // Max 12 chars — shown to customer
    description:      'Order #42',   // Max 13 chars
    callbackUrl:      'https://yourapp.co.ke/mpesa/callback', // override per request
);

if ($response->isAccepted()) {
    $checkoutId = $response->checkoutRequestId();
    // Store $checkoutId to poll status or match against the callback
}

// Buy Goods (till number) variant
$response = $mpesa->stk()->pushBuyGoods(
    phone:       '0712345678',
    amount:      250,
    till:        '123456',
    callbackUrl: 'https://yourapp.co.ke/mpesa/callback',
);

// Query status (when callback isn't received)
$status = $mpesa->stk()->query($checkoutId);
echo $status->resultDescription(); // "The service request is processed successfully."
```

**STK Push Callback payload** (POST to your `callbackUrl`):

```
{
  "Body": {
    "stkCallback": {
      "MerchantRequestID": "29115-34620561-1",
      "CheckoutRequestID": "ws_CO_191220191020363925",
      "ResultCode": 0,
      "ResultDesc": "The service request is processed successfully.",
      "CallbackMetadata": {
        "Item": [
          { "Name": "Amount",              "Value": 1500 },
          { "Name": "MpesaReceiptNumber",  "Value": "QHT3XXXXXXXXXXX" },
          { "Name": "PhoneNumber",         "Value": 254712345678 }
        ]
      }
    }
  }
}
```

---

### C2B — Register URLs

[](#c2b--register-urls)

Register validation and confirmation URLs before your customers start paying.

```
$mpesa->c2b()->registerUrls(
    confirmationUrl: 'https://yourapp.co.ke/mpesa/confirm',
    validationUrl:   'https://yourapp.co.ke/mpesa/validate', // optional
    responseType:    'Completed', // 'Completed' or 'Cancelled'
);

// Sandbox only — simulate a payment
$mpesa->c2b()->simulate(
    phone:         '0712345678',
    amount:        500,
    billRefNumber: 'TEST001',
    commandId:     'CustomerPayBillOnline',
);
```

---

### B2C — Disbursements

[](#b2c--disbursements)

Send money to customers (salaries, promotions, refunds).

> Requires `initiatorName` and `securityCredential` in Config.

```
// Salary payment
$mpesa->b2c()->sendSalary(
    phone:   '0712345678',
    amount:  45000,
    remarks: 'April Salary',
);

// Promotion / betting payout
$mpesa->b2c()->sendPromotion(
    phone:   '0733123456',
    amount:  500,
    remarks: 'Jackpot winnings',
);

// General payment
$mpesa->b2c()->sendBusinessPayment('0722123456', 1200, 'Refund - Order #112');
```

**B2C Result callback payload** (async, POST to `resultUrl`):

```
{
  "Result": {
    "ResultType": 0,
    "ResultCode": 0,
    "ResultDesc": "The service request is processed successfully.",
    "OriginatorConversationID": "29112-34801843-1",
    "ConversationID": "AG_20191219_00005797af5d7d75f652",
    "TransactionID": "QHT3XXXXXXXXXXX"
  }
}
```

---

### B2B — Pay Suppliers

[](#b2b--pay-suppliers)

```
// Pay a supplier's paybill
$mpesa->b2b()->payBill(
    receiverShortcode: '000001',
    amount:            75000,
    accountReference:  'SUPP-ACC-001',
    remarks:           'Invoice #INV-2025-03',
);

// Pay a merchant till
$mpesa->b2b()->buyGoods('987654', 12000, 'Office supplies');
```

---

### Transaction Status

[](#transaction-status)

Reconcile transactions when callbacks were missed.

```
use Daraja\Enums\IdentifierType;

$status = $mpesa->transactionStatus()->query(
    transactionId:  'QHT3XXXXXXXXXXX',  // M-Pesa receipt number
    identifierType: IdentifierType::Shortcode,
    remarks:        'Reconciliation check',
);
```

---

### Account Balance

[](#account-balance)

```
use Daraja\Enums\IdentifierType;

$mpesa->accountBalance()->query(
    identifierType: IdentifierType::Shortcode,
    remarks:        'EOD balance check',
);
// Result arrives asynchronously on your resultUrl
```

---

### Transaction Reversal

[](#transaction-reversal)

```
$mpesa->reversal()->reverse(
    transactionId: 'QHT3XXXXXXXXXXX',
    amount:        1500,
    remarks:       'Customer cancellation',
);
```

---

### Dynamic QR Code

[](#dynamic-qr-code)

```
use Daraja\Enums\QRCodeType;

$response = $mpesa->qr()->generate(
    merchantName: 'Asante Coffee',
    refNo:        'INV-001',
    amount:       350,
    type:         QRCodeType::DynamicMerchant,
    size:         400,
);

// Get the Base64 PNG to embed in an  tag
$base64 = $mpesa->qr()->extractImage($response);
echo '';

// Or save to disk
$mpesa->qr()->saveImage($response, '/var/www/html/qr/payment.png');
```

---

Phone Number Formats
--------------------

[](#phone-number-formats)

The `PhoneNumber` value object accepts any common Kenyan format:

```
use Daraja\ValueObjects\PhoneNumber;

PhoneNumber::from('0712345678');     // → 254712345678
PhoneNumber::from('+254712345678'); // → 254712345678
PhoneNumber::from('254712345678'); // → 254712345678
PhoneNumber::from('712345678');    // → 254712345678
```

Invalid numbers throw `Daraja\Exceptions\ValidationException`.

---

Security Credentials (B2C, B2B, Reversal, Balance)
--------------------------------------------------

[](#security-credentials-b2c-b2b-reversal-balance)

These APIs need the initiator password encrypted with Safaricom's public certificate.

**Step 1** — Download the certificate from the Daraja portal:

- Sandbox: `https://developer.safaricom.co.ke/sites/default/files/cert/sandbox/cert.cer`
- Production: `https://developer.safaricom.co.ke/sites/default/files/cert/prod/cert.cer`

**Step 2** — Generate the credential once and store it:

```
use Daraja\Concerns\HasSecurityCredential;

class CredentialGenerator
{
    use HasSecurityCredential;

    public function generate(string $password, string $certPath): string
    {
        return $this->generateSecurityCredential($password, $certPath);
    }
}

$gen        = new CredentialGenerator();
$credential = $gen->generate('MyInitiatorPassword', '/path/to/cert.cer');
// Store $credential in your .env as MPESA_SECURITY_CREDENTIAL
```

---

Error Handling
--------------

[](#error-handling)

```
use Daraja\Exceptions\ApiException;
use Daraja\Exceptions\AuthenticationException;
use Daraja\Exceptions\ValidationException;

try {
    $response = $mpesa->stk()->push(...);
} catch (ValidationException $e) {
    // Bad parameters — check before hitting the API
    foreach ($e->errors() as $field => $message) {
        echo "{$field}: {$message}\n";
    }
} catch (AuthenticationException $e) {
    // OAuth token failure — check consumer key/secret
    logger()->error('M-Pesa auth failed', ['error' => $e->getMessage()]);
} catch (ApiException $e) {
    // API returned an error response
    logger()->error('M-Pesa API error', [
        'status' => $e->statusCode(),
        'code'   => $e->errorCode(),
        'msg'    => $e->getMessage(),
    ]);
}
```

---

Laravel Integration
-------------------

[](#laravel-integration)

```
// config/mpesa.php
return [
    'consumer_key'        => env('MPESA_CONSUMER_KEY'),
    'consumer_secret'     => env('MPESA_CONSUMER_SECRET'),
    'shortcode'           => env('MPESA_SHORTCODE'),
    'passkey'             => env('MPESA_PASSKEY'),
    'environment'         => env('MPESA_ENVIRONMENT', 'sandbox'),
    'security_credential' => env('MPESA_SECURITY_CREDENTIAL'),
    'initiator_name'      => env('MPESA_INITIATOR_NAME'),
    'callback_url'        => env('MPESA_CALLBACK_URL'),
    'result_url'          => env('MPESA_RESULT_URL'),
    'timeout_url'         => env('MPESA_TIMEOUT_URL'),
];

// app/Providers/AppServiceProvider.php
use Daraja\DarajaClient;
use Daraja\Enums\Environment;

$this->app->singleton(DarajaClient::class, function () {
    return DarajaClient::make(
        consumerKey:        config('mpesa.consumer_key'),
        consumerSecret:     config('mpesa.consumer_secret'),
        shortcode:          config('mpesa.shortcode'),
        passkey:            config('mpesa.passkey'),
        environment:        Environment::from(config('mpesa.environment')),
        securityCredential: config('mpesa.security_credential'),
        initiatorName:      config('mpesa.initiator_name'),
        callbackUrl:        config('mpesa.callback_url'),
        resultUrl:          config('mpesa.result_url'),
        timeoutUrl:         config('mpesa.timeout_url'),
    );
});
```

---

Testing
-------

[](#testing)

```
composer test
```

---

Contributing
------------

[](#contributing)

1. Fork the repo and create a feature branch
2. Write tests first — every new feature needs passing tests
3. Run `composer test` and `composer analyse` before opening a PR
4. Follow PSR-12 coding style

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

20

↑

LowBetter than 13% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/76ecaa6973c625cf4cd3f53e4758b24bc97800719cb93292bab138030ce5d834?d=identicon)[jmuiruri](/maintainers/jmuiruri)

---

Top Contributors

[![j-muiruri](https://avatars.githubusercontent.com/u/30627018?v=4)](https://github.com/j-muiruri "j-muiruri (5 commits)")

### Embed Badge

![Health badge](/badges/j-muiruri-daraja-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/j-muiruri-daraja-php-sdk/health.svg)](https://phpackages.com/packages/j-muiruri-daraja-php-sdk)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
