appId = getenv('IKHOKHA_APP_ID') ?: ($_ENV['IKHOKHA_APP_ID'] ?? ''); $this->appSecret = getenv('IKHOKHA_APP_SECRET') ?: ($_ENV['IKHOKHA_APP_SECRET'] ?? ''); $this->apiUrl = getenv('IKHOKHA_API_URL') ?: ($_ENV['IKHOKHA_API_URL'] ?? ''); } /** * Make a request to the iKhokha API. Signs the payload per API docs. * $endpoint should be the path portion starting with '/public-api/...' */ private function request(string $endpoint, array $data, string $method = 'POST') { // Validate apiUrl if (empty($this->apiUrl)) { return ['error' => true, 'errno' => 3, 'message' => 'IKHOKHA_API_URL is not configured in environment']; } // If the configured API URL already contains the endpoint path, use it as-is. if ((function_exists('str_ends_with') && str_ends_with($this->apiUrl, $endpoint)) || (substr_compare($this->apiUrl, $endpoint, -strlen($endpoint)) === 0)) { $url = $this->apiUrl; } else { $url = rtrim($this->apiUrl, '/') . $endpoint; } $body = json_encode($data); // Build payload to sign: path + body and apply escape rules per iKhokha docs $parsed = parse_url($url); $path = $parsed['path'] ?? $endpoint; $payloadToSign = $path . $body; // Escape function from iKhokha example $escapeString = function ($str) { $escaped = preg_replace(['/[\\\"\'\"]/u', '/\x00/'], ['\\\\$0', '\\0'], (string)$str); $cleaned = str_replace('\/', '/', $escaped); return $cleaned; }; $escapedPayload = $escapeString($payloadToSign); $signature = hash_hmac('sha256', $escapedPayload, $this->appSecret); $ch = curl_init($url); $headers = [ 'Content-Type: application/json', "IK-APPID: {$this->appId}", "IK-SIGN: {$signature}" ]; // Optional debug logging to logs/ikhokha.log when IKHOKHA_DEBUG_LOG is true $debugLog = getenv('IKHOKHA_DEBUG_LOG') ?: ($_ENV['IKHOKHA_DEBUG_LOG'] ?? null); if ($debugLog) { $logPath = dirname(__DIR__) . '/logs/ikhokha.log'; $logEntry = [ 'time' => date('c'), 'url' => $url, 'headers' => $headers, 'body' => $data, 'signature' => $signature ]; @file_put_contents($logPath, json_encode(['request' => $logEntry]) . PHP_EOL, FILE_APPEND | LOCK_EX); } curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); if (strtoupper($method) === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $body); } else { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); } $response = curl_exec($ch); $errno = curl_errno($ch); $error = curl_error($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Log response if debug enabled if (!empty($debugLog)) { $logPath = dirname(__DIR__) . '/logs/ikhokha.log'; $respEntry = [ 'time' => date('c'), 'http_code' => $httpCode, 'errno' => $errno, 'error' => $error, 'response' => $response ]; @file_put_contents($logPath, json_encode(['response' => $respEntry]) . PHP_EOL, FILE_APPEND | LOCK_EX); } if ($response === false) { return ['error' => true, 'message' => $error, 'errno' => $errno]; } return json_decode($response, true); } /** * Create a payment link using the iKhokha create payment endpoint. * $body must match iKhokha request schema (amount in smallest unit, urls, externalTransactionID, etc.) */ public function createPaymentLink(array $body) { return $this->request('/public-api/v1/api/payment', $body, 'POST'); } public function getPaymentStatus($paymentId) { // Use the GET status endpoint $endpoint = '/public-api/v1/api/getStatus/' . urlencode($paymentId); return $this->request($endpoint, [], 'GET'); } }