[IMP] website_sale_aplicoop: pay on the checkout page, not a step later
The separate /eskaera/<slug>/payment step is gone. Members review the summary, choose home delivery and pick a payment method on the checkout, in one screen; the old URL redirects there so bookmarks and sessions that were mid-flow do not hit a 404. The checkout now renders the member's draft sale.order instead of the localStorage cart. That is what fixes the products appearing "out of nowhere" between the two pages: the summary was a snapshot of localStorage taken at page load, and `_autoLoadDraftOnInit` then pulled the draft back into localStorage without re-rendering. Deleting a product in the shop removed it from the cart but left the line on the draft, so the autoload resurrected it, the confirm button sent it back, and it only became visible one page later. The checkout no longer auto-loads the draft — it renders it, and what it shows is what the payment form charges. "Proceed to Checkout" pushes the cart to that draft before navigating. Saving is idempotent: `_merge_or_replace_draft` reuses the cycle's draft and, through the new `_draft_matches_lines`, rewrites `order_line` only when the lines actually differ — replacing them unlinks and recreates every one of them, which is pure churn when nothing changed. The home delivery checkbox goes through the new /eskaera/set-home-delivery so the delivery line moves on the order itself. Writing only to localStorage would have changed the summary and left the amount alone, which with online payment on is the amount being charged. Also fixes the confirmation notice nobody ever saw: saving answered with the payment step URL and the frontend followed it immediately, destroying the toast in the same tick. Saving no longer navigates; the caller decides whether it is staying or moving on. Along the way: checkout_labels.js and the eskaera_checkout_summary / eskaera_payment templates are removed, superseded by the server-rendered summary and checkout, and the stale sessionStorage delivery preference no longer overrides the checkbox the order just rendered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f81c1ca8e7
commit
e625b0c2f3
11 changed files with 873 additions and 746 deletions
|
|
@ -1,340 +0,0 @@
|
|||
/**
|
||||
* Checkout Labels Loading
|
||||
* Fetches translated labels for checkout table summary
|
||||
* IMPORTANT: This script waits for the cart to be loaded by website_sale.js
|
||||
* before rendering the checkout summary.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
console.log("[CHECKOUT] Script loaded");
|
||||
|
||||
// Get order ID from button
|
||||
var confirmBtn = document.getElementById("confirm-order-btn");
|
||||
if (!confirmBtn) {
|
||||
console.log("[CHECKOUT] No confirm button found");
|
||||
return;
|
||||
}
|
||||
|
||||
var orderId = confirmBtn.getAttribute("data-order-id");
|
||||
if (!orderId) {
|
||||
console.log("[CHECKOUT] No order ID found");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[CHECKOUT] Order ID:", orderId);
|
||||
|
||||
// Get summary div
|
||||
var summaryDiv = document.getElementById("checkout-summary");
|
||||
if (!summaryDiv) {
|
||||
console.log("[CHECKOUT] No summary div found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Function to fetch labels and render checkout
|
||||
var fetchLabelsAndRender = function () {
|
||||
console.log("[CHECKOUT] Fetching labels...");
|
||||
|
||||
// Wait for window.groupOrderShop.labels to be initialized (contains hardcoded labels)
|
||||
var waitForLabels = function (callback, maxWait = 3000, checkInterval = 50) {
|
||||
var startTime = Date.now();
|
||||
var checkLabels = function () {
|
||||
if (
|
||||
window.groupOrderShop &&
|
||||
window.groupOrderShop.labels &&
|
||||
Object.keys(window.groupOrderShop.labels).length > 0
|
||||
) {
|
||||
console.log("[CHECKOUT] ✅ Hardcoded labels found, proceeding");
|
||||
callback();
|
||||
} else if (Date.now() - startTime < maxWait) {
|
||||
setTimeout(checkLabels, checkInterval);
|
||||
} else {
|
||||
console.log("[CHECKOUT] ⚠️ Timeout waiting for labels, proceeding anyway");
|
||||
callback();
|
||||
}
|
||||
};
|
||||
checkLabels();
|
||||
};
|
||||
|
||||
waitForLabels(function () {
|
||||
// Now fetch additional labels from server
|
||||
// Detect current language from document or navigator
|
||||
var currentLang =
|
||||
document.documentElement.lang ||
|
||||
document.documentElement.getAttribute("lang") ||
|
||||
navigator.language ||
|
||||
"es_ES";
|
||||
console.log("[CHECKOUT] Detected language:", currentLang);
|
||||
|
||||
fetch("/eskaera/labels", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
lang: currentLang,
|
||||
}),
|
||||
})
|
||||
.then(function (response) {
|
||||
console.log("[CHECKOUT] Response status:", response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
console.log("[CHECKOUT] Response data:", data);
|
||||
var serverLabels = data.result || data;
|
||||
console.log(
|
||||
"[CHECKOUT] Server labels count:",
|
||||
Object.keys(serverLabels).length
|
||||
);
|
||||
console.log("[CHECKOUT] Sample server labels:", {
|
||||
draft_merged_success: serverLabels.draft_merged_success,
|
||||
home_delivery: serverLabels.home_delivery,
|
||||
});
|
||||
|
||||
// CRITICAL: Merge server labels with existing hardcoded labels
|
||||
// Hardcoded labels MUST take precedence over server labels
|
||||
if (window.groupOrderShop && window.groupOrderShop.labels) {
|
||||
var existingLabels = window.groupOrderShop.labels;
|
||||
console.log(
|
||||
"[CHECKOUT] Existing hardcoded labels count:",
|
||||
Object.keys(existingLabels).length
|
||||
);
|
||||
console.log("[CHECKOUT] Sample existing labels:", {
|
||||
draft_merged_success: existingLabels.draft_merged_success,
|
||||
home_delivery: existingLabels.home_delivery,
|
||||
});
|
||||
|
||||
// Start with server labels, then overwrite with hardcoded ones
|
||||
var mergedLabels = Object.assign({}, serverLabels);
|
||||
Object.assign(mergedLabels, existingLabels);
|
||||
|
||||
window.groupOrderShop.labels = mergedLabels;
|
||||
console.log(
|
||||
"[CHECKOUT] ✅ Merged labels - final count:",
|
||||
Object.keys(mergedLabels).length
|
||||
);
|
||||
console.log("[CHECKOUT] Verification:", {
|
||||
draft_merged_success: mergedLabels.draft_merged_success,
|
||||
home_delivery: mergedLabels.home_delivery,
|
||||
});
|
||||
} else {
|
||||
// If no existing labels, use server labels as fallback
|
||||
if (window.groupOrderShop) {
|
||||
window.groupOrderShop.labels = serverLabels;
|
||||
}
|
||||
console.log("[CHECKOUT] ⚠️ No existing labels, using server labels");
|
||||
}
|
||||
|
||||
window.renderCheckoutSummary(window.groupOrderShop.labels);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error("[CHECKOUT] Error:", error);
|
||||
// Fallback to translated labels
|
||||
window.renderCheckoutSummary(window.getCheckoutLabels());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Listen for cart ready event instead of polling
|
||||
if (window.groupOrderShop && window.groupOrderShop.orderId) {
|
||||
// Cart already initialized, render immediately
|
||||
console.log("[CHECKOUT] Cart already ready");
|
||||
fetchLabelsAndRender();
|
||||
} else {
|
||||
// Wait for cart initialization event
|
||||
console.log("[CHECKOUT] Waiting for cart ready event...");
|
||||
document.addEventListener(
|
||||
"groupOrderCartReady",
|
||||
function () {
|
||||
console.log("[CHECKOUT] Cart ready event received");
|
||||
fetchLabelsAndRender();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
// Fallback timeout in case event never fires
|
||||
setTimeout(function () {
|
||||
if (window.groupOrderShop && window.groupOrderShop.orderId) {
|
||||
console.log("[CHECKOUT] Fallback timeout triggered");
|
||||
fetchLabelsAndRender();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render order summary table or empty message
|
||||
* Exposed globally so other scripts can call it
|
||||
*/
|
||||
window.renderCheckoutSummary = function (labels) {
|
||||
labels = labels || window.getCheckoutLabels();
|
||||
|
||||
var summaryDiv = document.getElementById("checkout-summary");
|
||||
if (!summaryDiv) return;
|
||||
|
||||
var cartKey =
|
||||
"eskaera_" +
|
||||
(document.getElementById("confirm-order-btn")
|
||||
? document.getElementById("confirm-order-btn").getAttribute("data-order-id")
|
||||
: "1") +
|
||||
"_cart";
|
||||
var cart = JSON.parse(localStorage.getItem(cartKey) || "{}");
|
||||
|
||||
var summaryTable = summaryDiv.querySelector(".checkout-summary-table");
|
||||
var tbody = summaryDiv.querySelector("#checkout-summary-tbody");
|
||||
var totalSection = summaryDiv.querySelector(".checkout-total-section");
|
||||
|
||||
// If no table found, create it with headers (shouldn't happen, but fallback)
|
||||
if (!summaryTable) {
|
||||
var html =
|
||||
'<table class="table table-hover checkout-summary-table" id="checkout-summary-table" role="grid" aria-label="Purchase summary"><thead class="table-dark"><tr>' +
|
||||
'<th scope="col" class="col-name">' +
|
||||
escapeHtml(labels.product) +
|
||||
"</th>" +
|
||||
'<th scope="col" class="col-qty text-center">' +
|
||||
escapeHtml(labels.quantity) +
|
||||
"</th>" +
|
||||
'<th scope="col" class="col-price text-end">' +
|
||||
escapeHtml(labels.price) +
|
||||
"</th>" +
|
||||
'<th scope="col" class="col-subtotal text-end">' +
|
||||
escapeHtml(labels.subtotal) +
|
||||
"</th>" +
|
||||
'</tr></thead><tbody id="checkout-summary-tbody"></tbody></table>' +
|
||||
'<div class="checkout-total-section"><div class="total-row">' +
|
||||
'<span class="total-label">' +
|
||||
escapeHtml(labels.total) +
|
||||
"</span>" +
|
||||
'<span class="total-amount" id="checkout-total-amount">€0.00</span>' +
|
||||
"</div></div>";
|
||||
summaryDiv.innerHTML = html;
|
||||
summaryTable = summaryDiv.querySelector(".checkout-summary-table");
|
||||
tbody = summaryDiv.querySelector("#checkout-summary-tbody");
|
||||
totalSection = summaryDiv.querySelector(".checkout-total-section");
|
||||
}
|
||||
|
||||
// Clear only tbody, preserve headers
|
||||
tbody.innerHTML = "";
|
||||
|
||||
if (Object.keys(cart).length === 0) {
|
||||
// Show empty message if cart is empty
|
||||
var emptyRow = document.createElement("tr");
|
||||
emptyRow.id = "checkout-empty-row";
|
||||
emptyRow.className = "empty-message";
|
||||
emptyRow.innerHTML =
|
||||
'<td colspan="4" class="text-center text-muted py-4">' +
|
||||
'<i class="fa fa-inbox fa-2x mb-2"></i>' +
|
||||
"<p>" +
|
||||
escapeHtml(labels.empty) +
|
||||
"</p>" +
|
||||
"</td>";
|
||||
tbody.appendChild(emptyRow);
|
||||
|
||||
// Hide total section
|
||||
totalSection.style.display = "none";
|
||||
} else {
|
||||
// Hide empty row if visible
|
||||
var emptyRow = tbody.querySelector("#checkout-empty-row");
|
||||
if (emptyRow) emptyRow.remove();
|
||||
|
||||
// Get delivery product ID from page data
|
||||
var checkoutPage = document.querySelector(".eskaera-checkout-page");
|
||||
var deliveryProductId = checkoutPage
|
||||
? checkoutPage.getAttribute("data-delivery-product-id")
|
||||
: null;
|
||||
|
||||
// Separate normal products from delivery product
|
||||
var normalProducts = [];
|
||||
var deliveryProduct = null;
|
||||
|
||||
Object.keys(cart).forEach(function (productId) {
|
||||
if (productId === deliveryProductId) {
|
||||
deliveryProduct = { id: productId, item: cart[productId] };
|
||||
} else {
|
||||
normalProducts.push({ id: productId, item: cart[productId] });
|
||||
}
|
||||
});
|
||||
|
||||
// Sort normal products numerically
|
||||
normalProducts.sort(function (a, b) {
|
||||
return parseInt(a.id) - parseInt(b.id);
|
||||
});
|
||||
|
||||
var total = 0;
|
||||
|
||||
// Render normal products first
|
||||
normalProducts.forEach(function (product) {
|
||||
var item = product.item;
|
||||
var qty = parseFloat(item.quantity || item.qty || 1);
|
||||
if (isNaN(qty)) qty = 1;
|
||||
var price = parseFloat(item.price || 0);
|
||||
if (isNaN(price)) price = 0;
|
||||
var subtotal = qty * price;
|
||||
total += subtotal;
|
||||
|
||||
var row = document.createElement("tr");
|
||||
row.innerHTML =
|
||||
"<td>" +
|
||||
escapeHtml(item.name) +
|
||||
"</td>" +
|
||||
'<td class="text-center">' +
|
||||
qty.toFixed(2).replace(/\.?0+$/, "") +
|
||||
"</td>" +
|
||||
'<td class="text-end">€' +
|
||||
price.toFixed(2) +
|
||||
"</td>" +
|
||||
'<td class="text-end">€' +
|
||||
subtotal.toFixed(2) +
|
||||
"</td>";
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// Render delivery product last if present
|
||||
if (deliveryProduct) {
|
||||
var item = deliveryProduct.item;
|
||||
var qty = parseFloat(item.quantity || item.qty || 1);
|
||||
if (isNaN(qty)) qty = 1;
|
||||
var price = parseFloat(item.price || 0);
|
||||
if (isNaN(price)) price = 0;
|
||||
var subtotal = qty * price;
|
||||
total += subtotal;
|
||||
|
||||
var row = document.createElement("tr");
|
||||
row.innerHTML =
|
||||
"<td>" +
|
||||
escapeHtml(item.name) +
|
||||
"</td>" +
|
||||
'<td class="text-center">' +
|
||||
qty.toFixed(2).replace(/\.?0+$/, "") +
|
||||
"</td>" +
|
||||
'<td class="text-end">€' +
|
||||
price.toFixed(2) +
|
||||
"</td>" +
|
||||
'<td class="text-end">€' +
|
||||
subtotal.toFixed(2) +
|
||||
"</td>";
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
|
||||
// Update total
|
||||
var totalAmount = summaryDiv.querySelector("#checkout-total-amount");
|
||||
if (totalAmount) {
|
||||
totalAmount.textContent = "€" + total.toFixed(2);
|
||||
}
|
||||
|
||||
// Show total section
|
||||
totalSection.style.display = "block";
|
||||
}
|
||||
|
||||
console.log("[CHECKOUT] Summary rendered");
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape HTML to prevent XSS
|
||||
*/
|
||||
function escapeHtml(text) {
|
||||
var div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
})();
|
||||
|
|
@ -68,10 +68,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Get order ID from multiple possible sources
|
||||
// Get order ID from multiple possible sources. The checkout page
|
||||
// carries it on its wrapper: with online payment on there is no
|
||||
// confirm button there, the payment form takes its place.
|
||||
var confirmBtn = document.getElementById("confirm-order-btn");
|
||||
var cartContainer = document.getElementById("cart-items-container");
|
||||
var orderIdElement = confirmBtn || cartContainer;
|
||||
var orderIdElement =
|
||||
confirmBtn ||
|
||||
cartContainer ||
|
||||
(checkoutPage && checkoutPage.getAttribute("data-order-id") ? checkoutPage : null);
|
||||
|
||||
// The URL is not a fallback here: it carries the slug of the order,
|
||||
// not its id.
|
||||
|
|
@ -81,22 +86,16 @@
|
|||
|
||||
console.log("[HomeDelivery] orderId resolved:", this.orderId);
|
||||
|
||||
// Handle checkbox (only exists on checkout page)
|
||||
// Handle checkbox (only exists on checkout page). Its state is
|
||||
// rendered from `sale_order.home_delivery`, so it is not read back
|
||||
// from localStorage here: the order is what the payment form
|
||||
// charges, and the two must not disagree.
|
||||
var checkbox = document.getElementById("home-delivery-checkbox");
|
||||
if (checkbox) {
|
||||
var self = this;
|
||||
checkbox.addEventListener("change", function () {
|
||||
if (this.checked) {
|
||||
self.addDeliveryProduct();
|
||||
self.showDeliveryInfo();
|
||||
} else {
|
||||
self.removeDeliveryProduct();
|
||||
self.hideDeliveryInfo();
|
||||
}
|
||||
self.setDeliveryOnOrder(this.checked, this);
|
||||
});
|
||||
|
||||
// Check if delivery product is already in cart on page load
|
||||
this.checkDeliveryInCart();
|
||||
}
|
||||
|
||||
// Vincular botón Home Delivery en el shop SOLO si hay un producto de delivery válido
|
||||
|
|
@ -133,11 +132,6 @@
|
|||
homeDeliveryBtn.classList.remove("btn-outline-warning");
|
||||
homeDeliveryBtn.classList.add("active", "btn-warning");
|
||||
}
|
||||
|
||||
// Trigger cart reload to update UI
|
||||
if (typeof window.renderCheckoutSummary === "function") {
|
||||
window.renderCheckoutSummary();
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial button state
|
||||
|
|
@ -184,17 +178,92 @@
|
|||
}
|
||||
},
|
||||
|
||||
checkDeliveryInCart: function () {
|
||||
if (!this.deliveryProductId) return;
|
||||
/**
|
||||
* Move the delivery line on and off the draft order (checkout page).
|
||||
*
|
||||
* The checkout renders the sale.order and its total is what gets
|
||||
* charged, so the toggle has to reach the order — writing only to
|
||||
* localStorage would change the summary and leave the amount alone.
|
||||
* localStorage is kept in step so the shop cart agrees, and the page
|
||||
* is reloaded to pick up the new total and payment amount.
|
||||
*/
|
||||
setDeliveryOnOrder: function (isDelivery, checkbox) {
|
||||
var self = this;
|
||||
|
||||
var cart = this.getCart();
|
||||
if (cart[this.deliveryProductId]) {
|
||||
var checkbox = document.getElementById("home-delivery-checkbox");
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
this.showDeliveryInfo();
|
||||
}
|
||||
if (!this.orderId) {
|
||||
console.warn("[HomeDelivery] No order id, cannot set delivery");
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkbox) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/eskaera/set-home-delivery", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status !== 200) {
|
||||
console.error("[HomeDelivery] set-home-delivery failed:", xhr.status);
|
||||
self.revertCheckbox(checkbox, !isDelivery);
|
||||
return;
|
||||
}
|
||||
var data = {};
|
||||
try {
|
||||
data = JSON.parse(xhr.responseText);
|
||||
} catch (e) {
|
||||
data = {};
|
||||
}
|
||||
if (!data.success) {
|
||||
console.error("[HomeDelivery] set-home-delivery rejected:", data.error);
|
||||
self.revertCheckbox(checkbox, !isDelivery);
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the shop cart in step with the order before reloading.
|
||||
var cart = self.getCart();
|
||||
if (self.deliveryProductId) {
|
||||
if (data.is_delivery) {
|
||||
cart[self.deliveryProductId] = {
|
||||
id: self.deliveryProductId,
|
||||
name: self.deliveryProductName,
|
||||
price: self.deliveryProductPrice,
|
||||
qty: 1,
|
||||
};
|
||||
} else {
|
||||
delete cart[self.deliveryProductId];
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(
|
||||
"eskaera_" + self.orderId + "_cart",
|
||||
JSON.stringify(cart)
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn("[HomeDelivery] Could not update the local cart:", e);
|
||||
}
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
console.error("[HomeDelivery] set-home-delivery connection error");
|
||||
self.revertCheckbox(checkbox, !isDelivery);
|
||||
};
|
||||
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
order_id: self.orderId,
|
||||
is_delivery: isDelivery,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
revertCheckbox: function (checkbox, previousState) {
|
||||
if (!checkbox) return;
|
||||
checkbox.checked = previousState;
|
||||
checkbox.disabled = false;
|
||||
},
|
||||
|
||||
getCart: function () {
|
||||
|
|
@ -216,14 +285,6 @@
|
|||
window.groupOrderShop._updateCartDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
// Re-render checkout summary without reloading
|
||||
setTimeout(function () {
|
||||
// Use the global function from checkout_labels.js
|
||||
if (typeof window.renderCheckoutSummary === "function") {
|
||||
window.renderCheckoutSummary();
|
||||
}
|
||||
}, 50);
|
||||
},
|
||||
|
||||
addDeliveryProduct: function () {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,12 @@
|
|||
// Get order ID first (needed by i18nManager and other functions)
|
||||
var confirmBtn = document.getElementById("confirm-order-btn");
|
||||
var cartContainer = document.getElementById("cart-items-container");
|
||||
// The checkout page carries the id on its wrapper: with online
|
||||
// payment on there is no confirm button there, the payment form
|
||||
// takes its place.
|
||||
this._checkoutPage = document.querySelector(".eskaera-checkout-page[data-order-id]");
|
||||
|
||||
var orderIdElement = confirmBtn || cartContainer;
|
||||
var orderIdElement = confirmBtn || cartContainer || this._checkoutPage;
|
||||
if (!orderIdElement) {
|
||||
console.log("No elements found to get order ID");
|
||||
return false;
|
||||
|
|
@ -126,6 +130,15 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Never on the checkout page. It renders the draft server-side, so
|
||||
// pulling the same lines back into localStorage would only put the
|
||||
// two out of step — and it used to resurrect lines the member had
|
||||
// just deleted in the shop, which then reappeared at payment time.
|
||||
if (this._checkoutPage) {
|
||||
console.log("Auto-load draft skipped (checkout renders the order itself)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Only auto-load if cart is empty
|
||||
var cartItemsCount = Object.keys(this.cart).length;
|
||||
if (cartItemsCount > 0) {
|
||||
|
|
@ -442,8 +455,7 @@
|
|||
// Storage layout:
|
||||
// eskaera_<id>_cart → items as plain {productId: {...}}
|
||||
// (keeps the contract used by
|
||||
// checkout_labels.js, home_delivery.js
|
||||
// and _saveOrderDraft).
|
||||
// home_delivery.js and _saveOrderDraft).
|
||||
// eskaera_<id>_cart_cycle → "YYYY-MM-DD" the cart was saved at.
|
||||
// Eviction only fires when we KNOW the current cycle and it
|
||||
// disagrees with the stored one. If either side is missing
|
||||
|
|
@ -472,8 +484,8 @@
|
|||
return;
|
||||
}
|
||||
// Migration: v18.0.1.10.0 wrapped the cart as {cutoff_date, items}
|
||||
// in the same key, which broke checkout_labels.js / home_delivery.js
|
||||
// / _saveOrderDraft (they iterated Object.keys treating them as
|
||||
// in the same key, which broke home_delivery.js and
|
||||
// _saveOrderDraft (they iterated Object.keys treating them as
|
||||
// productIds). Unwrap once; subsequent saves use the canonical
|
||||
// split-key layout below.
|
||||
var items;
|
||||
|
|
@ -747,7 +759,8 @@
|
|||
},
|
||||
|
||||
_getLabels: function () {
|
||||
// Get current labels from window.groupOrderShop which is updated by checkout_labels.js
|
||||
// Get current labels from window.groupOrderShop, seeded by the
|
||||
// labels the page renders inline.
|
||||
console.log("[_getLabels] Starting label resolution...");
|
||||
console.log("[_getLabels] window.groupOrderShop exists:", !!window.groupOrderShop);
|
||||
|
||||
|
|
@ -880,7 +893,7 @@
|
|||
|
||||
_showConfirmation: function (message, onConfirm, onCancel) {
|
||||
var self = this;
|
||||
// Get current labels - may be updated by checkout_labels.js endpoint
|
||||
// Get current labels - seeded by the labels the page renders inline
|
||||
var labels = this._getLabels();
|
||||
console.log("[_showConfirmation] Using labels:", labels);
|
||||
|
||||
|
|
@ -998,29 +1011,15 @@
|
|||
});
|
||||
}
|
||||
|
||||
// On checkout page: apply sessionStorage delivery preference to checkbox.
|
||||
// The shop-page toggle may have stored a "false" preference even though
|
||||
// the checkbox is checked by default in the template.
|
||||
var checkoutCheckbox = document.getElementById("home-delivery-checkbox");
|
||||
if (checkoutCheckbox) {
|
||||
var storedDeliveryPref = sessionStorage.getItem(
|
||||
"eskaera_is_delivery_" + self.orderId
|
||||
);
|
||||
if (storedDeliveryPref !== null) {
|
||||
checkoutCheckbox.checked = storedDeliveryPref === "true";
|
||||
console.log(
|
||||
"[CHECKOUT] Restored delivery checkbox from sessionStorage:",
|
||||
checkoutCheckbox.checked
|
||||
);
|
||||
}
|
||||
// Sync sessionStorage when user manually changes the checkbox
|
||||
checkoutCheckbox.addEventListener("change", function () {
|
||||
sessionStorage.setItem(
|
||||
"eskaera_is_delivery_" + self.orderId,
|
||||
this.checked ? "true" : "false"
|
||||
);
|
||||
});
|
||||
}
|
||||
// The checkout delivery checkbox is no longer restored from
|
||||
// sessionStorage: it renders from `sale_order.home_delivery`,
|
||||
// and home_delivery.js writes any change straight back to the
|
||||
// order. A stale session preference could only contradict it.
|
||||
|
||||
// Send the cart to the server before opening the checkout, so
|
||||
// the page renders the member's current cart rather than
|
||||
// whatever the draft happened to hold.
|
||||
this._attachCheckoutLinkListeners();
|
||||
|
||||
// Button to reload from draft (in My Cart header - cart pages)
|
||||
var reloadCartBtn = document.getElementById("reload-cart-btn");
|
||||
|
|
@ -1621,6 +1620,103 @@
|
|||
self._executeSaveCartAsDraft(items);
|
||||
},
|
||||
|
||||
// Read the cart the way the server wants it. localStorage is the
|
||||
// source of truth while shopping: home_delivery.js writes to it
|
||||
// directly, so this.cart can lag behind by a tick.
|
||||
_collectCartItems: function () {
|
||||
var cartKey = "eskaera_" + this.orderId + "_cart";
|
||||
var storedCart = localStorage.getItem(cartKey);
|
||||
var cart;
|
||||
try {
|
||||
cart = storedCart ? JSON.parse(storedCart) : this.cart;
|
||||
} catch (e) {
|
||||
cart = this.cart;
|
||||
}
|
||||
return Object.keys(cart || {}).map(function (productId) {
|
||||
var item = cart[productId];
|
||||
return {
|
||||
product_id: productId,
|
||||
product_name: item.name,
|
||||
quantity: item.qty,
|
||||
product_price: item.price,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// The checkout renders the draft sale.order, so the cart has to reach
|
||||
// the server before we navigate. Saving is idempotent: the endpoint
|
||||
// reuses the cycle's draft and only rewrites its lines when they
|
||||
// actually differ, so a member who already saved from the shop just
|
||||
// gets their changes applied.
|
||||
_attachCheckoutLinkListeners: function () {
|
||||
var self = this;
|
||||
var links = document.querySelectorAll(".js-eskaera-checkout");
|
||||
console.log("[_attachEventListeners] checkout links found:", links.length);
|
||||
|
||||
links.forEach(function (link) {
|
||||
link.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
self._saveCartAndGoToCheckout(link.getAttribute("href"));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_saveCartAndGoToCheckout: function (checkoutUrl) {
|
||||
var self = this;
|
||||
var labels = this._getLabels();
|
||||
var items = this._collectCartItems();
|
||||
|
||||
if (items.length === 0) {
|
||||
this._showNotification(labels.empty_cart || "Your cart is empty", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
var orderData = {
|
||||
order_id: this.orderId,
|
||||
items: items,
|
||||
merge_action: "replace",
|
||||
};
|
||||
|
||||
// Delivery preference: the shop toggle owns it on this page.
|
||||
var deliveryBtn = document.getElementById("home-delivery-btn");
|
||||
if (deliveryBtn) {
|
||||
orderData.is_delivery = deliveryBtn.classList.contains("active");
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/eskaera/save-order", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
window.location.href = checkoutUrl;
|
||||
return;
|
||||
}
|
||||
if (self._isClosedOrderResponse(xhr)) {
|
||||
self._clearCurrentOrderCartSilently();
|
||||
self._updateCartDisplay();
|
||||
return;
|
||||
}
|
||||
if (self._handleAlreadyPlacedResponse(xhr)) {
|
||||
return;
|
||||
}
|
||||
var message = labels.error_saving_draft || "Error saving cart";
|
||||
try {
|
||||
var errorData = JSON.parse(xhr.responseText);
|
||||
message = errorData.error || message;
|
||||
} catch (e) {
|
||||
message = message + " (HTTP " + xhr.status + ")";
|
||||
}
|
||||
self._showNotification(message, "danger");
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
self._showNotification(labels.connection_error || "Connection error", "danger");
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify(orderData));
|
||||
},
|
||||
|
||||
_executeSaveCartAsDraft: function (items) {
|
||||
var self = this;
|
||||
|
||||
|
|
@ -1934,13 +2030,10 @@
|
|||
labels.draft_saved_success ||
|
||||
labels.draft_saved ||
|
||||
"Order saved as draft successfully";
|
||||
// No navigation here: the confirmation notice used
|
||||
// to be wiped out by an immediate redirect to the
|
||||
// payment step, so the member saw nothing at all.
|
||||
self._showNotification("\u2713 " + successMsg, "success", 5000);
|
||||
// With online payment on, the server answers with the
|
||||
// payment step URL: saving the cart is only half of
|
||||
// placing the order.
|
||||
if (data.redirect_url) {
|
||||
window.location.href = data.redirect_url;
|
||||
}
|
||||
} else {
|
||||
self._showNotification(
|
||||
"Error: " + (data.error || labels.error_unknown || "Unknown error"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue