PHPackages                             botnetdobbs/laravel-mpesa-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. [API Development](/categories/api)
4. /
5. botnetdobbs/laravel-mpesa-sdk

ActiveLibrary[API Development](/categories/api)

botnetdobbs/laravel-mpesa-sdk
=============================

Laravel M-Pesa Integration Package

1.0.4(6mo ago)64532MITPHPPHP ^8.2|^8.3|^8.4CI passing

Since Dec 7Pushed 5mo ago1 watchersCompare

[ Source](https://github.com/botnet-dobbs/laravel-mpesa-sdk)[ Packagist](https://packagist.org/packages/botnetdobbs/laravel-mpesa-sdk)[ RSS](/packages/botnetdobbs-laravel-mpesa-sdk/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (7)Versions (6)Used By (0)

Laravel M-Pesa Integration Package
==================================

[](#laravel-m-pesa-integration-package)

[![build](https://github.com/botnet-dobbs/laravel-mpesa-sdk/actions/workflows/main.yml/badge.svg)](https://github.com/botnet-dobbs/laravel-mpesa-sdk/actions/workflows/main.yml) [![Packagist Downloads](https://camo.githubusercontent.com/e338e0634c72734def6d4b7e4634169a7cc43bd1255f391d23401c603f276258/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f626f746e6574646f6262732f6c61726176656c2d6d706573612d73646b)](https://camo.githubusercontent.com/e338e0634c72734def6d4b7e4634169a7cc43bd1255f391d23401c603f276258/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f626f746e6574646f6262732f6c61726176656c2d6d706573612d73646b)

Laravel package for integrating with Safaricom"s M-Pesa payment gateway. Supports STK Push, B2C, B2B, balance queries, transaction status checks, and payment reversals.

For the most current API documentation and updates, always refer to the [Safaricom Developer Portal](https://developer.safaricom.co.ke/APIs).

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

[](#requirements)

- PHP 8.2+

Laravel VersionLaravel 10.xLaravel 11.xLaravel 12.xInstallation
------------

[](#installation)

Install the package via Composer:

```
composer require botnetdobbs/laravel-mpesa-sdk
```

Publish the configuration file:

```
php artisan vendor:publish --tag=mpesa-config
```

Configuration
-------------

[](#configuration)

Add the following variables to your `.env` file:

```
MPESA_CONSUMER_KEY=your_consumer_key
MPESA_CONSUMER_SECRET=your_consumer_secret
MPESA_LIPA_NA_MPESA_PASSKEY=your_lipa_na_mpesa_passkey
MPESA_INITIATOR_NAME=your_initiator_name
MPESA_INITIATOR_PASSWORD=your_initiator_password
MPESA_CERTIFICATE_PATH=your_downloaded_mpesa_certificate_path
MPESA_ENV=sandbox  # or "live" for production
```

More can be added as per the config file below.

### Configuration Options

[](#configuration-options)

The published config file (`config/mpesa.php`) contains the following options:

For the cerfiticate, [Download](https://developer.safaricom.co.ke/Documentation) under M-Pesa API Certificates.

```
return [
    "consumer_key" => env("MPESA_CONSUMER_KEY"),
    "consumer_secret" => env("MPESA_CONSUMER_SECRET"),
    "lipa_na_mpesa_passkey" => env(
        "MPESA_LIPA_NA_MPESA_PASSKEY",
        "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919"
    ),
    "certificate_path" => env("MPESA_CERTIFICATE_PATH"),
    "environment" => env("MPESA_ENV", "sandbox"),
    "initiator" => [
        'name' => env("MPESA_INITIATOR_NAME"),
        'password' => env("MPESA_INITIATOR_PASSWORD"),
    ],
    "callbacks" => [
        "base_url" => env("MPESA_CALLBACK_BASE_URL", "https://example.com"),
        "paths" => [
            "stk" => [
                "result" => "/api/mpesa/callback/stk",
            ],
            "b2c" => [
                "result" => "/api/mpesa/callback/b2c",
                "timeout" => "/api/mpesa/callback/b2c/timeout",
            ],
            // Add more callback paths here
        ]
    ],
    "business" => [
        "short_codes" => [
            "default" => env("MPESA_SHORT_CODE"),
            "till" => env("MPESA_TILL_NUMBER"),
            "paybill" => env("MPESA_PAYBILL_NUMBER"),
        ],

    ],
    "defaults" => [
        "timeout" => 60,
        "connect_timeout" => 15,
    ]
];
```

Usage
-----

[](#usage)

```
use Botnetdobbs\Mpesa\Contracts\Client;

class PaymentController extends Controller
{
    public function __construct(
        private readonly Client $mpesaClient
    ) {}

    public function initiatePayment()
    {
        $response = $this->mpesaClient->stkPush([...]);
    }
}
```

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

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

```
$response = $this->mpesaClient->stkPush([
    "BusinessShortCode" => "174379",    // Organization's shortcode  (Paybill or Buygoods - A 5 to 6-digit account number) used to identify an organization and receive the transaction.
    "TransactionType" => "CustomerPayBillOnline",    // or CustomerBuyGoodsOnline
    "Amount" => 1,
    "PhoneNumber" => "254722000000", // The Mobile Number to receive the STK Pin Prompt.
    "CallBackURL" => config('mpesa.callbacks.base_url', "https://example.com") . config('mpesa.callbacks.paths.stk_push.result', "/callback"),    // Valid secure URL that is used to receive notifications from M-Pesa API.
    "AccountReference" => "Test",
    "TransactionDesc" => "Test Payment"
]);
```

### STK Push (check the status of a Lipa Na M-Pesa Online Payment.)

[](#stk-push-check-the-status-of-a-lipa-na-m-pesa-online-payment)

```
$response = $this->mpesaClient->stkQuery([
    "BusinessShortCode" => "174379",
    "CheckoutRequestID" => "ws_CO_260520211133524545"
]);
```

### B2C Payment (Business to Customer)

[](#b2c-payment-business-to-customer)

```
$response = $this->mpesaClient->b2c([
    "OriginatorConversationID" => "unique-id",
    "InitiatorName" => "testapi",
    "CommandID" => "BusinessPayment",  // Or "SalaryPayment", "PromotionPayment"
    "Amount" => 100,
    "PartyA" => "600000",      // Your business shortcode
    "PartyB" => "254722000000", // Customer phone number
    "Remarks" => "Test payment",
    "QueueTimeOutURL" => "https://example.com/queue-timeout",   // The URL to be specified in your request that will be used by API Proxy to send notification incase the payment request is timed out while awaiting processing in the queue.
    "ResultURL" => "https://example.com/result",    // The URL to be specified in your request that will be used by M-PESA to send notification upon processing of the payment request.
    "Occasion" => "Test"
]);
```

### B2B Payment (Business to Business)

[](#b2b-payment-business-to-business)

B2B parameter naming convention is camelCase instead of PascalCase like the other endpoints on the [Safaricom Developer Portal](https://developer.safaricom.co.ke/APIs/B2BExpressCheckout). Retained as is

```
$response = $this->mpesaClient->b2b([
    "primaryShortCode" => "000001",    // Sender business shortcode
    "receiverShortCode" => "000002",   // Receiver business shortcode
    "amount" => 100,
    "paymentRef" => "INV001",          // Your reference
    "callbackUrl" => "https://example.com/callback",
    "partnerName" => "Vendor Name",
    "RequestRefID" => "unique-id-123"   // Unique identifier for the request
]);
```

### C2B Register (Customer to Business)

[](#c2b-register-customer-to-business)

```
$response = $this->mpesaClient->c2bRegister([
    "ShortCode" => "600000",
    "ResponseType" => "Completed",      // Or "Cancelled"
    "ConfirmationURL" => "https://example.com/confirmation",    // The URL that receives the confirmation request from API upon payment completion.
    "ValidationURL" => "https://example.com/validation",    // The URL that receives the validation request from the API upon payment submission. The validation URL is only called if the external validation on the registered shortcode is enabled. (By default External Validation is disabled).
]);
```

### C2B Simulate Payment (Sandbox Environment Only)

[](#c2b-simulate-payment-sandbox-environment-only)

```
$response = $this->mpesaClient->c2bSimulate([
    "ShortCode" => "600000",
    "CommandID" => "CustomerPayBillOnline",  // Or "CustomerBuyGoodsOnline"
    "Amount" => 100,
    "Msisdn" => "254722000000",             // Customer phone number
    "BillRefNumber" => "INV001"             // Optional reference
]);
```

### Account Balance Query

[](#account-balance-query)

```
$response = $this->mpesaClient->accountBalance([
    "Initiator" => "testapi",   // The credential/username used to authenticate the transaction request
    "CommandID" => "AccountBalance",
    "PartyA" => "600000",              // Your business shortcode
    "IdentifierType" => "4",           // 4 for organization shortcode
    "Remarks" => "Balance query",
    "QueueTimeOutURL" => "https://example.com/timeout", // The end-point that receives a timeout message.
    "ResultURL" => "https://example.com/result",    // It indicates the destination URL which Daraja should send the result message to.
]);
```

### Transaction Status Query

[](#transaction-status-query)

```
$response = $this->mpesaClient->transactionStatus([
    "Initiator" => "testapi",
    "CommandID" => "TransactionStatusQuery",
    "TransactionID" => "OEI2AK4Q16",    // The M-Pesa transaction ID
    "PartyA" => "600000",               // Your business shortcode
    "IdentifierType" => "4",            // 4 for organization shortcode
    "ResultURL" => "https://example.com/result",
    "QueueTimeOutURL" => "https://example.com/timeout",
    "Remarks" => "Status check",
    "Occasion" => "Transaction query",  // Optional parameter
]);
```

### Transaction Reversal

[](#transaction-reversal)

```
$response = $this->mpesaClient->reversal([
    "Initiator" => "testapi",
    "CommandID" => "TransactionReversal",
    "TransactionID" => "OEI2AK4Q16",     // The M-Pesa transaction ID to reverse
    "Amount" => 100,                      // Amount to reverse
    "ReceiverParty" => "600000",         // Organization receiving the reversal
    "RecieverIdentifierType" => "4",      // 4 for organization shortcode
    "ResultURL" => "https://example.com/result",
    "QueueTimeOutURL" => "https://example.com/timeout",
    "Remarks" => "Reversal request",
    "Occasion" => "Transaction reversal"
]);
```

### Response Handling

[](#response-handling)

All methods return a standard Response with the following methods:

```
// Get the raw response data
$data = $response->getData(): object

// Check if the request was successful
$isSuccessful = $response->isSuccessful(): bool

// Get specific response fields
$code = $response->getResponseCode(): int
$description = $response->getResponseDescription(): string

$resultCode = $response->getResultCode(): int // STK Query
$resultDescription = $response->getResultDescription(): string // STK Query
```

### Example Usage

[](#example-usage)

```
$response = $this->mpesaClient->stkPush([...]);
$data = $response->getData();

// Access properties using object syntax
$merchantRequestId = $data->MerchantRequestID;
$checkoutRequestId = $data->CheckoutRequestID;
```

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

[](#error-handling)

The package throws `MpesaException` for various error scenarios:

```
use Botnetdobbs\Mpesa\Exceptions\MpesaException;
try {
    $response = $this->mpesaClient->stkPush([...]);
    if ($response->isSuccessful()) {
        $data = $response->getData();
    }
} catch (MpesaException $e) {
    // Handle the error
    logger()->error("M-Pesa error: " . $e->getMessage());
}
```

Callback Handling
-----------------

[](#callback-handling)

The package provides callback handling system for processing M-Pesa payment notifications.

### Setup Callback Routes

[](#setup-callback-routes)

Register the callback routes in your `routes/api.php`:

```
use App\Http\Controllers\MpesaCallbackController;

Route::prefix('mpesa/callback')->group(function () {
    Route::post('stkpush', [MpesaCallbackController::class, 'handleStkCallback']);
});
```

### Create Callback Controller

[](#create-callback-controller)

Create a controller to handle M-Pesa callbacks:

You can use the provided `CallbackProcessor` which wraps the callback data in a TransactionResult object as shown below, or you can handle the raw data directly.

```
namespace App\Http\Controllers;

use Botnetdobbs\Mpesa\Contracts\CallbackProcessor;
use Botnetdobbs\Mpesa\Contracts\CallbackResponder;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;

class MpesaCallbackController extends Controller
{
    public function __construct(
        private readonly CallbackProcessor $processor,
        private readonly CallbackResponder $responder
    ) {}

    public function handleStkCallback(Request $request): Response
    {
        try {
            $result = $this->processor->handleStkCallback($request);

            if ($result->isSuccessful()) {
                $data = $result->getData();

                if (isset($data->Body->stkCallback)) {
                    Log::info('STK Push payment successful', [
                        'merchantRequestId' => $data->Body->stkCallback->MerchantRequestID,
                        'checkoutRequestId' => $data->Body->stkCallback->CheckoutRequestID,
                    ]);
                }

                // Update your database, trigger events, etc.
                return $this->responder->success('Payment processed');
            }
            Log::warning('STK Push payment failed', [
                'code' => $result->getResultCode(),
                'description' => $callback->getResultDescription()
            ]);

            return $this->responder->success('Failed payment');
        } catch (\Exception $e) {
            Log::error('Error processing STK callback', [
                'error' => $e->getMessage()
            ]);
            return $this->responder->failed('Internal server error');
        }
    }

    // Implement other callback handlers similarly...
}
```

### Available Callback Methods

[](#available-callback-methods)

Each callback type provides specific methods to access the payment data:

#### Common Methods Available in All Callbacks

[](#common-methods-available-in-all-callbacks)

```
$result->getData(): object // Get the raw callback data.
$result->isSuccessful(): bool
$result->getResultCode(): int
$result->getResultDescription(): string
```

The `CallbackResponder` provides two methods:

- `success(string $message = 'Payment processed'): Responsable` - Returns a success response with ResultCode 0
- `failed(string $message = 'Internal server error', int $statusCode = 500): Responsable` - Returns a failure response with ResultCode 1

Response Format: All responses are returned as JSON with the appropriate Content-Type header.

Success Response:

```
{
    "ResultCode": 0,
    "ResultDesc": "Payment processed"
}
```

Failed Response:

```
{
    "ResultCode": 1,
    "ResultDesc": "Internal server error"
}
```

For Contributors
----------------

[](#for-contributors)

This package includes comprehensive testing capabilities:

### Running Tests

[](#running-tests)

Run all tests

```
composer test
```

### Coverage Reports

[](#coverage-reports)

Generate HTML coverage report:

```
composer test:coverage
```

Then open `coverage/index.html` in your browser.

Code Quality
------------

[](#code-quality)

```
# Check code style
composer check-style

# Fix code style issues
composer fix-style

# Run static analysis
composer analyse
```

Credits
-------

[](#credits)

- [Lazarus Odhiambo](https://github.com/botnetdobbs)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance69

Regular maintenance activity

Popularity21

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity60

Established project with proven stability

 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.

###  Release Activity

Cadence

Every ~87 days

Total

5

Last Release

180d ago

PHP version history (2 changes)1.0.0PHP ^8.2

1.0.2PHP ^8.2|^8.3|^8.4

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/35170812?v=4)[Lazarus Odhiambo](/maintainers/botnetdobbs)[@botnetdobbs](https://github.com/botnetdobbs)

---

Top Contributors

[![botnetdobbs](https://avatars.githubusercontent.com/u/35170812?v=4)](https://github.com/botnetdobbs "botnetdobbs (27 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/botnetdobbs-laravel-mpesa-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/botnetdobbs-laravel-mpesa-sdk/health.svg)](https://phpackages.com/packages/botnetdobbs-laravel-mpesa-sdk)
```

###  Alternatives

[spatie/laravel-query-builder

Easily build Eloquent queries from API requests

4.4k26.9M220](/packages/spatie-laravel-query-builder)[essa/api-tool-kit

set of tools to build an api with laravel

52680.5k](/packages/essa-api-tool-kit)[esign/laravel-conversions-api

A laravel wrapper package around the Facebook Conversions API

69145.4k](/packages/esign-laravel-conversions-api)[surface/laravel-webfinger

A Laravel package to create an ActivityPub webfinger.

113.8k](/packages/surface-laravel-webfinger)

PHPackages © 2026

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