Introduction
In today's fast-paced financial ecosystem, digital wallets have become
the backbone of fintech innovation. They provide users with convenience,
security, and accessibility when transacting online. In Kenya, M-Pesa
dominates the mobile money space, making it an essential integration for
any wallet solution. In this article, we'll walk through the process of
building a fintech wallet system using Laravel and integrating it
with the M-Pesa API.
Why Laravel for a Fintech Wallet?
Laravel is one of the most popular PHP frameworks because of its
simplicity, scalability, and security. Here's why it's a great choice
for fintech applications:
- Authentication & Authorization -- Built-in user management and role handling.
- Database Migrations & Eloquent ORM -- Makes schema design and data manipulation smooth.
- Security Features -- Protects against SQL injection, CSRF, and XSS attacks.
- API Development -- Supports RESTful APIs for mobile/web apps.
Key Features of Our Wallet
- User Registration & Authentication (with roles such as admin and customer).
- Wallet Account Management -- Every user has a balance.
- M-Pesa Deposits & Withdrawals -- Secure integration with Daraja API.
- Transaction History -- Track deposits, withdrawals, and transfers.
- Admin Dashboard -- Monitor transactions, users, and revenue.
Step 1: Setting Up the Laravel Project
composer create-project laravel/laravel wallet
cd wallet
php artisan serve
You should now see Laravel's welcome page at http://127.0.0.1:8000
.
Step 2: Database Design
We'll need at least these tables:
- users → Stores user details.
- wallets → Each user's wallet balance.
- transactions → Records deposits, withdrawals, and transfers.
Example migration for wallets:
Schema::create('wallets', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->decimal('balance', 12, 2)->default(0);
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
Step 3: M-Pesa Integration
We'll integrate using Safaricom's Daraja API.
a) Get API Credentials
- Register an app on Safaricom Developer Portal.
- Obtain Consumer Key and Consumer Secret.
b) Setup an M-Pesa Service Class
namespace App\Services;
use Illuminate\Support\Facades\Http;
class MpesaService
{
protected $consumerKey;
protected $consumerSecret;
protected $shortcode;
protected $passkey;
public function __construct()
{
$this->consumerKey = config('mpesa.consumer_key');
$this->consumerSecret = config('mpesa.consumer_secret');
$this->shortcode = config('mpesa.shortcode');
$this->passkey = config('mpesa.passkey');
}
public function stkPush($phone, $amount, $accountReference, $transactionDesc)
{
$accessToken = $this->getAccessToken();
$timestamp = now()->format('YmdHis');
$password = base64_encode($this->shortcode.$this->passkey.$timestamp);
return Http::withToken($accessToken)->post(
'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest',
[
'BusinessShortCode' => $this->shortcode,
'Password' => $password,
'Timestamp' => $timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $this->shortcode,
'PhoneNumber' => $phone,
'CallBackURL' => route('mpesa.callback'),
'AccountReference' => $accountReference,
'TransactionDesc' => $transactionDesc,
]
);
}
private function getAccessToken()
{
$response = Http::withBasicAuth($this->consumerKey, $this->consumerSecret)
->get('https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials');
return $response['access_token'];
}
}
Step 4: Handling Deposits
When a user initiates a deposit:
- Call the stkPush method.
- Wait for M-Pesa callback.
- Update wallet balance.
- Save transaction.
Callback route example:
Route::post('/mpesa/callback', [MpesaController::class, 'callback'])->name('mpesa.callback');
In the controller:
public function callback(Request $request)
{
$data = $request->all();
if (isset($data['Body']['stkCallback']['ResultCode']) && $data['Body']['stkCallback']['ResultCode'] == 0) {
$amount = $data['Body']['stkCallback']['CallbackMetadata']['Item'][0]['Value'];
$phone = $data['Body']['stkCallback']['CallbackMetadata']['Item'][4]['Value'];
$user = User::where('phone', $phone)->first();
if ($user) {
$user->wallet->increment('balance', $amount);
Transaction::create([
'user_id' => $user->id,
'amount' => $amount,
'type' => 'deposit',
'status' => 'success',
]);
}
}
}
Step 5: Withdrawals
Withdrawals can be processed using M-Pesa B2C API:
Http::withToken($accessToken)->post(
'https://sandbox.safaricom.co.ke/mpesa/b2c/v1/paymentrequest',
[
'InitiatorName' => config('mpesa.initiator_name'),
'SecurityCredential' => config('mpesa.security_credential'),
'CommandID' => 'BusinessPayment',
'Amount' => $amount,
'PartyA' => $this->shortcode,
'PartyB' => $phone,
'Remarks' => 'Wallet Withdrawal',
'QueueTimeOutURL' => route('mpesa.timeout'),
'ResultURL' => route('mpesa.result'),
'Occasion' => 'Withdrawal',
]
);
Step 6: Transaction History
Each transaction (deposit/withdrawal) is stored in the transactions
table and displayed in the user dashboard:
Transaction::where('user_id', auth()->id())->latest()->get();
Step 7: Security Best Practices
- Always store M-Pesa credentials in
.env
. - Use HTTPS for callbacks.
- Log API requests and responses for troubleshooting.
- Implement role-based access control for admins vs customers.
Conclusion
We've built a basic fintech wallet using Laravel and M-Pesa. This
system supports deposits, withdrawals, and transaction tracking. With
further enhancements such as KYC verification, fraud detection, and
multi-currency support, this can scale into a robust fintech solution.
Next Steps
- Add notifications (SMS/Email) for successful transactions.
- Extend to support bank integrations.
- Deploy to production with SSL and monitoring tools.