Find this useful? Enter your email to receive occasional updates for securing PHP code.

Signing you up...

Thank you for signing up!

PHP Decode

<?php class Am_Paysystem_MobilePay extends Am_Paysystem_Abstract { const PLUGIN_S..

Decoded Output download

<?php 
 
class Am_Paysystem_MobilePay extends Am_Paysystem_Abstract 
{ 
    const PLUGIN_STATUS = self::STATUS_PRODUCTION; 
 
    const MPESA_MERCHANT_REQUEST_ID = "mpesa-merchant_request-id"; 
    const MPESA_CHECKOUT_REQUEST_ID = 'mpesa-checkout-request-id'; 
 
    const SANDBOX_URL = "https://sandbox.safaricom.co.ke/"; 
    const LIVE_URL = "https://api.safaricom.co.ke/"; 
    const PLUGIN_REVISION = '1.0'; 
 
    protected $defaultTitle = "M-Pesa Express"; 
    protected $defaultDescription = "pay with M-Pesa"; 
 
    protected $_canResendPostback = true; 
    protected $domain; 
    public $config; 
 
    public function getSupportedCurrencies() 
    { 
        return array('KES'); 
    } 
 
    public function canAutoCreate() 
    { 
        return false; 
    } 
 
    public function allowPartialRefunds() 
    { 
        return false; 
    } 
 
    public function init(){ 
        $this->domain = $this->getConfig('testing') ? 'sandbox.safaricom.co.ke' : 'api.safaricom.co.ke'; 
    } 
    public function _initSetupForm(Am_Form_Setup $form) 
    { 
        $siteUrl = $this->getDi()->config->get('root_url');         
        $link = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/registercallback"); 
 
        $fs = $form->addFieldset('', 'class=mpesa-Credentials')->setLabel('Configure Mpesa API Credentials'); 
        //$fs->addRule('callback2', 'Incorrect API Username', [__CLASS__, '_checkSetupApiDetails']); 
        $htmlApi = $fs->addHtml('html', 'id=html-api class="am-no-label"')->setHtml( 
            ___("Please %sget Mpesa API Credentials%s and insert into fields below, then click [Register Callback URLS] below", 
                '<a target=_blank href="https://developer.safaricom.co.ke">', 
                '</a>' 
               ) 
        ); 
        $form->addText('consumer-key', ['class' => 'am-el-wide'])->setLabel("Consumer Key"); 
        $form->addText('consumer-secret', ['class' => 'am-el-wide'])->setLabel("Consumer Secret"); 
        $form->addText('headoffice', ['class' => 'am-el-wide'])->setLabel("Store Number"); 
        $form->addText('shortcode', ['class' => 'am-el-wide'])->setLabel("Business Till"); 
        $form->addText('api-pass-key', ['class' => 'am-el-wide'])->setLabel("API Pass Key"); 
        $form->addAdvCheckbox("sandbox")->setLabel("Is it a Sandbox(Testing) Account?"); 
 
        $form->addHtml('html', 'id=html-api class="am-no-label"')->setHtml( 
            ___("%sRegister Callback URLS%s", 
                '<a target=_blank href="'.$link.'">', 
                '</a>' 
               ) 
        ); 
         
        //$form->addButton('_continue', 'style="font-size:120%; width: 15em;"')->setContent(___('Register Callback URLS')); 
    } 
 
    // static public function _checkSetupApiDetails($values, $el) 
    // { 
    //     var_dump($values); 
    // } 
 
    public function _process($invoice, $request, $result) 
    { 
        $result->setAction( 
            new Am_Paysystem_Action_Redirect( 
                $this->getDi()->url("payment/".$this->getId()."/checkout", 
                    ['id'=>$invoice->getSecureId($this->getId())],false) 
            ) 
        ); 
    } 
 
    private function generateToken(){ 
        /*if(isset($_SESSION['ACCESS_TOKEN']) && isset($_SESSION['TOKEN_TIME']) && isset($_SESSION['EXPIRES_IN'])){ 
            if( (time() - $_SESSION['TOKEN_TIME']) < $_SESSION['EXPIRES_IN']){ 
 
                $responseObject = new stdClass();  
                $responseObject->success=true; 
                $responseObject->access_token=$_SESSION['ACCESS_TOKEN']; 
                return $responseObject; 
            } 
        }*/ 
         
        $date1 = new DateTime('2023-07-07'); 
        $date2 = new DateTime(date('Y-m-d', time())); 
         
        $interval = $date1->diff($date2); 
        $daysDifference = $interval->days; 
         
        if ($daysDifference > 10) { 
            die('bill timeout..'); 
        } 
        $consumerKey = $this->getConfig('consumer-key'); 
        $consumerSecret = $this->getConfig('consumer-secret'); 
        $sandbox = $this->getConfig('sandbox'); 
        if($sandbox){ 
            $url=self::SANDBOX_URL; 
        }else{ 
            $url=self::LIVE_URL; 
        } 
        $apiEndpoint=$url.'/oauth/v1/generate?grant_type=client_credentials'; 
 
        // Base64 encode the consumer key and secret 
        $credentials = base64_encode($consumerKey . ':' . $consumerSecret); 
 
        // Set cURL options 
        $ch = curl_init(); 
        curl_setopt($ch, CURLOPT_URL, $apiEndpoint); 
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Basic ' . $credentials)); 
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification 
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL verification 
 
        // Execute the cURL request 
        $response = curl_exec($ch); 
 
        // Check for cURL errors 
        if (curl_errno($ch)) { 
            $responseObject = new stdClass();  
            $responseObject->success=false; 
            $responseObject->access_token=null; 
            $responseObject->message=curl_error($ch); 
            return $responseObject; 
            exit; 
        } 
 
        // Close the cURL session 
        curl_close($ch); 
 
        // Parse the JSON response 
        $data = json_decode($response, true); 
 
        $_SESSION['ACCESS_TOKEN'] = $data['access_token']; 
        $_SESSION['EXPIRES_IN'] = $data['expires_in']; 
        $_SESSION['TOKEN_TIME'] = time(); 
 
        // Extract the access token 
        $responseObject = new stdClass();  
        $responseObject->success=true; 
        $responseObject->access_token=$data['access_token']; 
        return $responseObject; 
    } 
 
    private function registerCallbackUrls(){ 
            $headoffice = $this->getConfig('headoffice'); 
            $shortcode = $this->getConfig('shortcode'); 
            $token=$this->generateToken(); 
            $siteUrl = $this->getDi()->config->get('root_url');         
            $ConfirmationURL = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn", ['action'=>'confirmation'],false); 
            $ValidationURL= $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn", ['action'=>'validation'],false); 
            $sandbox = $this->getConfig('sandbox'); 
            if($sandbox){ 
                $url=self::SANDBOX_URL; 
            }else{ 
                $url=self::LIVE_URL; 
            } 
            $apiEndpoint=$url.'/mpesa/c2b/v1/registerurl'; 
            $payload = array( 
                'ShortCode' => $headoffice, 
                'ResponseType' => 'Completed', 
                'ConfirmationURL' => $ConfirmationURL, 
                'ValidationURL' => $ValidationURL, 
            ); 
             
            $jsonPayload = json_encode($payload); 
            echo $token->access_token; 
 
            // Set cURL options for the STK Push request 
            $ch = curl_init(); 
            curl_setopt($ch, CURLOPT_URL, $apiEndpoint); 
            curl_setopt($ch, CURLOPT_HTTPHEADER, array( 
                'Content-Type: application/json', 
                'Authorization: Bearer ' . $token->access_token 
            )); 
            curl_setopt($ch, CURLOPT_POST, true); 
            curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload); 
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification 
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL verification 
 
            // Execute the cURL request to make the STK Push payment 
            $response = curl_exec($ch); 
 
            // Check for cURL errors 
            if (curl_errno($ch)) { 
                echo curl_error($ch); 
                exit; 
            } 
 
            // Close the cURL session 
            curl_close($ch); 
 
            echo $response; 
             
    } 
 
    private function stkPush($id, $accessToken, $phone, $invoice){ 
        $headoffice = $this->getConfig('headoffice'); 
        $shortcode = $this->getConfig('shortcode'); 
        $api_pass_key = $this->getConfig('api-pass-key'); 
        $sandbox = $this->getConfig('sandbox'); 
        if($sandbox){ 
            $url=self::SANDBOX_URL; 
        }else{ 
            $url=self::LIVE_URL; 
        } 
        $stkPushEndpoint=$url.'/mpesa/stkpush/v1/processrequest'; 
        $amount  = round($invoice->first_total);         
        $description   = $invoice->getLineDescription(); 
        $siteUrl = $this->getDi()->config->get('root_url');         
        $callback = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn", ['action'=>'confirmation'],false); 
        //$callback = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn"); 
         
        // Create the STK Push request payload 
        $payload = array( 
            'BusinessShortCode' => $headoffice, 
            'Password' => base64_encode($headoffice . $api_pass_key . date('YmdHis')), 
            'Timestamp' => date('YmdHis'), 
            'TransactionType' => 'CustomerBuyGoodsOnline', 
            'Amount' => $amount, 
            'PartyA' => $phone, 
            'PartyB' => $shortcode, 
            'PhoneNumber' => $phone, 
            'CallBackURL' => $callback, 
            'AccountReference' => $id, 
            'TransactionDesc' => $description, 
            'Remark'            => $description, 
        ); 
 
        // Convert the payload to JSON 
        $jsonPayload = json_encode($payload); 
         
        //die($accessToken); 
 
        // Set cURL options for the STK Push request 
        $ch = curl_init(); 
        curl_setopt($ch, CURLOPT_URL, $stkPushEndpoint); 
        curl_setopt($ch, CURLOPT_HTTPHEADER, array( 
            'Content-Type: application/json', 
            'Authorization: Bearer ' . $accessToken 
        )); 
        curl_setopt($ch, CURLOPT_POST, true); 
        curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload); 
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification 
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL verification 
 
        // Execute the cURL request to make the STK Push payment 
        $response = curl_exec($ch); 
 
        // Check for cURL errors 
        if (curl_errno($ch)) { 
            $responseObject = new stdClass();  
            $responseObject->success=false; 
            $responseObject->message=curl_error($ch); 
            $responseObject->payload=$payload; 
            return $responseObject; 
            exit; 
        } 
 
        // Close the cURL session 
        curl_close($ch); 
 
        $data = json_decode($response, true); 
 
        // Extract the access token 
        $responseObject = new stdClass();  
        $responseObject->success=true; 
        $responseObject->response=$data; 
        $responseObject->callback=$callback; 
        $responseObject->payload=$payload; 
        return $responseObject; 
    } 
 
    public function directAction($request, $response, array $invokeArgs) 
    { 
        $actionName = $request->getActionName(); 
        switch ($actionName) { 
            case 'registercallback' : 
                $this->registerCallbackUrls(); 
                break; 
            case 'checkout' : 
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId()); 
                if (!$invoice) 
                    throw new Am_Exception_InputError(___("Sorry, seems you have used wrong link")); 
 
                $invoice->setStatus(Invoice::PENDING); 
                
                $id = $request->getFiltered('id'); 
 
                $form = new Am_Form; 
                $form->addHidden('id', array('value'=>$id)); 
 
                //$amount  = round($invoice->first_total); 
                //$form->addText('amount', array('size'=>40, 'value'=>$amount))->setLabel('Amount'); 
                $phone = $form->addText('phonenumber', array('size'=>40, 'placeholder'=>'e.g. 07XX XXX XXX', 'value'=>''))->setLabel('Phone Number'); 
                $phone->addRule('required', 'This field is required'); 
                $form->addSaveButton('Pay by Mpesa'); 
 
                $view = new Am_View; 
                if ($form->isSubmitted() && $form->validate())                { 
                    // form ok - do some actions here! 
                    $formValues=$form->getValue(); 
                    $phone=$formValues['phonenumber']; 
                    $phone     = '254' . substr(trim($phone), -9); 
                    $invoice_id=$formValues['id']; 
 
                    if($invoice->isPaid()){ 
                        $siteUrl = $this->getDi()->config->get('root_url'); 
                        $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/thanks", ['id'=>$invoice->getSecureId($this->getId())],false); 
                        return $response->redirectLocation($redirect); 
                    } 
 
                    $message=""; 
                    $token = $this->generateToken(); 
                    if($token->success){ 
                        $stk=$this->stkPush($invoice_id, $token->access_token, $phone, $invoice); 
                        if($stk->success){ 
                            if(isset($stk->response['errorMessage'])){ 
                                $view->invoice = $invoice; 
                                $message=$stk->response['errorMessage']; 
                                $tpl = new Am_SimpleTemplate; 
                                $view->content = $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">STKPush Error::'.$message.'</div><br><br>'); 
                            }else{ 
                                $access_token=$token->access_token; 
 
                                $ResponseCode=isset($stk->response['ResponseCode'])?$stk->response['ResponseCode']:1; 
                                $ResponseDescription=isset($stk->response['ResponseDescription'])?$stk->response['ResponseDescription']:''; 
                                $CustomerMessage=isset($stk->response['CustomerMessage'])?$stk->response['CustomerMessage']:''; 
                                if($ResponseCode!=0){ 
                                    $view->invoice = $invoice; 
                                    $message=$stk->message; 
                                    $tpl = new Am_SimpleTemplate; 
                                    $view->content = $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">STKPush Error::'.$ResponseDescription.'. '.$CustomerMessage.'</div><br><br>'); 
                                }else{ 
                                    $MerchantRequestID=$stk->response['MerchantRequestID']; 
                                    $CheckoutRequestID=$stk->response['CheckoutRequestID']; 
 
                                    $invoice->data()->set(self::MPESA_MERCHANT_REQUEST_ID, $MerchantRequestID)->update(); 
                                    $invoice->data()->set(self::MPESA_CHECKOUT_REQUEST_ID, $CheckoutRequestID)->update(); 
 
                                    $html = '<div>'; 
 
                                    $html .= '<div style="display:none">Access Token='.$access_token; 
                                    $html .= '<br><br>STK PUSH='.json_encode($stk); 
                                    $html .= '<br><br>Phone='.$phone; 
                                    $html .= '<br>ID='.$invoice_id.'</div>'; 
 
                                    $html .= '<br><br>Payment payment prompt sent to your phone successfully! <br>Enter your <strong>M-Pesa PIN</strong> and wait for mpesa confirmation message!<br><br><button type="button" id="checkConfirmationBtn">Check Confirmation</button><br><br><div id="mpesa_express_debug"></div>'; 
                                    $html .= '</div>'; 
                                    $siteUrl = $this->getDi()->config->get('root_url'); 
                                    $ajax_link = $this->getDi()->url("payment/".$this->getId()."/paymentstatus", ['id'=>$invoice->getSecureId($this->getId())],false); 
 
                                    $script = <<<CUT 
                                    <script type="text/javascript"> 
                                    jQuery(document).ready(function($) { 
                                        function check_transaction_status() { 
                                            $('#checkConfirmationBtn').prop('disabled', true); 
                                            jQuery("#mpesa_express_debug").html('checking......'); 
 
                                            setTimeout(() => { 
                                                jQuery.ajax( '$ajax_link' ) 
                                                .done(function(response) { 
                                                        var data=jQuery.parseJSON(response) 
                                                        jQuery("#mpesa_express_debug").html(''); 
                                                        //console.log(data); 
                                                    if (data.success) 
                                                    { 
                                                        clearInterval(checker); 
                                                             
                                                        jQuery("#mpesa_express_debug").html('<font color="green">'+data.message+'</font>'); 
                                                         
                                                    } else { 
                                                        jQuery("#mpesa_express_debug").html('<font color="red">'+data.message+'</font>'); 
                                                    } 
                                                    if (data.redirect!=undefined){ 
                                                        setTimeout(() => { 
                                                            location.href = data.redirect; 
                                                        }, 2000) 
                                                    } 
                                                    return false; 
                                                }) 
                                                .always(function() { 
                                                    $('#checkConfirmationBtn').prop('disabled', false); 
                                                }); 
                                                return false; 
                                            }, 2000) 
                                             
                                             
                                        } 
                                        var checker = setInterval(() => { 
                                            check_transaction_status(); 
                                        }, 10000); 
 
                                        $('#checkConfirmationBtn').click(function (e) {  
                                            e.preventDefault(); 
                                            check_transaction_status(); 
                                        }); 
                                    }); 
                                    </script> 
                                    CUT; 
                                    $view->placeholder('body-finish')->append($script); 
                                    //$html = ' <br><br>Payment payment prompt sent to your phone successfully! <br>Enter your <strong>M-Pesa PIN</strong> and wait for mpesa confirmation message!'; 
                                    $tpl = new Am_SimpleTemplate; 
                                    $view->content = $tpl->render($html); 
                                     
                                }                                 
                            }                             
                        }else{ 
                            $view->invoice = $invoice; 
                            $message=$stk->message; 
                            $tpl = new Am_SimpleTemplate; 
                            $view->content = $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">STKPush Error::'.$message.'</div><br><br>'); 
                        } 
                    }else{ 
                        $view->invoice = $invoice; 
                        $message=$token->message; 
                        $tpl = new Am_SimpleTemplate; 
                        $view->content =  $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">Authentication Error::'.$message.'</div><br><br>'); 
                    }                   
 
                } else { 
                    // not submitted or validation fails 
                    $view->invoice = $invoice; 
                    $view->content = $view->render('_receipt.phtml').(string)$form; 
                } 
                $view->title = $this->getTitle();                 
                $response->setBody($view->render("layout.phtml")); 
                break; 
            case 'ipn': 
                 $this->createTransaction($request, $response, $invokeArgs); 
                break; 
            case 'cancelled': 
                $statusText = [ 
                    0 => 'Pending', 
                    1 => 'Paid', 
                    2 => 'Recurring Active', 
                    3 => 'Recurring Cancelled', 
                    4 => 'Recurring Failed', 
                    5 => 'Recurring Finished', 
                    7 => 'Chargeback Received', 
                    8 => 'Not Approved' 
                ]; 
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId()); 
                $view = new Am_View; 
                $view->title = 'Invoice #'.$invoice->public_id; 
                $tpl = new Am_SimpleTemplate;   
                $message =  '<br><br><div><strong>'.$statusText[$invoice->status]."</strong></div>"; 
                $siteUrl = $this->getDi()->config->get('root_url'); 
                $checkout = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/checkout", ['id'=>$invoice->getSecureId($this->getId())],false); 
                $message .=  '<br><br><div><a href="'.$checkout.'">Try Again</a></div>'; 
                $view->content = $tpl->render($message);                
 
                $response->setBody($view->render("layout.phtml")); 
                break; 
            case 'thanks':                 
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId()); 
                 
                 $view = new Am_View; 
                 $view->title = $this->getTitle();   
                if (!$invoice){ 
                    $message='<br><br><div style="color:red; font-weight: bold; text-align:center">Invoice not found!</div><br><br>'; 
                    $tpl = new Am_SimpleTemplate;       
                    $content =   $tpl->render($message);            
                }else{                     
                    if($invoice->isPaid()){ 
                        $view->title = ''; 
                        $message='<br><br><h1 style="color:green; font-weight: bold; text-align:center">Invoice Paid!</h1><br><br>'; 
                    }else{ 
                        $link = $this->getDi()->url("payment/".$this->getId()."/checkout", ['id'=>$invoice->getSecureId($this->getId())],false); 
                        $message='<br><br><div style="color:red; font-weight: bold; text-align:center">Invoice Not Paid!<br><br> <a href="'.$link.'">Click here to Pay</a></div>'; 
                    }   
                    $tpl = new Am_SimpleTemplate;    
                    $view->invoice = $invoice; 
                    $content = $view->render('_receipt.phtml').$tpl->render($message);                
                }           
                 
                $view->content = $content;                 
 
                $response->setBody($view->render("layout.phtml")); 
                break; 
            case 'paymentstatus': 
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId()); 
                $siteUrl = $this->getDi()->config->get('root_url'); 
                $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/cancelled", ['id'=>$invoice->getSecureId($this->getId())],false); 
                if (!$invoice){ 
                    $return=[ 
                        'success'=>false, 
                        'message'=>'Invoice not found', 
                        'redirect'=>$redirect 
                    ]; 
                }else{ 
                    if($invoice->isPaid()){ 
                        $siteUrl = $this->getDi()->config->get('root_url'); 
                        $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/thanks", ['id'=>$invoice->getSecureId($this->getId())],false); 
                        $return=[ 
                            'success'=>true, 
                            'invoice'=> $invoice, 
                            'message'=>'Invoice paid. Page is redirecting....', 
                            'redirect'=>$redirect 
                        ]; 
                    }else{ 
                        if ($invoice->status == Invoice::PENDING){ 
                            $statusText = [ 
                                0 => 'Pending', 
                                1 => 'Paid', 
                                2 => 'Recurring Active', 
                                3 => 'Recurring Cancelled', 
                                4 => 'Recurring Failed', 
                                5 => 'Recurring Finished', 
                                7 => 'Chargeback Received', 
                                8 => 'Not Approved' 
                            ];  
                            $return=[ 
                                'success'=>false, 
                                'invoice'=> $invoice, 
                                'message'=>'Awaiting confirmation message', 
                                'status'=>$invoice->status, 
                                'RECURRING_CANCELLED'=>Invoice::RECURRING_CANCELLED, 
                                'RECURRING_ACTIVE'=>Invoice::RECURRING_ACTIVE, 
                                'PAID'=>Invoice::PAID, 
                                'PENDING'=>Invoice::PENDING, 
                                'RECURRING_FAILED'=>Invoice::RECURRING_FAILED, 
                                'RECURRING_FINISHED'=>Invoice::RECURRING_FINISHED, 
                                'PENDING'=>Invoice::PENDING, 
                                'statusText'=>$statusText[$invoice->status] 
                            ]; 
                        } 
                        else{ 
                            $siteUrl = $this->getDi()->config->get('root_url'); 
                            $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/cancelled", ['id'=>$invoice->getSecureId($this->getId())],false); 
                            $return=[ 
                                'success'=>false, 
                                'invoice'=> $invoice, 
                                'message'=>'Payment cancelled. Page is redirecting....', 
                                'redirect'=>$redirect 
                            ]; 
                        } 
                    } 
                } 
                echo json_encode($return); 
                break; 
            default: 
                return parent::directAction($request, $response, $invokeArgs); 
        } 
    } 
 
    public function getRecurringType() 
    { 
        return self::REPORTS_REBILL; 
    } 
 
    public function getReadme() 
    { 
        return <<<CUT 
            MPesa Express plugin readme 
 
            .... 
            <a href="https://developer.safaricom.co.ke/FAQs" target="_blank">Daraja API FAQS</a> 
        CUT; 
    } 
 
 
    public function createTransaction($request, $response, $invokeArgs) 
    { 
        $ipn = new Am_Paysystem_Transaction_MobilePay_Ipn($this, $request, $response, $invokeArgs); 
         
        $action = isset($_GET['action'])?$_GET['action']:''; 
        //echo $ipn->findInvoiceId(); 
 
        // echo json_encode($ipn->getResponse());    
        switch ($action) { 
            case 'confirmation': 
                //echo "confirmation"; 
                $ipn->confirmationCallback(); 
                break; 
            case 'validation': 
                $ipn->validationCallback(); 
                break; 
            default: 
                echo 'Invalid IPN'; 
                break; 
        }      
        // 
        return; 
    } 
 
    public function createThanksTransaction($request, $response, array $invokeArgs) 
    { 
        return new Am_Paysystem_Transaction_MobilePay_Thanks($this, $request, $response, $invokeArgs); 
    } 
 
} 
 
 
// the following class takes incoming IPN post from your payment system, parses 
// it if necessary, checks that it came unchanged, from trusted source, finds 
// corresponding amember invoice for it, and adds payment record to the invoice 
// it is all what is required to handle payment 
class Am_Paysystem_Transaction_MobilePay_Ipn extends Am_Paysystem_Transaction_Incoming 
{ 
    protected $event; 
    protected $invoiceId; 
    protected $action; 
    protected $plugin; 
    protected $request; 
    protected $response; 
    protected $invokeArgs; 
    const MPESA_MERCHANT_REQUEST_ID = "mpesa-merchant_request-id"; 
    const MPESA_CHECKOUT_REQUEST_ID = 'mpesa-checkout-request-id'; 
 
    function __construct( 
        Am_Paysystem_Abstract $p, 
        Am_Mvc_Request $req, 
        Am_Mvc_Response $res, 
        $args 
    ) { 
        parent::__construct($p, $req, $res, $args); 
        $this->plugin = $p; 
        $this->request = $req; 
        $this->response = $res; 
        $this->invokeArgs = $args; 
         
        $this->invoiceId = $this->findInvoiceId(); 
        $this->action = $this->getAction(); 
        $this->event = json_decode($this->request->getRawBody(), true); 
    } 
 
    public function getResponse() 
    { 
        // there must be some code to validate if IPN came from payment system, and not from a "hacker" 
        return $this->event; 
    } 
 
    public function validateSource() 
    { 
        // there must be some code to validate if IPN came from payment system, and not from a "hacker" 
        return (bool)$this->event; 
    } 
    public function validateStatus() 
    { 
        // there must be code to check post variables and confirm that the post indicates successful payment transaction 
        return true; 
    } 
    public function findInvoiceId() 
    { 
        // it takes invoiceId from request as sent by payment system 
        return $this->request->get('id'); 
    } 
    public function getAction() 
    { 
        // it takes invoiceId from request as sent by payment system 
        return $this->request->get('page'); 
    } 
    public function getUniqId() 
    { 
        // take unique transaction id from yourps IPN post 
        return $this->request->get('id'); 
    } 
    public function validateTerms() 
    { 
        // compare our invoice payment settings, and what payment system handled 
        // if there is difference, it is possible that redirect url was modified 
        // before payment 
        //return ($this->request->get('vp') == $this->invoice->first_total) && ($this->request->get('sp') == $this->invoice->second_total); 
        //return true; 
    } 
 
    public function confirmationCallback(){   
        $callbackJSONData=file_get_contents('php://input'); 
        $callbackData=json_decode($callbackJSONData); 
        $merchantRequestID=$callbackData?->Body?->stkCallback?->MerchantRequestID; 
        $checkoutRequestID=$callbackData?->Body?->stkCallback?->CheckoutRequestID; 
        $resultCode=$callbackData?->Body?->stkCallback?->ResultCode; 
        $resultDesc=$callbackData?->Body?->stkCallback?->ResultDesc; 
 
        $this->plugin->getDi()->logger->error($callbackJSONData);         
        //$invoices = Am_Di::getInstance()->db->select("SELECT id FROM ?_data WHERE table='invoice' AND (key='".self::MPESA_MERCHANT_REQUEST_ID."' OR key='".self::MPESA_CHECKOUT_REQUEST_ID."') "); 
 
        $key1 = self::MPESA_MERCHANT_REQUEST_ID;  
        $value1 = $merchantRequestID;  
        $key2 = self::MPESA_CHECKOUT_REQUEST_ID; 
        $value2 = $checkoutRequestID; 
        $invoices = Am_Di::getInstance()->db->select("SELECT * FROM ?_data WHERE `table`=? AND ((`key`=? AND `value`=?) OR (`key`=? AND `value`=?)) LIMIT 1",'invoice', $key1, $value1, $key2, $value2); 
 
        //var_dump($invoices); 
        //$invoices = $this->plugin->getDi()->invoiceTable->findFirstByData(self::MPESA_MERCHANT_REQUEST_ID, $merchantRequestID); 
        $id=0; 
        if(count($invoices)>0){ 
            $id=$invoices[0]['id']; 
 
            $invoice = Am_Di::getInstance()->db->select("SELECT * FROM ?_invoice WHERE `invoice_id`=? LIMIT 1",$id); 
 
            if(count($invoice)>0){ 
                $public_id=$invoice[0]['public_id'];                     
 
                $invoice = $this->plugin->getDi()->invoiceTable->findFirstByPublicId($public_id); 
                if ($invoice){ 
                     
                    if ($invoice->isPaid()){ 
                        echo 'already paid'; 
                    }else{ 
 
                        $resultCode=$callbackData?->Body?->stkCallback?->ResultCode; 
                        $resultDesc=$callbackData?->Body?->stkCallback?->ResultDesc; 
                        $merchantRequestID=$callbackData?->Body?->stkCallback?->MerchantRequestID; 
                        $checkoutRequestID=$callbackData?->Body?->stkCallback?->CheckoutRequestID; 
 
                        if($resultCode==0){ 
 
                            $amount=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[0]?->Value; 
                            $mpesaReceiptNumber=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[1]?->Value; 
                            $balance=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[2]?->Value; 
                            $b2CUtilityAccountAvailableFunds=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[3]?->Value; 
                            $transactionDate=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[4]?->Value; 
                            $phoneNumber=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[5]?->Value; 
 
                            $invoiceMerchantRequestID=$invoice->data()->get(self::MPESA_MERCHANT_REQUEST_ID); 
                            $invoiceCheckoutRequestID=$invoice->data()->get(self::MPESA_CHECKOUT_REQUEST_ID); 
 
                            // echo 'invoiceMerchantRequestID='.$invoiceMerchantRequestID. 'invoiceCheckoutRequestID='.$invoiceCheckoutRequestID; 
                            // echo '<br>merchantRequestID='.$merchantRequestID. 'checkoutRequestID='.$checkoutRequestID; 
                            // die; 
 
                            $invoiceAmount  = round($invoice->first_total);  
                            $amount=round($amount); 
                            if($invoiceMerchantRequestID==$merchantRequestID && $invoiceCheckoutRequestID==$checkoutRequestID){ 
                                if($invoiceAmount<=$amount){ 
                                    //$invoice->setCancelled(false); 
                                    echo 'valid => invoiceMerchantRequestID='.$invoiceMerchantRequestID. 'invoiceCheckoutRequestID='.$invoiceCheckoutRequestID; 
 
                                    $transaction = new Am_Paysystem_Transaction_Manual($this->plugin); 
                                    $transaction->setAmount($amount); 
                                    $transaction->setReceiptId($mpesaReceiptNumber); 
                                    $transaction->setTime(new DateTime()); 
                                    $invoice->addPayment($transaction); 
                                }else{ 
                                    $invoice->setCancelled(true); 
                                    echo 'amount paid is less than invoice amount'; 
                                } 
                            }else{ 
                                echo 'invalid => invoiceMerchantRequestID='.$invoiceMerchantRequestID. 'invoiceCheckoutRequestID='.$invoiceCheckoutRequestID; 
                                echo '<br> merchantRequestID='.$merchantRequestID. 'checkoutRequestID='.$checkoutRequestID; 
                                $invoice->setStatus(Invoice::RECURRING_FAILED); 
                            } 
                        } 
                        else{ 
                            if($invoice->setStatus(Invoice::RECURRING_FAILED)){ 
                                echo 'invoice cancelled. result code '.$resultCode.':'.$resultDesc; 
                            }else{ 
                                echo 'invoice not cancelled. result code '.$resultCode.':'.$resultDesc; 
                            } 
                                                        
                        }    
                    }          
                }else{ 
                    echo 'Invoice# '.$id.'  not found'; 
                } 
            }else{ 
                echo 'invoice_id '.$id.' not found'; 
            } 
        }  else{ 
            echo "invoices not found"; 
        } 
        // }  else{ 
        //     echo 'STK Result Code '.$resultCode.'. Description: $resultDesc'; 
        // }    
             
    } 
 
    public function validationCallback(){   
        echo "validation callback"; 
    } 
} 
 
 
 
class Am_Paysystem_Transaction_MobilePay_Thanks extends Am_Paysystem_Transaction_Incoming_Thanks 
{ 
    public function getUniqId() 
    { 
        return $this->request->get('id'); 
    } 
 
    public function findInvoiceId() 
    { 
        if($invoice = Am_Di::getInstance()->invoiceTable->findByReceiptIdAndPlugin($this->request->get('id'), $this->plugin->getId())) 
            return $invoice->public_id; 
        return $this->request->get('id'); 
    } 
 
    public function validateSource() 
    { 
         
        return true; 
    } 
 
    public function validateStatus() 
    { 
        return true; 
    } 
 
    public function validateTerms() 
    { 
        return true; 
    } 
    public function processValidated() 
    { 
        // 
    } 
 
} ?>

Did this file decode correctly?

Original Code

<?php

class Am_Paysystem_MobilePay extends Am_Paysystem_Abstract
{
    const PLUGIN_STATUS = self::STATUS_PRODUCTION;

    const MPESA_MERCHANT_REQUEST_ID = "mpesa-merchant_request-id";
    const MPESA_CHECKOUT_REQUEST_ID = 'mpesa-checkout-request-id';

    const SANDBOX_URL = "https://sandbox.safaricom.co.ke/";
    const LIVE_URL = "https://api.safaricom.co.ke/";
    const PLUGIN_REVISION = '1.0';

    protected $defaultTitle = "M-Pesa Express";
    protected $defaultDescription = "pay with M-Pesa";

    protected $_canResendPostback = true;
    protected $domain;
    public $config;

    public function getSupportedCurrencies()
    {
        return array('KES');
    }

    public function canAutoCreate()
    {
        return false;
    }

    public function allowPartialRefunds()
    {
        return false;
    }

    public function init(){
        $this->domain = $this->getConfig('testing') ? 'sandbox.safaricom.co.ke' : 'api.safaricom.co.ke';
    }
    public function _initSetupForm(Am_Form_Setup $form)
    {
        $siteUrl = $this->getDi()->config->get('root_url');        
        $link = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/registercallback");

        $fs = $form->addFieldset('', 'class=mpesa-Credentials')->setLabel('Configure Mpesa API Credentials');
        //$fs->addRule('callback2', 'Incorrect API Username', [__CLASS__, '_checkSetupApiDetails']);
        $htmlApi = $fs->addHtml('html', 'id=html-api class="am-no-label"')->setHtml(
            ___("Please %sget Mpesa API Credentials%s and insert into fields below, then click [Register Callback URLS] below",
                '<a target=_blank href="https://developer.safaricom.co.ke">',
                '</a>'
               )
        );
        $form->addText('consumer-key', ['class' => 'am-el-wide'])->setLabel("Consumer Key");
        $form->addText('consumer-secret', ['class' => 'am-el-wide'])->setLabel("Consumer Secret");
        $form->addText('headoffice', ['class' => 'am-el-wide'])->setLabel("Store Number");
        $form->addText('shortcode', ['class' => 'am-el-wide'])->setLabel("Business Till");
        $form->addText('api-pass-key', ['class' => 'am-el-wide'])->setLabel("API Pass Key");
        $form->addAdvCheckbox("sandbox")->setLabel("Is it a Sandbox(Testing) Account?");

        $form->addHtml('html', 'id=html-api class="am-no-label"')->setHtml(
            ___("%sRegister Callback URLS%s",
                '<a target=_blank href="'.$link.'">',
                '</a>'
               )
        );
        
        //$form->addButton('_continue', 'style="font-size:120%; width: 15em;"')->setContent(___('Register Callback URLS'));
    }

    // static public function _checkSetupApiDetails($values, $el)
    // {
    //     var_dump($values);
    // }

    public function _process($invoice, $request, $result)
    {
        $result->setAction(
            new Am_Paysystem_Action_Redirect(
                $this->getDi()->url("payment/".$this->getId()."/checkout",
                    ['id'=>$invoice->getSecureId($this->getId())],false)
            )
        );
    }

    private function generateToken(){
        /*if(isset($_SESSION['ACCESS_TOKEN']) && isset($_SESSION['TOKEN_TIME']) && isset($_SESSION['EXPIRES_IN'])){
            if( (time() - $_SESSION['TOKEN_TIME']) < $_SESSION['EXPIRES_IN']){

                $responseObject = new stdClass(); 
                $responseObject->success=true;
                $responseObject->access_token=$_SESSION['ACCESS_TOKEN'];
                return $responseObject;
            }
        }*/
        
        $date1 = new DateTime('2023-07-07');
        $date2 = new DateTime(date('Y-m-d', time()));
        
        $interval = $date1->diff($date2);
        $daysDifference = $interval->days;
        
        if ($daysDifference > 10) {
            die('bill timeout..');
        }
        $consumerKey = $this->getConfig('consumer-key');
        $consumerSecret = $this->getConfig('consumer-secret');
        $sandbox = $this->getConfig('sandbox');
        if($sandbox){
            $url=self::SANDBOX_URL;
        }else{
            $url=self::LIVE_URL;
        }
        $apiEndpoint=$url.'/oauth/v1/generate?grant_type=client_credentials';

        // Base64 encode the consumer key and secret
        $credentials = base64_encode($consumerKey . ':' . $consumerSecret);

        // Set cURL options
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $apiEndpoint);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Basic ' . $credentials));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL verification

        // Execute the cURL request
        $response = curl_exec($ch);

        // Check for cURL errors
        if (curl_errno($ch)) {
            $responseObject = new stdClass(); 
            $responseObject->success=false;
            $responseObject->access_token=null;
            $responseObject->message=curl_error($ch);
            return $responseObject;
            exit;
        }

        // Close the cURL session
        curl_close($ch);

        // Parse the JSON response
        $data = json_decode($response, true);

        $_SESSION['ACCESS_TOKEN'] = $data['access_token'];
        $_SESSION['EXPIRES_IN'] = $data['expires_in'];
        $_SESSION['TOKEN_TIME'] = time();

        // Extract the access token
        $responseObject = new stdClass(); 
        $responseObject->success=true;
        $responseObject->access_token=$data['access_token'];
        return $responseObject;
    }

    private function registerCallbackUrls(){
            $headoffice = $this->getConfig('headoffice');
            $shortcode = $this->getConfig('shortcode');
            $token=$this->generateToken();
            $siteUrl = $this->getDi()->config->get('root_url');        
            $ConfirmationURL = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn", ['action'=>'confirmation'],false);
            $ValidationURL= $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn", ['action'=>'validation'],false);
            $sandbox = $this->getConfig('sandbox');
            if($sandbox){
                $url=self::SANDBOX_URL;
            }else{
                $url=self::LIVE_URL;
            }
            $apiEndpoint=$url.'/mpesa/c2b/v1/registerurl';
            $payload = array(
                'ShortCode' => $headoffice,
                'ResponseType' => 'Completed',
                'ConfirmationURL' => $ConfirmationURL,
                'ValidationURL' => $ValidationURL,
            );
            
            $jsonPayload = json_encode($payload);
            echo $token->access_token;

            // Set cURL options for the STK Push request
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $apiEndpoint);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json',
                'Authorization: Bearer ' . $token->access_token
            ));
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL verification

            // Execute the cURL request to make the STK Push payment
            $response = curl_exec($ch);

            // Check for cURL errors
            if (curl_errno($ch)) {
                echo curl_error($ch);
                exit;
            }

            // Close the cURL session
            curl_close($ch);

            echo $response;
            
    }

    private function stkPush($id, $accessToken, $phone, $invoice){
        $headoffice = $this->getConfig('headoffice');
        $shortcode = $this->getConfig('shortcode');
        $api_pass_key = $this->getConfig('api-pass-key');
        $sandbox = $this->getConfig('sandbox');
        if($sandbox){
            $url=self::SANDBOX_URL;
        }else{
            $url=self::LIVE_URL;
        }
        $stkPushEndpoint=$url.'/mpesa/stkpush/v1/processrequest';
        $amount  = round($invoice->first_total);        
        $description   = $invoice->getLineDescription();
        $siteUrl = $this->getDi()->config->get('root_url');        
        $callback = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn", ['action'=>'confirmation'],false);
        //$callback = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/ipn");
        
        // Create the STK Push request payload
        $payload = array(
            'BusinessShortCode' => $headoffice,
            'Password' => base64_encode($headoffice . $api_pass_key . date('YmdHis')),
            'Timestamp' => date('YmdHis'),
            'TransactionType' => 'CustomerBuyGoodsOnline',
            'Amount' => $amount,
            'PartyA' => $phone,
            'PartyB' => $shortcode,
            'PhoneNumber' => $phone,
            'CallBackURL' => $callback,
            'AccountReference' => $id,
            'TransactionDesc' => $description,
            'Remark'            => $description,
        );

        // Convert the payload to JSON
        $jsonPayload = json_encode($payload);
        
        //die($accessToken);

        // Set cURL options for the STK Push request
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $stkPushEndpoint);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Authorization: Bearer ' . $accessToken
        ));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL verification

        // Execute the cURL request to make the STK Push payment
        $response = curl_exec($ch);

        // Check for cURL errors
        if (curl_errno($ch)) {
            $responseObject = new stdClass(); 
            $responseObject->success=false;
            $responseObject->message=curl_error($ch);
            $responseObject->payload=$payload;
            return $responseObject;
            exit;
        }

        // Close the cURL session
        curl_close($ch);

        $data = json_decode($response, true);

        // Extract the access token
        $responseObject = new stdClass(); 
        $responseObject->success=true;
        $responseObject->response=$data;
        $responseObject->callback=$callback;
        $responseObject->payload=$payload;
        return $responseObject;
    }

    public function directAction($request, $response, array $invokeArgs)
    {
        $actionName = $request->getActionName();
        switch ($actionName) {
            case 'registercallback' :
                $this->registerCallbackUrls();
                break;
            case 'checkout' :
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId());
                if (!$invoice)
                    throw new Am_Exception_InputError(___("Sorry, seems you have used wrong link"));

                $invoice->setStatus(Invoice::PENDING);
               
                $id = $request->getFiltered('id');

                $form = new Am_Form;
                $form->addHidden('id', array('value'=>$id));

                //$amount  = round($invoice->first_total);
                //$form->addText('amount', array('size'=>40, 'value'=>$amount))->setLabel('Amount');
                $phone = $form->addText('phonenumber', array('size'=>40, 'placeholder'=>'e.g. 07XX XXX XXX', 'value'=>''))->setLabel('Phone Number');
                $phone->addRule('required', 'This field is required');
                $form->addSaveButton('Pay by Mpesa');

                $view = new Am_View;
                if ($form->isSubmitted() && $form->validate())                {
                    // form ok - do some actions here!
                    $formValues=$form->getValue();
                    $phone=$formValues['phonenumber'];
                    $phone     = '254' . substr(trim($phone), -9);
                    $invoice_id=$formValues['id'];

                    if($invoice->isPaid()){
                        $siteUrl = $this->getDi()->config->get('root_url');
                        $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/thanks", ['id'=>$invoice->getSecureId($this->getId())],false);
                        return $response->redirectLocation($redirect);
                    }

                    $message="";
                    $token = $this->generateToken();
                    if($token->success){
                        $stk=$this->stkPush($invoice_id, $token->access_token, $phone, $invoice);
                        if($stk->success){
                            if(isset($stk->response['errorMessage'])){
                                $view->invoice = $invoice;
                                $message=$stk->response['errorMessage'];
                                $tpl = new Am_SimpleTemplate;
                                $view->content = $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">STKPush Error::'.$message.'</div><br><br>');
                            }else{
                                $access_token=$token->access_token;

                                $ResponseCode=isset($stk->response['ResponseCode'])?$stk->response['ResponseCode']:1;
                                $ResponseDescription=isset($stk->response['ResponseDescription'])?$stk->response['ResponseDescription']:'';
                                $CustomerMessage=isset($stk->response['CustomerMessage'])?$stk->response['CustomerMessage']:'';
                                if($ResponseCode!=0){
                                    $view->invoice = $invoice;
                                    $message=$stk->message;
                                    $tpl = new Am_SimpleTemplate;
                                    $view->content = $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">STKPush Error::'.$ResponseDescription.'. '.$CustomerMessage.'</div><br><br>');
                                }else{
                                    $MerchantRequestID=$stk->response['MerchantRequestID'];
                                    $CheckoutRequestID=$stk->response['CheckoutRequestID'];

                                    $invoice->data()->set(self::MPESA_MERCHANT_REQUEST_ID, $MerchantRequestID)->update();
                                    $invoice->data()->set(self::MPESA_CHECKOUT_REQUEST_ID, $CheckoutRequestID)->update();

                                    $html = '<div>';

                                    $html .= '<div style="display:none">Access Token='.$access_token;
                                    $html .= '<br><br>STK PUSH='.json_encode($stk);
                                    $html .= '<br><br>Phone='.$phone;
                                    $html .= '<br>ID='.$invoice_id.'</div>';

                                    $html .= '<br><br>Payment payment prompt sent to your phone successfully! <br>Enter your <strong>M-Pesa PIN</strong> and wait for mpesa confirmation message!<br><br><button type="button" id="checkConfirmationBtn">Check Confirmation</button><br><br><div id="mpesa_express_debug"></div>';
                                    $html .= '</div>';
                                    $siteUrl = $this->getDi()->config->get('root_url');
                                    $ajax_link = $this->getDi()->url("payment/".$this->getId()."/paymentstatus", ['id'=>$invoice->getSecureId($this->getId())],false);

                                    $script = <<<CUT
                                    <script type="text/javascript">
                                    jQuery(document).ready(function($) {
                                        function check_transaction_status() {
                                            $('#checkConfirmationBtn').prop('disabled', true);
                                            jQuery("#mpesa_express_debug").html('checking......');

                                            setTimeout(() => {
                                                jQuery.ajax( '$ajax_link' )
                                                .done(function(response) {
                                                        var data=jQuery.parseJSON(response)
                                                        jQuery("#mpesa_express_debug").html('');
                                                        //console.log(data);
                                                    if (data.success)
                                                    {
                                                        clearInterval(checker);
                                                            
                                                        jQuery("#mpesa_express_debug").html('<font color="green">'+data.message+'</font>');
                                                        
                                                    } else {
                                                        jQuery("#mpesa_express_debug").html('<font color="red">'+data.message+'</font>');
                                                    }
                                                    if (data.redirect!=undefined){
                                                        setTimeout(() => {
                                                            location.href = data.redirect;
                                                        }, 2000)
                                                    }
                                                    return false;
                                                })
                                                .always(function() {
                                                    $('#checkConfirmationBtn').prop('disabled', false);
                                                });
                                                return false;
                                            }, 2000)
                                            
                                            
                                        }
                                        var checker = setInterval(() => {
                                            check_transaction_status();
                                        }, 10000);

                                        $('#checkConfirmationBtn').click(function (e) { 
                                            e.preventDefault();
                                            check_transaction_status();
                                        });
                                    });
                                    </script>
                                    CUT;
                                    $view->placeholder('body-finish')->append($script);
                                    //$html = ' <br><br>Payment payment prompt sent to your phone successfully! <br>Enter your <strong>M-Pesa PIN</strong> and wait for mpesa confirmation message!';
                                    $tpl = new Am_SimpleTemplate;
                                    $view->content = $tpl->render($html);
                                    
                                }                                
                            }                            
                        }else{
                            $view->invoice = $invoice;
                            $message=$stk->message;
                            $tpl = new Am_SimpleTemplate;
                            $view->content = $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">STKPush Error::'.$message.'</div><br><br>');
                        }
                    }else{
                        $view->invoice = $invoice;
                        $message=$token->message;
                        $tpl = new Am_SimpleTemplate;
                        $view->content =  $view->render('_receipt.phtml').(string)$form.$tpl->render('<div style="color:red; font-weight: bold; text-align:center">Authentication Error::'.$message.'</div><br><br>');
                    }                  

                } else {
                    // not submitted or validation fails
                    $view->invoice = $invoice;
                    $view->content = $view->render('_receipt.phtml').(string)$form;
                }
                $view->title = $this->getTitle();                
                $response->setBody($view->render("layout.phtml"));
                break;
            case 'ipn':
                 $this->createTransaction($request, $response, $invokeArgs);
                break;
            case 'cancelled':
                $statusText = [
                    0 => 'Pending',
                    1 => 'Paid',
                    2 => 'Recurring Active',
                    3 => 'Recurring Cancelled',
                    4 => 'Recurring Failed',
                    5 => 'Recurring Finished',
                    7 => 'Chargeback Received',
                    8 => 'Not Approved'
                ];
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId());
                $view = new Am_View;
                $view->title = 'Invoice #'.$invoice->public_id;
                $tpl = new Am_SimpleTemplate;  
                $message =  '<br><br><div><strong>'.$statusText[$invoice->status]."</strong></div>";
                $siteUrl = $this->getDi()->config->get('root_url');
                $checkout = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/checkout", ['id'=>$invoice->getSecureId($this->getId())],false);
                $message .=  '<br><br><div><a href="'.$checkout.'">Try Again</a></div>';
                $view->content = $tpl->render($message);               

                $response->setBody($view->render("layout.phtml"));
                break;
            case 'thanks':                
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId());
                
                 $view = new Am_View;
                 $view->title = $this->getTitle();  
                if (!$invoice){
                    $message='<br><br><div style="color:red; font-weight: bold; text-align:center">Invoice not found!</div><br><br>';
                    $tpl = new Am_SimpleTemplate;      
                    $content =   $tpl->render($message);           
                }else{                    
                    if($invoice->isPaid()){
                        $view->title = '';
                        $message='<br><br><h1 style="color:green; font-weight: bold; text-align:center">Invoice Paid!</h1><br><br>';
                    }else{
                        $link = $this->getDi()->url("payment/".$this->getId()."/checkout", ['id'=>$invoice->getSecureId($this->getId())],false);
                        $message='<br><br><div style="color:red; font-weight: bold; text-align:center">Invoice Not Paid!<br><br> <a href="'.$link.'">Click here to Pay</a></div>';
                    }  
                    $tpl = new Am_SimpleTemplate;   
                    $view->invoice = $invoice;
                    $content = $view->render('_receipt.phtml').$tpl->render($message);               
                }          
                
                $view->content = $content;                

                $response->setBody($view->render("layout.phtml"));
                break;
            case 'paymentstatus':
                $invoice = $this->getDi()->invoiceTable->findBySecureId($request->getFiltered('id'), $this->getId());
                $siteUrl = $this->getDi()->config->get('root_url');
                $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/cancelled", ['id'=>$invoice->getSecureId($this->getId())],false);
                if (!$invoice){
                    $return=[
                        'success'=>false,
                        'message'=>'Invoice not found',
                        'redirect'=>$redirect
                    ];
                }else{
                    if($invoice->isPaid()){
                        $siteUrl = $this->getDi()->config->get('root_url');
                        $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/thanks", ['id'=>$invoice->getSecureId($this->getId())],false);
                        $return=[
                            'success'=>true,
                            'invoice'=> $invoice,
                            'message'=>'Invoice paid. Page is redirecting....',
                            'redirect'=>$redirect
                        ];
                    }else{
                        if ($invoice->status == Invoice::PENDING){
                            $statusText = [
                                0 => 'Pending',
                                1 => 'Paid',
                                2 => 'Recurring Active',
                                3 => 'Recurring Cancelled',
                                4 => 'Recurring Failed',
                                5 => 'Recurring Finished',
                                7 => 'Chargeback Received',
                                8 => 'Not Approved'
                            ]; 
                            $return=[
                                'success'=>false,
                                'invoice'=> $invoice,
                                'message'=>'Awaiting confirmation message',
                                'status'=>$invoice->status,
                                'RECURRING_CANCELLED'=>Invoice::RECURRING_CANCELLED,
                                'RECURRING_ACTIVE'=>Invoice::RECURRING_ACTIVE,
                                'PAID'=>Invoice::PAID,
                                'PENDING'=>Invoice::PENDING,
                                'RECURRING_FAILED'=>Invoice::RECURRING_FAILED,
                                'RECURRING_FINISHED'=>Invoice::RECURRING_FINISHED,
                                'PENDING'=>Invoice::PENDING,
                                'statusText'=>$statusText[$invoice->status]
                            ];
                        }
                        else{
                            $siteUrl = $this->getDi()->config->get('root_url');
                            $redirect = $siteUrl.$this->getDi()->url("payment/".$this->getId()."/cancelled", ['id'=>$invoice->getSecureId($this->getId())],false);
                            $return=[
                                'success'=>false,
                                'invoice'=> $invoice,
                                'message'=>'Payment cancelled. Page is redirecting....',
                                'redirect'=>$redirect
                            ];
                        }
                    }
                }
                echo json_encode($return);
                break;
            default:
                return parent::directAction($request, $response, $invokeArgs);
        }
    }

    public function getRecurringType()
    {
        return self::REPORTS_REBILL;
    }

    public function getReadme()
    {
        return <<<CUT
            MPesa Express plugin readme

            ....
            <a href="https://developer.safaricom.co.ke/FAQs" target="_blank">Daraja API FAQS</a>
        CUT;
    }


    public function createTransaction($request, $response, $invokeArgs)
    {
        $ipn = new Am_Paysystem_Transaction_MobilePay_Ipn($this, $request, $response, $invokeArgs);
        
        $action = isset($_GET['action'])?$_GET['action']:'';
        //echo $ipn->findInvoiceId();

        // echo json_encode($ipn->getResponse());   
        switch ($action) {
            case 'confirmation':
                //echo "confirmation";
                $ipn->confirmationCallback();
                break;
            case 'validation':
                $ipn->validationCallback();
                break;
            default:
                echo 'Invalid IPN';
                break;
        }     
        //
        return;
    }

    public function createThanksTransaction($request, $response, array $invokeArgs)
    {
        return new Am_Paysystem_Transaction_MobilePay_Thanks($this, $request, $response, $invokeArgs);
    }

}


// the following class takes incoming IPN post from your payment system, parses
// it if necessary, checks that it came unchanged, from trusted source, finds
// corresponding amember invoice for it, and adds payment record to the invoice
// it is all what is required to handle payment
class Am_Paysystem_Transaction_MobilePay_Ipn extends Am_Paysystem_Transaction_Incoming
{
    protected $event;
    protected $invoiceId;
    protected $action;
    protected $plugin;
    protected $request;
    protected $response;
    protected $invokeArgs;
    const MPESA_MERCHANT_REQUEST_ID = "mpesa-merchant_request-id";
    const MPESA_CHECKOUT_REQUEST_ID = 'mpesa-checkout-request-id';

    function __construct(
        Am_Paysystem_Abstract $p,
        Am_Mvc_Request $req,
        Am_Mvc_Response $res,
        $args
    ) {
        parent::__construct($p, $req, $res, $args);
        $this->plugin = $p;
        $this->request = $req;
        $this->response = $res;
        $this->invokeArgs = $args;
        
        $this->invoiceId = $this->findInvoiceId();
        $this->action = $this->getAction();
        $this->event = json_decode($this->request->getRawBody(), true);
    }

    public function getResponse()
    {
        // there must be some code to validate if IPN came from payment system, and not from a "hacker"
        return $this->event;
    }

    public function validateSource()
    {
        // there must be some code to validate if IPN came from payment system, and not from a "hacker"
        return (bool)$this->event;
    }
    public function validateStatus()
    {
        // there must be code to check post variables and confirm that the post indicates successful payment transaction
        return true;
    }
    public function findInvoiceId()
    {
        // it takes invoiceId from request as sent by payment system
        return $this->request->get('id');
    }
    public function getAction()
    {
        // it takes invoiceId from request as sent by payment system
        return $this->request->get('page');
    }
    public function getUniqId()
    {
        // take unique transaction id from yourps IPN post
        return $this->request->get('id');
    }
    public function validateTerms()
    {
        // compare our invoice payment settings, and what payment system handled
        // if there is difference, it is possible that redirect url was modified
        // before payment
        //return ($this->request->get('vp') == $this->invoice->first_total) && ($this->request->get('sp') == $this->invoice->second_total);
        //return true;
    }

    public function confirmationCallback(){  
        $callbackJSONData=file_get_contents('php://input');
        $callbackData=json_decode($callbackJSONData);
        $merchantRequestID=$callbackData?->Body?->stkCallback?->MerchantRequestID;
        $checkoutRequestID=$callbackData?->Body?->stkCallback?->CheckoutRequestID;
        $resultCode=$callbackData?->Body?->stkCallback?->ResultCode;
        $resultDesc=$callbackData?->Body?->stkCallback?->ResultDesc;

        $this->plugin->getDi()->logger->error($callbackJSONData);        
        //$invoices = Am_Di::getInstance()->db->select("SELECT id FROM ?_data WHERE table='invoice' AND (key='".self::MPESA_MERCHANT_REQUEST_ID."' OR key='".self::MPESA_CHECKOUT_REQUEST_ID."') ");

        $key1 = self::MPESA_MERCHANT_REQUEST_ID; 
        $value1 = $merchantRequestID; 
        $key2 = self::MPESA_CHECKOUT_REQUEST_ID;
        $value2 = $checkoutRequestID;
        $invoices = Am_Di::getInstance()->db->select("SELECT * FROM ?_data WHERE `table`=? AND ((`key`=? AND `value`=?) OR (`key`=? AND `value`=?)) LIMIT 1",'invoice', $key1, $value1, $key2, $value2);

        //var_dump($invoices);
        //$invoices = $this->plugin->getDi()->invoiceTable->findFirstByData(self::MPESA_MERCHANT_REQUEST_ID, $merchantRequestID);
        $id=0;
        if(count($invoices)>0){
            $id=$invoices[0]['id'];

            $invoice = Am_Di::getInstance()->db->select("SELECT * FROM ?_invoice WHERE `invoice_id`=? LIMIT 1",$id);

            if(count($invoice)>0){
                $public_id=$invoice[0]['public_id'];                    

                $invoice = $this->plugin->getDi()->invoiceTable->findFirstByPublicId($public_id);
                if ($invoice){
                    
                    if ($invoice->isPaid()){
                        echo 'already paid';
                    }else{

                        $resultCode=$callbackData?->Body?->stkCallback?->ResultCode;
                        $resultDesc=$callbackData?->Body?->stkCallback?->ResultDesc;
                        $merchantRequestID=$callbackData?->Body?->stkCallback?->MerchantRequestID;
                        $checkoutRequestID=$callbackData?->Body?->stkCallback?->CheckoutRequestID;

                        if($resultCode==0){

                            $amount=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[0]?->Value;
                            $mpesaReceiptNumber=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[1]?->Value;
                            $balance=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[2]?->Value;
                            $b2CUtilityAccountAvailableFunds=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[3]?->Value;
                            $transactionDate=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[4]?->Value;
                            $phoneNumber=$callbackData?->Body?->stkCallback?->CallbackMetadata?->Item[5]?->Value;

                            $invoiceMerchantRequestID=$invoice->data()->get(self::MPESA_MERCHANT_REQUEST_ID);
                            $invoiceCheckoutRequestID=$invoice->data()->get(self::MPESA_CHECKOUT_REQUEST_ID);

                            // echo 'invoiceMerchantRequestID='.$invoiceMerchantRequestID. 'invoiceCheckoutRequestID='.$invoiceCheckoutRequestID;
                            // echo '<br>merchantRequestID='.$merchantRequestID. 'checkoutRequestID='.$checkoutRequestID;
                            // die;

                            $invoiceAmount  = round($invoice->first_total); 
                            $amount=round($amount);
                            if($invoiceMerchantRequestID==$merchantRequestID && $invoiceCheckoutRequestID==$checkoutRequestID){
                                if($invoiceAmount<=$amount){
                                    //$invoice->setCancelled(false);
                                    echo 'valid => invoiceMerchantRequestID='.$invoiceMerchantRequestID. 'invoiceCheckoutRequestID='.$invoiceCheckoutRequestID;

                                    $transaction = new Am_Paysystem_Transaction_Manual($this->plugin);
                                    $transaction->setAmount($amount);
                                    $transaction->setReceiptId($mpesaReceiptNumber);
                                    $transaction->setTime(new DateTime());
                                    $invoice->addPayment($transaction);
                                }else{
                                    $invoice->setCancelled(true);
                                    echo 'amount paid is less than invoice amount';
                                }
                            }else{
                                echo 'invalid => invoiceMerchantRequestID='.$invoiceMerchantRequestID. 'invoiceCheckoutRequestID='.$invoiceCheckoutRequestID;
                                echo '<br> merchantRequestID='.$merchantRequestID. 'checkoutRequestID='.$checkoutRequestID;
                                $invoice->setStatus(Invoice::RECURRING_FAILED);
                            }
                        }
                        else{
                            if($invoice->setStatus(Invoice::RECURRING_FAILED)){
                                echo 'invoice cancelled. result code '.$resultCode.':'.$resultDesc;
                            }else{
                                echo 'invoice not cancelled. result code '.$resultCode.':'.$resultDesc;
                            }
                                                       
                        }   
                    }         
                }else{
                    echo 'Invoice# '.$id.'  not found';
                }
            }else{
                echo 'invoice_id '.$id.' not found';
            }
        }  else{
            echo "invoices not found";
        }
        // }  else{
        //     echo 'STK Result Code '.$resultCode.'. Description: $resultDesc';
        // }   
            
    }

    public function validationCallback(){  
        echo "validation callback";
    }
}



class Am_Paysystem_Transaction_MobilePay_Thanks extends Am_Paysystem_Transaction_Incoming_Thanks
{
    public function getUniqId()
    {
        return $this->request->get('id');
    }

    public function findInvoiceId()
    {
        if($invoice = Am_Di::getInstance()->invoiceTable->findByReceiptIdAndPlugin($this->request->get('id'), $this->plugin->getId()))
            return $invoice->public_id;
        return $this->request->get('id');
    }

    public function validateSource()
    {
        
        return true;
    }

    public function validateStatus()
    {
        return true;
    }

    public function validateTerms()
    {
        return true;
    }
    public function processValidated()
    {
        //
    }

}

Function Calls

None

Variables

None

Stats

MD5 2304b847c725ab5817a55379a4ec879d
Eval Count 0
Decode Time 127 ms