Integrate Mpesa into Laravel

How to Integrate Mpesa Into your Laravel Application

Spread the love

Last Updated on September 24, 2023

Mpesa is a popular payment gateway in Kenya and Africa and is used by more than 52.4 million users on a day-to-day basis. Mpesa Provides various APIs which we can leverage and use to enhance business transactions.

In this tutorial, you will learn how to integrate Mpesa APIs into your Laravel application.

I also created a project using it. You can check out the GitHub repository containing the code and a hosted demo.

Prerequisites

  • Before you start interacting with Mpesa APIs, you need a developer account.
  • You might also need software to expose your local development environment to the internet. I recommend localtunnel or localhost.run. Ngrok is a good alternative although it is normally blocked by Safaricom thereby making tests a pain.
  • An HTTP Client such as Postman or Thunderclient.

Installation and configuration of Mpesa

This tutorial assumes that you already have a laravel 8,9,10 application. If not, you can create a new Laravel application.

laravel new payments

We then need to install a Laravel package. I personally created this package to ease and speed up the development process. You can find comprehensive documentation on GitHub.

composer require iankumu/mpesa

We can publish the configurations by running the mpesa:install command

php artisan mpesa:install

This will publish a config/mpesa.php file which contains the configurations such as passkeys, consumer keys, etc.

//config/mpesa.php

<?php

return [
    //This is the mpesa environment.Can be sanbox or production
    'environment' => env('MPESA_ENVIRONMENT', 'sandbox'),

    /*-----------------------------------------
        |The Mpesa Consumer Key
        |------------------------------------------
        */
    'mpesa_consumer_key' => env('MPESA_CONSUMER_KEY'),

    /*-----------------------------------------
        |The Mpesa Consumer Secret
        |------------------------------------------
        */
    'mpesa_consumer_secret' => env('MPESA_CONSUMER_SECRET'),

    /*-----------------------------------------
        |The Lipa na Mpesa Online Passkey
        |------------------------------------------
        */
    'passkey' => env('SAFARICOM_PASSKEY', 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919'),

    /*-----------------------------------------
        |The Lipa na Mpesa Online ShortCode
        |------------------------------------------
        */
    'shortcode' => env('MPESA_BUSINESS_SHORTCODE', '174379'),

    /*-----------------------------------------
        |The Mpesa Initator Name
        |------------------------------------------
        */
    'initiator_name' => env('MPESA_INITIATOR_NAME', 'testapi'),

    /*-----------------------------------------
        |The Mpesa Initator Password
        |------------------------------------------
        */
    'initiator_password' => env('MPESA_INITIATOR_PASSWORD'),

    /*-----------------------------------------
        |Mpesa B2C ShortCode
        |------------------------------------------
        */
    'b2c_shortcode' => env('MPESA_B2C_SHORTCODE'),

    /*-----------------------------------------
        |Mpesa C2B Validation url
        |------------------------------------------
        */
    'c2b_validation_url' => env('MPESA_C2B_VALIDATION_URL'),

    /*-----------------------------------------
        |Mpesa C2B Confirmation url
        |------------------------------------------
        */
    'c2b_confirmation_url' => env('MPESA_C2B_CONFIRMATION_URL'),

    /*-----------------------------------------
        |Mpesa B2C Result url
        |------------------------------------------
        */
    'b2c_result_url' => env('MPESA_B2C_RESULT_URL'),

    /*-----------------------------------------
        |Mpesa B2C Timeout url
        |------------------------------------------
        */
    'b2c_timeout_url' => env('MPESA_B2C_TIMEOUT_URL'),

    /*-----------------------------------------
        |Mpesa Lipa Na Mpesa callback url
        |------------------------------------------
        */
    'callback_url' => env('MPESA_CALLBACK_URL'),

    /*-----------------------------------------
        |Mpesa Transaction Status Result url
        |------------------------------------------
        */
    'status_result_url' => env('MPESA_STATUS_RESULT_URL'),

    /*-----------------------------------------
        |Mpesa Transaction Status Timeout url
        |------------------------------------------
        */
    'status_timeout_url' => env('MPESA_STATUS_TIMEOUT_URL'),

    /*-----------------------------------------
        |Mpesa Account Balance Result url
        |------------------------------------------
        */
    'balance_result_url' => env('MPESA_BALANCE_RESULT_URL'),

    /*-----------------------------------------
        |Mpesa Account Balance Timeout url
        |------------------------------------------
        */
    'balance_timeout_url' => env('MPESA_BALANCE_TIMEOUT_URL'),

    /*-----------------------------------------
        |Mpesa Reversal Result url
        |------------------------------------------
        */
    'reversal_result_url' => env('MPESA_REVERSAL_RESULT_URL'),

    /*-----------------------------------------
        |Mpesa Reversal Timeout url
        |------------------------------------------
        */
    'reversal_timeout_url' => env('MPESA_REVERSAL_TIMEOUT_URL'),
];

We can then add configurations to the .env file.

//.env 

APP_URL=https://your-app.com

MPESA_ENVIRONMENT=sandbox
SAFARICOM_PASSKEY=
MPESA_BUSINESS_SHORTCODE=
MPESA_CONSUMER_KEY=
MPESA_CONSUMER_SECRET=
MPESA_INITIATOR_PASSWORD=
MPESA_INITIATOR_NAME=
MPESA_B2C_SHORTCODE=

MPESA_CALLBACK_URL="${APP_URL}/api/v1/confirm"

MPESA_B2C_RESULT_URL="${APP_URL}/api/v1/b2c/result"
MPESA_B2C_TIMEOUT_URL="${APP_URL}/api/v1/b2c/timeout"

MPESA_C2B_CONFIRMATION_URL="${APP_URL}/api/confirmation"
MPESA_C2B_VALIDATION_URL="${APP_URL}/api/validation"

You will need to set the APP_URL variable to your desired app URL. Preferably if it is accessible through HTTPS.

When working with .env variables, ensure you run php artisan optimize if you are in production. This builds a configuration cache which Laravel uses when it requires any variable from the .env file.

In development you can ignore running this command although in some cases you might need to run the command if you change any .env configuration

With that, we have finished setting up the configurations. We can now start writing the Business Logic.

How to integrate Mpesa

Mpesa Express(STKPUSH)

This API is used to initiate a transaction on behalf of a customer. It contains all the prefilled details such as the business short code(pay bill), customer’s phone number, amount, and an Account Reference(Paybill’s Account Number).

This makes it easy for the customers to pay the amount with less friction as the only action needed from the customer is to authorize the transaction by entering their pin number.

Some key configurations we need are:

  • Safaricom Passkey
  • Consumer Key
  • Consumer Secret
  • Business Short Code(Pay bill Number)

To integrate stkpush into your laravel application, you can create a new controller.

php artisan make:controller MpesaSTKPUSHController 

We can then add the logic to the stkpush method.

//App/Http/Controllers/MpesaSTKPUSHController.php

<?php

namespace App\Http\Controllers;

use App\Mpesa\STKPush;
use App\Models\MpesaSTK;
use Iankumu\Mpesa\Facades\Mpesa;//import the Facade
use Illuminate\Http\Request;

class MpesaSTKPUSHController extends Controller
{
    public $result_code = 1;
    public $result_desc = 'An error occured';

    // Initiate  Stk Push Request
    public function STKPush(Request $request)
    {

        $amount = $request->input('amount');
        $phoneno = $request->input('phonenumber');
        $account_number = $request->input('account_number');

        $response = Mpesa::stkpush($phoneno, $amount, $account_number);
        $result = json_decode((string)$response, true);

        MpesaSTK::create([
            'merchant_request_id' =>  $result['MerchantRequestID'],
            'checkout_request_id' =>  $result['CheckoutRequestID']
        ]);

        return $result;
    }
}

In this example project, I am requesting the phone number, amount, and account number as inputs. Feel free to customize the parameters to your liking. You can read the full usage guide here.

It is also important to store the Merchant Request id and the Checkout request Id which will be useful when resolving callbacks we receive from Safaricom.

We also need to specify the routes in the web.php file

//routes/web.php

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MpesaSTKPUSHController;


Route::post('/v1/mpesatest/stk/push', [MpesaSTKPUSHController::class, 'STKPush']);

Callback

Callbacks are useful in notifying you of any events that happened to a transaction. Safaricom requires us to pass a valid URL that is accessible over the internet that they can use to pass any payload.

We can use this payload to resolve any system transactions on our end. An example could be to resolve that a specific subscription bill has been paid. We can set various actions to happen as soon as we receive the response from Safaricom.

In my case, I will just store the details in the database for future reference.

I will create a model and its corresponding migration file.

php artisan make:model MpesaSTK -m
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class MpesaSTK extends Model
{
    use HasFactory;

    protected $guarded = [];
    protected $table = 'mpesa_s_t_k_s';
}

STKPUSH Model

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMpesaSTKSTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mpesa_s_t_k_s', function (Blueprint $table) {
            $table->id();
            $table->string('result_desc')->nullable();
            $table->string('result_code')->nullable();
            $table->string('merchant_request_id')->nullable();
            $table->string('checkout_request_id')->nullable();
            $table->string('amount')->nullable();
            $table->string('mpesa_receipt_number')->nullable();
            $table->string('transaction_date')->nullable();
            $table->string('phonenumber')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mpesa_s_t_k_s');
    }
}

Corresponding Migration File

We can add another method to the controller we created earlier to handle the callback.

//App/Http/Controllers/MpesaSTKPUSHController.php
<?php

namespace App\Http\Controllers;

use App\Mpesa\STKPush;
use App\Models\MpesaSTK;
use Iankumu\Mpesa\Facades\Mpesa;
use Illuminate\Http\Request;


class MpesaSTKPUSHController extends Controller
{
    public $result_code = 1;
    public $result_desc = 'An error occured';


    // This function is used to review the response from Safaricom once a transaction is complete
    public function STKConfirm(Request $request)
    {
        $stk_push_confirm = (new STKPush())->confirm($request);

        if ($stk_push_confirm) {

            $this->result_code = 0;
            $this->result_desc = 'Success';
        }
        return response()->json([
            'ResultCode' => $this->result_code,
            'ResultDesc' => $this->result_desc
        ]);
    }
}

We can also create a route that will be accessible over the internet.

Note that this url should not contain any Safaricom or Mpesa brand names

//routes/api.php

<?php

use App\Http\Controllers\MpesaSTKPUSHController;


// Mpesa STK Push Callback Route
Route::post('v1/confirm', [MpesaSTKPUSHController::class, 'STKConfirm'])->name('mpesa.confirm');

I personally like separating my application’s URLs from callback URLs by adding the callback URLs as API endpoints as opposed to web routes.

I will create a new App\Mpesa folder that will house all the callback logic. I will then create a new App\Mpesa\STKPush.php file and add the logic.

//App/Mpesa/STKPush.php

<?php

namespace App\Mpesa;

use App\Models\MpesaSTK;
use Illuminate\Http\Request;

// This Class is responsible for getting a response from Safaricom and Storing the Transaction Details to the Database
class STKPush
{
    public $failed = false;
    public $response = 'An Unkown Error Occured';

    public function confirm(Request $request)
    {
        $payload = json_decode($request->getContent());

        if (property_exists($payload, 'Body') && $payload->Body->stkCallback->ResultCode == '0') {
            $merchant_request_id = $payload->Body->stkCallback->MerchantRequestID;
            $checkout_request_id = $payload->Body->stkCallback->CheckoutRequestID;
            $result_desc = $payload->Body->stkCallback->ResultDesc;
            $result_code = $payload->Body->stkCallback->ResultCode;
            $amount = $payload->Body->stkCallback->CallbackMetadata->Item[0]->Value;
            $mpesa_receipt_number = $payload->Body->stkCallback->CallbackMetadata->Item[1]->Value;
            $transaction_date = $payload->Body->stkCallback->CallbackMetadata->Item[3]->Value;
            $phonenumber = $payload->Body->stkCallback->CallbackMetadata->Item[4]->Value;

            $stkPush = MpesaSTK::where('merchant_request_id', $merchant_request_id)
                ->where('checkout_request_id', $checkout_request_id)->first();//fetch the trasaction based on the merchant and checkout ids

            $data = [
                'result_desc' => $result_desc,
                'result_code' => $result_code,
                'merchant_request_id' => $merchant_request_id,
                'checkout_request_id' => $checkout_request_id,
                'amount' => $amount,
                'mpesa_receipt_number' => $mpesa_receipt_number,
                'transaction_date' => $transaction_date,
                'phonenumber' => $phonenumber,
            ];

            if ($stkPush) {
                $stkPush->fill($data)->save();
            } else {
                MpesaSTK::create($data);
            }
        } else {
            $this->failed = true;
        }
        return $this;
    }
}

All I am simply doing here is receiving the response, decode it and store in the database. You can add any relevant logic such as activate subscriptions here.

Notice how I use the Merchant Request id and the Checkout request Id I had stored earlier to determine which transaction I am dealing with. This makes it easy to resolve transactions at scale.

Query Transaction Status.

At times, we might want to check the status of a STKPUSH transaction. This is because the transaction is created from the server side and therefore, we somehow need to know what happened on the client side(customer’s side).

Most of the time a customer might accidentally close the prompt and therefore the transaction will not succeed and your callback URL will not receive any response. Another common issue is that a customer might input the wrong pin/password and therefore the transaction fails.

To check the status of the transaction, we can use the stkquery method from the package.

use Iankumu\Mpesa\Facades\Mpesa;

$response=Mpesa::stkquery($checkoutRequestId);

$result = json_decode((string)$response);
return $result;

The method requires a checkout Request ID to be passed as a parameter. Luckily for us, we were storing this parameter in the database.

You can schedule a job or implement it in any other way to check the response and then perform the next step, which might be initiating a new prompt or sending a message alerting the customer that the transaction had not succeeded.

You can read more on this in the documentation.

That’s it. Mpesa Express is now configured and ready for use.

Customer to Business(C2B)

The next API on our list is the Customer to Business(C2B). This API allows users to initiate transactions on their mobile phones and have our systems resolve them as soon as they have paid.

The difference between C2B and Mpesa Express is that instead of initiating the transaction at the server level, the transaction is initiated at the client’s level.

Some key configurations we need are:

  • Consumer Key
  • Consumer Secret
  • Set up the Validation Url
  • Set up the Confirmation Url

I will create a new controller. I prefer Separating the various APIs into their own Controllers.

php artisan make:controller MpesaC2BController 

When it comes to C2B, there are only two things I personally implement: register URLs and handle callbacks. If you want to implement C2B Simulation, you can read this guide.

I prefer this approach because the transactions are usually on the client side, so there is no intrinsic way to handle it.

The only thing that I have control over is the payload I receive from Safaricom. This should be the main implementation for this API, as you can resolve any transactions at the server level.

I will add the register URL logic to the controller.

//App\Http\Controllers\MPESAC2BController.php
<?php

namespace App\Http\Controllers;

use Iankumu\Mpesa\Facades\Mpesa;//import the Facade
use Illuminate\Http\Request;

class MPESAC2BController extends Controller
{

    public function registerURLS(Request $request)
    {
        $shortcode = $request->input('shortcode');
        $response = Mpesa::c2bregisterURLS($shortcode);
        $result = json_decode((string)$response, true);

        return $result;
    }
}

In my case, I request the business shortcode as an input, but you can set it to automatically register URLs without requiring any user input. You can set up a job to do this.

I will also add a route.

//routes/web.php

<?php
use App\Http\Controllers\MPESAC2BController;
use Illuminate\Support\Facades\Route;

Route::post('register-urls', [MPESAC2BController::class, 'registerURLS']);

You should get a valid 200 OK response that validates your URLs have been registered.

Callback

This is the bread and butter of the C2B API. It allows us to handle any transactions at scale.

We will first start by specifying the routes.

//routes/api.php

<?php

use App\Http\Controllers\MPESAC2BController;
use Illuminate\Support\Facades\Route;

//MPESA C2B

Route::post('validation', [MPESAC2BController::class, 'validation'])->name('c2b.validate');
Route::post('confirmation', [MPESAC2BController::class, 'confirmation'])->name('c2b.confirm');

We can then add the logic for validation and confirmation in the controller.

//App/Http/Controllers/MPESAC2BController.php

<?php

namespace App\Http\Controllers;

use App\Mpesa\C2B;
use Iankumu\Mpesa\Facades\Mpesa;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class MPESAC2BController extends Controller
{
   public function validation()
    {
        Log::info('Validation endpoint has been hit');
        $result_code = "0";
        $result_description = "Accepted validation request";
        return Mpesa::validationResponse($result_code, $result_description);
    }
    public function confirmation(Request $request)
    {

        return (new C2B())->confirm($request);
    }
}

The validation URL basically alerts Mpesa that your application is ready to receive any callbacks. The Confirmation URL, on the other hand, is the one responsible for receiving the payload.

We can now create the C2B Model and Migration file

php artisan make:model MpesaC2B -m
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class MpesaC2B extends Model
{
    use HasFactory;

    protected $guarded = [];
    protected $table = 'mpesa_c2b';
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMpesaC2BSTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mpesa_c2b', function (Blueprint $table) {
            $table->id();
            $table->string('Transaction_type')->nullable();
            $table->string('Transaction_ID')->nullable();
            $table->string('Transaction_Time')->nullable();
            $table->string('Amount')->nullable();
            $table->string('Business_Shortcode')->nullable();
            $table->string('Account_Number')->nullable();
            $table->string('Invoice_no')->nullable();
            $table->string('Organization_Account_Balance')->nullable();
            $table->string('ThirdParty_Transaction_ID')->nullable();
            $table->string('Phonenumber')->nullable();
            $table->string('FirstName')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mpesa_c2b');
    }
}

I then will create a new App\Mpesa\C2B.php file and add the following logic

//App/Mpesa/C2B.php

<?php

namespace App\Mpesa;

use App\Models\MpesaC2B;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;

class C2B
{
    public function confirm(Request $request)
    {
        Log::info('Confirmation endpoint has been hit');
        $payload = $request->all();

        $c2b = new MpesaC2B();
        $c2b->Transaction_type = $payload['TransactionType'];
        $c2b->mpesa_receipt_number = $payload['TransID'];
        $c2b->transaction_date = $payload['TransTime'];
        $c2b->amount = $payload['TransAmount'];
        $c2b->Business_Shortcode = $payload['BusinessShortCode'];
        $c2b->Account_Number = $payload['BillRefNumber'];
        $c2b->Invoice_no = $payload['InvoiceNumber'];
        $c2b->Organization_Account_Balance = $payload['OrgAccountBalance'];
        $c2b->ThirdParty_Transaction_ID = $payload['ThirdPartyTransID'];
        $c2b->phonenumber = $payload['MSISDN'];
        $c2b->FirstName = $payload['FirstName'];
        $c2b->save();

        return $payload;
    }
}

As before, I will store it in the database.

Business to Customer(B2C)

The Business to Customer API is useful if you want to perform any funds remittance(payouts) to customers. These remittances can include user withdrawals, salary payments, and promotion payments among other use cases.

This is the main API used by Banks when you withdraw funds from your Bank Account to your Mpesa Account.

Some key configurations are:

  • Consumer Key
  • Consumer Secret
  • Set up the result Url
  • Set up the Timeout Url
  • Initiator name
  • Initiator Password
  • Business to Customer Shortcode

A unique property of B2C is that it requires a business to use another shortcode which will be responsible for remitting funds.

A typical flow is a business will have 2 Shortcodes; a C2B Shortcode(to receive funds from Customers) and a B2C Shortcode(To remit funds to customers).

To integrate the B2C API, I will create another controller

php artisan make:controller MPESAB2CController

I will add a simulate method that will be responsible for initiating the transaction.

// App/Http/Controllers/MPESAB2CController.php

<?php

namespace App\Http\Controllers;

use App\Mpesa\B2C;
use Iankumu\Mpesa\Facades\Mpesa;
use Illuminate\Http\Request;

class MPESAB2CController extends Controller
{

    public function simulate(Request $request)
    {
        $phoneno = $request->input('phonenumber');
        $amount = $request->input('amount');
        $remarks = $request->input('remarks');
        $command = $request->input('command');


        $response = Mpesa::b2c($phoneno, $command, $amount, $remarks);

        $result = json_decode((string)$response);

        return $result
    }
}

In this example, I am also requesting various inputs from a user. You are free to prefill any parameters to suit your business needs.

I will also add its corresponding route

//routes/web.php


<?php

use App\Http\Controllers\MPESAB2CController;
use Illuminate\Support\Facades\Route;

Route::post('/v1/b2c/simulate', [MPESAB2CController::class, 'simulate']);

Callback

Similar to the other APIs discussed earlier, we need to handle any relevant payloads sent to us by Safaricom. Some key things we might want to handle are: updating the customer’s account balance(Incase of withdrawals) and Sending out any emails/SMS among others.

In my case, I will just store the payload in the database.

I will first add the relevant routes and add the methods responsible for handling the callbacks.

//routes/api.php


<?php

use App\Http\Controllers\MPESAB2CController;
use Illuminate\Support\Facades\Route;

// MPESA B2C
Route::post('v1/b2c/result', [MPESAB2CController::class, 'result'])->name('b2c.result');
Route::post('v1/b2c/timeout', [MPESAB2CController::class, 'timeout'])->name('b2c.timeout');

I will then add the logic to the Mpesa/B2C.php file.

// App\Mpesa\B2C.php

<?php

namespace App\Mpesa;

use App\Models\MpesaB2C;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;

class B2C
{
    public function results(Request $request)
    {
        Log::info("Result URL has been hit");
        $payload = json_decode($request->getContent());

        $withdrawal = MpesaB2C::where('ConversationID', $payload->Result->ConversationID)
            ->where('OriginatorConversationID', $payload->Result->OriginatorConversationID)->first();

        if ($payload->Result->ResultCode == 0) {
            $b2cDetails = [
                'ResultType' => $payload->Result->ResultType,
                'ResultCode' => $payload->Result->ResultCode,
                'ResultDesc' => $payload->Result->ResultDesc,
                'OriginatorConversationID' => $payload->Result->OriginatorConversationID,
                'ConversationID' => $payload->Result->ConversationID,
                'TransactionID' => $payload->Result->TransactionID,
                'TransactionAmount' => $payload->Result->ResultParameters->ResultParameter[0]->Value,
                'RegisteredCustomer' => $payload->Result->ResultParameters->ResultParameter[2]->Value,
                'ReceiverPartyPublicName' => $payload->Result->ResultParameters->ResultParameter[4]->Value, //Details of Recepient
                'TransactionDateTime' => $payload->Result->ResultParameters->ResultParameter[5]->Value,
                'B2CChargesPaidAccountAvailableFunds' => $payload->Result->ResultParameters->ResultParameter[3]->Value, //Charges Paid Account Balance
                'B2CUtilityAccountAvailableFunds' => $payload->Result->ResultParameters->ResultParameter[6]->Value, //Utility Account Balance
                'B2CWorkingAccountAvailableFunds' => $payload->Result->ResultParameters->ResultParameter[7]->Value, //Working Account Balance
            ];

            $withdrawal = MpesaB2C::where('ConversationID', $payload->Result->ConversationID)
                ->where('OriginatorConversationID', $payload->Result->OriginatorConversationID)->first();

            if ($withdrawal) {
                $withdrawal->fill($b2cDetails)->save();

                return response()->json([
                    'message' => 'Withdrawal successful'
                ], Response::HTTP_OK);
            }
        }
    }
}

Finally, I will create the corresponding Model and migration files.

php artisan make:model MpesaB2C -m
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class MpesaB2C extends Model
{
    use HasFactory;

    protected $guarded = [];
    protected $table = 'mpesa_b2_c_s';
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('mpesa_b2_c_s', function (Blueprint $table) {
            $table->id();
            $table->string('ResultType')->nullable();
            $table->string('ResultCode')->nullable();
            $table->string('ResultDesc')->nullable();
            $table->string('OriginatorConversationID')->nullable();
            $table->string('ConversationID')->nullable();
            $table->string('TransactionID')->nullable();
            $table->string('TransactionAmount')->nullable();
            $table->string('RegisteredCustomer')->nullable();
            $table->string('ReceiverPartyPublicName')->nullable();
            $table->string('B2CChargesPaidAccountAvailableFunds')->nullable();
            $table->string('B2CUtilityAccountAvailableFunds')->nullable();
            $table->string('B2CWorkingAccountAvailableFunds')->nullable();
            $table->string('TransactionDateTime')->nullable();


            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('mpesa_b2_c_s');
    }
};

And that’s it.

Going Live

Once you are done with testing and everything is ok, It is now time to go Live.

You will first need to change the Mpesa Environment in the config/mpesa.php file to production/live so that the production endpoints are called.

MPESA_ENVIRONMENT=production

The next step is to create a business admin in the Mpesa Portal. You will be provided with a username and password which you will use as the initiator name and initiator password respectively

You also need to get any relevant shortcodes (Paybill, Till Number, or Disbursement (B2C) codes).

The last step is to head over to your developer account’s Go Live Page and fill in any relevant information.

You will have all the necessary configurations except the passkey which will be sent to you through email as soon as Safaricom has approved your application.

Ensure you update your .env file with production/live configurations

Conclusion

In this tutorial, we have integrated with the main Safaricom APIs that will be responsible for most of the transactions in most systems.

There are other APIs available to us that I have not covered but the package has them all implemented. You can read them in the Documentation.

I hope this guide has helped you integrate Mpesa into your Application. If you have any concerns, feel free to reach out to me. You can find the full demo application here.

Thank you for reading.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *