atendwa / mpesa-artisan
A Laravel package designed to seamlessly integrate M-Pesa daraja APIs in your laravel project.
Fund package maintenance!
atendwa
Requires
- php: ^8.2
- ext-openssl: *
Requires (Dev)
- larastan/larastan: ^2.0
- laravel/pint: ^1.16
- nunomaduro/collision: ^8.1.1||^7.10.0
- nunomaduro/phpinsights: ^2.11
- orchestra/testbench: ^9.0.0||^8.22.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-arch: ^2.7
- pestphp/pest-plugin-laravel: ^2.3
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
- spatie/laravel-ray: ^1.35
README
This Laravel package seamlessly integrates M-Pesa payment services, allowing you to effortlessly handle M-Pesa transactions within your Laravel applications. It's the perfect solution for businesses and individuals seeking a reliable mobile money integration.
Shipped with a little magic 🥳🤯
This package has a lot to offer, but I think you'll love these features the most:
-
Secure Callbacks with IP Whitelisting: Our
AllowWhitelistedMpesaIps
middleware adds an extra layer of security by verifying that the IP address sending the callback is whitelisted and valid. -
I've implemented
12/13
of M-Pesa's APIs for your convenience. The 13th API is on its way! Stay tuned for even more functionality. 🎉🚀 -
Yes, you guessed it
drivers!
With this package, you can set up multiple drivers to handle different M-Pesa credentials for your app. Just add them to the mpesa.php config file, and you're good to go! 🚗💨stk()->driver('web-store')->amount(5); //... chain more methods stk()->driver('donation')->amount(80); //... chain more methods
-
Our package includes useful helper classes and functions like a phone number sanitizer, a callback request class and more. Plus, most classes have corresponding global helper functions for added convenience! 🛠️🌟
$sanitise = new SanitisePhoneNumber(); $number = SanitisePhoneNumber::index('0712345678'); // returns 254712345678 //or $number = sanitise_phone_number('0712345678');
-
And finally, why not try the
Route::callback()
macro? It lets you set up callback routes in a snap, right in your route file. It handlesIP whitelisting
andremoves CSRF token verification
, making integration a breeze! 🚀✨Route::callback('/some-url', [SomeClass, 'do-something'])->name('name');
Installation via Composer
-
Run the following command in your terminal to install the package:
composer require atendwa/mpesa-artisan
-
Publish the Configuration File
After installing the package, publish the configuration file using the following Artisan command:
php artisan mpesa-artisan:install
Published Configuration File
The configuration file
config/mpesa.php
should look like this:<?php return [ // This determines if the application is in 'sandbox' or 'live' mode 'environment' => env('MPESA_ENVIRONMENT', 'sandbox'), // These are the base URLs for sandbox and live environments 'urls' => [ 'sandbox' => env('MPESA_SANDBOX_URL', 'https://sandbox.safaricom.co.ke'), 'live' => env('MPESA_LIVE_URL', 'https://api.safaricom.co.ke'), ], // These are the various API endpoints used in the application 'endpoints' => [ 'access_token' => env('MPESA_ACCESS_TOKEN', 'oauth/v1/generate?grant_type=client_credentials'), 'account_balance' => env('MPESA_ACCOUNT_BALANCE_ENDPOINT', 'mpesa/accountbalance/v1/query'), 'transaction_status' => env('MPESA_TRANSACTION_STATUS', 'mpesa/transactionstatus/v1/query'), 'c2b_register_url' => env('MPESA_C2B_REGISTER_URL_ENDPOINT', 'mpesa/c2b/v2/registerurl'), 'business_buy_goods' => env('MPESA_BUSINESS_BUY_GOODS', 'mpesa/b2b/v1/paymentrequest'), 'b2b_express_checkout' => env('MPESA_B2B_EXPRESS_ENDPOINT', 'v1/ussdpush/get-msisdn'), 'business_pay_bill' => env('MPESA_BUSINESS_PAY_BILL', 'mpesa/b2b/v1/paymentrequest'), 'mpesa_express' => env('MPESA_EXPRESS_ENDPOINT', 'mpesa/stkpush/v1/processrequest'), 'stk_query' => env('MPESA_EXPRESS_QUERY_ENDPOINT', 'mpesa/stkpushquery/v1/query'), 'dynamic_qr' => env('MPESA_DYNAMIC_QR_ENDPOINT', 'mpesa/qrcode/v1/generate'), 'tax_remittance' => env('MPESA_TAX_REMITTANCE', 'mpesa/b2b/v1/remittax'), 'reversal' => env('MPESA_REVERSAL_ENDPOINT', 'mpesa/reversal/v1/request'), 'b2c' => env('MPESA_B2C_ENDPOINT', 'mpesa/b2c/v1/paymentrequest'), ], // Contains settings for different MPESA drivers 'drivers' => [ 'default' => [ 'initiator_name' => env('MPESA_INITIATOR_NAME', ''), 'passkey' => env('MPESA_PASSKEY', ''), 'security_credential' => env('MPESA_SECURITY_CREDENTIAL', ''), 'shortcodes' => [ 'business' => env('MPESA_BUSINESS_SHORTCODE', ''), 'payment' => env('MPESA_PAYMENT_SHORTCODE', ''), ], 'key_pairs' => [ 'consumer' => [ 'key' => env('MPESA_CONSUMER_KEY', ''), 'secret' => env('MPESA_CONSUMER_SECRET', ''), ], 'payment' => [ 'key' => env('MPESA_PAYMENT_CONSUMER_KEY', ''), 'secret' => env('MPESA_PAYMENT_CONSUMER_SECRET', ''), ], ], 'callbacks' => [ 'default' => env('MPESA_DEFAULT_CALLBACK', ''), 'default_timeout_url' => env('MPESA_DEFAULT_TIMEOUT_URL', ''), ], ], ], // Additional configuration settings 'configuration' => [ 'default_driver' => env('MPESA_DRIVER', 'default'), 'sanitise_phone_number' => env('SANITISE_PHONE_NUMBER', true), 'use_whitelist' => env('USE_IP_WHITELIST', true), ], // Cache settings for MPESA responses 'cache' => [ 'enabled' => env('MPESA_CACHE_ENABLED', true), 'prefix' => env('MPESA_CACHE_PREFIX', 'mpesa_artisan_cache'), ], // List of IPs allowed to access the application 'whitelisted_ips' => [ '196.201.214.200', '196.201.214.206', '196.201.213.114', '196.201.214.207', '196.201.214.208', '196. 201.213.44', '196.201.212.127', '196.201.212.138', '196.201.212.129', '196.201.212.136', '196.201.212.74', '196.201.212.69', ], ];
-
Configure your .env File
Update your
.env
file with the necessary M-Pesa credentials:MPESA_ENVIRONMENT="live" MPESA_INITIATOR_NAME="" MPESA_PASSKEY="" MPESA_SECURITY_CREDENTIAL="" MPESA_BUSINESS_SHORTCODE="" MPESA_PAYMENT_SHORTCODE="" MPESA_CONSUMER_KEY="" MPESA_CONSUMER_SECRET="" MPESA_PAYMENT_CONSUMER_KEY="" MPESA_PAYMENT_CONSUMER_SECRET="" MPESA_DEFAULT_CALLBACK="" MPESA_DEFAULT_TIMEOUT_URL=""
Usage
Before diving into the array of available API endpoints, let's cover how you can easily call any of the service classes.:
// instantiate the class $stk = new STK(); $response = $stk->amount(34)->post(); // use the global helper stk()->amount(34)->post(); // use the Daraja Facade \Atendwa\MpesaArtisan\Facades\Daraja::stk()->amount(56)->post(); // use the daraja global helper daraja()->stk()->buyGoods()->amount(78)->post();
Here are a few pointers to keep in mind:
-
All API services provide a
driver('default')
method to change thedriver
dynamically. However, your custom driver must match the default driver's configuration keys. Exceptions will be thrown if the driver is misconfigured (e.g., theinitiator_name
key must always be present). -
All API services also offer a
useConsumerKeyPair(true)
method to toggle between key pairs. By default,consumer key pairs
are used. Passfalse
to use payment keys, ensuring they match the provided shortcode. Incorrect pairs can lock your security credentials. -
All API services return instances of
Illuminate\Support\Collection
with keys in camel case. For example,ResponseCode
in the response body will be transformed toresponseCode
. -
The
amount()
method accepts onlyintegers
, while other methods acceptstrings
. -
Type safety is enforced in the package. Passing an invalid type to a method will throw an exception or use a fallback value. Empty strings may be used if an invalid string type is encountered.
-
Different API services assume default values for the driver, key pairs, remarks, occasion, shortcode, and more, so you don't need to call every single method.
$response = stk_query()->checkoutRequestID($id)->post();
-
Each API service specifies its required attributes for the payload. If any required attribute is blank, an exception will be thrown.
-
To use a different
API version
, simply override theendpoint URL
for the desired endpoint in your.env
file. -
Phone numbers should be in the format
254#########
. While the package doesn't validate the values you pass, you can use theAtendwa\MpesaArtisan\Support\SanitisePhoneNumber
class or its global helper function to help sanitize and transform phone numbers to the correct format.$number = sanitise_phone_number('0712345678'); // returns 25471234578 $number = sanitise_phone_number('+254712345678'); // returns 25471234578 $number = sanitise_phone_number('254712345678'); // returns 25471234578
1. Dynamic QR
Use this API to generate a Dynamic QR code, enabling Safaricom M-PESA customers with the My Safaricom App or M-PESA app to scan the QR code. They can then capture the till number and amount and authorize payment for goods and services at select LIPA NA M-PESA (LNM) merchant outlets.
https://developer.safaricom.co.ke/APIs/DynamicQRCode
$response = dynamic_qr() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->amount(100) // Required: sets the 'Amount' attribute ->buyGoods() // Optional: defaults the TrxCode attribute to 'BG' // ->paybill() sets TrxCode to 'PB' // ->sendMoney() sets TrxCode to 'SM' // ->sendToBusiness() sets TrxCode to 'SB' // ->withdrawFromAgent() sets TrxCode to 'WA' ->size(400) // Optional: default is 300 ->merchantName('atendwa') // Required: sets the 'MerchantName' attribute ->reference('buy me a coffee') // Required: sets the 'RefNo' attribute ->creditPartyIdentifier('123456') // Required: sets the 'CreditPartyIdentifier (CPI)' attribute ->post();
2. M-Pesa Express Simulate (STK Push)
The Lipa na M-PESA online API, also known as M-PESA Express (STK Push/NI push), facilitates Merchant/Business-initiated C2B (Customer to Business) payments.
https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate
$response = stk() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->partyB('1234567') // Optional: Specify a shortcode, defaults to the business shortcode ->buyGoods() // Optional: Choose between 'buyGoods' or 'payBill', defaults to 'buyGoods' // ->payBill() // Optional: Choose between 'buyGoods' or 'payBill', defaults to 'buyGoods' ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->phoneNumber('254712345678') // Required: Specify the customer's phone number ->amount(1) // Required: Specify the amount to be transacted ->reference('test') // Required: Specify an account reference ->description('test') // Required: Specify a transaction description ->post();
3. M-Pesa Express Query (STK Query)
Use this API to check the status of a Lipa Na M-Pesa Online Payment.
https://developer.safaricom.co.ke/APIs/MpesaExpressQuery
$response = stk_query() ->driver('default') // Optional: defaults to 'default' ->useConsumerKeyPair(true) // Optional: defaults to true ->shortCode('1234567') // Optional: the package will attempt to use the business shortcode if not set ->checkoutRequestID($someID) // Required: The CheckoutRequestID from the STK push response ->post();
4. Customer To Business Register URL
The Register URL API complements the Customer to Business (C2B) APIs by enabling the receipt of payment notifications to your paybill. This API allows you to register callback URLs through which you will receive notifications for payments made to your pay bill/till number.
https://developer.safaricom.co.ke/APIs/CustomerToBusinessRegisterURL
$response = register_c2b_urls() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->shortCode('123456') // Required: Specify the short code ->completed() // Optional: the ResponseType is 'Completed' by default // ->cancelled() // Optional: sets the ResponseType attribute to 'Cancelled' ->confirmationUrl('https://tendwa.dev/package-sale/confirm') // Required: Specify the confirmation URL ->validationUrl('https://tendwa.dev/package-sale/validate') // Required: Specify the validation URL ->post();
5. Business To Customer (B2C)
The B2C API allows businesses to make payments directly to customers, such as salary payments, cashback, promotional payments, winnings, withdrawals, and loan disbursements.
https://developer.safaricom.co.ke/APIs/BusinessToCustomer
$response = b2c() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(false) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->timeoutUrl('https://tendwa.dev') // Required: Specify the timeout URL ->partyA('123456') ->partyB('254712345678') ->initiator('123456') // Optional: defaults to the initiator name in the config file ->amount(100) // Required: Specify the amount. Must be > 50 for safaricom to process the request ->remarks('test') // Required: Specify the transaction remarks ->occasion('test') // Required: Specify the transaction occasion ->post()
6. Transaction Status
Check the status of a transaction.
https://developer.safaricom.co.ke/APIs/TransactionStatus
$response = transaction_status() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(false) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->initiator('initiator') // Optional: defaults to the initiator name in the config file ->partyA('1234567') ->transactionID('NEF61H8J60') // Optional: use this if you have a transaction ID // ->originatorConversationID('AG_20190826_0000777ab7d848b9e721') // Optional: use this if you don't have a transaction ID ->timeoutUrl('https://tendwa.dev') // Required: Specify the timeout URL ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->remarks('Transaction status request.') // Optional: defaults to 'Transaction status request.' ->occasion('Transaction status request.') // Optional: defaults to 'Transaction status request.' ->post();
7. Account Balance
The Account Balance API is used to request the balance of a short code, applicable to B2C, buy goods, and pay bill accounts.
https://developer.safaricom.co.ke/APIs/AccountBalance
$response = account_balance() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->partyA('123456') ->remarks('Get account balance.') // Optional: defaults to 'Get account balance.' ->timeoutUrl('https://tendwa.dev') // Required: Specify the timeout URL ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->post();
8. Reversals
https://developer.safaricom.co.ke/APIs/Reversal
$response = reversal() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->occasion('Transaction reversal request.') // Required: Specify the occasion transaction or defaults to 'Transaction reversal request.'. ->remarks('Transaction reversal request.') // Required: Specify the occasion transaction or defaults to 'Transaction reversal request.'. ->timeoutUrl('https://tendwa.dev') // Required: Specify the timeout URL ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->initiator('initiator') // Optional: defaults to the initiator name in the config file ->receiverParty('1234567') ->amount(100) // Required: Specify the amount ->transactionID('NEF61H8J60') // Optional: use this if you have a transaction ID ->post();
9. Tax Remittance
https://developer.safaricom.co.ke/APIs/TaxRemittance
$response = tax_remittance() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->initiator('initiator') // Optional: defaults to the initiator name in the config file ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->timeoutUrl('https://tendwa.dev') // Required: Specify the timeout URL ->partyA('1234567') // Required: Specify the shortcode ->partyB('572572') // Required: Specify the shortcode ->amount(100) // Required: Specify the amount ->remarks('Tax Remittance') // Optional: defaults to 'Tax Remittance' ->reference('Tax Remittance') // Optional: defaults to 'Tax Remittance' ->post();
10. Business Pay Bill
https://developer.safaricom.co.ke/APIs/BusinessPayBill
$response = business_pay_bill() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->initiator('initiator') // Optional: defaults to the initiator name in the config file ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->timeoutUrl('https://tendwa.dev') // Required: Specify the timeout URL ->partyA('1234567') // Required: Specify the shortcode ->partyB('572572') // Required: Specify the shortcode ->amount(100) // Required: Specify the amount ->remarks('Business Payment') // Optional: defaults to 'Business Payment' ->reference('Business Payment') // Optional: defaults to 'Business Payment' ->post();
11. Business Buy Goods
https://developer.safaricom.co.ke/APIs/BusinessBuyGoods
$response = business_buy_goods() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->initiator('initiator') // Optional: defaults to the initiator name in the config file ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->timeoutUrl('https://tendwa.dev') // Required: Specify the timeout URL ->partyA('1234567') // Required: Specify the shortcode ->partyB('572572') // Required: Specify the shortcode ->amount(100) // Required: Specify the amount ->remarks('Business Payment') // Optional: defaults to 'Business Payment' ->reference('Business Payment') // Optional: defaults to 'Business Payment' ->post();
12. B2B Express CheckOut
https://developer.safaricom.co.ke/APIs/B2BExpressCheckout
$response = b2b_express_checkout() ->driver('default') // Optional: Specify a custom driver ->useConsumerKeyPair(true) // Optional: Toggle key pairs, defaults to true for consumer key and secret ->primaryShortCode('1234567') // Required: Specify the primary short code ->receiverShortCode('572572') // Required: Specify the receiver short code ->amount(100) // Required: Specify the amount ->reference('Business Payment') // Required: Specify the reference ->callbackUrl('https://tendwa.dev') // Required: Specify the callback URL ->requestRefID($someUniqueID) // Required ->partnerName($yourStoreName) // Required ->post();
Helpers
Check out some of the handy helper classes included in the package:
Mpesa Callback Request (MpesaCallbackRequest::class)
This class extends Laravel's Http Request class with additional features for parsing M-Pesa callback results and checking transaction success status.
-
All methods from the
Illuminate\Http\Request
class are available. -
The
fetchResponse()
method automatically parses the response body, allowing you to directly access the result code without needing to navigate through nested arrays for different callback types (e.g., STK, B2C).
use Atendwa\MpesaArtisan\Http\Requests\MpesaCallbackRequest; public function handleStkCallback(MpesaCallbackRequest $request) { $response = $request->fetchResponse(); // returns the response body as a collection $response->get('resultCode'); // returns the result code from the response body // for stk push $response->get('merchantRequestID'); // for b2c $response->get('TransactionID'); // returns the result code from the response body $request->log(); // logs the response to your logging system with the info level $success = $request->successful(); // returns a boolean indicating if the transaction was successful by checking the result code }
Sanitise Phone Number (SanitisePhoneNumber::class)
This class sanitizes phone numbers, transforming them to the desired format. It has a single method, index
, which accepts a phone number and returns the sanitized version.
$number = sanitise_phone_number('0712345678'); // returns 254712345678
Parse Response (ParseResponse::class)
This class parses API responses from Safaricom. It has a single method, index
, which accepts a response
and returns a parsed version. The parsed response will have camel case keys, successfulResponse
and successfulResult
indicators for success, and a hasErrors
key for errors. This class is used internally to ensure consistent response parsing.
$response = parse_response(stk()->post()); $response->get('successfulResponse'); // returns true if the response was successful $response->get('responseCode');