PHPackages                             felixmuhoro/laravel-mpesa - 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. felixmuhoro/laravel-mpesa

ActiveLibrary[Payment Processing](/categories/payments)

felixmuhoro/laravel-mpesa
=========================

Modern, fully-typed M-Pesa Daraja 2.0 integration for Laravel 10 / 11 / 12. STK Push, C2B, B2C, callbacks, events, and an exhaustive result-code dictionary — battle-tested in production.

v1.2.1(1mo ago)1820MITPHPPHP ^8.1

Since Apr 18Pushed 5d agoCompare

[ Source](https://github.com/felixmuhoro/laravel-mpesa)[ Packagist](https://packagist.org/packages/felixmuhoro/laravel-mpesa)[ Docs](https://github.com/felixmuhoro/laravel-mpesa)[ RSS](/packages/felixmuhoro-laravel-mpesa/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (2)Dependencies (6)Versions (5)Used By (20)

Laravel M-Pesa
==============

[](#laravel-m-pesa)

[![Latest Version on Packagist](https://camo.githubusercontent.com/4a557d2c4889918c4db1e8b49c5e1bafbf8675486428c705fa75aa298ef05139/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f66656c69786d75686f726f2f6c61726176656c2d6d706573612e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/felixmuhoro/laravel-mpesa)[![Total Downloads](https://camo.githubusercontent.com/709d537fe108977f207ab8bc5be1906894c6afa2ed9ec550c34867ac08b2f536/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f66656c69786d75686f726f2f6c61726176656c2d6d706573612e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/felixmuhoro/laravel-mpesa)[![License](https://camo.githubusercontent.com/5741bbc7726c4f7ec2152d8571123d3b974ac2e8b65c2a6a9511f535083ff4fe/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f66656c69786d75686f726f2f6c61726176656c2d6d706573612e7376673f7374796c653d666c61742d737175617265)](LICENSE)

A modern, fully-typed **M-Pesa Daraja 2.0** integration for **Laravel 10 / 11 / 12 / 13**. Battle-tested in production against real customer traffic — including the undocumented error codes Safaricom's own docs don't mention.

Quick start
-----------

[](#quick-start)

```
composer require felixmuhoro/laravel-mpesa
```

```
use FelixMuhoro\Mpesa\Facades\Mpesa;

$response = Mpesa::stkPush(
    phone: '0712345678',
    amount: 100,
    reference: 'ORDER-1234',
    description: 'Payment for order 1234',
);

if ($response->accepted()) {
    // Persist $response->checkoutRequestId to reconcile with the callback
}
```

Full setup, callbacks, C2B, B2C, and result-code handling below.

Why this package
----------------

[](#why-this-package)

Most M-Pesa Laravel packages on Packagist were built for Laravel 7/8 and return raw arrays. This one is different:

- **Laravel 10 / 11 / 12 / 13** first-class — PHP 8.1+ enums, readonly DTOs, typed properties
- **Exhaustive result-code dictionary** — 15+ Safaricom codes mapped including the undocumented `4999` (still processing, NOT failed)
- **Correct async handling** — STK query correctly distinguishes "payment pending" from "payment failed" so you never mark a successful payment as failed because you polled too early
- **Events-driven** — `PaymentSuccessful`, `PaymentFailed`, `StkPushInitiated` dispatched on every terminal state
- **Secure callbacks** — IP allow-listing (Safaricom's 12 production IPs preloaded) + optional query-string shared-secret middleware
- **HTTP Faking friendly** — uses Laravel's `Illuminate\Http\Client\Factory`, so tests never hit real Daraja

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

[](#installation)

```
composer require felixmuhoro/laravel-mpesa
```

Publish the config:

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

Add credentials to `.env`:

```
MPESA_ENVIRONMENT=sandbox               # or "production"
MPESA_CONSUMER_KEY=your-consumer-key
MPESA_CONSUMER_SECRET=your-consumer-secret

# STK Push
MPESA_STK_SHORT_CODE=174379
MPESA_STK_PASSKEY=bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919
MPESA_STK_CALLBACK_URL=https://yourapp.com/mpesa/callback/stk

# Optional: callback shared secret
MPESA_CALLBACK_SECRET_KEY=some-long-random-string
```

Get sandbox credentials free at [developer.safaricom.co.ke](https://developer.safaricom.co.ke).

Usage
-----

[](#usage)

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

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

```
use FelixMuhoro\Mpesa\Facades\Mpesa;

$response = Mpesa::stkPush(
    phone: '0712345678',
    amount: 100,
    reference: 'ORDER-1234',
    description: 'Payment for order 1234'
);

if ($response->accepted()) {
    // Save $response->checkoutRequestId so you can match the callback later
    session(['mpesa_checkout' => $response->checkoutRequestId]);
}
```

Phone numbers are accepted in any Kenyan format — `0712...`, `712...`, `254712...`, `+254 712 345 678` — all normalise to Safaricom's required `2547XXXXXXXX`.

### 2. STK Query (check payment status)

[](#2-stk-query-check-payment-status)

```
$result = Mpesa::stkQuery($checkoutRequestId);

if ($result->isCompleted()) {
    // Mark order paid
} elseif ($result->isPending()) {
    // Retry in a few seconds — the customer hasn't acted yet
} elseif ($result->isFailed()) {
    // Customer cancelled / wrong PIN / etc. — $result->message has details
}
```

### 3. Callbacks — handle via events

[](#3-callbacks--handle-via-events)

The package ships routes at `/mpesa/callback/stk`, `/mpesa/callback/c2b/confirm`, etc., already protected by IP allow-listing + optional shared-secret middleware.

Your job is to listen for events:

```
// app/Providers/EventServiceProvider.php
use FelixMuhoro\Mpesa\Events\PaymentSuccessful;
use FelixMuhoro\Mpesa\Events\PaymentFailed;

protected $listen = [
    PaymentSuccessful::class => [MarkOrderPaid::class],
    PaymentFailed::class     => [NotifyCustomerOfFailure::class],
];
```

```
// app/Listeners/MarkOrderPaid.php
public function handle(PaymentSuccessful $event): void
{
    Order::where('checkout_request_id', $event->payload->checkoutRequestId)
        ->update([
            'status'        => 'paid',
            'mpesa_receipt' => $event->payload->mpesaReceiptNumber,
            'paid_amount'   => $event->payload->amount,
            'paid_at'       => now(),
        ]);
}
```

### 4. C2B — receive paybill / till payments

[](#4-c2b--receive-paybill--till-payments)

Register your confirmation + validation URLs **once**:

```
Mpesa::c2bRegisterUrls(
    confirmationUrl: route('mpesa.callback.c2b.confirm'),
    validationUrl:   route('mpesa.callback.c2b.validate'),
);
```

Listen for the same events (the C2B confirmation controller also dispatches `PaymentSuccessful`).

In **sandbox** you can simulate an inbound payment:

```
Mpesa::c2bSimulate('0712345678', 50, 'BILL-99');
```

### 5. B2C — send money to customers

[](#5-b2c--send-money-to-customers)

```
Mpesa::b2cSend(
    phone: '0712345678',
    amount: 500,
    commandId: 'BusinessPayment',   // or SalaryPayment / PromotionPayment
    remarks: 'Referral bonus',
);
```

### 6. Account balance, status queries, reversals

[](#6-account-balance-status-queries-reversals)

```
Mpesa::accountBalance();
Mpesa::transactionStatus('LKXXXX1234');
Mpesa::reverse('LKXXXX1234', 100, 'Wrong recipient');
```

Handling result codes
---------------------

[](#handling-result-codes)

Any time you receive a result code from Safaricom you can normalise it:

```
use FelixMuhoro\Mpesa\Enums\ResultCode;

ResultCode::isCompleted('0');        // true
ResultCode::isFailed('1032');        // true — customer cancelled
ResultCode::isPending('4999');       // true — undocumented "still processing"
ResultCode::isPending('random-code');// true — unknown codes are treated as pending

ResultCode::resolve('1');
// ['status' => 'failed', 'message' => 'Insufficient M-Pesa balance...', 'code' => '1']
```

Callback security
-----------------

[](#callback-security)

Production callbacks are protected out of the box:

- **IP allow-listing** — Safaricom's 12 production callback IPs are preloaded in config. Set `MPESA_CALLBACK_ALLOWED_IPS=""` to disable (NOT recommended in production).
- **Shared secret** — set `MPESA_CALLBACK_SECRET_KEY=...` and include `?key=...` in the callback URL you register with Safaricom.

Both are layered — requests that fail either check throw `InvalidCallbackException`.

Testing
-------

[](#testing)

The package ships PHPUnit tests that mock Daraja responses using `Http::fake()`:

```
composer install
composer test
```

Supported Laravel / PHP versions
--------------------------------

[](#supported-laravel--php-versions)

PackagePHPLaravel1.x8.1 – 8.410, 11, 12, 13Credits
-------

[](#credits)

- Author — [Felix Muhoro](https://felixmuhoro.dev) (`hi@felixmuhoro.dev`)
- Safaricom Daraja API docs —

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance95

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

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 ~1 days

Total

4

Last Release

49d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4e43995a8ec026a33d25263c30f437674d6d8481092cb3acd64e96e4ba3f4e4?d=identicon)[felixmuhoro](/maintainers/felixmuhoro)

---

Tags

b2cc2bdarajakenyalaravellaravel-11laravel-12laravel-packagelipa-na-mpesampesapaymentsphpsafaricomstk-pushlaravelpaymentsmpesakenyaB2Csafaricomdarajac2bstk-pushlipa-na-mpesa

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[calebdw/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

15104.9k4](/packages/calebdw-larastan)

PHPackages © 2026

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