iKhokha integration complete

This commit is contained in:
twotalesanimation
2025-12-15 00:36:34 +02:00
parent a66382661d
commit 477c2f2e04
26 changed files with 1625 additions and 81 deletions

View File

@@ -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;
}