diff --git a/assets/images/logos/ikhokha.png b/assets/images/logos/ikhokha.png new file mode 100644 index 00000000..32f6fc3f Binary files /dev/null and b/assets/images/logos/ikhokha.png differ diff --git a/src/api/ikhokha_webhook.php b/src/api/ikhokha_webhook.php index 1519f2bd..17412cbc 100644 --- a/src/api/ikhokha_webhook.php +++ b/src/api/ikhokha_webhook.php @@ -251,7 +251,7 @@ if (in_array($normalized, ['PAID', 'SUCCESS', 'COMPLETED', 'SETTLED'], true)) { $upd->bind_param('s', $localPaymentId); $upd->execute(); $upd->close(); - sendAdminNotification('4WDCSA.co.za - New Membership Application - '.getFullName($user_id) , 'A new member has signed up, '.getFullName($user_id)); + sendAdminNotification('4WDCSA.co.za - Membership Payment Received - '.getFullName($user_id) , 'A Membership Payment has been received from '.getFullName($user_id)); } } diff --git a/src/config/functions.php b/src/config/functions.php index e51f42bb..ab95e405 100644 --- a/src/config/functions.php +++ b/src/config/functions.php @@ -1456,7 +1456,7 @@ function getInitialSurname($user_id) if ($stmt->fetch()) { $initial = strtoupper(substr($first_name, 0, 1)); - return $initial . ". " . $last_name; + return $initial . "." . $last_name; } else { return null; } @@ -1467,6 +1467,89 @@ function getInitialSurname($user_id) } } +function generatePaymentRef(string $type, ?int $course_trip_id, int $user_id): string +{ + $conn = openDatabaseConnection(); + + // 1. Normalize type + $type = strtoupper($type); + + // 2. Build prefix + switch ($type) { + case 'SUBS': + $year = (int)date('Y'); + $month = (int)date('n'); + + // If December, subscriptions are for next year + if ($month === 12) { + $year++; + } + + $prefix = "SUBS_" . $year; + break; + + case 'COURSE': + if (!$course_trip_id) { + throw new Exception("course_trip_id is required for COURSE payments"); + } + + $stmt = $conn->prepare( + "SELECT code FROM courses WHERE course_id = ?" + ); + $stmt->bind_param("i", $course_trip_id); + $stmt->execute(); + $stmt->bind_result($code); + + if (!$stmt->fetch()) { + throw new Exception("Invalid course_id: {$course_trip_id}"); + } + + $stmt->close(); + $prefix = "COURSE_" . strtoupper($code); + break; + + case 'TRIP': + if (!$course_trip_id) { + throw new Exception("course_trip_id is required for TRIP payments"); + } + + $stmt = $conn->prepare( + "SELECT trip_code FROM trips WHERE trip_id = ?" + ); + $stmt->bind_param("i", $course_trip_id); + $stmt->execute(); + $stmt->bind_result($trip_code); + + if (!$stmt->fetch()) { + throw new Exception("Invalid trip_id: {$course_trip_id}"); + } + + $stmt->close(); + $prefix = "TRIP_" . strtoupper($trip_code); + break; + + default: + throw new Exception("Unknown payment type: {$type}"); + } + + // 3. Get user initials + surname + $namePart = strtoupper(getInitialSurname($user_id)); + + if (!$namePart) { + throw new Exception("User not found for user_id: {$user_id}"); + } + + // 4. Add short entropy (trimmed for aesthetics) + $entropy = substr(shortEntropy(), -3); + + return "{$prefix}_{$namePart}_{$entropy}"; +} + +function shortEntropy(): string { + return strtoupper(base_convert((string)(microtime(true) * 1000), 10, 36)); +} + + function getLastName($user_id) { $conn = openDatabaseConnection(); diff --git a/src/pages/bookings/bookings.php b/src/pages/bookings/bookings.php index 9e215473..02406bb9 100644 --- a/src/pages/bookings/bookings.php +++ b/src/pages/bookings/bookings.php @@ -114,6 +114,7 @@ $user_id = $_SESSION['user_id']; // Loop through each row while ($row = $result->fetch_assoc()) { $booking_id = $row['booking_id']; + $payment_id = $row['payment_id']; $booking_type = $row['booking_type']; $from_date = $row['from_date']; $to_date = $row['to_date']; @@ -267,8 +268,8 @@ $user_id = $_SESSION['user_id'];
num_rows == 0) { $button_text = "No booking dates available"; @@ -189,8 +189,9 @@ $page_id = 'driver_training'; +
diff --git a/src/pages/bookings/trip-details.php b/src/pages/bookings/trip-details.php
index 54071272..3e4e39d5 100644
--- a/src/pages/bookings/trip-details.php
+++ b/src/pages/bookings/trip-details.php
@@ -205,30 +205,30 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
-
- Amount: R
+Reference:
+ + Pay Now with iKhokha + + +You will be redirected to iKhokha's Secure payment gateway.
Amount: R
-Reference:
- - Pay Now with iKhokha - - -You will be redirected to iKhokha's Secure Payment Gateway.
- -Please upload your proof of payment below.
-The Four Wheel Drive Club of Southern Africa
FNB
Account Number: 58810022334
Branch code: 250655
Reference:
Amount: R
+
+ Please upload your proof of payment below.
+The Four Wheel Drive Club of Southern Africa
FNB
Account Number: 58810022334
Branch code: 250655
Reference:
Amount: R
diff --git a/src/processors/process_application.php b/src/processors/process_application.php
index d4045c4b..e8d65c6d 100644
--- a/src/processors/process_application.php
+++ b/src/processors/process_application.php
@@ -6,7 +6,7 @@ require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php");
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null;
-$payment_id = uniqid();
+$payment_id = generatePaymentRef('SUBS', null, $user_id);
$status = 'AWAITING PAYMENT';
// If current month is December, attribute the membership year to the next year
$currentYear = intval(date('Y'));
@@ -210,9 +210,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
->format('Y-m-d');
}
- $stmt = $conn->prepare("INSERT INTO membership_fees (user_id, payment_amount, payment_date, membership_start_date, membership_end_date, payment_status, payment_id)
- VALUES (?, ?, ?, ?, ?, 'AWAITING PAYMENT', ?)");
- $stmt->bind_param("idssss", $user_id, $payment_amount, $payment_date, $membership_start_date, $membership_end_date, $payment_id);
+ $stmt = $conn->prepare("INSERT INTO membership_fees (user_id, payment_amount, payment_date, membership_start_date, membership_end_date, renewal_period_end, payment_status, payment_id)
+ VALUES (?, ?, ?, ?, ?, ?, 'AWAITING PAYMENT', ?)");
+ $stmt->bind_param("idsssss", $user_id, $payment_amount, $payment_date, $membership_start_date, $membership_end_date, $membership_end_date, $payment_id);
if ($stmt->execute()) {
// Commit the transaction
diff --git a/src/processors/process_course_booking.php b/src/processors/process_course_booking.php
index 7d2af6f0..27d20a86 100644
--- a/src/processors/process_course_booking.php
+++ b/src/processors/process_course_booking.php
@@ -93,11 +93,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$status = "AWAITING PAYMENT";
$type = 'course';
- $payment_id = uniqid();
+ $payment_id = generatePaymentRef('COURSE', $course_id, $user_id);
$publicRef = bin2hex(random_bytes(16));
$num_vehicles = 1;
$discountAmount = 0;
- $eft_id = strtoupper("COURSE ".date("m-d", strtotime($date))." ".getInitialSurname($user_id));
+ $eft_id = $payment_id;
$notes = "";
if ($pending_member){
$notes = "Membership Payment pending at time of booking. Please confirm payment has been received.";
diff --git a/src/processors/process_signature.php b/src/processors/process_signature.php
index 7f1eaef9..cc02df88 100644
--- a/src/processors/process_signature.php
+++ b/src/processors/process_signature.php
@@ -77,7 +77,7 @@ if (isset($_POST['signature'])) {
if ($mf && isset($mf['payment_amount'])) {
$amount = floatval($mf['payment_amount']);
// Use existing payment_id or generate one
- $payment_id = $mf['payment_id'] ?? uniqid('mem_', true);
+ $payment_id = $mf['payment_id'] ?? generatePaymentRef('SUBS', null, $user_id);;
if (empty($mf['payment_id'])) {
// Persist generated payment_id back to membership_fees
@@ -128,6 +128,7 @@ if (isset($_POST['signature'])) {
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
// After creating paylink, update paymentStatus to AWAITING PAYMENT
$paymentStatus = $paylink ? 'AWAITING PAYMENT' : $paymentStatus;
+ $token = encryptData($payment_id, $_ENV['SALT']);
} catch (Exception $e) {
// Log but do not fail signature save
error_log('iKhokha create error: ' . $e->getMessage());
@@ -140,7 +141,8 @@ if (isset($_POST['signature'])) {
$response = [
'status' => 'success',
'message' => 'Signature saved successfully!',
- 'paymentStatus' => $paymentStatus
+ 'paymentStatus' => $paymentStatus,
+ 'token' => $token ?? null
];
if (!empty($paylink)) {
$response['paylinkUrl'] = $paylink;
diff --git a/src/processors/process_trip_booking.php b/src/processors/process_trip_booking.php
index 5391d7e5..83b97929 100644
--- a/src/processors/process_trip_booking.php
+++ b/src/processors/process_trip_booking.php
@@ -105,10 +105,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$status = "AWAITING PAYMENT";
$description = $trip_name;
$type = 'trip';
- $payment_id = uniqid();
+ $payment_id = generatePaymentRef('TRIP', $trip_id, $user_id);
$publicRef = bin2hex(random_bytes(16));
// $eft_id = strtoupper(base_convert(time(), 10, 36)); // Convert timestamp to base36
- $eft_id = strtoupper($trip_code." ".getInitialSurname($user_id));
+ // $eft_id = strtoupper($trip_code." ".getInitialSurname($user_id));
// Insert booking into the database
@@ -147,7 +147,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Send invoice and admin notification
// sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description);
- sendAdminNotification('New Trip Booking - '.getFullName($user_id), getFullName($user_id).' has booked for '.$description);
+ // sendAdminNotification('New Trip Booking - '.getFullName($user_id), getFullName($user_id).' has booked for '.$description);
// Redirect to payment link if available
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;