A virtual wallet system for Laravel backed by M-Pesa deposits and withdrawals.
felixmuhoro/laravel-mpesa-wallet is a Laravel package for a virtual wallet system for laravel backed by m-pesa deposits and withdrawals..
It currently has 0 GitHub stars and 1 downloads on Packagist (latest version v1.0.0).
Install it with composer require felixmuhoro/laravel-mpesa-wallet.
Discover more Laravel packages by felixmuhoro
or browse all Laravel packages to compare alternatives.
Last updated
A production-ready virtual wallet system for Laravel, backed by M-Pesa deposits and withdrawals via felixmuhoro/laravel-mpesa.
SELECT ... FOR UPDATEMoney value objectCreditWalletOnPayment) auto-credits wallet on PaymentSuccessful| Dependency | Version |
|-----------------------------|--------------------------------------------|
| PHP | ^8.1 |
| Laravel | ^10.0 || ^11.0 || ^12.0 || ^13.0 |
| felixmuhoro/laravel-mpesa | ^1.2 |
composer require felixmuhoro/laravel-mpesa-wallet
Publish config and migrations:
php artisan vendor:publish --tag=mpesa-wallet-config
php artisan vendor:publish --tag=mpesa-wallet-migrations
php artisan migrate
use FelixMuhoro\MpesaWallet\Concerns\HasWallet;
class User extends Authenticatable
{
use HasWallet;
}
.envMPESA_WALLET_CURRENCY=KES
MPESA_WALLET_MIN_DEPOSIT=10
MPESA_WALLET_MAX_DEPOSIT=150000
MPESA_WALLET_MIN_WITHDRAWAL=10
MPESA_WALLET_MAX_WITHDRAWAL=150000
MPESA_WALLET_CALLBACK_URL=https://yourdomain.com/api/mpesa/callback
$user = auth()->user(); // User uses HasWallet
// Initiate M-Pesa STK push deposit (returns pending WalletTransaction)
$txn = $user->deposit(1000, '254712345678');
// Initiate B2C withdrawal (locks funds immediately)
$txn = $user->withdraw(500, '254712345678');
// Wallet-to-wallet transfer
[$debit, $credit] = $user->transferTo($anotherUser, 300);
// Check balance (spendable, excludes locked funds)
echo $user->walletBalance()->format(); // "KES 5,000.00"
echo $user->formattedWalletBalance();
use FelixMuhoro\MpesaWallet\WalletManager;
class PaymentService
{
public function __construct(private WalletManager $wallet) {}
public function topUp(User $user, int $amount, string $phone): void
{
$pendingTxn = $this->wallet->deposit($user, $amount, $phone);
// CreditWalletOnPayment listener will settle on M-Pesa callback
}
}
All routes require auth:sanctum by default, prefixed api/wallet.
| Method | URI | Description |
|--------|-----------------------------------|--------------------------|
| GET | /api/wallet/balance | Current balance |
| POST | /api/wallet/deposit | Initiate STK push |
| POST | /api/wallet/withdraw | Initiate B2C withdrawal |
| POST | /api/wallet/transfer | Wallet-to-wallet transfer|
| GET | /api/wallet/transactions | Paginated history |
| GET | /api/wallet/transactions/{uuid} | Single transaction |
{ "amount": 1000, "phone": "254712345678" }
{ "amount": 500, "phone": "254712345678" }
{ "recipient_id": 42, "amount": 300 }
User -> POST /deposit -> WalletManager::deposit()
creates pending WalletTransaction (balance unchanged)
-> caller fires STK Push via felixmuhoro/laravel-mpesa
Safaricom -> POST /callback -> PaymentSuccessful event
-> CreditWalletOnPayment listener (queued, 3 retries)
-> WalletManager::creditWallet() — lockForUpdate -> balance++
-> pending transaction -> completed
User -> POST /withdraw -> WalletManager::withdraw()
lockForUpdate -> locked_balance += amount
creates pending WalletTransaction
-> caller fires B2C via felixmuhoro/laravel-mpesa
Safaricom -> POST /callback
Success -> settleWithdrawal(): balance -= amount, locked -= amount
Failure -> failWithdrawal(): locked -= amount (funds refunded)
// config/mpesa-wallet.php
return [
'currency' => 'KES',
'auto_create' => true,
'limits' => [
'min_deposit' => 10,
'max_deposit' => 150_000,
'min_withdrawal' => 10,
'max_withdrawal' => 150_000,
],
'routes' => [
'enabled' => true,
'prefix' => 'api/wallet',
'middleware' => ['api', 'auth:sanctum'],
'name' => 'mpesa-wallet.',
],
'tables' => [
'wallets' => 'wallets',
'transactions' => 'wallet_transactions',
],
];
composer install
vendor/bin/phpunit --testdox
MIT