1097 lines
49 KiB
PHP
1097 lines
49 KiB
PHP
<?php
|
||
// Prevent direct access to file
|
||
defined(security_key) or exit;
|
||
|
||
// ---------------------------------------
|
||
// Defaults
|
||
// ---------------------------------------
|
||
$account = [
|
||
'account_id' => $_SESSION['account_id'] ?? '',
|
||
'email' => $_POST['email'] ?? '',
|
||
'first_name' => $_POST['first_name'] ?? '',
|
||
'last_name' => $_POST['last_name'] ?? '',
|
||
'address_street' => $_POST['address_street'] ?? '',
|
||
'address_city' => $_POST['address_city'] ?? '',
|
||
'address_state' => $_POST['address_state'] ?? '',
|
||
'address_zip' => $_POST['address_zip'] ?? '',
|
||
'address_country' => $_POST['address_country'] ?? '',
|
||
'address_phone' => $_POST['address_phone'] ?? ''
|
||
];
|
||
|
||
$products_in_cart = isset($_SESSION['cart']) ? $_SESSION['cart'] : [];
|
||
$subtotal = 0.00;
|
||
$total = 0.00;
|
||
$shippingtotal = 0.00;
|
||
$discounttotal = 0.00;
|
||
$taxtotal = 0.00;
|
||
$tax_rate = '';
|
||
$weighttotal = 0;
|
||
$shipping_methods = [];
|
||
|
||
$checkout_input = [
|
||
"selected_country" => isset($_POST['address_country']) ? $_POST['address_country'] : (isset($account['address_country']) ? $account['address_country'] : 21) ,
|
||
"selected_shipment_method" => isset($_POST['shipping_method']) ? $_POST['shipping_method'] : '',
|
||
"business_type" => 'b2c',
|
||
"discount_code" => isset($_SESSION['discount']) ? $_SESSION['discount'] : ''
|
||
];
|
||
|
||
// Error array, output errors on the form
|
||
$errors = [];
|
||
|
||
// ---------------------------------------------
|
||
// End defaults --------------------------------
|
||
// ---------------------------------------------
|
||
|
||
// Redirect the user if the shopping cart is empty
|
||
if (empty($_SESSION['cart'])) {
|
||
header('Location: ' . url('index.php?page=cart'));
|
||
exit;
|
||
}
|
||
|
||
// Check if user is logged in
|
||
if (isset($_SESSION['account_loggedin'])) {
|
||
$api_url = '/v2/identity/userkey='.$_SESSION['account_id'];
|
||
$account = ioAPIv2($api_url,'',$clientsecret);
|
||
if (!empty($account)){$account = json_decode($account,true);}
|
||
$account = $account[0];
|
||
//RESET ACCOUNT_ID
|
||
$account['account_id'] = $account['userkey'];
|
||
}
|
||
|
||
// Update discount code
|
||
if (isset($_POST['discount_code']) && !empty($_POST['discount_code'])) {
|
||
$_SESSION['discount'] = $_POST['discount_code'];
|
||
} else if (isset($_POST['discount_code']) && empty($_POST['discount_code']) && isset($_SESSION['discount'])) {
|
||
unset($_SESSION['discount']);
|
||
}
|
||
//-------------------------------
|
||
// If there are products in cart handle the checkout
|
||
//-------------------------------
|
||
if ($products_in_cart) {
|
||
|
||
// First, calculate cart to get initial totals (without shipping)
|
||
$initial_payload = json_encode(array("cart" => $products_in_cart, "checkout_input" => $checkout_input), JSON_UNESCAPED_UNICODE);
|
||
$initial_cart = ioAPIv2('/v2/checkout/',$initial_payload,$clientsecret);
|
||
$initial_cart = json_decode($initial_cart,true);
|
||
|
||
// Get initial totals for shipping method calculation
|
||
$initial_subtotal = $initial_cart['totals']['subtotal'] ?? 0;
|
||
$initial_weighttotal = $initial_cart['totals']['weighttotal'] ?? 0;
|
||
|
||
// Now retrieve shipping methods with correct totals
|
||
$shipping_methods = ioAPIv2('/v2/shipping/list=methods&country='.$checkout_input['selected_country'].'&price_total='.$initial_subtotal.'&weight_total='.$initial_weighttotal,'',$clientsecret);
|
||
$shipping_methods = json_decode($shipping_methods,true);
|
||
|
||
// If no shipping method is selected and shipping methods are available, select the first one as default
|
||
if (empty($checkout_input['selected_shipment_method']) && !empty($shipping_methods) && count($shipping_methods) > 0) {
|
||
$checkout_input['selected_shipment_method'] = $shipping_methods[0]['id'];
|
||
}
|
||
|
||
// Recalculate shopping_cart with selected shipping method
|
||
$payload = json_encode(array("cart" => $products_in_cart, "checkout_input" => $checkout_input), JSON_UNESCAPED_UNICODE);
|
||
$products_in_cart = ioAPIv2('/v2/checkout/',$payload,$clientsecret);
|
||
$products_in_cart = json_decode($products_in_cart,true);
|
||
|
||
//GET SPECIFIC TOTALS FROM API RESULTS
|
||
$subtotal = $products_in_cart['totals']['subtotal'];
|
||
$shippingtotal = $products_in_cart['totals']['shippingtotal'];
|
||
$discounttotal = $products_in_cart['totals']['discounttotal'];
|
||
$taxtotal = $products_in_cart['totals']['taxtotal'];
|
||
$tax_rate = $products_in_cart['totals']['tax_rate'];
|
||
$weighttotal = $products_in_cart['totals']['weighttotal'];
|
||
$total = $products_in_cart['totals']['total'];
|
||
|
||
// Redirect the user if the shopping cart is empty
|
||
if (empty($products_in_cart)) {
|
||
header('Location: ' . url('index.php?page=cart'));
|
||
exit;
|
||
}
|
||
|
||
|
||
//-------------------------------
|
||
// END Checkout handler
|
||
//-------------------------------
|
||
}
|
||
|
||
|
||
//-------------------------------
|
||
//Place order
|
||
//-------------------------------
|
||
// Make sure when the user submits the form all data was submitted and shopping cart is not empty
|
||
if (isset($_POST['method'], $_POST['first_name'], $_POST['last_name'], $_POST['address_street'], $_POST['address_city'], $_POST['address_state'], $_POST['address_zip'], $_POST['address_country'], $_SESSION['cart']) && !isset($_POST['update'])) {
|
||
$account_id = null;
|
||
|
||
// If the user is already logged in
|
||
if (isset($_SESSION['account_loggedin'])) {
|
||
// Account logged-in, update the user's details
|
||
$payload = json_encode(
|
||
array(
|
||
"language" => $_SESSION['country_code'],
|
||
"first_name" => $_POST['first_name'],
|
||
"last_name" => $_POST['last_name'],
|
||
"address_street" => $_POST['address_street'],
|
||
"address_city" => $_POST['address_city'],
|
||
"address_state" => $_POST['address_state'],
|
||
"address_zip" => $_POST['address_zip'],
|
||
"address_country" => $_POST['address_country'],
|
||
"address_phone" => $_POST['address_phone'] ?? '',
|
||
"userkey" => $_SESSION['account_id']), JSON_UNESCAPED_UNICODE);
|
||
$account_update = ioAPIv2('/v2/identity/',$payload,$clientsecret);
|
||
$account_update = json_decode($account_update,true);
|
||
$account_id = $account['account_id'] = $_SESSION['account_id'];
|
||
|
||
} else if (isset($_POST['email'], $_POST['password'], $_POST['cpassword']) && filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) && !empty($_POST['password']) && !empty($_POST['cpassword'])) {
|
||
// User is not logged in, check if the account already exists with the email they submitted
|
||
// Check if the account exists
|
||
$account = ioAPIv2('/v2/identity/email='.$_POST['email'],'',$clientsecret);
|
||
$account = json_decode($account,true);
|
||
|
||
|
||
if ($account) {
|
||
// Email exists, user should login instead...
|
||
$errors[] = $error_account_name;
|
||
}
|
||
if (strlen($_POST['password']) > 20 || strlen($_POST['password']) < 5) {
|
||
// Password must be between 5 and 20 characters long.
|
||
$errors[] = $error_account_password_rules;
|
||
}
|
||
if ($_POST['password'] != $_POST['cpassword']) {
|
||
// Password and confirm password fields do not match...
|
||
$errors[] = $error_account_password_match;
|
||
}
|
||
if (!$errors) {
|
||
// Account doesnt exist, create new account
|
||
$payload = json_encode(
|
||
array(
|
||
"email" => $_POST['email'],
|
||
"password" => $_POST['password'],
|
||
"language" => $_SESSION['country_code'],
|
||
"first_name" => $_POST['first_name'],
|
||
"last_name" => $_POST['last_name'],
|
||
"address_street" => $_POST['address_street'],
|
||
"address_city" => $_POST['address_city'],
|
||
"address_state" => $_POST['address_state'],
|
||
"address_zip" => $_POST['address_zip'],
|
||
"address_country" => $_POST['address_country'],
|
||
"address_phone" => $_POST['address_phone'] ?? ''), JSON_UNESCAPED_UNICODE);
|
||
|
||
$account = ioAPIv2('/v2/identity/',$payload,$clientsecret);
|
||
$account= json_decode($account,true);
|
||
$account_id = $account['account_id'] = $account['accountID'];
|
||
|
||
if ($account && isset($account['accountID'])) {
|
||
//SEND VERIFICATION EMAIL
|
||
include dirname(__FILE__).'/custom/email/email_template_register.php';
|
||
$register_mail = $message;
|
||
|
||
send_mail_by_PHPMailer($account['identity'], $subject, $register_mail,'', '');
|
||
$register_error = 'Email send to verify your account';
|
||
}
|
||
}
|
||
} else if (account_required) {
|
||
$errors[] = $error_account;
|
||
}
|
||
if (!$errors && $products_in_cart) {
|
||
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
//Process checkout => add payment_method to checkout_input array
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
$checkout_input['payment_method'] = $_POST['method'];
|
||
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
// Calculate shopping_cart based on session
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
$payload = json_encode(array("cart" => $_SESSION['cart'], "checkout_input" => $checkout_input, "customer_details" => $account), JSON_UNESCAPED_UNICODE);
|
||
$place_order = ioAPIv2('/v2/placeorder/',$payload,$clientsecret);
|
||
$place_order = json_decode($place_order,true);
|
||
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
//Check if transaction is succesfull and send order confirmation to customer
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
||
if ($place_order['error'] == '' && $place_order['id'] != ''){
|
||
|
||
// Push purchase event to dataLayer
|
||
if (isset($place_order['products_checked-out']) && is_array($place_order['products_checked-out'])) {
|
||
$productIds = [];
|
||
$totalQuantity = 0;
|
||
$products = [];
|
||
|
||
foreach ($place_order['products_checked-out'] as $p) {
|
||
if (is_array($p)) {
|
||
$productIds[] = $p['id'] ?? '';
|
||
$totalQuantity += $p['quantity'] ?? 0;
|
||
$products[] = [
|
||
'id' => $p['id'] ?? '',
|
||
'name' => $p['meta']['name'] ?? '',
|
||
'price' => $p['options_price'] ?? 0,
|
||
'quantity' => $p['quantity'] ?? 0
|
||
];
|
||
}
|
||
}
|
||
|
||
echo "<script>
|
||
window.dataLayer = window.dataLayer || [];
|
||
window.dataLayer.push({
|
||
'event': 'purchase',
|
||
'ecommerce': {
|
||
'purchase': {
|
||
'actionField': {
|
||
'id': '" . $place_order['transaction_id'] . "',
|
||
'revenue': '" . $place_order['payment_amount'] . "',
|
||
'tax': '" . $place_order['taxtotal'] . "',
|
||
'shipping': '" . $place_order['shippingtotal'] . "'
|
||
},
|
||
'products': " . json_encode($products) . "
|
||
}
|
||
},
|
||
'content_type': 'product',
|
||
'content_ids': " . json_encode($productIds) . ",
|
||
'value': " . floatval($place_order['payment_amount']) . ",
|
||
'currency': 'EUR',
|
||
'num_items': " . $totalQuantity . "
|
||
});
|
||
</script>";
|
||
}
|
||
|
||
// Email will be sent by webhook after successful payment
|
||
|
||
//Disable giftcard
|
||
if (isset($_SESSION['discount'])){
|
||
if (preg_match("/[#][0-9]/", $_SESSION['discount']) == 1){
|
||
useGiftCart($pdo, $_SESSION['discount']);
|
||
}
|
||
}
|
||
|
||
// Authenticate the user
|
||
if ($account_id != null) {
|
||
// Log the user in with the details provided
|
||
session_regenerate_id();
|
||
$_SESSION['account_loggedin'] = TRUE;
|
||
$_SESSION['account_id'] = $account_id;
|
||
$_SESSION['account_role'] = $account ? $account['profile'] : 0;
|
||
}
|
||
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
//Pay on delivery = 2
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
||
if (pay_on_delivery_enabled && $place_order['payment_method'] == 2){
|
||
header('Location: ' . url('index.php?page=placeorder'));
|
||
exit;
|
||
}
|
||
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
// Mollie = 3 ++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
||
if (mollie_enabled && $_POST['method'] == 3) {
|
||
|
||
try {
|
||
/*
|
||
* Initialize the Mollie API library with your API key.
|
||
*
|
||
* See: https://www.mollie.com/dashboard/developers/api-keys
|
||
*/
|
||
require "initialize.php";
|
||
|
||
/*
|
||
* Generate a unique order id for this example. It is important to include this unique attribute
|
||
* in the redirectUrl (below) so a proper return page can be shown to the customer.
|
||
*/
|
||
$orderId = $place_order['transaction_id'];
|
||
$value = number_format($place_order['payment_amount'],2,'.','');
|
||
|
||
/*
|
||
* Determine the url parts to these example files.
|
||
*/
|
||
$protocol = isset($_SERVER['HTTPS']) && strcasecmp('off', $_SERVER['HTTPS']) !== 0 ? "https" : "http";
|
||
$hostname = $_SERVER['HTTP_HOST'];
|
||
$path = dirname($_SERVER['REQUEST_URI'] ?? $_SERVER['PHP_SELF']);
|
||
|
||
/*
|
||
* Payment parameters:
|
||
* amount Amount in EUROs.
|
||
* description Description of the payment.
|
||
* redirectUrl Redirect location. The customer will be redirected there after the payment.
|
||
* webhookUrl Webhook location, used to report when the payment changes state.
|
||
* metadata Custom metadata that is stored with the payment.
|
||
*/
|
||
|
||
if (rewrite_url){
|
||
$redirectURL = $protocol.'://'.$hostname.$path.'placeorder/'.$orderId;
|
||
}else{
|
||
$redirectURL = $protocol.'://'.$hostname.$path.'index.php?page=placeorder&order_id='.$orderId;
|
||
}
|
||
|
||
$payment = $mollie->payments->create([
|
||
"amount" => [
|
||
"currency" => "EUR",
|
||
"value" => "{$value}", // You must send the correct number of decimals, thus we enforce the use of strings
|
||
],
|
||
"description" => "Order #{$orderId}",
|
||
"redirectUrl" => "$redirectURL",
|
||
"webhookUrl" => "{$protocol}://{$hostname}{$path}webhook.php",
|
||
"metadata" => [
|
||
"order_id" => $orderId,
|
||
],
|
||
]);
|
||
/*
|
||
* Send the customer off to complete the payment.
|
||
* This request should always be a GET, thus we enforce 303 http response code
|
||
*/
|
||
|
||
// Send customer to checkout
|
||
header("Location: " . $payment->getCheckoutUrl(), true, 303);
|
||
|
||
} catch (\Mollie\Api\Exceptions\ApiException $e) {
|
||
echo "API call failed: " . htmlspecialchars($e->getMessage());
|
||
}
|
||
exit;
|
||
}
|
||
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
// PayPal Payment = 1 +++++++++++++++++++++++++++++++++++++++++
|
||
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
||
if (paypal_enabled && $_POST['method'] == 1) {
|
||
|
||
//Process Payment
|
||
require_once __DIR__."/lib/paypal/paypal.php";
|
||
|
||
$base = PAYPAL_URL;
|
||
$id = PAYPAL_CLIENT_ID;
|
||
$secret = PAYPAL_CLIENT_SECRET;
|
||
|
||
//init input
|
||
$order = $place_order['transaction_id'];
|
||
$price = number_format($place_order['payment_amount'],2,'.','');
|
||
$currency = "EUR";
|
||
|
||
//make payment
|
||
$paypal = new paypalCurl();
|
||
$paypal->init($id,$secret,$base);
|
||
$result = $paypal->makePaymentURL($order,$price,$currency);
|
||
|
||
if ($result->status === true) {
|
||
header("location:". $result->url);
|
||
die;
|
||
}
|
||
else { //raise error
|
||
echo $result->msg;
|
||
die;
|
||
}
|
||
}
|
||
} else {
|
||
foreach ($place_order['error'] as $error){
|
||
$errors[] = $error;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------
|
||
// END PLACE ORDER
|
||
//-------------------------------
|
||
$terms_link = url('index.php?page=termsandconditions');
|
||
$privacy_link = url('index.php?page=privacy');
|
||
|
||
$view = template_header(($checkout_header ?? 'Checkout'),'');
|
||
|
||
$view .= '
|
||
<div class="checkout checkout-wizard">
|
||
|
||
<h1>'.($h1_Checkout ?? 'Checkout').'</h1>
|
||
|
||
<!-- Progress Steps -->
|
||
<div class="progress-steps">
|
||
<div class="progress-step active" data-step="1">
|
||
<div class="step-number">1</div>
|
||
<div class="step-label">'.($step_contact ?? 'Contact').'</div>
|
||
</div>
|
||
<div class="progress-step" data-step="2">
|
||
<div class="step-number">2</div>
|
||
<div class="step-label">'.($step_payment ?? 'Payment').'</div>
|
||
</div>
|
||
<div class="progress-step" data-step="3">
|
||
<div class="step-number">3</div>
|
||
<div class="step-label">'.($step_shipping ?? 'Shipping').'</div>
|
||
</div>
|
||
<div class="progress-step" data-step="4">
|
||
<div class="step-number">4</div>
|
||
<div class="step-label">'.($step_review ?? 'Review').'</div>
|
||
</div>
|
||
</div>';
|
||
|
||
if ($errors) {
|
||
foreach($errors as $error) {
|
||
$view .= '<p class="error">'.$error.'</p>';
|
||
}
|
||
}
|
||
|
||
$view .= '
|
||
<form id="checkout-form" method="post" autocomplete="on">
|
||
|
||
<div class="container">
|
||
|
||
<div class="checkout-steps">
|
||
|
||
<!-- STEP 1: Contact Information -->
|
||
<div class="step-section active" id="step-1" data-step="1">
|
||
<div class="step-header" onclick="editStep(1)">
|
||
<div class="step-header-content">
|
||
<div class="step-badge">1</div>
|
||
<h2 class="step-title">'.($step_contact ?? 'Contact Information').'</h2>
|
||
</div>
|
||
<div class="step-edit">'.($edit_text ?? 'Edit').'</div>
|
||
</div>
|
||
|
||
<div class="step-content">';
|
||
|
||
if (!isset($_SESSION['account_loggedin'])) {
|
||
$view .= '
|
||
<label for="email">'.($customer_email ?? 'Email').' *</label>
|
||
<input type="email" name="email" id="email" placeholder="you@example.com" class="form-field" required>
|
||
|
||
<div class="collapsible-section" style="margin-top: 1.5rem;">
|
||
<button type="button" class="collapsible-header" onclick="toggleCollapsible(this)">
|
||
<span>'.$account_create.((!account_required) ? ' '.($account_optional ?? '(Optional)') : '').'</span>
|
||
<svg class="collapsible-icon" width="20" height="20" viewBox="0 0 20 20">
|
||
<path d="M5 7.5L10 12.5L15 7.5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
<div class="collapsible-content">
|
||
<label for="password">'.$account_create_password.'</label>
|
||
<input type="password" name="password" id="password" placeholder="'.$account_create_password.'" class="form-field" autocomplete="new-password">
|
||
|
||
<label for="cpassword">'.$account_create_password_confirm.'</label>
|
||
<input type="password" name="cpassword" id="cpassword" placeholder="'.$account_create_password_confirm.'" class="form-field" autocomplete="new-password">
|
||
</div>
|
||
</div>';
|
||
} else {
|
||
$view .= '
|
||
<label for="email_display">'.($customer_email ?? 'Email').'</label>
|
||
<input type="email" id="email_display" value="'.htmlspecialchars($account['email'], ENT_QUOTES).'" class="form-field" readonly style="background-color: #f3f4f6;">
|
||
<input type="hidden" name="email" value="'.htmlspecialchars($account['email'], ENT_QUOTES).'">';
|
||
}
|
||
|
||
$view .= '
|
||
<div class="step-buttons">
|
||
<button type="button" class="btn-continue" onclick="goToStep(2)">
|
||
'.($continue_text ?? 'Continue to Payment').'
|
||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<path d="M7.5 15L12.5 10L7.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STEP 2: Payment Method -->
|
||
<div class="step-section" id="step-2" data-step="2">
|
||
<div class="step-header" onclick="editStep(2)">
|
||
<div class="step-header-content">
|
||
<div class="step-badge">2</div>
|
||
<h2 class="step-title">'.$payment_method.'</h2>
|
||
</div>
|
||
<div class="step-edit">'.($edit_text ?? 'Edit').'</div>
|
||
</div>
|
||
|
||
<div class="step-content">
|
||
<div class="payment-methods-grid">';
|
||
if (mollie_enabled){
|
||
$view .= ' <div class="payment-option">
|
||
<input id="mollie-ideal" type="radio" name="method" value="3" '. ((mollie_default)? 'checked':'') .'>
|
||
<label for="mollie-ideal" class="payment-card">
|
||
<div class="payment-card-content">
|
||
<div class="payment-logos">
|
||
<img src="./custom/assets/wero.svg" alt="Bank Transfer" class="payment-logo" style="width:120px">
|
||
</div>
|
||
<span class="payment-name">'.($bank_transfer ?? 'Bank Transfer').'</span>
|
||
</div>
|
||
<div class="payment-check">✓</div>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="payment-option">
|
||
<input id="mollie-card" type="radio" name="method" value="3">
|
||
<label for="mollie-card" class="payment-card">
|
||
<div class="payment-card-content">
|
||
<div class="payment-logos">
|
||
<img src="./custom/assets/mastercard.png" alt="Mastercard" class="payment-logo">
|
||
<img src="./custom/assets/visa.png" alt="Visa" class="payment-logo">
|
||
</div>
|
||
<span class="payment-name">'.($card_payment ?? 'Credit / Debit Card').'</span>
|
||
</div>
|
||
<div class="payment-check">✓</div>
|
||
</label>
|
||
</div>';
|
||
}
|
||
|
||
if (paypal_enabled){
|
||
$view .= ' <div class="payment-option">
|
||
<input id="paypal" type="radio" name="method" value="1" '. ((paypal_default)? 'checked':'') .'>
|
||
<label for="paypal" class="payment-card">
|
||
<div class="payment-card-content">
|
||
<div class="payment-logos">
|
||
<img src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png" alt="PayPal" class="payment-logo">
|
||
</div>
|
||
<span class="payment-name">PayPal</span>
|
||
</div>
|
||
<div class="payment-check">✓</div>
|
||
</label>
|
||
</div>';
|
||
}
|
||
|
||
if (pay_on_delivery_enabled){
|
||
$view .= ' <div class="payment-option">
|
||
<input id="payondelivery" type="radio" name="method" value="2" '. ((pay_on_delivery_default)? 'checked':'') .'>
|
||
<label for="payondelivery" class="payment-card">
|
||
<div class="payment-card-content">
|
||
<div class="payment-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
||
</svg>
|
||
</div>
|
||
<span class="payment-name">'.$payment_method_2.'</span>
|
||
</div>
|
||
<div class="payment-check">✓</div>
|
||
</label>
|
||
</div>';
|
||
}
|
||
|
||
$view .= ' </div>
|
||
|
||
<div class="step-buttons">
|
||
<button type="button" class="btn-back" onclick="goToStep(1)">
|
||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<path d="M12.5 15L7.5 10L12.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||
</svg>
|
||
'.($back_text ?? 'Back').'
|
||
</button>
|
||
<button type="button" class="btn-continue" onclick="goToStep(3)">
|
||
'.($continue_text ?? 'Continue to Shipping').'
|
||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<path d="M7.5 15L12.5 10L7.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STEP 3: Shipping Details -->
|
||
<div class="step-section" id="step-3" data-step="3">
|
||
<div class="step-header" onclick="editStep(3)">
|
||
<div class="step-header-content">
|
||
<div class="step-badge">3</div>
|
||
<h2 class="step-title">'.$h2_Shipping_details.'</h2>
|
||
</div>
|
||
<div class="step-edit">'.($edit_text ?? 'Edit').'</div>
|
||
</div>
|
||
|
||
<div class="step-content">
|
||
<div class="form-row">
|
||
<div class="form-col">
|
||
<label for="first_name">'.$shipping_first_name.' *</label>
|
||
<input type="text" value="'.htmlspecialchars($account['first_name'], ENT_QUOTES).'" name="first_name" id="first_name" placeholder="'.$shipping_first_name.'" class="form-field" required>
|
||
</div>
|
||
<div class="form-col">
|
||
<label for="last_name">'.$shipping_last_name.' *</label>
|
||
<input type="text" value="'.htmlspecialchars($account['last_name'], ENT_QUOTES).'" name="last_name" id="last_name" placeholder="'.$shipping_last_name.'" class="form-field" required>
|
||
</div>
|
||
</div>
|
||
|
||
<label for="address_street">'.$shipping_address.' *</label>
|
||
<input type="text" value="'.htmlspecialchars($account['address_street'], ENT_QUOTES).'" name="address_street" id="address_street" placeholder="'.$shipping_address.'" class="form-field" required>
|
||
|
||
<div class="form-row">
|
||
<div class="form-col">
|
||
<label for="address_city">'.$shipping_city.' *</label>
|
||
<input type="text" value="'.htmlspecialchars($account['address_city'], ENT_QUOTES).'" name="address_city" id="address_city" placeholder="'.$shipping_city.'" class="form-field" required>
|
||
</div>
|
||
<div class="form-col">
|
||
<label for="address_zip">'.$shipping_zip.' *</label>
|
||
<input type="text" value="'.htmlspecialchars($account['address_zip'], ENT_QUOTES).'" name="address_zip" id="address_zip" placeholder="'.$shipping_zip.'" class="form-field" required>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-col">
|
||
<label for="address_state">'.$shipping_state.'</label>
|
||
<input type="text" value="'.htmlspecialchars($account['address_state'], ENT_QUOTES).'" name="address_state" id="address_state" placeholder="'.$shipping_state.'" class="form-field">
|
||
</div>
|
||
<div class="form-col">
|
||
<label for="address_country">'.$shipping_country.' *</label>
|
||
<select name="address_country" class="ajax-update form-field" required>';
|
||
foreach($countries_in_scope as $key => $value){
|
||
$view .= ' <option value="'.$key.'" '.($key==(isset($_SESSION['account_loggedin']) ? $account['address_country'] : 21) ? ' selected' : '').'>'.(${$value} ?? $value).'</option>';
|
||
}
|
||
$view .= ' </select>
|
||
</div>
|
||
</div>
|
||
|
||
<label for="address_phone">'.$shipping_phone.'</label>
|
||
<input type="tel" value="'.htmlspecialchars(($account['address_phone'] ?? ''), ENT_QUOTES).'" name="address_phone" id="address_phone" placeholder="'.$shipping_phone.'" class="form-field">
|
||
|
||
<div class="step-buttons">
|
||
<button type="button" class="btn-back" onclick="goToStep(2)">
|
||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<path d="M12.5 15L7.5 10L12.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||
</svg>
|
||
'.($back_text ?? 'Back').'
|
||
</button>
|
||
<button type="button" class="btn-continue" onclick="goToStep(4)">
|
||
'.($continue_text ?? 'Continue to Review').'
|
||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<path d="M7.5 15L12.5 10L7.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STEP 4: Order Review -->
|
||
<div class="step-section" id="step-4" data-step="4">
|
||
<div class="step-header" onclick="editStep(4)">
|
||
<div class="step-header-content">
|
||
<div class="step-badge">4</div>
|
||
<h2 class="step-title">'.($step_review ?? 'Review Order').'</h2>
|
||
</div>
|
||
<div class="step-edit">'.($edit_text ?? 'Edit').'</div>
|
||
</div>
|
||
|
||
<div class="step-content">
|
||
|
||
<h2>'.$h2_shoppingcart.'</h2>
|
||
|
||
<table class="cart-table">';
|
||
foreach($products_in_cart['cart_details']['products'] as $product){
|
||
$view .= ' <tr>
|
||
<td class="cart-img"><img src="'.img_url.$product['meta']['img'].'" width="50" height="50" alt="'.$product['meta']['name'].'"></td>
|
||
<td class="cart-info">'.$product['quantity'].' × '.$product['meta']['name'].'</td>
|
||
<td class="cart-price">'.currency_code.''.number_format($product['options_price'] * $product['quantity'],2).'</td>
|
||
</tr>';
|
||
}
|
||
$view .= ' </table>
|
||
|
||
<div class="collapsible-section discount-section">
|
||
<button type="button" class="collapsible-header discount-header" onclick="toggleCollapsible(this)">
|
||
<span>'.$discount_label.'</span>
|
||
<svg class="collapsible-icon" width="20" height="20" viewBox="0 0 20 20">
|
||
<path d="M5 7.5L10 12.5L15 7.5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||
</svg>
|
||
</button>
|
||
<div class="collapsible-content">
|
||
<div class="discount-input-wrapper">
|
||
<input type="text" class="ajax-update form-field" name="discount_code" placeholder="'.$discount_label.'" value="'.(isset($_SESSION['discount']) ? $_SESSION['discount'] : '').'">
|
||
</div>
|
||
<span class="discount-result">';
|
||
if (isset($_SESSION['discount'], $products_in_cart['totals']['discounttotal'])){
|
||
$view .= $products_in_cart['totals']['discount_message'];
|
||
}
|
||
$view .= ' </span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="shipping-methods-wrapper" id="shipping-method-section">';
|
||
|
||
if (isset($shipping_methods) && count($shipping_methods) > 0){
|
||
$view .= ' <h3 class="shipping-title">'.$h3_shipping_method.' *</h3>
|
||
<div class="shipping-methods">';
|
||
|
||
foreach($shipping_methods as $method){
|
||
$view .= ' <label class="shipping-method">
|
||
<input type="radio" class="ajax-update" id="sm'.$method['id'].'" name="shipping_method" value="'.$method['id'].'" required'.(($checkout_input['selected_shipment_method']==$method['id'] || count($shipping_methods) == 1) ? ' checked':'').'>
|
||
<span class="shipping-info">
|
||
<span class="shipping-name">'.$method['name'].'</span>
|
||
<span class="shipping-price">'.currency_code.''.number_format($method['price'], 2).'</span>
|
||
</span>
|
||
</label>';
|
||
}
|
||
$view .= ' </div>';
|
||
|
||
}
|
||
$view .= ' </div>
|
||
|
||
<div class="order-summary">
|
||
<div class="summary-row">
|
||
<span>'.$total_subtotal.'</span>
|
||
<span class="summary-value">'.currency_code.''.number_format($subtotal,2).'</span>
|
||
</div>
|
||
|
||
<div class="summary-row">
|
||
<span>'.$total_shipping.'</span>
|
||
<span class="summary-value">'.currency_code.''.number_format($shippingtotal,2).'</span>
|
||
</div>';
|
||
|
||
if ($discounttotal > 0){
|
||
$view .= ' <div class="summary-row discount-row">
|
||
<span>'.$total_discount.'</span>
|
||
<span class="summary-value">-'.currency_code.''.number_format(round($discounttotal, 1),2).'</span>
|
||
</div>';
|
||
}
|
||
|
||
if ($taxtotal > 0){
|
||
$view .= ' <div class="summary-row">
|
||
<span>'.($tax_text ?? 'VAT').' <span class="tax-rate">('.$tax_rate.')</span></span>
|
||
<span class="summary-value">'.currency_code.''.number_format($taxtotal,2).'</span>
|
||
</div>';
|
||
}
|
||
$view .= ' </div>
|
||
|
||
<div class="order-total">
|
||
<span class="total-label">'.$total_total.' <span class="total-note">'.$total_total_note.'</span></span>
|
||
<span class="total-amount">'.currency_code.''.number_format($total,2).'</span>
|
||
</div>
|
||
|
||
<div class="consent-section" id="consent-section" style="">
|
||
<label class="consent-checkbox">
|
||
<input type="checkbox" id="consent_comms" name="consent_comms" value="1">
|
||
<span class="consent-text">'.$order_consent_1.'</span>
|
||
</label>
|
||
<label class="consent-checkbox required">
|
||
<input type="checkbox" id="consent" name="consent" value="1" required>
|
||
<span class="consent-text">'.$order_consent_2.' * <a href="'.$terms_link.'" target="_blank">'.$order_consent_3.'</a> '.$order_consent_4.' <a href="'.$privacy_link.'" target="_blank">'.$order_consent_5.'</a></span>
|
||
</label>
|
||
</div>
|
||
|
||
<button type="submit" name="checkout" class="checkout-btn" id="final-checkout-btn">
|
||
<span>'.$btn_place_order.'</span>
|
||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<path d="M7.5 15L12.5 10L7.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="step-buttons">
|
||
<button type="button" class="btn-back" onclick="goToStep(3)">
|
||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<path d="M12.5 15L7.5 10L12.5 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||
</svg>
|
||
'.($back_text ?? 'Back').'
|
||
</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</form>
|
||
|
||
</div>
|
||
';
|
||
|
||
echo '<style>
|
||
/* Sticky step buttons for mobile - CSS solution like cart.php and product.php */
|
||
.step-buttons {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.step-buttons {
|
||
position: fixed !important;
|
||
left: 0 !important;
|
||
right: 0 !important;
|
||
bottom: 0 !important;
|
||
width: 100% !important;
|
||
z-index: 1000 !important;
|
||
background: #fff !important;
|
||
border-radius: 0 !important;
|
||
margin: 0 !important;
|
||
padding: 1rem !important;
|
||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.15) !important;
|
||
display: flex !important;
|
||
gap: 0.5rem !important;
|
||
}
|
||
|
||
/* Add padding at bottom for sticky buttons */
|
||
.step-section.active .step-content {
|
||
padding-bottom: 80px;
|
||
}
|
||
|
||
/* Ensure buttons take full width on mobile */
|
||
.step-buttons .btn-back,
|
||
.step-buttons .btn-continue {
|
||
flex: 1;
|
||
}
|
||
|
||
/* When only one button on step 1, make container and button full orange background */
|
||
#step-1 .step-buttons {
|
||
background: var(--color-primary) !important;
|
||
padding: 0 !important;
|
||
}
|
||
|
||
#step-1 .step-buttons .btn-continue {
|
||
background-color: var(--color-primary) !important;
|
||
color: white !important;
|
||
border: none !important;
|
||
padding: 1rem !important;
|
||
margin: 0 !important;
|
||
}
|
||
|
||
/* Add scroll margin to step sections for mobile */
|
||
.step-section {
|
||
scroll-margin-top: 20px;
|
||
}
|
||
|
||
.progress-steps {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Make checkout button sticky on step 4 */
|
||
#step-4 .checkout-btn {
|
||
position: fixed !important;
|
||
left: 0 !important;
|
||
right: 0 !important;
|
||
bottom: 0 !important;
|
||
width: 100% !important;
|
||
z-index: 1000 !important;
|
||
border-radius: 0 !important;
|
||
margin: 0 !important;
|
||
padding: 1rem !important;
|
||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.15) !important;
|
||
background-color: var(--color-primary) !important;
|
||
color: white !important;
|
||
border: none !important;
|
||
}
|
||
|
||
/* Hide step-buttons on step 4 since checkout button is sticky */
|
||
#step-4 .step-buttons {
|
||
display: none !important;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
let currentStep = 1;
|
||
let maxStepReached = 1;
|
||
|
||
function goToStep(step) {
|
||
// Only validate if moving forward to a new step
|
||
if (step > maxStepReached && !validateStep(currentStep)) {
|
||
return;
|
||
}
|
||
|
||
// Save current step data
|
||
saveStepSummary(currentStep);
|
||
|
||
// Update max step reached
|
||
if (step > maxStepReached) {
|
||
maxStepReached = step;
|
||
}
|
||
|
||
// Hide all steps
|
||
document.querySelectorAll(".step-section").forEach(s => {
|
||
s.classList.remove("active");
|
||
});
|
||
|
||
// Mark all steps up to maxStepReached as completed
|
||
document.querySelectorAll(".step-section").forEach((s, i) => {
|
||
if (i < maxStepReached - 1) {
|
||
s.classList.add("completed");
|
||
} else if (i === maxStepReached - 1) {
|
||
s.classList.remove("completed");
|
||
}
|
||
});
|
||
|
||
// Show target step
|
||
const targetStep = document.getElementById("step-" + step);
|
||
if (targetStep) {
|
||
targetStep.classList.add("active");
|
||
targetStep.classList.remove("completed");
|
||
}
|
||
|
||
// Update progress
|
||
document.querySelectorAll(".progress-step").forEach((s, i) => {
|
||
if (i < step - 1) {
|
||
s.classList.add("completed");
|
||
s.classList.remove("active");
|
||
} else if (i === step - 1) {
|
||
s.classList.add("active");
|
||
s.classList.remove("completed");
|
||
} else if (i < maxStepReached) {
|
||
s.classList.add("completed");
|
||
} else {
|
||
s.classList.remove("active", "completed");
|
||
}
|
||
});
|
||
|
||
currentStep = step;
|
||
|
||
// Scroll to guide user through the step
|
||
setTimeout(() => {
|
||
const targetStep = document.getElementById("step-" + step);
|
||
if (targetStep) {
|
||
// Close any active keyboard
|
||
if (document.activeElement) {
|
||
document.activeElement.blur();
|
||
}
|
||
|
||
// Scroll with explicit position calculation
|
||
const rect = targetStep.getBoundingClientRect();
|
||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||
const offset = 10; // Small offset from top
|
||
|
||
window.scrollTo({
|
||
top: scrollTop + rect.top - offset,
|
||
behavior: "smooth"
|
||
});
|
||
}
|
||
}, 300);
|
||
}
|
||
|
||
function editStep(step) {
|
||
// Allow navigating to any step that has been reached
|
||
if (step <= maxStepReached) {
|
||
goToStep(step);
|
||
}
|
||
}
|
||
|
||
function validateStep(step) {
|
||
const stepElement = document.getElementById("step-" + step);
|
||
const inputs = stepElement.querySelectorAll("input[required], select[required]");
|
||
|
||
// Remove previous error highlights
|
||
stepElement.querySelectorAll(".error-highlight").forEach(el => {
|
||
el.classList.remove("error-highlight");
|
||
});
|
||
|
||
let firstInvalidField = null;
|
||
let hasErrors = false;
|
||
|
||
for (let input of inputs) {
|
||
if (!input.checkValidity()) {
|
||
// Add visual highlight for Step 3
|
||
if (step === 3) {
|
||
input.classList.add("error-highlight");
|
||
if (!firstInvalidField) {
|
||
firstInvalidField = input;
|
||
}
|
||
hasErrors = true;
|
||
} else {
|
||
input.reportValidity();
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// For Step 3, scroll to first error and show validation message
|
||
if (step === 3 && hasErrors) {
|
||
if (firstInvalidField) {
|
||
firstInvalidField.scrollIntoView({ behavior: "smooth", block: "center" });
|
||
firstInvalidField.reportValidity();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Additional validation for payment method
|
||
if (step === 2) {
|
||
const paymentSelected = document.querySelector("input[name=\"method\"]:checked");
|
||
if (!paymentSelected) {
|
||
alert("'.($select_payment ?? 'Please select a payment method').'");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
function saveStepSummary(step) {
|
||
// Step summaries removed - validation handled by form
|
||
}
|
||
|
||
function toggleCollapsible(button) {
|
||
const section = button.closest(".collapsible-section");
|
||
const content = section.querySelector(".collapsible-content");
|
||
const icon = button.querySelector(".collapsible-icon");
|
||
|
||
section.classList.toggle("active");
|
||
|
||
if (section.classList.contains("active")) {
|
||
content.style.maxHeight = content.scrollHeight + "px";
|
||
icon.style.transform = "rotate(180deg)";
|
||
} else {
|
||
content.style.maxHeight = "0";
|
||
icon.style.transform = "rotate(0deg)";
|
||
}
|
||
}
|
||
|
||
// Auto-expand discount if already applied
|
||
window.addEventListener("DOMContentLoaded", function() {
|
||
const discountValue = document.querySelector("input[name=\"discount_code\"]").value;
|
||
if (discountValue) {
|
||
const discountButton = document.querySelector(".discount-header");
|
||
if (discountButton) {
|
||
toggleCollapsible(discountButton);
|
||
}
|
||
}
|
||
|
||
// Step 1: Enter key on email field
|
||
const emailField = document.getElementById("email");
|
||
if (emailField) {
|
||
emailField.addEventListener("keypress", function(e) {
|
||
if (e.key === "Enter") {
|
||
e.preventDefault();
|
||
goToStep(2);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Step 2: Auto-advance after payment method selection
|
||
const paymentMethods = document.querySelectorAll("input[name=\"method\"]");
|
||
paymentMethods.forEach(function(radio) {
|
||
radio.addEventListener("change", function() {
|
||
setTimeout(function() {
|
||
goToStep(3);
|
||
}, 300);
|
||
});
|
||
});
|
||
|
||
// Form submission validation
|
||
const checkoutForm = document.getElementById("checkout-form");
|
||
if (checkoutForm) {
|
||
checkoutForm.addEventListener("submit", function(e) {
|
||
// Ensure user is on step 4
|
||
if (currentStep !== 4) {
|
||
e.preventDefault();
|
||
alert("Please complete all steps before placing your order.");
|
||
return false;
|
||
}
|
||
|
||
// Validate all required fields exist
|
||
const requiredFields = ["method", "first_name", "last_name", "address_street", "address_city", "address_zip", "address_country"];
|
||
for (let field of requiredFields) {
|
||
const input = checkoutForm.querySelector("[name=\"" + field + "\"]");
|
||
if (!input || !input.value) {
|
||
e.preventDefault();
|
||
alert("Please complete all required fields.");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Validate consent checkbox
|
||
const consentCheckbox = document.getElementById("consent");
|
||
if (consentCheckbox && !consentCheckbox.checked) {
|
||
e.preventDefault();
|
||
consentCheckbox.classList.add("error-highlight");
|
||
consentCheckbox.scrollIntoView({ behavior: "smooth", block: "center" });
|
||
setTimeout(() => {
|
||
consentCheckbox.reportValidity();
|
||
}, 300);
|
||
return false;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
';
|
||
|
||
$view .= template_footer();
|
||
|
||
//OUTPUT
|
||
echo $view;
|
||
|
||
?>
|