I am using the MPESA-Express also called the STK push api V1 to receive payments from my clients. To get the customer paying, I am looking for the PhoneNumber value in the results body of the response if the payment is successful. This way I can associate a payment with a customer. However now that we'll be having data minimisation on the MPesa api, the PhoneNumber will not be displayed fully, and I am facing a challenge of how to associate a payment transaction with a client. I have tried setting the AcccountReference in the request as shown below, but I can't get this AccountReference back in the response results body. I was thinking of setting a unique AcccountReference for each customer.
The data I am sending to the endpoint https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest
$postData = json_encode([
"BusinessShortCode" => Yii::$app->params['businessShortCode'],
"Password" => $this->createMpesaRequestsPassword($timestamp),
"Timestamp" => $timestamp,
"TransactionType" => $transactionType,
"Amount" => $amount,
"PartyA" => $phoneNumber,
"PartyB" => Yii::$app->params['businessShortCode'],
"PhoneNumber" => $phoneNumber,
"CallBackURL" => $callBackUrl,
"AccountReference" => $phoneNumber,
"TransactionDesc" => $transactionDesc
]);
On my callback url I get this response:
{
"Body": {
"stkCallback": {
"MerchantRequestID": "9183-42212949-1",
"CheckoutRequestID": "ws_CO_23072022133552132714385056",
"ResultCode": 0,
"ResultDesc": "The service request is processed successfully.",
"CallbackMetadata": {
"Item": [
{
"Name": "Amount",
"Value": 1
},
{
"Name": "MpesaReceiptNumber",
"Value": "QGN2XSH6MQ"
},
{
"Name": "Balance"
},
{
"Name": "TransactionDate",
"Value": 20220723133617
},
{
"Name": "PhoneNumber",
"Value": 254711111111
}
]
}
}
}
}
How do know which transaction belongs to which user?
This is too late to respond but maybe for who ever may be looking for a similar answer.
1st, you initiate the STK Push: This can be done using a submit button.
<?php
if(isset($_POST['mpesastk'])){
$app_id = mysqli_real_escape_string($conx, $_POST['app_id']);// Value to be updated in a different table during the mpesa callback url process.
$amount = '1'; //Amount to be paid
$phone = mysqli_real_escape_string($conx, $_POST['pay_phone']); //Phone Number
$config = array(
"env" => "sandbox",
"BusinessShortCode"=> "174379",
"key" => "", //Enter your consumer key here
"secret" => "", //Enter your consumer secret here
"username" => "apitest",
"TransactionType" => "CustomerPayBillOnline",
"passkey" => "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919", //Enter your passkey here
"CallBackURL" => "", //Must have SSL When using localhost, Use Ngrok to forward the response to your Localhost
"AccountReference" => "Name to appear.",
"TransactionDesc" => "Payment of X Fee for ",
);
$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";
//$access_token = "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 = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest";
$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"){
$MerchantRequestID = $result['MerchantRequestID'];
$CheckoutRequestID = $result['CheckoutRequestID'];
$sql = "INSERT INTO mpesastk(mpesastk_appid,mpesastk_phone,mpesastk_amount,CheckoutRequestID,MerchantRequestID)
VALUES('$app_id','$phone','$amount','$CheckoutRequestID','$MerchantRequestID')";
if ($conx->query($sql) === TRUE){
//Response to user
$err_color = "success";
$err_title = "SUCCESS!";
$err_message = '<h4><font color="#fff">Payment of X fee was sent to your phone.</font></h4>';
header("refresh:15;");
}else{
$errors['database'] = "Unable to initiate your order: ".$conx->error;;
foreach($errors as $error) {
$err_message .= $error . '<br />';
}
}
}else{
$err_color = "error";
$err_title = "ERROR!";
$err_message = '<h4><font color="#fff">Failed to send Payment Request of X fee to your phone.</font></h4>';
header("refresh:3;");
}
}
?>
Note that
$CheckoutRequestID = $result['CheckoutRequestID'];
$app_id = mysqli_real_escape_string($conx, $_POST['app_id']);
was inserted. This value will be used by CallBack URL to update the database accordingly.
Now the CallBack URL
<?php
echo '<a href="../../">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'],
" MpesaReceiptNumber: ".$res['Body']['stkCallback']['CallbackMetadata']['Item'][1]['Value'],
);
$data = implode(" - ", $dataToLog);
$data .= PHP_EOL;
file_put_contents('mpesastk_log', $data, FILE_APPEND); //Create a txt file and log the results to our log file
//Saves the result to the database
//Change the values accordingly to your system setup
$conn=new PDO("mysql:host=localhost;dbname=dbname","root","password");
$conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$stmt = $conn->query("SELECT * FROM mpesastk ORDER BY mpesastk_id DESC LIMIT 1");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($rows as $row){
$mpesastk_id = $row['mpesastk_id'];
$app_id = $row['mpesastk_appid'];//remember this, it will be
$ResultCode = $res['Body']['stkCallback']['ResultCode'];
$ResultDesc = $res['Body']['stkCallback']['ResultDesc'];
$MpesaReceiptNumber = $res['Body']['stkCallback']['CallbackMetadata']['Item'][1]['Value'];
if($res['Body']['stkCallback']['ResultCode'] == '1032'){//if transaction canceled
$sql = $conn->query("UPDATE mpesastk SET mpesastk_status = '0',ResultCode = '$ResultCode',
ResultDesc='$ResultDesc',MpesaReceiptNumber='$MpesaReceiptNumber' WHERE mpesastk_id = $mpesastk_id");
$rs = $sql->execute();
}else{//if transaction was paid
$sql = $conn->query("UPDATE mpesastk SET mpesastk_status = '1',ResultCode = '$ResultCode',
ResultDesc='$ResultDesc',MpesaReceiptNumber='$MpesaReceiptNumber' WHERE mpesastk_id = $mpesastk_id");
$rs = $sql->execute();
//Now update a different table in the database
// Not the $app_id as set in the submit :)
$asql = $conn->query("UPDATE tblX SET tblX_status = '3' WHERE tblX_id = $app_id");
$ars = $asql->execute();
}
if($rs){
file_put_contents('error_log', "Records Inserted", FILE_APPEND);;
}else{
file_put_contents('error_log', "Failed to insert Records", FILE_APPEND);
}
}
?>
Happy Coding