phpmpesa

How do I asscociate a payment transaction with a user while consuming the MPESA Express (STKPUSH) api v1


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?


Solution

  • 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