Current File : /home/inlingua/public_html/icentex/inlingua_payment/payment/PaymentHandler.php
<?php
namespace PaymentHandler;

use DateTime;
use DateTimeZone;
use Exception;
use RuntimeException;

class PaymentHandler {

    /**
     * @property PaymentHandlerConfig $paymentHandlerConfig
     */
    public $paymentHandlerConfig;
    public function __construct($configPath,  $paymentHandlerConfig = null) {
        if ($paymentHandlerConfig != null) {
            $this->paymentHandlerConfig = $paymentHandlerConfig;
        } else {
            $config = file_get_contents(normalizePath($configPath));
            $config = json_decode($config, true);
            $merchantId = null;
            if (array_key_exists("MERCHANT_ID", $config)) {
                $merchantId = $config["MERCHANT_ID"];
            } else {
                throw new RuntimeException("MERCHANT ID NOT FOUND IN CONFIG");
            }
            $apiKey = null;
            if (array_key_exists("API_KEY", $config)) {
                $apiKey = $config["API_KEY"];
            } else {
                throw new RuntimeException("API_KEY NOT FOUND IN CONFIG");
            }
            $paymentPageClientId = null;
            if (array_key_exists("PAYMENT_PAGE_CLIENT_ID", $config)) {
                $paymentPageClientId = $config["PAYMENT_PAGE_CLIENT_ID"];
            }
            $baseUrl = null;
            if (array_key_exists("BASE_URL", $config)) {
                $baseUrl = $config["BASE_URL"];
            }
            $enableLogging = false;
            if (array_key_exists("ENABLE_LOGGING", $config)) {
                $enableLogging = $config["ENABLE_LOGGING"];
            }
            $loggingPath = "logs\\PaymentHandler.log";
            if (array_key_exists("LOGGING_PATH", $config)) {
                $loggingPath = $config["LOGGING_PATH"];
            }
            $responseKey = null;
            if (array_key_exists("RESPONSE_KEY", $config)) {
                $responseKey = $config["RESPONSE_KEY"];
            }
            $caPath = null;
            if (array_key_exists("CA_PATH", $config)) {
                $caPath = $config["CA_PATH"];
            }
            $this->paymentHandlerConfig = PaymentHandlerConfig::getInstance()->withInstance($merchantId, $apiKey, $paymentPageClientId, $baseUrl, $enableLogging, $loggingPath, $responseKey, $caPath);
        }
    }

    /**
     * @param array $params
     * @return array
     */
    public function orderStatus($orderId) {
        $apiTag = "ORDER_STATUS";
        return PaymentEntity::makeServiceCall ( "/orders/{$orderId}", null, RequestMethod::GET, $apiTag);
    }

    /**
     * @param array $params
     * @return array
     */

    public function orderSession($params) {
        $this->paramsCheck($params);
        $apiTag = "ORDER_SESSION";
        if (!array_key_exists("payment_page_client_id", $params)) {
             $params["payment_page_client_id"] = $this->paymentHandlerConfig->getPaymentPageClientId();
        }
		
		
        return PaymentEntity::makeServiceCall ( "/session", $params, RequestMethod::POST, $apiTag, ContentType::JSON);
    }
    
    /**
     * @param array $params
     * @return array
     */
    public function refund($params) {
        $this->paramsCheck($params);
        $apiTag = "ORDER_REFUND";
        return PaymentEntity::makeServiceCall("/refunds", $params, RequestMethod::POST, $apiTag, ContentType::X_WWW_FORM_URLENCODED);
    }

    public function validateHMAC_SHA256($params, $secret = null) {
        try {
            if ($secret === null) {
                $secret = $this->paymentHandlerConfig->getResponseKey();
            }
            if ($secret == null) return false;
            $paramsList = [];
            $paramsString = "";
            $expectedHash = null;
            foreach ($params as $key => $value) {
                if ($key != "signature" && $key != 'signature_algorithm') {
                    $paramsList[$key] = $value;
                } else if ($key == "signature") {
                    $expectedHash = urldecode($value);
                }
            }
            ksort($paramsList);
            foreach ($paramsList as $key => $value) {
                $paramsString = $paramsString . $key . "=" . $value . "&";
            }
            $paramsString = urlencode(substr($paramsString, 0, strlen($paramsString) - 1));
            $hash = base64_encode(hash_hmac("sha256", $paramsString, $secret, true));
            if (urldecode($hash) == $expectedHash) return true;
            else {
                $logger = new SimpleLogger();
                $logger->info(json_encode(["computeHash" => urldecode($hash), "expectedHash" => $expectedHash]));
                return false;
            }
        } catch (Exception $e) {
            $logger = new SimpleLogger();
            $logger->info($e->getMessage());
            return false;
        }
    }

    private function paramsCheck($params) {
        if ($params == null || count ( $params ) == 0) {
            throw new APIException (-1, "INVALID_PARAMS", "INVALID_PARAMS", "Params is empty");
        }
    }


}

class PaymentEntity {

    private static function http_parse_headers( $raw_headers ) {
        $headers = array();
        $key = '';

        foreach(explode("\n", $raw_headers) as $i => $h) {
            $h = explode(':', $h, 2);

            if (isset($h[1])) {
                if (!isset($headers[$h[0]])) {
                    if ($h[0] == 'x-response-id') {
                        $headers[$h[0]] = trim($h[1]);
                        break;
                    }
                }
            }
        }
        
        return $headers;
    }
    
    /**
     *
     * @param string $path
     * @param array|null $params
     * @param string $method
     * @param string $contentType
     * @return array
     *
     * @throws APIException
     */
    public static function makeServiceCall($path, $params, $method, $apiTag, $contentType = null) {
        $logger = new SimpleLogger();
        $paymentRequestId = uniqid();
        $paymentHandlerConfig = PaymentHandlerConfig::getInstance();
        $url = $paymentHandlerConfig->getBaseUrl() . $path;
        $curlObject = curl_init ();
        $log = array();
        curl_setopt ( $curlObject, CURLOPT_RETURNTRANSFER, true );
        curl_setopt ( $curlObject, CURLOPT_HEADER, true );
        curl_setopt ( $curlObject, CURLOPT_NOBODY, false );
        curl_setopt ( $curlObject, CURLOPT_USERPWD, $paymentHandlerConfig->getApiKey () );
        curl_setopt ( $curlObject, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
        curl_setopt ( $curlObject, CURLOPT_USERAGENT,  "SAMPLE_KIT/" . $paymentHandlerConfig->getMerchantId());
        $headers = array('version: ' . $paymentHandlerConfig->getAPIVersion());
        if ($paymentHandlerConfig->getMerchantId()) array_push($headers, 'x-merchantid:'. $paymentHandlerConfig->getMerchantId());
        
        if ($method == RequestMethod::GET) {
            curl_setopt ( $curlObject, CURLOPT_HTTPHEADER, $headers);
            curl_setopt ( $curlObject, CURLOPT_HTTPGET, 1 );
            $log["method"] = "GET";
            if ($params != null) {
                $encodedParams = http_build_query ( $params );
                if ($encodedParams != null && $encodedParams != "") {
                    $url = $url . "?" . $encodedParams;
                }
            }
        } else if ($contentType == ContentType::JSON) {
            array_push( $headers, 'Content-Type: application/json' );
            curl_setopt ( $curlObject, CURLOPT_HTTPHEADER, $headers);
            curl_setopt ( $curlObject, CURLOPT_POST, 1 );
            $log["method"] = "POST";
            if ($params != null) {
                $encodedParams = json_encode($params);
                $log["request_params"] = $encodedParams;
                curl_setopt ( $curlObject, CURLOPT_POSTFIELDS, $encodedParams );
            }
        } else {
            array_push( $headers, 'Content-Type: application/x-www-form-urlencoded' );
            
            curl_setopt ( $curlObject, CURLOPT_HTTPHEADER, $headers);
        
            curl_setopt ( $curlObject, CURLOPT_POST, 1 );
            $log["method"] = "POST";
            if ($params != null) {
                $body = http_build_query($params);
                $log["request_params"] = $body;
                curl_setopt ( $curlObject, CURLOPT_POSTFIELDS, $body);
            }
        }
        $log["headers"] = $headers;
        $logger->setLoggerContext(array("api_tag" => $apiTag, "payment_request_id" => $paymentRequestId));
        $logger->info("Executing Request:".$url);
        $logger->info("Request:".json_encode($log));
        curl_setopt ( $curlObject, CURLOPT_URL, $url );
        $ca = ini_get('curl.cainfo');
        $ca = $ca === null || $ca === "" ? ini_get('openssl.cafile') : $ca;
        if ($ca === null || $ca === "") {
            $caCertificatePath = PaymentHandlerConfig::getInstance()->getCacert();
            curl_setopt ($curlObject, CURLOPT_CAINFO, $caCertificatePath);
        }
        $response = curl_exec ( $curlObject );
        if ($response == false) {
            $curlError = curl_error ( $curlObject );
            $logger->error('connection error:'. $curlError);
            throw new APIException ( - 1, "connection_error", "connection_error",  $curlError);
        } else {
            $log = array();
            $responseCode = curl_getinfo ( $curlObject, CURLINFO_HTTP_CODE );
            $headerSize = curl_getinfo ( $curlObject, CURLINFO_HEADER_SIZE );
            $encodedResponse = substr ( $response, $headerSize );
            $responseBody = json_decode ($encodedResponse, true );
            $responseHeaders = self::http_parse_headers(substr($response, 0, $headerSize));
            
            $log = [ "http_status_code" => $responseCode,  "response" => $encodedResponse, "response_headers" => json_encode($responseHeaders)];
            curl_close ( $curlObject );
            if ($responseCode >= 200 && $responseCode < 300) {
                $logger->info("Received response:" . json_encode($log));
                return $responseBody;
            } else {
                $status = null;
                $errorCode = null;
                $errorMessage = null;
                if ($responseBody != null) {
                    if (array_key_exists ( "status", $responseBody ) != null) {
                        $status = $responseBody ['status'];
                    }
                    if (array_key_exists ( "error_code", $responseBody ) != null) {
                        $errorCode = $responseBody ['error_code'];
                    }
                    if (array_key_exists ( "error_message", $responseBody ) != null) {
                        $errorMessage = $responseBody ['error_message'];
                    } else {
                        $errorMessage = $status;
                    }
                }
                $logger->error("Received response:" . json_encode($log));
                throw new APIException ( $responseCode, $status, $errorCode, $errorMessage );
            }
        }
    }
    
}
class APIException extends Exception {
    private $httpResponseCode;
    private $status;
    private $errorCode;
    private $errorMessage;
    public function __construct($httpResponseCode, $status, $errorCode, $errorMessage) {
        parent::__construct ( $errorMessage == null ? "Something went wrong" : $errorMessage );
        $this->httpResponseCode = $httpResponseCode;
        $this->status = $status;
        $this->errorCode = $errorCode;
        $this->errorMessage = $errorMessage;
    }
    public function getHttpResponseCode() {
        return $this->httpResponseCode;
    }
    public function getStatus() {
        return $this->status;
    }
    public function getErrorCode() {
        return $this->errorCode;
    }
    public function getErrorMessage() {
        return $this->errorMessage;
    }
}

class PaymentHandlerConfig {
    /**
     * @property PaymentHandlerConfig $instance
     */
    private static $instance;
    private function __construct() {}

    public function __destruct() {
       $this->closeLogFileHandler();
    }
    /**
     * @property string $apiKey
     */
    private $apiKey;

    /**
     * @property string $merchantId
     */
    private $merchantId;


    /**
     * @property string $paymentPageClientId
     */
    private $paymentPageClientId;

    /**
     * @property string $baseUrl
     */
    private $baseUrl;

     /**
     * @property bool $enableLogging
     */
    private $enableLogging;

      /**
     * @property string $loggingPath
     */
    private  $loggingPath;

      /**
     * @property string $responseKey
     */
    
    private $responseKey;

    /**
     * @property string $API_VERSION
     */
    private $API_VERSION = "2024-02-01";

    /**
     * @property resource $logFileHandle
     */
    private $logFileHandle;

    /**
     * @property string $cacert
     */
    private $cacert;

    /**
     * @param string $merchantId
     * @param string $apiKey
     * @param string $paymentPageClientId
     * @param string $baseUrl
     * @param bool   $enableLogging
     * @param string $loggingPath
     * @param string $responseKey
     * @return PaymentHandlerConfig
     */
    public function withInstance($merchantId = null, $apiKey = null, $paymentPageClientId = null, $baseUrl = null, $enableLogging = null, $loggingPath = null, $responseKey = null, $caPath = null) {
        $this->apiKey = $apiKey;
        $this->merchantId = $merchantId;
        $this->paymentPageClientId = $paymentPageClientId;
        $this->baseUrl = $baseUrl;
        $this->enableLogging = $enableLogging;
        $this->loggingPath = normalizePath($loggingPath);
        $this->setLogFileHandler();
        $this->responseKey = $responseKey;
        $this->withCacert($caPath);
        return $this;
    }

    public function closeLogFileHandler() {
        if ($this->logFileHandle !== null) {
            try {
                fclose($this->logFileHandle);
                $this->logFileHandle = null;
            } catch (Exception $e) {
                $this->logFileHandle = null;
            }
        }
    }

    /**
     * @return PaymentHandlerConfig
     */
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * @return string
     */
    public function getApiKey() {
        return $this->apiKey;
    }

     /**
     * @return string
     */
    public function getMerchantId() {
        return $this->merchantId;
    }

     /**
     * @return string
     */
    public function getPaymentPageClientId() {
        return $this->paymentPageClientId;
    }

     /**
     * @return string
     */
    public function getBaseUrl() {
        return $this->baseUrl;
    }

     /**
     * @return bool
     */
    public function getEnableLogging() {
        return $this->enableLogging;
    }

     /**
     * @return string
     */
    public function getLoggingPath() {
        return $this->loggingPath;
    }

     /**
     * @return string
     */
    public function getResponseKey() {
        return $this->responseKey;
    }

     /**
     * @return string
     */
    public function getAPIVersion() {
        return $this->API_VERSION;
    }

    /**
     * @return resource
     */
    public function getLogFileHandle() {
        return $this->logFileHandle;
    }

     /**
     * @return string
     */
    public function getCacert() {
        return $this->cacert;
    }


    /**
     * @param string $apiVersion
     */
    public function withAPIVersion($apiVersion) {
        $this->API_VERSION = $apiVersion;
    }

    /**
     * @param string $cacertPath
     */
    public function withCacert($cacertPath) {
        if ($cacertPath !== null) { 
            $this->cacert = normalizePath($cacertPath);
            if (!file_exists($this->cacert)) {
                throw new RuntimeException("CA File Not Found");
            }
        }
    }

    public function setLogFileHandler() {
        $this->closeLogFileHandler();
        try {
            if ($this->loggingPath !== null && $this->enableLogging) {
                if (file_exists($this->loggingPath)) {
                    $this->logFileHandle = fopen($this->loggingPath, 'a');
                } else {
                    if (!file_exists(dirname($this->loggingPath))) {
                        mkdir(dirname($this->loggingPath), 0777, true);
                    }
                    $this->logFileHandle = fopen($this->loggingPath, 'w');
                }
            }
        } catch (Exception $e) {
            throw new RuntimeException("Failed Opening Log File Handler with message: " . $e->getMessage());
        }
        if ($this->logFileHandle === false) {
            throw new RuntimeException("Failed to open log file for writing: {$this->loggingPath}");
        }
    }
}

function normalizePath($pathString) {
    if (empty($pathString) || $pathString === null) {
        return '';
    }

    $pathString = str_replace('\\', '/', $pathString);

    $pathString = preg_replace('/^~/', getenv('HOME'), $pathString);

    $pathString = preg_replace('/^\.\//', getcwd() . '/', $pathString);

    $pathString = preg_replace('/\.\.\//', '', $pathString);

    $pattern = '/\$\{(.*?)\}/';
    $pathString = preg_replace_callback($pattern, function ($matches) {
        $propertyName = $matches[1];
        $replacement = getenv($propertyName) ?: '';
        return $replacement;
    }, $pathString);

    if ($pathString[0] !== '/') {
        if (!($pathString[1] === ':' || $pathString[2] === '\\')) {
            $pathString = getcwd() . '/' . $pathString;
        }
    }

    return $pathString;
}

class SimpleLogger
{
    // Define the log levels
    const ERROR = 'error';
    const INFO = 'info';
    const DEBUG = 'debug';

    private $context;
    public function setLoggerContext($context)
    {
       $this->context = $context;
    }

    public function arrayToSpaceSeparatedString($array) {
        $result = '';
        if (!is_array($array)) {
            return $array;
        }
        foreach ($array as $key => $value) {
            $result .= "$key=$value, ";
        }
        $result = rtrim($result, ', ');
        return $result;
    }

    public function log($level, $message)
    {
        // Check if the log level is above or equal to the configured level
        $callerFunction = $this->getCallerFunction();

        if (PaymentHandlerConfig::getInstance()->getEnableLogging()) {
            $dateTime = new DateTime('now', new DateTimeZone('UTC'));

            // Format the timestamp in the desired format
            $formattedTimestamp = $dateTime->format('M d, Y h:i:s A');

            $formattedMessage = sprintf("%s in.PaymentHandler.%s\n%s: %s value=%s", $formattedTimestamp, $callerFunction, strtoupper($level), $this->arrayToSpaceSeparatedString($this->context), $message);

            // Log to console
            if ($level == self::ERROR) {
                $this->logToError($formattedMessage);
            } else {
                $this->logToConsole($formattedMessage);
            }
            // Log to file
            $this->logToFile($formattedMessage);
        }
    }

    protected function logToConsole($message)
    {
        // fwrite(STDERR, $message, PHP_EOL);
    }

    protected function logToFile($message)
    {
        fwrite(PaymentHandlerConfig::getInstance()->getLogFileHandle(), $message . PHP_EOL);
        fflush(PaymentHandlerConfig::getInstance()->getLogFileHandle());
    }

    protected function logToError($message)
    {
        // Log to standard error (stderr) instead of standard output (stdout)
        // fwrite(STDERR, $message . PHP_EOL);
    }
    // ... (same as before)

    protected function getCallerFunction()
    {
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 4);

        if (isset($backtrace[3]['function'])) {
            return $backtrace[3]['function'];
        }

        return null;
    }


    public function error($message) {
        $this->log(self::ERROR, $message);
    }
    public function info($message) {
        $this->log(self::INFO, $message);
    }

    public function debug($message) {
        $this->log(self::DEBUG, $message);
    }
}


abstract class RequestMethod {
    const POST = 'POST';
    const GET = 'GET';
}

class ContentType {
    const X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
    const JSON = "application/json";
}