mostly complete payment system

This commit is contained in:
twotalesanimation
2025-12-15 10:18:25 +02:00
parent f4934e9c13
commit d2c99e86b4
15 changed files with 223 additions and 108 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -251,7 +251,7 @@ 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));
} }
} }

View File

@@ -1456,7 +1456,7 @@ function getInitialSurname($user_id)
if ($stmt->fetch()) { if ($stmt->fetch()) {
$initial = strtoupper(substr($first_name, 0, 1)); $initial = strtoupper(substr($first_name, 0, 1));
return $initial . ". " . $last_name; return $initial . "." . $last_name;
} else { } else {
return null; 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) 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

@@ -205,30 +205,30 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
</nav> </nav>
</div> </div>
<!-- Draft Notice for Admin --> <!-- Draft Notice for Admin -->
<?php if ($is_admin && isset($row['published']) && $row['published'] == 0): ?> <?php if ($is_admin && isset($row['published']) && $row['published'] == 0): ?>
<div class="alert alert-warning mt-3" role="alert"> <div class="alert alert-warning mt-3" role="alert">
<strong><i class="fas fa-exclamation-triangle"></i> Draft Trip</strong><br> <strong><i class="fas fa-exclamation-triangle"></i> Draft Trip</strong><br>
This trip is currently in draft status and is not visible to regular users. Only admins and superadmins can preview it. This trip is currently in draft status and is not visible to regular users. Only admins and superadmins can preview it.
</div> </div>
<?php endif; ?> <?php endif; ?>
<!-- Publish/Unpublish Button --> <!-- Publish/Unpublish Button -->
<?php <?php
$user_role = getUserRole(); $user_role = getUserRole();
if (in_array($user_role, ['admin', 'superadmin'])): if (in_array($user_role, ['admin', 'superadmin'])):
// Use published status from the main query // Use published status from the main query
$is_published = $row['published'] ?? 0; $is_published = $row['published'] ?? 0;
?> ?>
<div class="admin-actions mt-20"> <div class="admin-actions mt-20">
<button type="button" class="theme-btn" style="width: 100%; id="publishBtn" onclick="toggleTripPublished(<?php echo $trip_id; ?>)"> <button type="button" class="theme-btn" style="width: 100%; id=" publishBtn" onclick="toggleTripPublished(<?php echo $trip_id; ?>)">
<?php if ($is_published): ?> <?php if ($is_published): ?>
<i class="fas fa-eye-slash"></i> Unpublish Trip <i class="fas fa-eye-slash"></i> Unpublish Trip
<?php else: ?> <?php else: ?>
<i class="fas fa-eye"></i> Publish Trip <i class="fas fa-eye"></i> Publish Trip
<?php endif; ?> <?php endif; ?>
</button> </button>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</section> </section>
@@ -296,8 +296,8 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
</div> </div>
<span class="subtitle mb-15"><?php echo $badge_text; ?></span> <span class="subtitle mb-15"><?php echo $badge_text; ?></span>
</div> </div>
<!-- <div class="col-xl-4 col-lg-5 text-lg-end" data-aos="fade-right" data-aos-duration="1500" data-aos-offset="50"> <!-- <div class="col-xl-4 col-lg-5 text-lg-end" data-aos="fade-right" data-aos-duration="1500" data-aos-offset="50">
<div class="tour-header-social mb-10"> <div class="tour-header-social mb-10">
@@ -558,33 +558,33 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
</li> </li>
</ul> </ul>
<div style="margin: 20px 0;"> <div style="margin: 20px 0;">
<div id="indemnityBox" style="border: 1px solid #ccc; padding: 10px; height: 150px; overflow-y: scroll; background: #f9f9f9; font-size: 12px;"> <div id="indemnityBox" style="border: 1px solid #ccc; padding: 10px; height: 150px; overflow-y: scroll; background: #f9f9f9; font-size: 12px;">
<p><strong>INDEMNITY AND WAIVER</strong></p> <p><strong>INDEMNITY AND WAIVER</strong></p>
<p>1. I agree to abide by the Code of Conduct as listed below, as well as any reasonable instructions given by any Member of the Committee of the Club, or any person appointed by the Club to organise or control any event (Club Officer).</p> <p>1. I agree to abide by the Code of Conduct as listed below, as well as any reasonable instructions given by any Member of the Committee of the Club, or any person appointed by the Club to organise or control any event (Club Officer).</p>
<p>2. I acknowledge that driving the off-road track is inherently dangerous, and that I am fully aware of the dangers thereof. I warrant that I will make all members of my party aware of such dangers prior to driving the track.</p> <p>2. I acknowledge that driving the off-road track is inherently dangerous, and that I am fully aware of the dangers thereof. I warrant that I will make all members of my party aware of such dangers prior to driving the track.</p>
<p>3. While I, or any member of my party, enjoy the facilities at Base 4 including overnight camping, picnicking, driving the track, using the swimming pool facility or activity or any other activity while at Base 4, I agree that under no circumstances shall the Club be liable for any loss or damage of any kind whatsoever (including consequential loss) which I or any of my party may suffer, regardless of how such loss or damage may have been caused or sustained, and whether or not as a result of the negligence or breach of contract (whether fundamental or otherwise) or other wrongful act of the Club, or any Club Officer, or any of the Clubs agents or contractors, and I hereby indemnify and hold harmless the Club and any Club Officer against all such loss or damage.</p> <p>3. While I, or any member of my party, enjoy the facilities at Base 4 including overnight camping, picnicking, driving the track, using the swimming pool facility or activity or any other activity while at Base 4, I agree that under no circumstances shall the Club be liable for any loss or damage of any kind whatsoever (including consequential loss) which I or any of my party may suffer, regardless of how such loss or damage may have been caused or sustained, and whether or not as a result of the negligence or breach of contract (whether fundamental or otherwise) or other wrongful act of the Club, or any Club Officer, or any of the Clubs agents or contractors, and I hereby indemnify and hold harmless the Club and any Club Officer against all such loss or damage.</p>
<p>4. The expression, member of my party, means all persons who accompany me or attending any event at my specific invitation, request or suggestion, and includes without limitation, members of family, guests and invitees.</p> <p>4. The expression, member of my party, means all persons who accompany me or attending any event at my specific invitation, request or suggestion, and includes without limitation, members of family, guests and invitees.</p>
<p>5. I understand that I am responsible for ensuring my vehicle and equipment and that all members of my party have adequate health and medical insurance to cover any and all likely occurrences.</p> <p>5. I understand that I am responsible for ensuring my vehicle and equipment and that all members of my party have adequate health and medical insurance to cover any and all likely occurrences.</p>
<p>6. This indemnity is irrevocable and shall apply to me and the members of my party for any Club events in which I may participate or attend.</p> <p>6. This indemnity is irrevocable and shall apply to me and the members of my party for any Club events in which I may participate or attend.</p>
<p><strong>BASE 4 CODE OF CONDUCT</strong></p> <p><strong>BASE 4 CODE OF CONDUCT</strong></p>
<p>1. No motorbikes or quadbikes.</p> <p>1. No motorbikes or quadbikes.</p>
<p>2. No loud music (unless authorised by the Committee or its representatives).</p> <p>2. No loud music (unless authorised by the Committee or its representatives).</p>
<p>3. Dogs to be controlled by their owners who take full responsibility for the animals behaviour.</p> <p>3. Dogs to be controlled by their owners who take full responsibility for the animals behaviour.</p>
<p>4. No dogs belonging to non-members are allowed at Base 4 unless with the express permission of the Committee.</p> <p>4. No dogs belonging to non-members are allowed at Base 4 unless with the express permission of the Committee.</p>
<p>5. No person in the rear of open vehicles when driving on obstacles.</p> <p>5. No person in the rear of open vehicles when driving on obstacles.</p>
<p>6. When driving the obstacles stay on the tracks.</p> <p>6. When driving the obstacles stay on the tracks.</p>
<p>7. Engage 4WD when driving the obstacles to minimise wear and damage to the track.</p> <p>7. Engage 4WD when driving the obstacles to minimise wear and damage to the track.</p>
<p>8. No alcohol to be consumed while driving the track.</p> <p>8. No alcohol to be consumed while driving the track.</p>
<p>9. No littering (please pick up cigarette butts etc.)</p> <p>9. No littering (please pick up cigarette butts etc.)</p>
<p>10. All rubbish is to be taken away with you when leaving. Dustbins and refuse collection is not provided.</p> <p>10. All rubbish is to be taken away with you when leaving. Dustbins and refuse collection is not provided.</p>
<p>11. Use water sparingly. Please bring your own water and a little extra for the Club.</p> <p>11. Use water sparingly. Please bring your own water and a little extra for the Club.</p>
<p>I am a member of the Four Wheel Drive Club of Southern Africa and I will strive to uphold these Codes.</p> <p>I am a member of the Four Wheel Drive Club of Southern Africa and I will strive to uphold these Codes.</p>
</div>
<div style="margin-top: 10px;">
<input type="checkbox" id="agreeCheckbox" name="agree" disabled required>
<label for="agreeCheckbox" id="agreeLabel" style="color: #888;">I have read and agree to the indemnity terms</label>
</div>
</div> </div>
<div style="margin-top: 10px;">
<input type="checkbox" id="agreeCheckbox" name="agree" disabled required>
<label for="agreeCheckbox" id="agreeLabel" style="color: #888;">I have read and agree to the indemnity terms</label>
</div>
</div>
<h6>Total: <span id="booking_total" class="price">-</span></h6> <h6>Total: <span id="booking_total" class="price">-</span></h6>
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>"> <input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<?php if ($remaining_places < 1): ?> <?php if ($remaining_places < 1): ?>
@@ -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>
@@ -727,7 +728,7 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
// Update button and status badge // Update button and status badge
const publishBtn = $('#publishBtn'); const publishBtn = $('#publishBtn');
const statusBadge = $('#publishStatus'); const statusBadge = $('#publishStatus');
if (response.published === 1) { if (response.published === 1) {
publishBtn.html('<i class="fas fa-eye-slash"></i> Unpublish Trip'); publishBtn.html('<i class="fas fa-eye-slash"></i> Unpublish Trip');
statusBadge.html('<span class="badge bg-success">Published</span>'); statusBadge.html('<span class="badge bg-success">Published</span>');
@@ -735,7 +736,7 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
publishBtn.html('<i class="fas fa-eye"></i> Publish Trip'); publishBtn.html('<i class="fas fa-eye"></i> Publish Trip');
statusBadge.html('<span class="badge bg-warning">Draft</span>'); statusBadge.html('<span class="badge bg-warning">Draft</span>');
} }
// Show success message // Show success message
alert(response.message); alert(response.message);
} else { } else {
@@ -750,4 +751,4 @@ include_once(dirname(dirname(dirname(__DIR__))) . '/header.php');
} }
</script> </script>
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?> <?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php') ?>

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");
@@ -90,36 +98,39 @@ if ($payment_id) {
$pageTitle = 'Membership Payment'; $pageTitle = 'Membership Payment';
$breadcrumbs = [['Home' => 'index.php'], ['Membership' => 'membership.php']]; $breadcrumbs = [['Home' => 'index.php'], ['Membership' => 'membership.php']];
require_once($rootPath . '/components/banner.php'); require_once($rootPath . '/components/banner.php');
?> ?>
<!-- Contact Form Area start --> <!-- Contact Form Area start -->
<section class="about-us-area py-100 rpb-90 rel z-1"> <section class="about-us-area py-100 rpb-90 rel z-1">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="section-title mb-25"> <div class="section-title mb-25">
<span class="h2 mb-15">New Membership Payment:</span> <span class="h2 mb-15">New Membership Payment:</span>
<?php echo <?php echo
'<h5>Membership Start Date: ' . $membership_start_date . '<br>Membership Renewal Date: ' . $membership_end_date . '</h5>'; ?> '<h5>Membership Start Date: ' . $membership_start_date . '<br>Membership Renewal Date: ' . $membership_end_date . '</h5>'; ?>
</div>
<?php if (!empty($payment_link)) { ?>
<h5>Payment Details:</h5>
<p>Amount: R <?php echo number_format($payment_amount, 2); ?></p>
<p>Reference: <?php echo htmlspecialchars($payment_id); ?></p>
<a href="<?php echo htmlspecialchars($payment_link); ?>" class="theme-btn style-two style-three" style="width:100%;" target="_blank" rel="noopener noreferrer">
<span data-hover="Pay Now with iKhokha">Pay Now with iKhokha</span>
<i class="fal fa-arrow-right"></i>
</a>
<div class="text-center">
<p>You will be redirected to iKhokha's Secure payment gateway.</p>
</div> </div>
<img src="assets/images/logos/ikhokha.png" alt="Secure Payment Badges" style="max-width: 200px; display: block; margin: 10px auto 0;">
<?php if (!empty($payment_link)) { ?> <?php } else { ?>
<h5>Payment Details:</h5> <p>Please upload your proof of payment below.</p>
<p>Amount: R <?php echo number_format($payment_amount, 2); ?></p> <h5>Payment Details:</h5>
<p>Reference: <?php echo htmlspecialchars($payment_id); ?></p> <p>The Four Wheel Drive Club of Southern Africa<br>FNB<br>Account Number: 58810022334<br>Branch code: 250655<br>Reference: <?php echo htmlspecialchars($eft_id); ?><br>Amount: R <?php echo number_format($payment_amount, 2); ?></p>
<a href="<?php echo htmlspecialchars($payment_link); ?>" class="theme-btn style-two style-three" style="width:100%;" target="_blank" rel="noopener noreferrer"> <a href="submit_pop" class="theme-btn style-two style-three" style="width:100%;">
<span data-hover="Pay Now with iKhokha">Pay Now with iKhokha</span> <span data-hover="Submit Proof of Payment">Submit Proof of Payment</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> <?php } ?>
<?php } else { ?>
<p>Please upload your proof of payment below.</p>
<h5>Payment Details:</h5>
<p>The Four Wheel Drive Club of Southern Africa<br>FNB<br>Account Number: 58810022334<br>Branch code: 250655<br>Reference: <?php echo htmlspecialchars($eft_id); ?><br>Amount: R <?php echo number_format($payment_amount, 2); ?></p>
<a href="submit_pop" class="theme-btn style-two style-three" style="width:100%;">
<span data-hover="Submit Proof of Payment">Submit Proof of Payment</span>
<i class="fal fa-arrow-right"></i>
</a>
<?php } ?>
</div> </div>
<div class="col-lg-6" data-aos="fade-right" data-aos-duration="1500" data-aos-offset="50"> <div class="col-lg-6" data-aos="fade-right" data-aos-duration="1500" data-aos-offset="50">
@@ -132,4 +143,4 @@ if ($payment_id) {
</div> </div>
</section> </section>
<?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?> <?php include_once(dirname(dirname(dirname(__DIR__))) . '/components/insta_footer.php'); ?>

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);
@@ -65,8 +77,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

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.";

View File

@@ -77,7 +77,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
@@ -128,6 +128,7 @@ 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']);
} 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());
@@ -140,7 +141,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;

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
@@ -147,7 +147,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Send invoice and admin notification // Send invoice and admin notification
// sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description); // 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 // Redirect to payment link if available
$paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null; $paylink = $resp['paylinkUrl'] ?? $resp['paylinkURL'] ?? $resp['paylink_url'] ?? null;