Building a Fintech Wallet Using Laravel and M-Pesa
Vincent Kioko

Vincent Kioko @sntaks

About: Maximum Effort

Joined:
Aug 16, 2024

Building a Fintech Wallet Using Laravel and M-Pesa

Publish Date: Aug 18
1 0

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

  1. User Registration & Authentication (with roles such as admin and customer).
  2. Wallet Account Management -- Every user has a balance.
  3. M-Pesa Deposits & Withdrawals -- Secure integration with Daraja API.
  4. Transaction History -- Track deposits, withdrawals, and transfers.
  5. 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
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode

Step 3: M-Pesa Integration

We'll integrate using Safaricom's Daraja API.

a) Get API Credentials

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'];
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Handling Deposits

When a user initiates a deposit:

  1. Call the stkPush method.
  2. Wait for M-Pesa callback.
  3. Update wallet balance.
  4. Save transaction.

Callback route example:

Route::post('/mpesa/callback', [MpesaController::class, 'callback'])->name('mpesa.callback');
Enter fullscreen mode Exit fullscreen mode

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',
            ]);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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',
    ]
);
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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.

Comments 0 total

    Add comment