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.
Top comments (0)