laravel-mpesa-livewire maintained by felixmuhoro
laravel-mpesa-livewire
Livewire v3 components for M-Pesa payment flows. Drop-in checkout button, payment history, real-time status polling, and a compact wallet widget — all styled with Tailwind and wired with proper Livewire 3 patterns.
Requirements
- PHP 8.1+
- Laravel 10 / 11 / 12 / 13
- Livewire 3.x
- felixmuhoro/laravel-mpesa ^1.2
Installation
composer require felixmuhoro/laravel-mpesa-livewire
The service provider auto-registers via Laravel package discovery.
Publish the config:
php artisan vendor:publish --tag=mpesa-livewire-config
Optionally publish views to customise them:
php artisan vendor:publish --tag=mpesa-livewire-views
Components
<livewire:mpesa-stk-push-button />
Full STK push checkout flow with state machine: idle → initiating → awaiting_confirmation → success | failed. Polls the Daraja STK query endpoint every 3 seconds while waiting for the user's PIN.
{{-- Fixed amount --}}
<livewire:mpesa-stk-push-button
:amount="1500"
reference="ORDER-{{ $order->id }}"
description="Payment for order #{{ $order->id }}"
/>
{{-- User-entered amount --}}
<livewire:mpesa-stk-push-button
reference="TOP-UP"
description="Wallet top-up"
/>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
amount |
float | 0 |
Amount in KES. If 0, renders an amount input. |
phone |
string | '' |
Pre-fill phone. Falls back to authenticated user's phone column. |
reference |
string | 'Payment' |
Account reference (max 12 chars). |
description |
string | 'M-Pesa Payment' |
Transaction description (max 13 chars). |
Events dispatched
| Event | Payload | Description |
|---|---|---|
mpesa-stk-initiated |
{checkoutRequestId, phone, amount} |
STK push accepted |
mpesa-payment-success |
{checkoutRequestId, receiptNumber, amount, phone} |
Payment confirmed |
mpesa-payment-failed |
{checkoutRequestId, resultCode} |
Payment failed/cancelled |
Listening to events in parent components:
#[On('mpesa-payment-success')]
public function onPaymentSuccess(array $event): void
{
Order::where('reference', $this->reference)
->update([
'status' => 'paid',
'mpesa_receipt' => $event['receiptNumber'],
]);
}
<livewire:mpesa-payment-history />
Paginated, filterable, sortable transaction table scoped to the authenticated user.
<livewire:mpesa-payment-history />
Filters: date range, status, amount range, free-text search (phone / receipt / reference).
URL query parameters are synced via #[Url] so filters survive page refresh.
<livewire:mpesa-payment-status />
Real-time status card. Given a checkout_request_id, polls every 3 seconds and shows animated feedback.
<livewire:mpesa-payment-status
checkout-request-id="{{ $checkoutRequestId }}"
/>
States: pending (spinning) → completed (green checkmark) → failed/cancelled (red X).
Events dispatched
| Event | Payload |
|---|---|
mpesa-status-confirmed |
{checkoutRequestId, receiptNumber} |
Also listens to echo:payments,PaymentConfirmed for instant broadcast updates if you have Laravel Echo configured.
<livewire:mpesa-widget />
Compact collapsible widget combining a mini balance display with a quick-pay form. Ideal for sidebars or dashboards.
<livewire:mpesa-widget reference="Top Up" />
Configure how balance is resolved in config/mpesa-livewire.php:
// Use a column on the users table
'user_balance_column' => 'wallet_balance',
// Or a custom callable
'balance_resolver' => fn ($user) => $user->wallet->availableBalance(),
Configuration
config/mpesa-livewire.php:
return [
'transactions_table' => 'mpesa_transactions', // DB table
'user_phone_column' => 'phone', // Column for pre-filling phone
'user_balance_column' => 'balance', // Column for wallet balance
'balance_resolver' => null, // Custom callable (overrides column)
'scope_to_user' => true, // Scope history to auth user
'poll_max_attempts' => 20, // Polls before timeout (3s each = 60s)
'receipt_route' => 'mpesa.receipt', // Named route for receipt downloads
];
Styling
All components use Tailwind CSS utility classes. No additional CSS is required if you have Tailwind in your project. To customise, publish the views and edit them directly.
M-Pesa brand green: #00A651 / #007A3D.
Events & Broadcasting
To enable instant confirmation (instead of polling), fire a PaymentConfirmed broadcast event on your server after receiving the M-Pesa callback:
broadcast(new PaymentConfirmed($checkoutRequestId, $receiptNumber))
->toOthers();
The PaymentStatus component listens on echo:payments,PaymentConfirmed and will stop polling immediately on receipt.
Testing
composer test
Tests use Livewire's Livewire::test() helper and mock the mpesa service binding.
License
MIT — Felix Muhoro