Netflix Scholarship in Kenya 2022: Call For Applications!
April 1, 20225 Web design tips 2022 – A Guide to web design for law firms
June 15, 2022In this article, we are going to take you through a step-by-step procedure for integrating mpesa stk push API to your PHP Website. Mpesa is one of the most convenient ways people make transactions in Kenya. MPESA has come a long way from just sending and receiving the money to making payments to businesses. With the rapid transformation in the e-commerce industry, combined with the COVID-19 pandemic affecting businesses across the world, businesses have moved online. You can now integrate MPESA into your online business for faster transactions with your customers. In this article, we are going to learn how to do the MPESA STK Push API using PHP.
Types of Mpesa Integration Services
- Business-to-Customer Integration (B2C) – This enables businesses to pay customers directly.
- Business-to-Business Integration (B2B) – This allows businesses to send money to another business directly from their paybill.
- Customer-to-Business Integration (C2B) – This allows businesses to receive payments from their customers directly.
- Lipa an MPESA Online – This allows initiating an M-Pesa transaction on behalf of a customer using STK Push
MPESA STK Integration on Daraja
Firstly, What is STK push? STK Push is a prefilled pop-up notification on a customer’s MPESA Menu to confirm the initiated transaction by the merchant by inputting their PIN.
In MPESA STK API integration, the M-Pesa registered customer gets a pop-up notification to confirm the initiated transaction by the merchant. The transaction is then processed after the customer keys in their MPESA PIN and confirms the transaction. Both the customer and merchant get transaction confirmation messages. Learn more about STK push M-PESA API.
Now that we know the types of MPESA Integration services, the next step is to make sure that we have an account with Safaricom Developers Account. To create an account visit the Daraja Safaricom website. If you have an account with Safaricom Daraja you can log in else sign up.
The next step is to create a new sandbox app by clicking on the Add a New App button and giving it a name. Ensure you select both Lipa na Mpesa Sandbox and Mpesa Sandbox and hit Create App button. You will get the following success message. Awesome! Now that we have our app created successfully. Click on your newly created app.
The Consumer Key and Consumer Secret are personal and therefore should be secured and not shared. Learn more here
How to Integrate Lipa na Mpesa STK Push in PHP
Firstly, let’s set up your MPESA API Folder: mpesa.
checkout.php – This is a sample checkout page for testing the API.
express-stk.php – This is the code used to initiate the stk push.
confirm-payment.php – This is the page where the user confirms their transaction
callback.php – This has the code that receives the result from Safaricom.
status.php – This has the API code that checks if the transaction was a success
transaction_log – This file will help us log the result.
I. Checkout Page
Your checkout page (checkout.php) should be something like this:
<?php include('express-stk.php'); ?> <!DOCTYPE html> <html> <head> <style> @import url(https://fonts.googleapis.com/css?family=Lato:400,100,300,700,900); @import url(https://fonts.googleapis.com/css?family=Source+Code+Pro:400,200,300,500,600,700,900); .container { display: flex; align-items: center; justify-content: center; height: 100vh; flex-direction: column; } * { box-sizing: border-box; } html { background-color: #171A3D; font-family: 'Lato', sans-serif; } .price h1 { font-weight: 300; color: #18C2C0; letter-spacing: 2px; text-align:center; } .card { margin-top: 30px; margin-bottom: 30px; width: 520px; } .card .row { width: 100%; padding: 1rem 0; border-bottom: 1.2px solid #292C58; } .card .row.number{ background-color: #242852; } .cardholder .info, .number .info { position: relative; margin-left: 40px; } .cardholder .info label, .number .info label { display: inline-block; letter-spacing: 0.5px; color: #8F92C3; width: 40%; } .cardholder .info input, .number .info input { display: inline-block; width: 55%; background-color: transparent; font-family: 'Source Code Pro'; border: none; outline: none; margin-left: 1%; color: white; } .cardholder .info input::placeholder, .number .info input::placeholder { font-family: 'Source Code Pro'; color: #444880; } #cardnumber, #cardnumber::placeholder { letter-spacing: 2px; font-size:16px; } .button button { font-size: 1.2rem; font-weight: 400; letter-spacing: 1px; width: 520px; background-color: #18C2C0; border: none; color: #fff; padding: 18px; border-radius: 5px; outline: none; cursor:pointer; transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .button button:hover { background-color: #15aeac; } .button button:active { background-color: #139b99; } .button button i { font-size: 1.2rem; margin-right: 5px; } </style> </head> <body> <div class="container"> <form action='<?php echo $_SERVER['PHP_SELF'] ?>' method='POST'> <div class="price"> <h1>Awesome, that's KES 100</h1> <!-- For testing purposes, we have added the amount manually. This should proceed from your website --> </div> <div class="card__container"> <div class="card"> <div class="row"> <img src="mpesa.png" style="width:30%;margin: 0 35%;"> <p style="color:#8F92C3;line-height:1.7;">1. Enter the <b>phone number</b> and press "<b>Confirm and Pay</b>"</br>2. You will receive a popup on your phone. Enter your <b>MPESA PIN</b></p> <?php if ($errmsg != ''): ?> <p style=" background: #cc2a24;padding: .8rem;color: #ffffff;"><?php echo $errmsg; ?></p> <?php endif; ?> </div> <div class="row number"> <div class="info"> <input type="hidden" name="orderNo" value="#O2JDI2I3R" /> <!-- For testing purposes, we have added the value. This should proceed from your website --> <label for="cardnumber">Phone number</label> <input id="cardnumber" type="text" name="phone_number" maxlength="10" placeholder="0700000000"/> </div> </div> </div> </div> <div class="button"> <button type="submit"><i class="ion-locked"></i> Confirm and Pay</button> </div> </form> <p style="color:#8F92C3;line-height:1.7;margin-top:5rem;">Copyright 2022 | All Rights Reserved | Made by MediaForce</p> </div> </body> </html>
II. Express STK API file
When the customer enters the phone number and presses SEND, the POST request will be handled by an external PHP file in the API folder: express-stk.php.
<?php session_start(); $errors = array(); $errmsg = ''; $config = array( "env" => "sandbox", "BusinessShortCode"=> "174379", "key" => "", //Enter your consumer key here "secret" => "", //Enter your consumer secret here "username" => "apitest", "TransactionType" => "CustomerPayBillOnline", "passkey" => "", //Enter your passkey here "CallBackURL" => "https://f899-41-90-64-220.ngrok.io/mpesa/callback.php", //When using localhost, Use Ngrok to forward the response to your Localhost "AccountReference" => "CompanyXLTD", "TransactionDesc" => "Payment of X" , ); if (isset($_POST['phone_number'])) { $phone = $_POST['phone_number']; $orderNo = $_POST['orderNo']; $amount = 1; $phone = (substr($phone, 0, 1) == "+") ? str_replace("+", "", $phone) : $phone; $phone = (substr($phone, 0, 1) == "0") ? preg_replace("/^0/", "254", $phone) : $phone; $phone = (substr($phone, 0, 1) == "7") ? "254{$phone}" : $phone; $access_token = ($config['env'] == "live") ? "https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials" : "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"; $credentials = base64_encode($config['key'] . ':' . $config['secret']); $ch = curl_init($access_token); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Basic " . $credentials]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response); $token = isset($result->{'access_token'}) ? $result->{'access_token'} : "N/A"; $timestamp = date("YmdHis"); $password = base64_encode($config['BusinessShortCode'] . "" . $config['passkey'] ."". $timestamp); $curl_post_data = array( "BusinessShortCode" => $config['BusinessShortCode'], "Password" => $password, "Timestamp" => $timestamp, "TransactionType" => $config['TransactionType'], "Amount" => $amount, "PartyA" => $phone, "PartyB" => $config['BusinessShortCode'], "PhoneNumber" => $phone, "CallBackURL" => $config['CallBackURL'], "AccountReference" => $config['AccountReference'], "TransactionDesc" => $config['TransactionDesc'], ); $data_string = json_encode($curl_post_data); $endpoint = ($config['env'] == "live") ? "https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest" : "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"; $ch = curl_init($endpoint ); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer '.$token, 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close($ch); $result = json_decode(json_encode(json_decode($response)), true); if(!preg_match('/^[0-9]{10}+$/', $phone) && array_key_exists('errorMessage', $result)){ $errors['phone'] = $result["errorMessage"]; } if($result['ResponseCode'] === "0"){ //STK Push request successful $MerchantRequestID = $result['MerchantRequestID']; $CheckoutRequestID = $result['CheckoutRequestID']; //Saves your request to a database $conn = mysqli_connect("localhost","root","","mpesa"); $sql = "INSERT INTO `orders`(`ID`, `OrderNo`, `Amount`, `Phone`, `CheckoutRequestID`, `MerchantRequestID`) VALUES ('','".$orderNo."','".$amount."','".$phone."','".$CheckoutRequestID."','".$MerchantRequestID."');"; if ($conn->query($sql) === TRUE){ $_SESSION["MerchantRequestID"] = $MerchantRequestID; $_SESSION["CheckoutRequestID"] = $CheckoutRequestID; $_SESSION["phone"] = $phone; $_SESSION["orderNo"] = $orderNo; header('location: confirm-payment.php'); }else{ $errors['database'] = "Unable to initiate your order: ".$conn->error;; foreach($errors as $error) { $errmsg .= $error . '<br />'; } } }else{ $errors['mpesastk'] = $result['errorMessage']; foreach($errors as $error) { $errmsg .= $error . '<br />'; } } } ?>
At the end of the transaction, the user is redirected to the page confirm-payment.php which confirms if the transaction was successful or not.
III. Callback file
However, note that the response on the transaction status (whether the customer PAID, CANCELED, or if there was an ERROR) is sent by Safaricom API to your callback URL in ou case, callback.php. To implement this, the callback URL is supposed to receive this response from Safaricom. Therefore, you need to set up the code in the callback URL such that it receives the response in form of JSON data and then stores it in your database. It should be something like this:
<?php echo '<a href="index.php">Home<br /></a>'; $content = file_get_contents('php://input'); //Receives the JSON Result from safaricom $res = json_decode($content, true); //Convert the json to an array $dataToLog = array( date("Y-m-d H:i:s"), //Date and time " MerchantRequestID: ".$res['Body']['stkCallback']['MerchantRequestID'], " CheckoutRequestID: ".$res['Body']['stkCallback']['CheckoutRequestID'], " ResultCode: ".$res['Body']['stkCallback']['ResultCode'], " ResultDesc: ".$res['Body']['stkCallback']['ResultDesc'], ); $data = implode(" - ", $dataToLog); $data .= PHP_EOL; file_put_contents('transaction_log', $data, FILE_APPEND); //Logs the results to our log file //Saves the result to the database $conn=new PDO("mysql:host=localhost;dbname=mpesa","root",""); $conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); $stmt = $conn->query("SELECT * FROM orders ORDER BY ID DESC LIMIT 1"); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach($rows as $row){ $ID = $row['ID']; if($res['Body']['stkCallback']['ResultCode'] == '1032'){ $sql = $conn->query("UPDATE `orders` SET `Status` = 'CANCELLED' WHERE `orders`.`ID` = $ID"); $rs = $sql->execute(); }else{ $sql = $conn->query("UPDATE `orders` SET `Status` = 'SUCCESS' WHERE `orders`.`ID` = $ID"); $rs = $sql->execute(); } if($rs){ file_put_contents('error_log', "Records Inserted", FILE_APPEND);; }else{ file_put_contents('error_log', "Failed to insert Records", FILE_APPEND); } } ?>
Your database will have the following columns: MerchantRequestID, CheckoutRequestID, ResultCode (0 means successful processing and any other code means an error occurred or the transaction failed.), ResultDesc, MpesaCode, TransactionDate, and PhoneNumber.
IV. Payment Confirmation page
Back to the user end, after the stk push is initiated, the user is redirected to the confirmation page confirmation-payment.php which should be close to something like this:
When the user clicks “Confirm Payment”, we initiate an MPESA Transaction status query to check the status of the transaction (To see if the user actually PAID, CANCELED or if there was an ERROR). This is handled by: status.php
<?php header("Content-Type:application/json"); /*Call function with these configurations*/ $env="sandbox"; $type = 4; $shortcode = '600988'; $key = ""; //Put your key here $secret = ""; //Put your secret here $initiatorName = "testapi"; $initiatorPassword = "Safaricom978!"; $results_url = "https://mydomain.com/TransactionStatus/result/"; //Endpoint to receive results Body $timeout_url = "https://mydomain.com/TransactionStatus/queue/"; //Endpoint to to go to on timeout /*End configurations*/ /*Ensure transaction code is entered*/ if (!isset($_GET["transactionID"])) { echo "Technical error"; exit(); } /*End transaction code validation*/ $transactionID = $_GET["transactionID"]; //$transactionID = "OEI2AK4Q16"; $command = "TransactionStatusQuery"; $remarks = "Transaction Status Query"; $occasion = "Transaction Status Query"; $callback = null ; $access_token = ($env == "live") ? "https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials" : "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"; $credentials = base64_encode($key . ':' . $secret); $ch = curl_init($access_token); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Basic " . $credentials]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response); //echo $result->{'access_token'}; $token = isset($result->{'access_token'}) ? $result->{'access_token'} : "N/A"; $publicKey = file_get_contents(__DIR__ . "/mpesa_public_cert.cer"); $isvalid = openssl_public_encrypt($initiatorPassword, $encrypted, $publicKey, OPENSSL_PKCS1_PADDING); $password = base64_encode($encrypted); //echo $token; $curl_post_data = array( "Initiator" => $initiatorName, "SecurityCredential" => $password, "CommandID" => $command, "TransactionID" => $transactionID, "PartyA" => $shortcode, "IdentifierType" => $type, "ResultURL" => $results_url, "QueueTimeOutURL" => $timeout_url, "Remarks" => $remarks, "Occasion" => $occasion, ); $data_string = json_encode($curl_post_data); //echo $data_string; $endpoint = ($env == "live") ? "https://api.safaricom.co.ke/mpesa/transactionstatus/v1/query" : "https://sandbox.safaricom.co.ke/mpesa/transactionstatus/v1/query"; $ch2 = curl_init($endpoint); curl_setopt($ch2, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer '.$token, 'Content-Type: application/json' ]); curl_setopt($ch2, CURLOPT_POST, 1); curl_setopt($ch2, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch2); curl_close($ch2); //echo "Authorization: ". $response; $result = json_decode($response); $verified = $result->{'ResponseCode'}; if($verified === "0"){ echo "Transaction verified as TRUE"; }else{ echo "Transaction doesnt exist"; }
See also how to integrate it on Laravel: MPESA API Integration in Laravel
I hope this tutorial has been helpful and please leave a comment if you benefited from it or experience any challenges. Peace✌️
Custom Integration
- PHP Language
- Custom Website
- Safaricom daraja API creation
- Going live
Framework Integration
- PHP Language
- Laravel Website
- Safaricom daraja API creation
- Going live
WordPress Integration
- WordPress Website
- Plugin integration
- Safaricom daraja API creation
- Going live