iKhokha integration complete
This commit is contained in:
@@ -29,6 +29,39 @@ function openDatabaseConnection()
|
||||
return $conn;
|
||||
}
|
||||
|
||||
function progress_log($message, $context = null)
|
||||
{
|
||||
try {
|
||||
// Site root (same logic you already use elsewhere)
|
||||
$rootPath = dirname(dirname(__DIR__));
|
||||
$logFile = $rootPath . '/progress.log';
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
|
||||
// Normalize message
|
||||
if (is_array($message) || is_object($message)) {
|
||||
$message = json_encode($message, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
// Normalize context (optional extra data)
|
||||
if ($context !== null) {
|
||||
if (is_array($context) || is_object($context)) {
|
||||
$context = json_encode($context, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
}
|
||||
$message .= ' | CONTEXT: ' . $context;
|
||||
}
|
||||
|
||||
$line = "[{$timestamp}] {$message}" . PHP_EOL;
|
||||
|
||||
// Append atomically
|
||||
file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Never allow logging failures to break execution
|
||||
// Silent by design
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getPriceByDescription($description)
|
||||
{
|
||||
@@ -725,6 +758,120 @@ function processPayment($payment_id, $amount, $description)
|
||||
|
||||
}
|
||||
|
||||
function createIkhokhaPayment($payment_id, $amount, $description, $publicRef)
|
||||
{
|
||||
|
||||
// Base requester URL: prefer explicit env var, otherwise build from request
|
||||
$baseUrl = rtrim($_ENV['IKHOKHA_REQUESTER_URL'] ?? ($_SERVER['REQUEST_SCHEME'] ?? 'https') . '://' . ($_SERVER['HTTP_HOST'] ?? ''), '/');
|
||||
|
||||
$endpoint = $_ENV['IKHOKHA_ENDPOINT'];
|
||||
$appID = $_ENV['IKHOKHA_APP_ID'];
|
||||
$appSecret = $_ENV['IKHOKHA_APP_SECRET'];
|
||||
$requestBody = [
|
||||
"entityID" => $payment_id,
|
||||
"externalEntityID" => $payment_id,
|
||||
"amount" => $amount * 100,
|
||||
"currency" => "ZAR",
|
||||
"requesterUrl" => $_ENV['IKHOKHA_REQUESTER_URL'] ?? $baseUrl,
|
||||
"description" => $description,
|
||||
"paymentReference" => $description,
|
||||
"mode" => $_ENV['IKHOKHA_MODE'] ?? 'live',
|
||||
"externalTransactionID" => $payment_id,
|
||||
"urls" => [
|
||||
"callbackUrl" => $_ENV['IKHOKHA_CALLBACK_URL'],
|
||||
|
||||
"successPageUrl" => $_ENV['IKHOKHA_SUCCESS_URL']
|
||||
. "?ref=" . urlencode($publicRef),
|
||||
|
||||
"failurePageUrl" => $_ENV['IKHOKHA_FAILURE_URL']
|
||||
. "?ref=" . urlencode($publicRef),
|
||||
|
||||
"cancelUrl" => $_ENV['IKHOKHA_CANCEL_URL']
|
||||
. "?ref=" . urlencode($publicRef),
|
||||
]
|
||||
|
||||
];
|
||||
$stringifiedBody = json_encode($requestBody);
|
||||
$payloadToSign = createPayloadToSign($endpoint, $stringifiedBody);
|
||||
$ikSign = generateSignature($payloadToSign, $appSecret);
|
||||
// Initialize cURL session
|
||||
$ch = curl_init($endpoint);
|
||||
// Set cURL options
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $stringifiedBody);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"Content-Type: application/json",
|
||||
"IK-APPID: $appID",
|
||||
"IK-SIGN: $ikSign"
|
||||
]);
|
||||
// Execute cURL session
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Decode and output the response
|
||||
$resp = json_decode($response, true);
|
||||
|
||||
// Persist provider metadata into payments table if we have a response
|
||||
$conn = openDatabaseConnection();
|
||||
if ($conn === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$provider = 'ikhokha';
|
||||
$provider_payment_id = $resp['paylinkID'] ?? $resp['paylinkId'] ?? $resp['paylink_id'] ?? null;
|
||||
$payment_link = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
|
||||
$provider_status = $resp['responseCode'] ?? ($resp['status'] ?? null);
|
||||
$provider_response = json_encode($resp);
|
||||
|
||||
// Update payments row with provider info. If a paylink was created (responseCode == '00'), keep status awaiting payment.
|
||||
$newStatus = null;
|
||||
if (!empty($payment_link) && ($provider_status === '00' || $provider_status === '0' || $provider_status === 0)) {
|
||||
$newStatus = 'AWAITING PAYMENT';
|
||||
}
|
||||
|
||||
if ($newStatus) {
|
||||
$stmt = $conn->prepare("UPDATE payments SET provider = ?, provider_payment_id = ?, payment_link = ?, provider_status = ?, provider_response = ?, status = ? WHERE payment_id = ? LIMIT 1");
|
||||
if ($stmt) {
|
||||
$stmt->bind_param('sssssss', $provider, $provider_payment_id, $payment_link, $provider_status, $provider_response, $newStatus, $payment_id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
} else {
|
||||
$stmt = $conn->prepare("UPDATE payments SET provider = ?, provider_payment_id = ?, payment_link = ?, provider_status = ?, provider_response = ? WHERE payment_id = ? LIMIT 1");
|
||||
if ($stmt) {
|
||||
$stmt->bind_param('ssssss', $provider, $provider_payment_id, $payment_link, $provider_status, $provider_response, $payment_id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
function escapeString($str) {
|
||||
$escaped = preg_replace(['/[\\"\'\"]/u', '/\x00/'], ['\\\\$0', '\\0'], (string)$str);
|
||||
$cleaned = str_replace('\/', '/', $escaped);
|
||||
return $cleaned;
|
||||
}
|
||||
|
||||
function createPayloadToSign($urlPath, $body) {
|
||||
$parsedUrl = parse_url($urlPath);
|
||||
$basePath = $parsedUrl['path'];
|
||||
if (!$basePath) {
|
||||
throw new Exception("No path present in the URL");
|
||||
}
|
||||
$payload = $basePath . $body;
|
||||
$escapedPayloadString = escapeString($payload);
|
||||
return $escapedPayloadString;
|
||||
}
|
||||
|
||||
function generateSignature($payloadToSign, $secret) {
|
||||
return hash_hmac('sha256', $payloadToSign, $secret);
|
||||
}
|
||||
|
||||
function processMembershipPayment($payment_id, $amount, $description)
|
||||
{
|
||||
$conn = openDatabaseConnection();
|
||||
@@ -1984,6 +2131,8 @@ function processLegacyMembership($user_id) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SECURITY WARNING: This function uses dynamic table/column names which makes it vulnerable to SQL injection.
|
||||
* ONLY call this function with whitelisted table and column names.
|
||||
@@ -3225,3 +3374,38 @@ function unlinkSecondaryUser($link_id, $primary_user_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the payment_link for a given internal payment_id from the payments table.
|
||||
* Returns the payment_link string on success or null if not found / on error.
|
||||
*
|
||||
* @param string $payment_id
|
||||
* @return string|null
|
||||
*/
|
||||
function getPaymentLinkByPaymentId($payment_id)
|
||||
{
|
||||
$conn = openDatabaseConnection();
|
||||
if ($conn === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("SELECT payment_link FROM payments WHERE payment_id = ? LIMIT 1");
|
||||
if (!$stmt) {
|
||||
$conn->close();
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt->bind_param('s', $payment_id);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($payment_link);
|
||||
$found = $stmt->fetch();
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
|
||||
if ($found) {
|
||||
return $payment_link;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user