james-kabz/mpesapkg

Laravel package for integrating Safaricom M-Pesa APIs

Maintainers

Package info

github.com/James-Kabz/mpesapkg

pkg:composer/james-kabz/mpesapkg

Statistics

Installs: 12

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.0.6 2026-02-24 07:12 UTC

This package is auto-updated.

Last update: 2026-02-24 07:12:35 UTC


README

Plug-and-play M-Pesa package with STK, B2C, C2B, and utility APIs. Includes request/response persistence, callback storage, and optional webhook validation.

Quick Start

  1. Publish config (optional):
php artisan vendor:publish --tag=mpesa-config
  1. Run migrations:
php artisan migrate
  1. Place certificates:
storage/app/private/certs/SandboxCertificate.cer
storage/app/private/certs/ProductionCertificate.cer
  1. Configure env (see template below).

Environment Variables (example)

MPESA_ENV=sandbox
MPESA_BASE_URL=https://sandbox.safaricom.co.ke
MPESA_ROUTE_PREFIX=payments
MPESA_STORE_REQUESTS=true
MPESA_STORE_CALLBACKS=true

MPESA_CONSUMER_KEY=...
MPESA_CONSUMER_SECRET=...

# STK
MPESA_STK_SHORT_CODE=174379
MPESA_STK_PASSKEY=...
MPESA_STK_CALLBACK_URL=https://example.ngrok-free.app/payments/stk/callback

# B2C
MPESA_B2C_INITIATOR=testapi
MPESA_B2C_INITIATOR_PASSWORD=
MPESA_B2C_SECURITY_CREDENTIAL=...
MPESA_B2C_SHORT_CODE=600997
MPESA_B2C_COMMAND_ID=BusinessPayment
MPESA_B2C_RESULT_URL=https://example.ngrok-free.app/payments/b2c/result
MPESA_B2C_TIMEOUT_URL=https://example.ngrok-free.app/payments/b2c/timeout

# C2B
MPESA_C2B_SHORT_CODE=600997
MPESA_C2B_RESPONSE_TYPE=Completed
MPESA_C2B_VALIDATION_URL=https://example.ngrok-free.app/payments/c2b/validation
MPESA_C2B_CONFIRMATION_URL=https://example.ngrok-free.app/payments/c2b/confirmation

# Utility callbacks
MPESA_TRANSACTION_STATUS_RESULT_URL=https://example.ngrok-free.app/payments/transaction/status/result
MPESA_TRANSACTION_STATUS_TIMEOUT_URL=https://example.ngrok-free.app/payments/transaction/status/timeout
MPESA_ACCOUNT_BALANCE_RESULT_URL=https://example.ngrok-free.app/payments/account/balance/result
MPESA_ACCOUNT_BALANCE_TIMEOUT_URL=https://example.ngrok-free.app/payments/account/balance/timeout
MPESA_REVERSAL_RESULT_URL=https://example.ngrok-free.app/payments/reversal/result
MPESA_REVERSAL_TIMEOUT_URL=https://example.ngrok-free.app/payments/reversal/timeout

# Webhook validation (optional)
MPESA_WEBHOOK_VALIDATION=false
MPESA_WEBHOOK_HEADER=X-Mpesa-Token
MPESA_WEBHOOK_TOKEN=
MPESA_WEBHOOK_ALLOWED_IPS=

Note: some sandbox environments reject callback URLs containing the word mpesa in the path. If you see Invalid ValidationURL, use a different prefix such as payments.

Routes (under MPESA_ROUTE_PREFIX)

POST /<prefix>/stk/push
POST /<prefix>/stk/query
POST /<prefix>/stk/callback

POST /<prefix>/b2c/send
POST /<prefix>/b2c/validated
POST /<prefix>/b2c/result
POST /<prefix>/b2c/timeout

POST /<prefix>/c2b/register
POST /<prefix>/c2b/simulate
POST /<prefix>/c2b/validation
POST /<prefix>/c2b/confirmation

POST /<prefix>/transaction/status
POST /<prefix>/transaction/status/result
POST /<prefix>/transaction/status/timeout
POST /<prefix>/account/balance
POST /<prefix>/account/balance/result
POST /<prefix>/account/balance/timeout
POST /<prefix>/reversal
POST /<prefix>/reversal/result
POST /<prefix>/reversal/timeout

Response Format

All API responses return:

{
  "ok": true,
  "status": 200,
  "data": {},
  "error": null,
  "body": null
}

Generate Security Credential

php artisan mpesa:security-credential

Using the Config Service

The package exposes a typed config service so you don't need to call config('mpesa...') repeatedly.

use JamesKabz\MpesaPkg\Services\MpesaConfig;

class Example
{
    public function __construct(private MpesaConfig $config) {}

    public function handle(): void
    {
        $prefix = $this->config->routePrefix();
        $stkShortCode = $this->config->stkShortCode();
    }
}

MpesaClient and MpesaHelper are resolved from the container, so you can type-hint them and let Laravel inject:

use JamesKabz\MpesaPkg\MpesaClient;

class PaymentsController
{
    public function __construct(private MpesaClient $mpesa) {}
}

Test Commands (examples)

Set:

BASE=https://example.ngrok-free.app
PREFIX=payments

STK Push

curl -X POST "$BASE/$PREFIX/stk/push" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "254700000000",
    "amount": 1,
    "account_reference": "TEST-001",
    "transaction_desc": "STK Test",
    "callback_url": "'"$BASE"'/'"$PREFIX"'/stk/callback"
  }'

STK Query

curl -X POST "$BASE/$PREFIX/stk/query" \
  -H "Content-Type: application/json" \
  -d '{
    "checkout_request_id": "ws_CO_123456789"
  }'

B2C Send

curl -X POST "$BASE/$PREFIX/b2c/send" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "254700000000",
    "amount": 10,
    "remarks": "B2C Test",
    "occasion": "Test"
  }'

B2C Validated

curl -X POST "$BASE/$PREFIX/b2c/validated" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "254700000000",
    "amount": 10,
    "remarks": "B2C Validate",
    "id_number": "12345678"
  }'

C2B Register URLs

curl -X POST "$BASE/$PREFIX/c2b/register" \
  -H "Content-Type: application/json" \
  -d '{
    "short_code": "600997",
    "confirmation_url": "'"$BASE"'/'"$PREFIX"'/c2b/confirmation",
    "validation_url": "'"$BASE"'/'"$PREFIX"'/c2b/validation",
    "response_type": "Completed"
  }'

C2B Simulate

curl -X POST "$BASE/$PREFIX/c2b/simulate" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "254700000000",
    "amount": 10,
    "short_code": "600997",
    "command_id": "CustomerPayBillOnline",
    "bill_ref_number": "TEST-001"
  }'

C2B Validation (manual test)

curl -X POST "$BASE/$PREFIX/c2b/validation" \
  -H "Content-Type: application/json" \
  -d '{
    "ResultCode": 0,
    "ResultDesc": "Accepted",
    "TransID": "TEST123",
    "TransAmount": "10",
    "MSISDN": "254700000000",
    "BusinessShortCode": "600997",
    "BillRefNumber": "TEST-001"
  }'

C2B Confirmation (manual test)

curl -X POST "$BASE/$PREFIX/c2b/confirmation" \
  -H "Content-Type: application/json" \
  -d '{
    "ResultCode": 0,
    "ResultDesc": "Accepted",
    "TransID": "TEST123",
    "TransAmount": "10",
    "MSISDN": "254700000000",
    "BusinessShortCode": "600997",
    "BillRefNumber": "TEST-001"
  }'

Transaction Status

curl -X POST "$BASE/$PREFIX/transaction/status" \
  -H "Content-Type: application/json" \
  -d '{
    "short_code": "600997",
    "transaction_id": "TEST123",
    "identifier_type": "4",
    "remarks": "Status Check"
  }'

Transaction Status Result (callback test)

curl -X POST "$BASE/$PREFIX/transaction/status/result" \
  -H "Content-Type: application/json" \
  -d '{
    "Result": {
      "ResultCode": 0,
      "ResultDesc": "Accepted",
      "OriginatorConversationID": "TEST123",
      "ConversationID": "TEST456",
      "TransactionID": "TEST789"
    }
  }'

Transaction Status Timeout (callback test)

curl -X POST "$BASE/$PREFIX/transaction/status/timeout" \
  -H "Content-Type: application/json" \
  -d '{
    "Result": {
      "ResultCode": 1,
      "ResultDesc": "Timeout",
      "OriginatorConversationID": "TEST123",
      "ConversationID": "TEST456"
    }
  }'

Account Balance

curl -X POST "$BASE/$PREFIX/account/balance" \
  -H "Content-Type: application/json" \
  -d '{
    "short_code": "600997",
    "identifier_type": "4",
    "remarks": "Balance Check"
  }'

Account Balance Result (callback test)

curl -X POST "$BASE/$PREFIX/account/balance/result" \
  -H "Content-Type: application/json" \
  -d '{
    "Result": {
      "ResultCode": 0,
      "ResultDesc": "Accepted",
      "OriginatorConversationID": "TEST123",
      "ConversationID": "TEST456"
    }
  }'

Account Balance Timeout (callback test)

curl -X POST "$BASE/$PREFIX/account/balance/timeout" \
  -H "Content-Type: application/json" \
  -d '{
    "Result": {
      "ResultCode": 1,
      "ResultDesc": "Timeout",
      "OriginatorConversationID": "TEST123",
      "ConversationID": "TEST456"
    }
  }'

Reversal

curl -X POST "$BASE/$PREFIX/reversal" \
  -H "Content-Type: application/json" \
  -d '{
    "short_code": "600997",
    "transaction_id": "TEST123",
    "amount": 10,
    "remarks": "Reversal Test"
  }'

Reversal Result (callback test)

curl -X POST "$BASE/$PREFIX/reversal/result" \
  -H "Content-Type: application/json" \
  -d '{
    "Result": {
      "ResultCode": 0,
      "ResultDesc": "Accepted",
      "OriginatorConversationID": "TEST123",
      "ConversationID": "TEST456",
      "TransactionID": "TEST789"
    }
  }'

Reversal Timeout (callback test)

curl -X POST "$BASE/$PREFIX/reversal/timeout" \
  -H "Content-Type: application/json" \
  -d '{
    "Result": {
      "ResultCode": 1,
      "ResultDesc": "Timeout",
      "OriginatorConversationID": "TEST123",
      "ConversationID": "TEST456"
    }
  }'

Notes

  • Requests and callbacks are persisted when MPESA_STORE_REQUESTS=true and MPESA_STORE_CALLBACKS=true.
  • Callbacks can be protected using MPESA_WEBHOOK_VALIDATION with token/IP allow-listing.
  • Account balance is for the merchant organization’s short code or BuyGoods till, not an end-customer balance check.