3 Commits

Author SHA1 Message Date
twotalesanimation
0e6ecd127f post auditlog implementation for bookings and payments 2025-12-15 10:52:09 +02:00
twotalesanimation
702e04e9bf pre auditlog implementations 2025-12-15 10:44:56 +02:00
twotalesanimation
d2c99e86b4 mostly complete payment system 2025-12-15 10:18:25 +02:00
17 changed files with 365 additions and 124 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -19,6 +19,27 @@ if (!isset($_SESSION['updates_modal_shown'])) {
$showUpdatesModal = false; $showUpdatesModal = false;
} }
// Show renew membership modal for logged-in users and where membership_fees payment_status is not PENDING RENEWAL. only show once per session
$showRenewModal = isset($_SESSION['user_id']) ? true : false;
if ($showRenewModal) {
if (!isset($_SESSION['renew_modal_shown'])) {
$_SESSION['renew_modal_shown'] = true;
} else {
$showRenewModal = false;
}
$user_id = $_SESSION['user_id'];
$stmt = $conn->prepare("SELECT payment_status FROM membership_fees WHERE user_id = ? LIMIT 1");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$stmt->bind_result($payment_status);
$stmt->fetch();
$stmt->close();
if ($payment_status === 'PENDING RENEWAL') {
$showRenewModal = false;
}
}
if (isset($_SESSION['user_id']) && isset($conn) && $conn !== null) { if (isset($_SESSION['user_id']) && isset($conn) && $conn !== null) {
$userId = $_SESSION['user_id']; $userId = $_SESSION['user_id'];
$stmt = $conn->prepare("SELECT user_id FROM membership_application WHERE user_id = ? AND accept_indemnity = 0 LIMIT 1"); $stmt = $conn->prepare("SELECT user_id FROM membership_application WHERE user_id = ? AND accept_indemnity = 0 LIMIT 1");
@@ -657,34 +678,72 @@ if (countUpcomingTrips() > 0) { ?>
const modal = document.getElementById('updatesModal'); const modal = document.getElementById('updatesModal');
const closeBtn = document.querySelector('.updates-modal-close'); const closeBtn = document.querySelector('.updates-modal-close');
const showModal = <?php echo $showUpdatesModal ? 'true' : 'false'; ?>; const showModal = <?php echo $showUpdatesModal ? 'true' : 'false'; ?>;
const showRenewModal = <?php echo $showRenewModal ? 'true' : 'false'; ?>;
if (showModal) { if (showModal && modal) {
// Show modal after a short delay for better UX // Show updates modal after a short delay for better UX
setTimeout(function() { setTimeout(function() {
modal.style.display = 'flex'; modal.style.display = 'flex';
}, 500); }, 500);
} }
// Close modal when X is clicked // Close updates modal when X is clicked
if (closeBtn) {
closeBtn.addEventListener('click', function() { closeBtn.addEventListener('click', function() {
modal.style.display = 'none'; if (modal) modal.style.display = 'none';
}); });
}
// Close modal when clicking outside the modal content // Close updates modal when clicking outside the modal content
if (modal) {
modal.addEventListener('click', function(event) { modal.addEventListener('click', function(event) {
if (event.target === modal) { if (event.target === modal) {
modal.style.display = 'none'; modal.style.display = 'none';
} }
}); });
}
// Show renew membership Bootstrap modal for logged-in users
try {
const renewModalEl = document.getElementById('renewModal');
if (showRenewModal && renewModalEl && typeof bootstrap !== 'undefined') {
setTimeout(function() {
const renewModal = new bootstrap.Modal(renewModalEl);
renewModal.show();
}, 700);
}
} catch (e) {
console.warn('Renew modal show failed', e);
}
}); });
</script> </script>
<!-- Updates Modal -->
<!-- Renew Membership Modal (shown to logged-in users) -->
<div class="modal fade" id="renewModal" tabindex="-1" aria-labelledby="renewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<!-- <div class="modal-header bg-secondary text-white">
<h5 class="modal-title" id="renewModalLabel">Membership Renewal Reminder</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> -->
<div class="modal-body">
Your membership will be expiring soon. Click below to renew now.
<a style="width:100%; display:block;" href="renew_membership" class="theme-btn style-two style-three mt-3">Renew Now</a>
</div>
<div class="modal-footer">
<button type="button" style="width:100%; display:block;" class="theme-btn" data-bs-dismiss="modal">Remind Me Later</button>
</div>
</div>
</div>
</div>
<!-- Updates Modal --> <!-- Updates Modal -->
<div id="updatesModal" class="updates-modal"> <div id="updatesModal" class="updates-modal">
<div class="updates-modal-content"> <div class="updates-modal-content">
<span class="updates-modal-close">&times;</span> <span class="updates-modal-close">&times;</span>
<div class="updates-modal-header"> <div class="updates-modal-header">
<h2>What's New</h2> <h2>What's New on 4WDCSA.co.za</h2>
</div> </div>
<div class="updates-modal-body"> <div class="updates-modal-body">
<div class="update-item"> <div class="update-item">

View File

@@ -78,6 +78,10 @@ if (!$bypass) {
progress_log('iKhokha webhook: signature mismatch'); progress_log('iKhokha webhook: signature mismatch');
progress_log('EXPECTED SIGN: ' . $expected); progress_log('EXPECTED SIGN: ' . $expected);
progress_log('RECEIVED SIGN: ' . $ikSign); progress_log('RECEIVED SIGN: ' . $ikSign);
// Audit signature mismatch
if (function_exists('auditLog')) {
auditLog(null, 'IKHOKHA_SIGNATURE_MISMATCH', 'webhook', null, ['expected' => $expected, 'received' => $ikSign]);
}
exit('Invalid signature'); exit('Invalid signature');
} }
@@ -183,6 +187,9 @@ if (!$localPaymentId) {
http_response_code(404); http_response_code(404);
progress_log('iKhokha webhook: payment not found'); progress_log('iKhokha webhook: payment not found');
progress_log(json_encode([$externalTransactionID, $providerPaymentId])); progress_log(json_encode([$externalTransactionID, $providerPaymentId]));
if (function_exists('auditLog')) {
auditLog(null, 'IKHOKHA_PAYMENT_NOT_FOUND', 'payment', null, ['externalTransactionID' => $externalTransactionID, 'providerPaymentId' => $providerPaymentId]);
}
exit('Payment not found'); exit('Payment not found');
} }
@@ -209,6 +216,9 @@ if ($update) {
); );
$update->execute(); $update->execute();
$update->close(); $update->close();
if (function_exists('auditLog')) {
auditLog($user_id, 'PAYMENT_PROVIDER_RESPONSE_SAVED', 'payment', null, ['payment_id' => $localPaymentId, 'provider_payment_id' => $providerPaymentId, 'provider_status' => $providerStatus]);
}
} }
/** /**
@@ -228,6 +238,9 @@ if (in_array($normalized, ['PAID', 'SUCCESS', 'COMPLETED', 'SETTLED'], true)) {
$setPaid->bind_param('s', $localPaymentId); $setPaid->bind_param('s', $localPaymentId);
$setPaid->execute(); $setPaid->execute();
$setPaid->close(); $setPaid->close();
if (function_exists('auditLog')) {
auditLog($user_id, 'PAYMENT_MARKED_PAID', 'payment', null, ['payment_id' => $localPaymentId]);
}
} }
// Booking or membership update // Booking or membership update
@@ -240,6 +253,9 @@ if (in_array($normalized, ['PAID', 'SUCCESS', 'COMPLETED', 'SETTLED'], true)) {
$upd->execute(); $upd->execute();
$upd->close(); $upd->close();
sendAdminNotification('4WDCSA.co.za - New Booking - '.getFullName($user_id) , 'We have received a payment for a new booking for '.$description.' from '.getFullName($user_id)); sendAdminNotification('4WDCSA.co.za - New Booking - '.getFullName($user_id) , 'We have received a payment for a new booking for '.$description.' from '.getFullName($user_id));
if (function_exists('auditLog')) {
auditLog($user_id, 'BOOKING_PAYMENT_MARKED_PAID', 'bookings', $booking_id, ['payment_id' => $localPaymentId]);
}
} }
} else { } else {
$upd = $conn->prepare( $upd = $conn->prepare(
@@ -251,7 +267,10 @@ if (in_array($normalized, ['PAID', 'SUCCESS', 'COMPLETED', 'SETTLED'], true)) {
$upd->bind_param('s', $localPaymentId); $upd->bind_param('s', $localPaymentId);
$upd->execute(); $upd->execute();
$upd->close(); $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));
if (function_exists('auditLog')) {
auditLog($user_id, 'MEMBERSHIP_PAYMENT_MARKED_PAID', 'membership_fees', null, ['payment_id' => $localPaymentId]);
}
} }
} }

View File

@@ -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) function getLastName($user_id)
{ {
$conn = openDatabaseConnection(); $conn = openDatabaseConnection();

View File

@@ -114,6 +114,7 @@ $user_id = $_SESSION['user_id'];
// Loop through each row // Loop through each row
while ($row = $result->fetch_assoc()) { while ($row = $result->fetch_assoc()) {
$booking_id = $row['booking_id']; $booking_id = $row['booking_id'];
$payment_id = $row['payment_id'];
$booking_type = $row['booking_type']; $booking_type = $row['booking_type'];
$from_date = $row['from_date']; $from_date = $row['from_date'];
$to_date = $row['to_date']; $to_date = $row['to_date'];
@@ -267,8 +268,8 @@ $user_id = $_SESSION['user_id'];
<div class="destination-footer"> <div class="destination-footer">
<span class="price"><span>Booking Total: R ' . number_format($amount, 2) . '</span></span>'; <span class="price"><span>Booking Total: R ' . number_format($amount, 2) . '</span></span>';
if ($status == "AWAITING PAYMENT") { if ($status == "AWAITING PAYMENT") {
echo '<a href="' . url('payment_confirmation') . '?token=' . encryptData($booking_id, $salt) . '" class="theme-btn style-two style-three"> echo '<a href="' . getPaymentLinkByPaymentId($payment_id) . '" class="theme-btn style-two style-three">
<span data-hover="PAYMENT INFO">' . $status . '</span> <span data-hover="PAY NOW">' . $status . '</span>
</a>'; </a>';
} else { } else {
echo '<a href="" class="theme-btn style-two style-three"> echo '<a href="" class="theme-btn style-two style-three">

View File

@@ -177,7 +177,7 @@ $page_id = 'driver_training';
</div> </div>
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>"> <input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<?php <?php
$button_text = "Book Now"; $button_text = "PROCEED TO PAYMENT";
$button_disabled = ""; $button_disabled = "";
if (!$result || $result->num_rows == 0) { if (!$result || $result->num_rows == 0) {
$button_text = "No booking dates available"; $button_text = "No booking dates available";
@@ -189,8 +189,9 @@ $page_id = 'driver_training';
<i class="fal fa-arrow-right"></i> <i class="fal fa-arrow-right"></i>
</button> </button>
<div class="text-center"> <div class="text-center">
<a href="contact">Need some help?</a> <a href="contact">You will be redirected to iKhokha's Secure payment gateway.</a>
</div> </div>
<img src="assets/images/logos/ikhokha.png"alt="Secure Payment Badges" style="max-width: 200px; display: block; margin: 10px auto 0;">
</form> </form>
</div> </div>

View File

@@ -594,13 +594,14 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
</button> </button>
<?php else: ?> <?php else: ?>
<button type="submit" class="theme-btn style-two w-100 mt-15 mb-5"> <button type="submit" class="theme-btn style-two w-100 mt-15 mb-5">
<span data-hover="Book Now">Book Now</span> <span data-hover="PROCEED TO PAYMENT">PROCEED TO PAYMENT</span>
<i class="fal fa-arrow-right"></i> <i class="fal fa-arrow-right"></i>
</button> </button>
<?php endif; ?> <?php endif; ?>
<div class="text-center"> <div class="text-center">
<a href="contact">Need some help?</a> <a href="contact">You will be redirected to iKhokha's Secure payment gateway.</a>
</div> </div>
<img src="assets/images/logos/ikhokha.png" alt="Secure Payment Badges" style="max-width: 200px; display: block; margin: 10px auto 0;">
</form> </form>
</div> </div>

View File

@@ -68,7 +68,15 @@ $stmt->fetch();
$stmt->close(); $stmt->close();
// If request includes payment_id, fetch provider paylink from payments table // If request includes payment_id, fetch provider paylink from payments table
$payment_id = $_GET['payment_id'] ?? null; if (!isset($_GET['token']) || empty($_GET['token'])) {
header("Location: membership_details");
exit();
}
$token = $_GET['token'];
// echo $token;
// Sanitize the trip_id to prevent SQL injection
$payment_id = decryptData($token, $_ENV['SALT']);
$payment_link = null; $payment_link = null;
if ($payment_id) { if ($payment_id) {
$pstmt = $conn->prepare("SELECT payment_link, amount, status, provider FROM payments WHERE payment_id = ? LIMIT 1"); $pstmt = $conn->prepare("SELECT payment_link, amount, status, provider FROM payments WHERE payment_id = ? LIMIT 1");
@@ -110,7 +118,10 @@ if ($payment_id) {
<span data-hover="Pay Now with iKhokha">Pay Now with iKhokha</span> <span data-hover="Pay Now with iKhokha">Pay Now with iKhokha</span>
<i class="fal fa-arrow-right"></i> <i class="fal fa-arrow-right"></i>
</a> </a>
<p style="margin-top:10px;">You will be redirected to iKhokha's Secure Payment Gateway.</p> <div class="text-center">
<p>You will be redirected to iKhokha's Secure payment gateway.</p>
</div>
<img src="assets/images/logos/ikhokha.png" alt="Secure Payment Badges" style="max-width: 200px; display: block; margin: 10px auto 0;">
<?php } else { ?> <?php } else { ?>
<p>Please upload your proof of payment below.</p> <p>Please upload your proof of payment below.</p>
<h5>Payment Details:</h5> <h5>Payment Details:</h5>

View File

@@ -11,8 +11,20 @@ if (isset($_SESSION['user_id'])) {
exit(); // Stop further script execution exit(); // Stop further script execution
} }
//if membership_fees payment_status is PENDING RENEWAL, redirect to membership_details.php
$stmt = $conn->prepare("SELECT payment_status FROM membership_fees WHERE user_id = ? LIMIT 1");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$stmt->bind_result($payment_status);
$stmt->fetch();
$stmt->close();
$payment_id = uniqid(); if ($payment_status === 'PENDING RENEWAL') {
header("Location: membership_details.php");
exit();
}
$payment_id = generatePaymentRef('SUBS', null, $user_id);
$payment_amount = getPriceByDescription('membership_fees'); $payment_amount = getPriceByDescription('membership_fees');
$payment_date = date('Y-m-d'); $payment_date = date('Y-m-d');
$renewal_period_end = getMembershipEndDate($user_id); $renewal_period_end = getMembershipEndDate($user_id);
@@ -29,6 +41,11 @@ if ($stmt->execute()) {
// Commit the transaction // Commit the transaction
$conn->commit(); $conn->commit();
// Audit: user initiated membership renewal
if (function_exists('auditLog')) {
auditLog($user_id, 'MEMBERSHIP_RENEWAL_INITIATED', 'membership_fees', null, ['payment_id' => $payment_id, 'amount' => $payment_amount]);
}
$checkP = $conn->prepare("SELECT COUNT(*) AS cnt FROM payments WHERE payment_id = ? LIMIT 1"); $checkP = $conn->prepare("SELECT COUNT(*) AS cnt FROM payments WHERE payment_id = ? LIMIT 1");
if ($checkP) { if ($checkP) {
$checkP->bind_param('s', $payment_id); $checkP->bind_param('s', $payment_id);
@@ -65,8 +82,9 @@ if ($stmt->execute()) {
$publicRef = $publicRef ?? bin2hex(random_bytes(16)); $publicRef = $publicRef ?? bin2hex(random_bytes(16));
$resp = createIkhokhaPayment($payment_id, $payment_amount, $description, $publicRef); $resp = createIkhokhaPayment($payment_id, $payment_amount, $description, $publicRef);
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null; $paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
$token = encryptData($payment_id, $_ENV['SALT']);
if ($paylink) { if ($paylink) {
header('Location: membership_payment?payment_id=' . $payment_id); header('Location: membership_payment?token=' . $token);
exit(); exit();
} else { } else {
header("Location: membership_details"); header("Location: membership_details");

View File

@@ -156,7 +156,7 @@ $page_id = 'bush_mechanics';
</div> </div>
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>"> <input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<?php <?php
$button_text = "Book Now"; $button_text = "PROCEED TO PAYMENT";
$button_disabled = ""; $button_disabled = "";
if (!$result || $result->num_rows == 0) { if (!$result || $result->num_rows == 0) {
$button_text = "No booking dates available"; $button_text = "No booking dates available";
@@ -168,8 +168,9 @@ $page_id = 'bush_mechanics';
<i class="fal fa-arrow-right"></i> <i class="fal fa-arrow-right"></i>
</button> </button>
<div class="text-center"> <div class="text-center">
<a href="contact">Need some help?</a> <a href="contact">You will be redirected to iKhokha's Secure payment gateway.</a>
</div> </div>
<img src="assets/images/logos/ikhokha.png"alt="Secure Payment Badges" style="max-width: 200px; display: block; margin: 10px auto 0;">
</form> </form>
</div> </div>

View File

@@ -107,14 +107,14 @@ if (isset($_SESSION['user_id'])) {
if (response.status === 'success') { if (response.status === 'success') {
// If provider returned a direct paylink, go there immediately // If provider returned a direct paylink, go there immediately
if (response.paylinkUrl) { if (response.paylinkUrl) {
window.location.href = 'membership_payment.php?payment_id=' + encodeURIComponent(response.payment_id); window.location.href = 'membership_payment?token=' + encodeURIComponent(response.token);
return; return;
} }
// If we have a payment_id, redirect to membership_payment with it // If we have a payment_id, redirect to membership_payment with it
// if (response.payment_id) { // if (response.payment_id) {
// setTimeout(function() { // setTimeout(function() {
// window.location.href = 'membership_payment.php?payment_id=' + encodeURIComponent(response.payment_id); // window.location.href = 'membership_payment.php?payment_id=' + encodeURIComponent(response.token);
// }, 800); // }, 800);
// return; // return;
// } // }

View File

@@ -154,7 +154,7 @@ $page_id = 'rescue_recovery';
</div> </div>
</div> </div>
<?php <?php
$button_text = "Book Now"; $button_text = "PROCEED TO PAYMENT";
$button_disabled = ""; $button_disabled = "";
if (!$result || $result->num_rows == 0) { if (!$result || $result->num_rows == 0) {
$button_text = "No booking dates available"; $button_text = "No booking dates available";
@@ -165,9 +165,11 @@ $page_id = 'rescue_recovery';
<span data-hover="<?php echo $button_text; ?>"><?php echo $button_text; ?></span> <span data-hover="<?php echo $button_text; ?>"><?php echo $button_text; ?></span>
<i class="fal fa-arrow-right"></i> <i class="fal fa-arrow-right"></i>
</button> </button>
<div class="text-center"> <div class="text-center">
<a href="mailto:info@4wdcsa.co.za">Need some help?</a> <a href="contact">You will be redirected to iKhokha's Secure payment gateway.</a>
</div> </div>
<img src="assets/images/logos/ikhokha.png"alt="Secure Payment Badges" style="max-width: 200px; display: block; margin: 10px auto 0;">
</form> </form>
</div> </div>

View File

@@ -6,7 +6,7 @@ require_once($rootPath . "/src/config/connection.php");
require_once($rootPath . "/src/config/functions.php"); require_once($rootPath . "/src/config/functions.php");
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null; $user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null;
$payment_id = uniqid(); $payment_id = generatePaymentRef('SUBS', null, $user_id);
$status = 'AWAITING PAYMENT'; $status = 'AWAITING PAYMENT';
// If current month is December, attribute the membership year to the next year // If current month is December, attribute the membership year to the next year
$currentYear = intval(date('Y')); $currentYear = intval(date('Y'));
@@ -210,9 +210,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
->format('Y-m-d'); ->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) $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', ?)"); VALUES (?, ?, ?, ?, ?, ?, 'AWAITING PAYMENT', ?)");
$stmt->bind_param("idssss", $user_id, $payment_amount, $payment_date, $membership_start_date, $membership_end_date, $payment_id); $stmt->bind_param("idsssss", $user_id, $payment_amount, $payment_date, $membership_start_date, $membership_end_date, $membership_end_date, $payment_id);
if ($stmt->execute()) { if ($stmt->execute()) {
// Commit the transaction // Commit the transaction
@@ -221,6 +221,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Optionally send an invoice referencing the internal payment id // Optionally send an invoice referencing the internal payment id
// sendInvoice(getEmail($user_id), getFullName($user_id), $payment_id, formatCurrency($payment_amount), $description); // sendInvoice(getEmail($user_id), getFullName($user_id), $payment_id, formatCurrency($payment_amount), $description);
// sendAdminNotification('4WDCSA.co.za - New Membership Application - '.$last_name , 'A new member has signed up, '.$first_name.' '.$last_name); // sendAdminNotification('4WDCSA.co.za - New Membership Application - '.$last_name , 'A new member has signed up, '.$first_name.' '.$last_name);
// Audit: membership application submitted
if (function_exists('auditLog')) {
auditLog($user_id, 'MEMBERSHIP_APPLICATION_SUBMITTED', 'membership_application', null, ['payment_id' => $payment_id, 'amount' => $payment_amount ?? null]);
}
header("Location: indemnity"); header("Location: indemnity");
// Success message // Success message
$response = [ $response = [

View File

@@ -79,6 +79,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt->bind_param('sissiiiidd', $type, $user_id, $from_date, $to_date, $num_vehicles, $num_adults, $num_children, $add_firewood, $total_amount, $discount_amount); $stmt->bind_param('sissiiiidd', $type, $user_id, $from_date, $to_date, $num_vehicles, $num_adults, $num_children, $add_firewood, $total_amount, $discount_amount);
if ($stmt->execute()) { if ($stmt->execute()) {
// Get booking id and audit
$booking_id = $conn->insert_id;
if (function_exists('auditLog')) {
auditLog($user_id, 'BOOKING_CREATED', 'bookings', $booking_id, ['total_amount' => $total_amount, 'from' => $from_date, 'to' => $to_date]);
}
// Redirect to success page or display success message // Redirect to success page or display success message
echo "<script>alert('Booking successfully created!'); window.location.href = 'booking.php';</script>"; echo "<script>alert('Booking successfully created!'); window.location.href = 'booking.php';</script>";
} else { } else {

View File

@@ -93,11 +93,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$status = "AWAITING PAYMENT"; $status = "AWAITING PAYMENT";
$type = 'course'; $type = 'course';
$payment_id = uniqid(); $payment_id = generatePaymentRef('COURSE', $course_id, $user_id);
$publicRef = bin2hex(random_bytes(16)); $publicRef = bin2hex(random_bytes(16));
$num_vehicles = 1; $num_vehicles = 1;
$discountAmount = 0; $discountAmount = 0;
$eft_id = strtoupper("COURSE ".date("m-d", strtotime($date))." ".getInitialSurname($user_id)); $eft_id = $payment_id;
$notes = ""; $notes = "";
if ($pending_member){ if ($pending_member){
$notes = "Membership Payment pending at time of booking. Please confirm payment has been received."; $notes = "Membership Payment pending at time of booking. Please confirm payment has been received.";
@@ -118,6 +118,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($stmt->execute()) { if ($stmt->execute()) {
$booking_id = $conn->insert_id; $booking_id = $conn->insert_id;
// Audit booking creation
if (function_exists('auditLog')) {
auditLog($user_id, 'COURSE_BOOKING_CREATED', 'bookings', $booking_id, ['course_id' => $course_id, 'payment_id' => $payment_id, 'amount' => $payment_amount]);
}
if ($payment_amount < 1) { if ($payment_amount < 1) {
if (processZeroPayment($payment_id, $payment_amount, $description)) { if (processZeroPayment($payment_id, $payment_amount, $description)) {
echo "<script>alert('Booking successfully created!'); window.location.href = 'bookings.php';</script>"; echo "<script>alert('Booking successfully created!'); window.location.href = 'bookings.php';</script>";

View File

@@ -56,6 +56,10 @@ if (isset($_POST['signature'])) {
$stmt->bind_param('si', $display_path, $user_id); $stmt->bind_param('si', $display_path, $user_id);
if ($stmt->execute()) { if ($stmt->execute()) {
// Audit: signature saved
if (function_exists('auditLog')) {
auditLog($user_id, 'SIGNATURE_SAVED', 'membership_application', null, ['path' => $display_path]);
}
// Check the payment status // Check the payment status
$paymentStatus = checkMembershipPaymentStatus($user_id) ? 'PAID' : 'NOT_PAID'; $paymentStatus = checkMembershipPaymentStatus($user_id) ? 'PAID' : 'NOT_PAID';
@@ -77,7 +81,7 @@ if (isset($_POST['signature'])) {
if ($mf && isset($mf['payment_amount'])) { if ($mf && isset($mf['payment_amount'])) {
$amount = floatval($mf['payment_amount']); $amount = floatval($mf['payment_amount']);
// Use existing payment_id or generate one // 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'])) { if (empty($mf['payment_id'])) {
// Persist generated payment_id back to membership_fees // Persist generated payment_id back to membership_fees
@@ -116,7 +120,12 @@ if (isset($_POST['signature'])) {
$ins = $conn->prepare("INSERT INTO payments (payment_id, user_id, amount, status, description, public_ref) VALUES (?, ?, ?, ?, ?, ?)"); $ins = $conn->prepare("INSERT INTO payments (payment_id, user_id, amount, status, description, public_ref) VALUES (?, ?, ?, ?, ?, ?)");
if ($ins) { if ($ins) {
$ins->bind_param('sidsss', $payment_id, $user_id, $amount, $status, $description, $publicRef); $ins->bind_param('sidsss', $payment_id, $user_id, $amount, $status, $description, $publicRef);
$ins->execute(); if ($ins->execute()) {
// Audit: payment row created for membership
if (function_exists('auditLog')) {
auditLog($user_id, 'MEMBERSHIP_PAYMENT_CREATED', 'payments', null, ['payment_id' => $payment_id, 'amount' => $amount]);
}
}
$ins->close(); $ins->close();
} }
} }
@@ -128,9 +137,17 @@ if (isset($_POST['signature'])) {
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null; $paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;
// After creating paylink, update paymentStatus to AWAITING PAYMENT // After creating paylink, update paymentStatus to AWAITING PAYMENT
$paymentStatus = $paylink ? 'AWAITING PAYMENT' : $paymentStatus; $paymentStatus = $paylink ? 'AWAITING PAYMENT' : $paymentStatus;
$token = encryptData($payment_id, $_ENV['SALT']);
// Audit: paylink created (or attempted)
if (function_exists('auditLog')) {
auditLog($user_id, 'IKHOKHA_PAYLINK_CREATED', 'payments', null, ['payment_id' => $payment_id, 'paylink' => $paylink]);
}
} catch (Exception $e) { } catch (Exception $e) {
// Log but do not fail signature save // Log but do not fail signature save
error_log('iKhokha create error: ' . $e->getMessage()); error_log('iKhokha create error: ' . $e->getMessage());
if (function_exists('auditLog')) {
auditLog($user_id, 'IKHOKHA_PAYLINK_FAILED', 'payments', null, ['payment_id' => $payment_id, 'error' => $e->getMessage()]);
}
} }
} }
} }
@@ -140,7 +157,8 @@ if (isset($_POST['signature'])) {
$response = [ $response = [
'status' => 'success', 'status' => 'success',
'message' => 'Signature saved successfully!', 'message' => 'Signature saved successfully!',
'paymentStatus' => $paymentStatus 'paymentStatus' => $paymentStatus,
'token' => $token ?? null
]; ];
if (!empty($paylink)) { if (!empty($paylink)) {
$response['paylinkUrl'] = $paylink; $response['paylinkUrl'] = $paylink;
@@ -150,6 +168,10 @@ if (isset($_POST['signature'])) {
} }
echo json_encode($response); echo json_encode($response);
} else { } else {
// Audit: signature save failed
if (function_exists('auditLog')) {
auditLog($user_id, 'SIGNATURE_SAVE_FAILED', 'membership_application', null, ['user_id' => $user_id]);
}
ob_end_clean(); ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Database update failed']); echo json_encode(['status' => 'error', 'message' => 'Database update failed']);
} }
@@ -161,6 +183,10 @@ if (isset($_POST['signature'])) {
echo json_encode(['status' => 'error', 'message' => 'Failed to save signature']); echo json_encode(['status' => 'error', 'message' => 'Failed to save signature']);
} }
} else { } else {
// Audit: no signature provided in request
if (function_exists('auditLog') && isset($_SESSION['user_id'])) {
auditLog($_SESSION['user_id'], 'SIGNATURE_NOT_PROVIDED', 'membership_application', null, ['endpoint' => 'process_signature.php']);
}
ob_end_clean(); ob_end_clean();
echo json_encode(['status' => 'error', 'message' => 'Signature not provided']); echo json_encode(['status' => 'error', 'message' => 'Signature not provided']);
} }

View File

@@ -105,10 +105,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$status = "AWAITING PAYMENT"; $status = "AWAITING PAYMENT";
$description = $trip_name; $description = $trip_name;
$type = 'trip'; $type = 'trip';
$payment_id = uniqid(); $payment_id = generatePaymentRef('TRIP', $trip_id, $user_id);
$publicRef = bin2hex(random_bytes(16)); $publicRef = bin2hex(random_bytes(16));
// $eft_id = strtoupper(base_convert(time(), 10, 36)); // Convert timestamp to base36 // $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 // Insert booking into the database
@@ -126,6 +126,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get the generated booking_id // Get the generated booking_id
$booking_id = $conn->insert_id; $booking_id = $conn->insert_id;
// Audit booking creation
if (function_exists('auditLog')) {
auditLog($user_id, 'TRIP_BOOKING_CREATED', 'bookings', $booking_id, ['trip_id' => $trip_id, 'payment_id' => $payment_id, 'amount' => $payment_amount]);
}
if ($payment_amount < 1) { if ($payment_amount < 1) {
if (processZeroPayment($payment_id, $payment_amount, $description)) { if (processZeroPayment($payment_id, $payment_amount, $description)) {
echo "<script>alert('Booking successfully created!'); window.location.href = 'bookings.php';</script>"; echo "<script>alert('Booking successfully created!'); window.location.href = 'bookings.php';</script>";