setApiKey($config);$this->setIsTest($config);$this->setVcCode($config);$this->config['loggerObj']=empty($config['loggerObj'])?null:$config['loggerObj'];$this->config['loggerFunc']=empty($config['loggerFunc'])?null:$config['loggerFunc'];self::$loggerObj=$this->config['loggerObj'];self::$loggerFunc=$this->config['loggerFunc'];$code=$this->config['vcCode'];$this->apiURL=$this->config['isTest']?$mfCountries[$code]['testv2']:$mfCountries[$code]['v2'];}public function getApiURL(){return $this->apiURL;}protected function setApiKey($config){if(empty($config['apiKey'])){throw new Exception('Config array must have the "apiKey" key.');}$config['apiKey']=trim($config['apiKey']);if(empty($config['apiKey'])){throw new Exception('The "apiKey" key is required and must be a string.');}$this->config['apiKey']=$config['apiKey'];}protected function setIsTest($config){if(!isset($config['isTest'])){throw new Exception('Config array must have the "isTest" key.');}if(!is_bool($config['isTest'])){throw new Exception('The "isTest" key must be boolean.');}$this->config['isTest']=$config['isTest'];}protected function setVcCode($config){$config['vcCode']=$config['vcCode']?? $config['countryCode']?? '';if(empty($config['vcCode'])){throw new Exception('Config array must have the "vcCode" key.');}$mfCountries=self::getMFCountries();$countriesCodes=array_keys($mfCountries);$config['vcCode']=strtoupper($config['vcCode']);if(!in_array($config['vcCode'],$countriesCodes)){throw new Exception('The "vcCode" key must be one of ('.implode(', ',$countriesCodes).').');}$this->config['vcCode']=$config['vcCode'];}public function callAPI($url,$postFields=null,$orderId=null,$function=null){ini_set('precision','14');ini_set('serialize_precision','-1');$request=isset($postFields)?'POST':'GET';$fields=empty($postFields)?json_encode($postFields,JSON_FORCE_OBJECT):json_encode($postFields,JSON_UNESCAPED_UNICODE);$msgLog="Order #$orderId ----- $function";$this->log("$msgLog - Request: $fields");$curl=curl_init($url);$options=[CURLOPT_CUSTOMREQUEST=>$request,CURLOPT_POSTFIELDS=>$fields,CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$this->config['apiKey'],'Content-Type: application/json'],CURLOPT_RETURNTRANSFER=>true];curl_setopt_array($curl,$options);$res=curl_exec($curl);$err=curl_error($curl);curl_close($curl);if($err){$this->log("$msgLog - cURL Error: $err");throw new Exception('cURL Error: '.$err);}$this->log("$msgLog - Response: $res");$json=json_decode((string) $res);$error=self::getAPIError($json,(string) $res);if($error){$this->log("$msgLog - Error: $error");throw new Exception($error);}return $json;}protected static function getAPIError($json,$res){$isSuccess=$json->IsSuccess ?? false;if($isSuccess){return '';}$hErr=self::getHtmlErrors($res);if($hErr){return $hErr;}if(is_string($json)){return $json;}if(empty($json)){return(!empty($res)?$res:'Kindly review your MyFatoorah admin configuration due to a wrong entry.');}return self::getJsonErrors($json);}protected static function getHtmlErrors($res){$stripHtml=strip_tags($res);if($res!=$stripHtml&&stripos($stripHtml,'apple-developer-merchantid-domain-association')!==false){return trim(preg_replace('/\s+/',' ',$stripHtml));}return '';}protected static function getJsonErrors($json){$errorsVar=isset($json->ValidationErrors)?'ValidationErrors':'FieldsErrors';if(isset($json->$errorsVar)){$blogDatas=array_column($json->$errorsVar,'Error','Name');$mapFun=function($k,$v){return"$k: $v";};$errArr=array_map($mapFun,array_keys($blogDatas),array_values($blogDatas));return implode(', ',$errArr);}if(isset($json->Data->ErrorMessage)){return $json->Data->ErrorMessage;}return empty($json->Message)?'':$json->Message;}public static function log($msg){$loggerObj=self::$loggerObj;$loggerFunc=self::$loggerFunc;if(empty($loggerObj)){return;}if(is_string($loggerObj)){error_log(PHP_EOL.date('d.m.Y h:i:s').' - '.$msg,3,$loggerObj);}elseif(method_exists($loggerObj,$loggerFunc)){$loggerObj->{$loggerFunc}($msg);}}}class MyFatoorahHelper{public static $loggerObj;public static $loggerFunc;public static function getPhone($inputString){$string3=self::convertArabicDigitstoEnglish($inputString);$string4=preg_replace('/[^0-9]/','',$string3);if(strpos($string4,'00')===0){$string4=substr($string4,2);}if(!$string4){return['',''];}$len=strlen($string4);if($len<3||$len>14){throw new Exception('Phone Number lenght must be between 3 to 14 digits');}if(strlen(substr($string4,3))>3){return[substr($string4,0,3),substr($string4,3)];}return['',$string4];}protected static function convertArabicDigitstoEnglish($inputString){$newNumbers=range(0,9);$persianDecimal=['۰','۱','۲','۳','۴','۵','۶','۷','۸','۹'];$arabicDecimal=['٠','١','٢','٣','٤','٥','٦','٧','٨','٩'];$arabic=['٠','١','٢','٣','٤','٥','٦','٧','٨','٩'];$persian=['۰','۱','۲','۳','۴','۵','۶','۷','۸','۹'];$string0=str_replace($persianDecimal,$newNumbers,$inputString);$string1=str_replace($arabicDecimal,$newNumbers,$string0);$string2=str_replace($arabic,$newNumbers,$string1);return str_replace($persian,$newNumbers,$string2);}public static function getWeightRate($unit){$lUnit=strtolower($unit);$rateUnits=['1'=>['kg','kgs','كج','كلغ','كيلو جرام','كيلو غرام'],'0.001'=>['g','جرام','غرام','جم'],'0.453592'=>['lbs','lb','رطل','باوند'],'0.0283495'=>['oz','اوقية','أوقية'],];foreach($rateUnits as $rate=>$unitArr){if(array_search($lUnit,$unitArr)!==false){return (double) $rate;}}throw new Exception('Weight units must be in kg, g, lbs, or oz. Default is kg');}public static function getDimensionRate($unit){$lUnit=strtolower($unit);$rateUnits=['1'=>['cm','سم'],'100'=>['m','متر','م'],'0.1'=>['mm','مم'],'2.54'=>['in','انش','إنش','بوصه','بوصة'],'91.44'=>['yd','يارده','ياردة'],];foreach($rateUnits as $rate=>$unitArr){if(array_search($lUnit,$unitArr)!==false){return (double) $rate;}}throw new Exception('Dimension units must be in cm, m, mm, in, or yd. Default is cm');}public static function isSignatureValid($dataArray,$secret,$signature,$eventType=0){if($eventType==2){unset($dataArray['GatewayReference']);}uksort($dataArray,'strcasecmp');$mapFun=function($v,$k){return sprintf("%s=%s",$k,$v);};$outputArr=array_map($mapFun,$dataArray,array_keys($dataArray));$output=implode(',',$outputArr);$hash=base64_encode(hash_hmac('sha256',$output,$secret,true));return $signature===$hash;}public static function getMFCountries(){$cachedFile=dirname(__FILE__).'/mf-config.json';if(file_exists($cachedFile)){if((time()-filemtime($cachedFile)>3600)){$countries=self::getMFConfigFileContent($cachedFile);}if(!empty($countries)){return $countries;}$cache=file_get_contents($cachedFile);return($cache)?json_decode($cache,true):[];}else{return self::getMFConfigFileContent($cachedFile);}}protected static function getMFConfigFileContent($cachedFile){$curl=curl_init('https://portal.myfatoorah.com/Files/API/mf-config.json');$option=[CURLOPT_HTTPHEADER=>['Content-Type: application/json'],CURLOPT_RETURNTRANSFER=>true];curl_setopt_array($curl,$option);$response=curl_exec($curl);$http_code=curl_getinfo($curl,CURLINFO_HTTP_CODE);curl_close($curl);if($http_code==200&&is_string($response)){$responseText=trim($response,'');file_put_contents($cachedFile,$responseText);return json_decode($responseText,true);}elseif($http_code==403){touch($cachedFile);$fileContent=file_get_contents($cachedFile);if(!empty($fileContent)){return json_decode($fileContent,true);}}return[];}public static function filterInputField($name,$type='GET'){if(isset($GLOBALS["_$type"][$name])){return htmlspecialchars($GLOBALS["_$type"][$name]);}return null;}public static function getPaymentStatusLink($url,$paymentId){$pattern='/MpgsAuthentication.*|ApplePayComplete.*|GooglePayComplete.*/i';return preg_replace($pattern,"Result?paymentId=$paymentId",$url);}}class MyFatoorahList extends MyFatoorah{public static function getOneCurrencyRate($currency,$allRates){foreach($allRates as $value){if($value->Text==$currency){return (double) $value->Value;}}throw new Exception('The selected currency is not supported by MyFatoorah');}public function getCurrencyRate($currency){$allRates=$this->getCurrencyRates();return self::getOneCurrencyRate($currency,$allRates);}public function getCurrencyRates(){$url="$this->apiURL/v2/GetCurrenciesExchangeList";return (array) $this->callAPI($url,null,null,'Get Currencies Exchange List');}}class MyFatoorahRefund extends MyFatoorah{public function refund($keyId,$amount,$currency=null,$comment=null,$orderId=null,$keyType='PaymentId'){$postFields=['Key'=>$keyId,'KeyType'=>$keyType,'RefundChargeOnCustomer'=>false,'ServiceChargeOnCustomer'=>false,'Amount'=>$amount,'CurrencyIso'=>$currency,'Comment'=>$comment,];return $this->makeRefund($postFields,$orderId);}public function makeRefund($curlData,$logId=null){$url="$this->apiURL/v2/MakeRefund";$json=$this->callAPI($url,$curlData,$logId,'Make Refund');return $json->Data;}}class MyFatoorahShipping extends MyFatoorah{public function getShippingCountries(){$url="$this->apiURL/v2/GetCountries";$json=$this->callAPI($url,null,null,'Get Countries');return $json->Data;}public function getShippingCities($method,$countryCode,$searchValue=''){$url=$this->apiURL.'/v2/GetCities'.'?shippingMethod='.$method.'&countryCode='.$countryCode.'&searchValue='.urlencode(substr($searchValue,0,30));$json=$this->callAPI($url,null,null,"Get Cities: $countryCode");return array_map('ucwords',$json->Data->CityNames);}public function calculateShippingCharge($curlData){if(!empty($curlData['Items'])){foreach($curlData['Items']as &$item){$item['ProductName']=strip_tags($item['ProductName']);$item['Description']=strip_tags($item['Description']);}}$url="$this->apiURL/v2/CalculateShippingCharge";$json=$this->callAPI($url,$curlData,null,'Calculate Shipping Charge');return $json->Data;}}class MyFatoorahSupplier extends MyFatoorah{public function getSupplierDashboard($supplierCode){$url=$this->apiURL.'/v2/GetSupplierDashboard?SupplierCode='.$supplierCode;return $this->callAPI($url,null,null,"Get Supplier Documents");}public function isSupplierApproved($supplierCode){$supplier=$this->getSupplierDashboard($supplierCode);return($supplier->IsApproved&&$supplier->IsActive);}}class MyFatoorahPayment extends MyFatoorah{public static $pmCachedFile=__DIR__.'/mf-methods.json';public function initiatePayment($invoiceAmount=0,$currencyIso='',$isCached=false){$postFields=['InvoiceAmount'=>$invoiceAmount,'CurrencyIso'=>$currencyIso,];$json=$this->callAPI("$this->apiURL/v2/InitiatePayment",$postFields,null,'Initiate Payment');$paymentMethods=($json->Data->PaymentMethods)??[];if(!empty($paymentMethods)&&$isCached){file_put_contents(self::$pmCachedFile,json_encode($paymentMethods));}return $paymentMethods;}public function getCachedVendorGateways(){if(file_exists(self::$pmCachedFile)){$cache=file_get_contents(self::$pmCachedFile);return($cache)?json_decode($cache):[];}else{return $this->initiatePayment(0,'',true);}}public function getCachedCheckoutGateways($isApRegistered=false){$gateways=$this->getCachedVendorGateways();$cachedGateways=['all'=>[],'cards'=>[],'form'=>[],'ap'=>[],'gp'=>[]];foreach($gateways as $gateway){$cachedGateways=$this->addGatewayToCheckout($gateway,$cachedGateways,$isApRegistered);}if($isApRegistered){$cachedGateways['ap']=$cachedGateways['ap'][0]??[];}return $cachedGateways;}protected function addGatewayToCheckout($gateway,$checkoutGateways,$isApRegistered){if($gateway->PaymentMethodCode=='gp'){$checkoutGateways['gp']=$gateway;$checkoutGateways['all'][]=$gateway;}elseif($gateway->PaymentMethodCode=='ap'){if($isApRegistered){$checkoutGateways['ap'][]=$gateway;}else{$checkoutGateways['cards'][]=$gateway;}$checkoutGateways['all'][]=$gateway;}elseif($gateway->PaymentMethodCode=='stc'){$checkoutGateways['cards'][]=$gateway;$checkoutGateways['all'][]=$gateway;}else{if($gateway->IsEmbeddedSupported){$checkoutGateways['form'][]=$gateway;$checkoutGateways['all'][]=$gateway;}elseif(!$gateway->IsDirectPayment){$checkoutGateways['cards'][]=$gateway;$checkoutGateways['all'][]=$gateway;}}return $checkoutGateways;}public function getOnePaymentMethod($gateway,$searchKey='PaymentMethodId',$invoiceAmount=0,$currencyIso=''){$paymentMethods=$this->initiatePayment($invoiceAmount,$currencyIso);$paymentMethod=null;foreach($paymentMethods as $pm){if($pm->$searchKey==$gateway){$paymentMethod=$pm;break;}}if(!isset($paymentMethod)){throw new Exception('Please contact Account Manager to enable the used payment method in your account');}return $paymentMethod;}public function getInvoiceURL($curlData,$gatewayId=0,$orderId=null,$sessionId=null,$ntfOption='Lnk'){$this->log('------------------------------------------------------------');$curlData['CustomerReference']=$curlData['CustomerReference']?? $orderId;if(!empty($sessionId)){$curlData['SessionId']=$sessionId;$data=$this->executePayment($curlData);return['invoiceURL'=>$data->PaymentURL,'invoiceId'=>$data->InvoiceId];}elseif($gatewayId=='myfatoorah'||empty($gatewayId)){if(empty($curlData['NotificationOption'])){$curlData['NotificationOption']=$ntfOption;}$data=$this->sendPayment($curlData);return['invoiceURL'=>$data->InvoiceURL,'invoiceId'=>$data->InvoiceId];}else{$curlData['PaymentMethodId']=$gatewayId;$data=$this->executePayment($curlData);return['invoiceURL'=>$data->PaymentURL,'invoiceId'=>$data->InvoiceId];}}public function sendPayment($curlData){$this->preparePayment($curlData);$json=$this->callAPI("$this->apiURL/v2/SendPayment",$curlData,$curlData['CustomerReference'],'Send Payment');return $json->Data;}public function executePayment($curlData){$this->preparePayment($curlData);$json=$this->callAPI("$this->apiURL/v2/ExecutePayment",$curlData,$curlData['CustomerReference'],'Execute Payment');return $json->Data;}private function preparePayment(&$curlData){$curlData['CustomerReference']=$curlData['CustomerReference']?? null;$curlData['SourceInfo']=$curlData['SourceInfo']?? 'MyFatoorah PHP Library '.$this->version;if(!empty($curlData['CustomerName'])){$curlData['CustomerName']=preg_replace('/[^\p{L}\p{N}\s]/u','',$curlData['CustomerName']);}if(!empty($curlData['InvoiceItems'])){foreach($curlData['InvoiceItems']as &$item){$item['ItemName']=strip_tags($item['ItemName']);}}if(empty($curlData['CustomerEmail'])){$curlData['CustomerEmail']=null;}}public function getEmbeddedSession($userDefinedField='',$logId=null){$curlData=['CustomerIdentifier'=>$userDefinedField];return $this->InitiateSession($curlData,$logId);}public function InitiateSession($curlData,$logId=null){$json=$this->callAPI("$this->apiURL/v2/InitiateSession",$curlData,$logId,'Initiate Session');return $json->Data;}public function registerApplePayDomain($url){$domainName=['DomainName'=>parse_url($url,PHP_URL_HOST)];return $this->callAPI("$this->apiURL/v2/RegisterApplePayDomain",$domainName,'','Register Apple Pay Domain');}}class MyFatoorahPaymentEmbedded extends MyFatoorahPayment{protected static $checkoutGateways;public function getCheckoutGateways($invoiceAmount,$currencyIso,$isApRegistered){if(!empty(self::$checkoutGateways)){return self::$checkoutGateways;}$gateways=$this->initiatePayment($invoiceAmount,$currencyIso);$mfListObj=new MyFatoorahList($this->config);$allRates=$mfListObj->getCurrencyRates();$currencyRate=MyFatoorahList::getOneCurrencyRate($currencyIso,$allRates);self::$checkoutGateways=['all'=>[],'cards'=>[],'form'=>[],'ap'=>[],'gp'=>[]];foreach($gateways as $gateway){$gateway->PaymentTotalAmount=$this->getPaymentTotalAmount($gateway,$allRates,$currencyRate);$gateway->GatewayData=['GatewayTotalAmount'=>number_format($gateway->PaymentTotalAmount,2),'GatewayCurrency'=>$gateway->PaymentCurrencyIso,'GatewayTransCurrency'=>self::getTranslatedCurrency($gateway->PaymentCurrencyIso),];self::$checkoutGateways=$this->addGatewayToCheckout($gateway,self::$checkoutGateways,$isApRegistered);}if($isApRegistered){self::$checkoutGateways['ap']=$this->getOneEmbeddedGateway(self::$checkoutGateways['ap'],$currencyIso,$allRates);}return self::$checkoutGateways;}private function getPaymentTotalAmount($paymentMethod,$allRates,$currencyRate){$dbTrucVal=((int)($paymentMethod->TotalAmount*1000))/1000;if($paymentMethod->PaymentCurrencyIso==$paymentMethod->CurrencyIso){return $this->roundUp($dbTrucVal,2);}$dueVal=($currencyRate==1)?$dbTrucVal:round($paymentMethod->TotalAmount/$currencyRate,3);$baseTotalAmount=$this->roundUp($dueVal,2);$paymentCurrencyRate=MyFatoorahList::getOneCurrencyRate($paymentMethod->PaymentCurrencyIso,$allRates);if($paymentCurrencyRate!=1){$paymentTotalAmount=$baseTotalAmount*$paymentCurrencyRate;return $this->roundUp($paymentTotalAmount,2);}return $baseTotalAmount;}private function roundUp($number,$decimalPlaces){$multi=pow(10,$decimalPlaces);$nrAsStr=(string)($number*$multi);return ceil((float) $nrAsStr)/$multi;}private function getOneEmbeddedGateway($gateways,$displayCurrency,$allRates){$displayCurrencyIndex=array_search($displayCurrency,array_column($gateways,'PaymentCurrencyIso'));if($displayCurrencyIndex){return $gateways[$displayCurrencyIndex];}$defCurKey=array_search('1',array_column($allRates,'Value'));$defaultCurrency=$allRates[$defCurKey]->Text;$defaultCurrencyIndex=array_search($defaultCurrency,array_column($gateways,'PaymentCurrencyIso'));if($defaultCurrencyIndex){return $gateways[$defaultCurrencyIndex];}if(isset($gateways[0])){return $gateways[0];}return[];}public static function getTranslatedCurrency($currency){$currencies=['KWD'=>['en'=>'KD','ar'=>'د.ك'],'SAR'=>['en'=>'SR','ar'=>'ريال'],'BHD'=>['en'=>'BD','ar'=>'د.ب'],'EGP'=>['en'=>'LE','ar'=>'ج.م'],'QAR'=>['en'=>'QR','ar'=>'ر.ق'],'OMR'=>['en'=>'OR','ar'=>'ر.ع'],'JOD'=>['en'=>'JD','ar'=>'د.أ'],'AED'=>['en'=>'AED','ar'=>'د'],'USD'=>['en'=>'USD','ar'=>'دولار'],'EUR'=>['en'=>'EUR','ar'=>'يورو']];return $currencies[$currency]??['en'=>'','ar'=>''];}}class MyFatoorahPaymentStatus extends MyFatoorahPayment{public function getPaymentStatus($keyId,$KeyType,$orderId=null,$price=null,$currency=null){$curlData=['Key'=>$keyId,'KeyType'=>$KeyType];$json=$this->callAPI("$this->apiURL/v2/GetPaymentStatus",$curlData,$orderId,'Get Payment Status');$data=$json->Data;$msgLog='Order #'.$data->CustomerReference.' ----- Get Payment Status';if(!self::checkOrderInformation($data,$orderId,$price,$currency)){$err='Trying to call data of another order';$this->log("$msgLog - Exception is $err");throw new Exception($err);}if($data->InvoiceStatus=='Paid'||$data->InvoiceStatus=='DuplicatePayment'){$data=self::getSuccessData($data);$this->log("$msgLog - Status is Paid");}elseif($data->InvoiceStatus!='Paid'){$data=self::getErrorData($data,$keyId,$KeyType);$this->log("$msgLog - Status is ".$data->InvoiceStatus.'. Error is '.$data->InvoiceError);}return $data;}private static function checkOrderInformation($data,$orderId=null,$price=null,$currency=null){if($orderId&&$orderId!=$data->CustomerReference){return false;}list($valStr,$mfCurrency)=explode(' ',$data->InvoiceDisplayValue);$mfPrice=(double)(preg_replace('/[^\d.]/','',$valStr));if($price&&$price!=$mfPrice){return false;}return!($currency&&$currency!=$mfCurrency);}private static function getSuccessData($data){foreach($data->InvoiceTransactions as $transaction){if($transaction->TransactionStatus=='Succss'){$data->InvoiceStatus='Paid';$data->InvoiceError='';$data->focusTransaction=$transaction;return $data;}}return $data;}private static function getErrorData($data,$keyId,$KeyType){$focusTransaction=self::{"getLastTransactionOf$KeyType"}($data->InvoiceTransactions,$keyId);if($focusTransaction&&$focusTransaction->TransactionStatus=='Failed'){$data->InvoiceStatus='Failed';$data->InvoiceError=$focusTransaction->Error.'.';$data->focusTransaction=$focusTransaction;return $data;}$ExpiryDateTime=$data->ExpiryDate.' '.$data->ExpiryTime;$ExpiryDate=new \DateTime($ExpiryDateTime,new \DateTimeZone('Asia/Kuwait'));$currentDate=new \DateTime('now',new \DateTimeZone('Asia/Kuwait'));if($ExpiryDate<$currentDate){$data->InvoiceStatus='Expired';$data->InvoiceError='Invoice is expired since '.$data->ExpiryDate.'.';return $data;}$data->InvoiceStatus='Pending';$data->InvoiceError='Pending Payment.';return $data;}private static function getLastTransactionOfPaymentId($transactions,$paymentId){foreach($transactions as $transaction){if($transaction->PaymentId==$paymentId&&$transaction->Error){return $transaction;}}return null;}private static function getLastTransactionOfInvoiceId($transactions){$usortFun=function($a,$b){return strtotime($a->TransactionDate)-strtotime($b->TransactionDate);};usort($transactions,$usortFun);return end($transactions);}}