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 |
Stats
MD5 | 2304b847c725ab5817a55379a4ec879d |
Eval Count | 0 |
Decode Time | 127 ms |