How to Integrate M-Pesa Payments into Laravel Using Daraja API

M-Pesa is an important payment method for businesses in Kenya. Whether you're building an e-commerce website, a booking platform, or a service marketplace, integrating mobile payments can make it easier for customers to pay.

Laravel provides tools for building web applications, while Safaricom's Daraja API allows developers to integrate supported M-Pesa payment services.

In this beginner-friendly guide, we'll explore how to integrate M-Pesa Express (STK Push) into a Laravel application, from configuring credentials to handling payment callbacks.

The examples demonstrate the main concepts. A production implementation requires additional validation, error handling, and testing.

1. What Is Daraja API?

Daraja is Safaricom's developer platform for integrating M-Pesa services into applications.

With STK Push, a business can initiate a payment request that prompts a customer to enter their M-Pesa PIN on their phone.

The typical process is:

  1. The customer initiates a payment on your website.
  2. Laravel sends an STK Push request to Daraja.
  3. The customer responds to the payment prompt.
  4. Daraja sends the transaction outcome to your callback URL.
  5. Laravel processes the result and updates the payment record.

You can find the official developer resources at the Safaricom Daraja Developer Portal.

2. What You Need Before Starting

You'll need:

  • A working Laravel application.
  • PHP and Composer.
  • Basic knowledge of Laravel routes, controllers, and environment variables.
  • A Daraja developer account and configured application.
  • The appropriate API credentials, shortcode, and STK Push passkey.
  • A publicly accessible HTTPS callback URL for testing callbacks.

To create a new Laravel project, run:

composer create-project laravel/laravel mpesa-laravel
cd mpesa-laravel

If you already have a Laravel application, you can integrate the payment functionality into that project instead.

3. Configure Your Daraja Credentials

Register your application through the Daraja Developer Portal and follow the current setup instructions.

For development, use the sandbox environment and its appropriate test credentials.

In your Laravel .env file, add configuration similar to this:

DARAJA_BASE_URL=https://sandbox.safaricom.co.ke
DARAJA_CONSUMER_KEY=your_consumer_key
DARAJA_CONSUMER_SECRET=your_consumer_secret
DARAJA_SHORTCODE=your_business_shortcode
DARAJA_PASSKEY=your_stk_passkey
DARAJA_CALLBACK_URL=https://your-domain.example/api/payments/callback

Replace the example values with your actual configuration. The callback domain above is only a placeholder.

Next, add the settings to config/services.php:

'daraja' => [
    'base_url' => env('DARAJA_BASE_URL'),
    'consumer_key' => env('DARAJA_CONSUMER_KEY'),
    'consumer_secret' => env('DARAJA_CONSUMER_SECRET'),
    'shortcode' => env('DARAJA_SHORTCODE'),
    'passkey' => env('DARAJA_PASSKEY'),
    'callback_url' => env('DARAJA_CALLBACK_URL'),
],

Keep your credentials on the server. Never expose consumer secrets or passkeys in frontend JavaScript, public repositories, or screenshots.

4. Obtain an Access Token

Daraja uses OAuth authentication to authorise API requests. Laravel's HTTP client can send the request for an access token.

use Illuminate\Support\Facades\Http;

$response = Http::withBasicAuth(
    config('services.daraja.consumer_key'),
    config('services.daraja.consumer_secret')
)->get(
    config('services.daraja.base_url')
        . '/oauth/v1/generate',
    ['grant_type' => 'client_credentials']
);

$response->throw();

$accessToken = $response->json('access_token');

This example demonstrates the basic request. A production implementation should also handle authentication failures, timeouts, and token expiry. Caching a token until shortly before it expires can help avoid unnecessary requests.

Consult the current Daraja documentation for the exact endpoint and requirements for your configured environment.

5. Generate the STK Push Password

An STK Push request uses a password generated from the business shortcode, passkey, and timestamp.

$timestamp = now()->format('YmdHis');

$password = base64_encode(
    config('services.daraja.shortcode')
    . config('services.daraja.passkey')
    . $timestamp
);

The timestamp and password must follow Safaricom's current API specification. This example assumes the required configuration is available and valid.

6. Send an STK Push Request

Once you have an access token and the necessary payment details, you can submit an STK Push request.

$response = Http::withToken($accessToken)
    ->acceptJson()
    ->post(
        config('services.daraja.base_url')
            . '/mpesa/stkpush/v1/processrequest',
        [
            'BusinessShortCode' => $shortcode,
            'Password' => $password,
            'Timestamp' => $timestamp,
            'TransactionType' => 'CustomerPayBillOnline',
            'Amount' => $amount,
            'PartyA' => $phoneNumber,
            'PartyB' => $shortcode,
            'PhoneNumber' => $phoneNumber,
            'CallBackURL' => config('services.daraja.callback_url'),
            'AccountReference' => $accountReference,
            'TransactionDesc' => $description,
        ]
    );

$response->throw();

$result = $response->json();

The variables in this example must be defined by your application. The transaction type, shortcode, phone number format, and other values must match the requirements of your payment setup.

Important: An accepted STK Push request does not mean the customer has paid. It means the request has been accepted for processing.

Store the returned checkout request identifier against your pending payment record so that you can match the eventual result to the correct payment attempt.

7. Record the Payment Attempt

Before initiating a payment, create a database record for the attempt.

Useful fields include:

  • Payment ID and associated order ID.
  • Expected amount.
  • Customer phone number.
  • Checkout request identifier.
  • M-Pesa receipt number, once confirmed.
  • Payment status and timestamps.

Common statuses include pending, paid, and failed.

Creating the record first helps you track incomplete payments, investigate errors, and prevent duplicate processing. Update the record if the initial API request fails.

8. Handle the Payment Callback

Daraja sends the transaction outcome to the callback URL configured in your request.

A Laravel route could look like this:

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

Route::post(
    '/payments/callback',
    [PaymentCallbackController::class, 'handle']
);

Configure the route appropriately for your Laravel version so that the external callback can reach it without being rejected by browser-oriented CSRF protection.

Your callback handler should:

  1. Parse the incoming payload.
  2. Match the checkout request identifier to an existing payment attempt.
  3. Determine whether the transaction succeeded or failed.
  4. Validate the reported amount and relevant transaction details.
  5. Save the outcome and receipt information.
  6. Update the associated order only after the payment is verified.

Successful and unsuccessful callbacks may contain different fields, so handle missing metadata carefully.

A reachable callback URL is essential. Test it independently rather than assuming that a successful STK Push request means the callback will work.

9. Secure and Verify Payments

Payment verification is one of the most important parts of the integration.

Do not mark an order as paid simply because the initial request was accepted. Also, do not blindly trust an incoming callback without validating it against the payment attempt created by your application.

Important safeguards include:

  • Match callbacks to known payment attempts.
  • Confirm the transaction's result indicates success.
  • Compare the reported amount with the expected amount.
  • Validate the receipt and other relevant transaction details.
  • Prevent the same payment from being processed twice.
  • Use supported transaction-query or verification mechanisms when appropriate.

Callback handling should be idempotent: receiving the same callback more than once must not fulfil an order or grant a service multiple times.

Use database transactions and appropriate uniqueness constraints where needed. For higher-risk transactions, consider independent verification using the Daraja functionality available to your account.

HTTPS protects information in transit, but it does not by itself prove that a callback represents a valid payment.

10. Test Before Going Live

Test the complete payment workflow using the sandbox before accepting real payments.

Include scenarios such as:

  • Valid payment requests.
  • Invalid credentials or phone numbers.
  • Cancelled or unsuccessful transactions.
  • Network errors and timeouts.
  • Repeated callbacks.
  • Missing or mismatched payment details.
  • Payments that remain pending.

Check that your database records the correct state for each scenario.

When you're ready for production, confirm that you have the required live access and credentials, the correct shortcode and passkey, a publicly accessible HTTPS callback URL, and appropriate monitoring and error handling.

Do not switch to live payments simply by changing the base URL.

Common Mistakes to Avoid

Exposing credentials: Keep secrets out of frontend code and public repositories.

Treating an accepted request as a completed payment: Wait for the transaction outcome and verify it.

Ignoring callback failures: An inaccessible endpoint can leave payment records pending.

Processing payments more than once: Use unique identifiers and idempotent processing.

Skipping amount verification: Compare the confirmed amount against the amount expected for the order.

Testing only successful payments: Test cancellations, failures, timeouts, and repeated callbacks too.

Conclusion

Integrating M-Pesa into Laravel can make online payments more convenient for customers in Kenya. Daraja provides the payment APIs, while Laravel helps manage requests, configuration, database records, and business logic.

A reliable integration involves more than sending an STK Push. You must also record payment attempts, handle asynchronous callbacks, verify transaction outcomes, and prevent duplicate processing.

Start in the sandbox, follow the current official documentation, and test the complete workflow before accepting real payments.

Explore my software development work

Visit EstherKathini.com to explore my portfolio and learn more about my web and application development projects.


This article is an introductory guide, not a complete production-ready implementation. Confirm current API requirements, account permissions, and security recommendations in the official documentation before deployment.

Official resources