(function () { const THEME_KEY = "corner-cafe-theme"; const SERVICE_KEY = "corner-cafe-order-service"; const MODE_KEY = "corner-cafe-order-mode"; const CART_KEY = "corner-cafe-cart"; const serviceLabels = { delivery: "Delivery", collection: "Collection", "sitting-in": "Sitting in", }; const modeLabels = { menu: "Browse menu", }; const root = document.documentElement; const metaTheme = document.getElementById("meta-theme-color"); const toggle = document.querySelector("[data-theme-toggle]"); const applyTheme = (theme) => { const next = theme === "light" ? "light" : "dark"; root.setAttribute("data-theme", next); if (metaTheme) metaTheme.setAttribute("content", next === "light" ? "#f6f3ee" : "#070707"); if (toggle) { toggle.setAttribute( "aria-label", next === "light" ? "Switch to dark theme" : "Switch to light theme" ); } }; try { applyTheme(localStorage.getItem(THEME_KEY) || "dark"); } catch (e) { applyTheme("dark"); } toggle?.addEventListener("click", () => { const next = root.getAttribute("data-theme") === "light" ? "dark" : "light"; applyTheme(next); try { localStorage.setItem(THEME_KEY, next); } catch (e) { /* ignore */ } }); const params = new URLSearchParams(window.location.search); const money = (n) => `£${Number(n).toFixed(2)}`; const priceText = (item) => item.priceDisplay || money(item.price); const readCart = () => { try { const raw = sessionStorage.getItem(CART_KEY); const parsed = raw ? JSON.parse(raw) : []; return Array.isArray(parsed) ? parsed : []; } catch (e) { return []; } }; const writeCart = (next) => { try { sessionStorage.setItem(CART_KEY, JSON.stringify(next)); } catch (e) { /* ignore */ } }; let cart = readCart(); let menuRenderList = null; const cartCount = () => cart.reduce((n, line) => n + line.qty, 0); const cartTotal = () => cart.reduce((n, line) => n + line.qty * line.price, 0); const qtyOf = (id) => cart.find((line) => line.id === id)?.qty || 0; const setQty = (item, qty) => { const next = Math.max(0, Math.floor(qty)); const idx = cart.findIndex((line) => line.id === item.id); if (next <= 0) { if (idx >= 0) cart.splice(idx, 1); } else if (idx >= 0) { cart[idx].qty = next; } else { cart.push({ id: item.id, name: item.name, price: item.price, qty: next, section: item.section || "", }); } writeCart(cart); renderCartChrome(); if (typeof menuRenderList === "function") menuRenderList(); }; const addToCart = (item) => setQty(item, qtyOf(item.id) + 1); const removeOne = (item) => setQty(item, qtyOf(item.id) - 1); const clearCart = () => { cart = []; writeCart(cart); renderCartChrome(); if (typeof menuRenderList === "function") menuRenderList(); }; const pageFile = (location.pathname.split("/").pop() || "").toLowerCase(); const onOrderFlowPage = pageFile === "order.html" || pageFile === "checkout.html"; let suppressUnloadWarn = false; let orderPlaced = false; const clearPayRefs = () => { try { const keys = []; for (let i = 0; i < sessionStorage.length; i += 1) { const key = sessionStorage.key(i); if (key && key.startsWith("corner-cafe-pay-ref")) keys.push(key); } keys.forEach((key) => sessionStorage.removeItem(key)); } catch (e) { /* ignore */ } }; const abandonBasket = () => { cart = []; writeCart(cart); clearPayRefs(); renderCartChrome(); if (typeof menuRenderList === "function") menuRenderList(); }; const isSameOriginFlowUrl = (href) => { try { const url = new URL(href, location.href); if (url.origin !== location.origin) return false; const file = (url.pathname.split("/").pop() || "").toLowerCase(); return file === "order.html" || file === "checkout.html"; } catch (e) { return false; } }; const ensureLeaveDialog = () => { let dlg = document.querySelector("[data-leave-dialog]"); if (dlg) return dlg; dlg = document.createElement("dialog"); dlg.className = "leave-dialog"; dlg.setAttribute("data-leave-dialog", ""); dlg.innerHTML = `

Leave this order?

If you go back or leave now, your selected menu items will be cleared and you’ll need to start again.

`; document.body.appendChild(dlg); return dlg; }; const confirmLeaveOrder = () => new Promise((resolve) => { const dlg = ensureLeaveDialog(); const onClose = () => { dlg.removeEventListener("close", onClose); resolve(dlg.returnValue === "leave"); }; dlg.addEventListener("close", onClose); if (typeof dlg.showModal === "function") dlg.showModal(); else resolve(window.confirm("Leave this order? Your basket will be cleared.")); }); const leaveOrderTo = async (href) => { if (cartCount() <= 0 || orderPlaced) { suppressUnloadWarn = true; window.location.href = href; return; } const leave = await confirmLeaveOrder(); if (!leave) return; suppressUnloadWarn = true; abandonBasket(); window.location.href = href; }; // Abandoned baskets must not linger on home / path / reserve. if (!onOrderFlowPage && cart.length) { cart = []; writeCart(cart); clearPayRefs(); } if (onOrderFlowPage) { window.addEventListener("beforeunload", (e) => { if (suppressUnloadWarn || orderPlaced || cartCount() <= 0) return; e.preventDefault(); e.returnValue = ""; }); history.pushState({ ccLeaveGuard: true }, "", location.href); window.addEventListener("popstate", () => { if (orderPlaced) return; if (pageFile === "checkout.html") { const service = params.get("service") || sessionStorage.getItem(SERVICE_KEY) || ""; suppressUnloadWarn = true; window.location.replace( serviceLabels[service] ? `order.html?service=${encodeURIComponent(service)}&mode=menu` : "./" ); return; } history.pushState({ ccLeaveGuard: true }, "", location.href); if (cartCount() <= 0) { suppressUnloadWarn = true; window.location.replace("./"); return; } confirmLeaveOrder().then((leave) => { if (!leave) return; suppressUnloadWarn = true; abandonBasket(); window.location.replace("./"); }); }); document.addEventListener( "click", (e) => { const link = e.target.closest?.("a[href]"); if (!link || link.hasAttribute("download") || link.target === "_blank") return; const href = link.getAttribute("href"); if (!href || href.startsWith("#") || href.startsWith("javascript:")) return; if (isSameOriginFlowUrl(href)) { suppressUnloadWarn = true; return; } if (cartCount() <= 0 || orderPlaced) return; e.preventDefault(); e.stopPropagation(); leaveOrderTo(link.href); }, true ); } const qtyControlsHtml = (item, qty) => { if (qty <= 0) { return ``; } return `
${qty}
`; }; const bindQtyControls = (rootEl, findItem) => { rootEl.querySelectorAll("[data-add]").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const item = findItem(btn.getAttribute("data-add")); if (item) addToCart(item); }); }); rootEl.querySelectorAll("[data-inc]").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const item = findItem(btn.getAttribute("data-inc")); if (item) addToCart(item); }); }); rootEl.querySelectorAll("[data-dec]").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const item = findItem(btn.getAttribute("data-dec")); if (item) removeOne(item); }); }); }; const renderCartChrome = () => { const bar = document.querySelector("[data-cart-bar]"); const countEl = document.querySelector("[data-cart-count]"); const totalEl = document.querySelector("[data-cart-total]"); const linesEl = document.querySelector("[data-cart-lines]"); const dialogTotal = document.querySelector("[data-cart-dialog-total]"); if (!bar) return; const count = cartCount(); if (countEl) countEl.textContent = count === 1 ? "1 item" : `${count} items`; if (totalEl) totalEl.textContent = money(cartTotal()); if (dialogTotal) dialogTotal.textContent = money(cartTotal()); bar.hidden = count === 0; if (linesEl) { linesEl.innerHTML = ""; const menu = window.CornerCafeMenu || []; cart.forEach((line) => { const item = menu.find((m) => m.id === line.id) || line; const li = document.createElement("li"); li.className = "cart-line cart-line--edit"; li.innerHTML = ` ${line.name} ${money(line.qty * line.price)} ${qtyControlsHtml(item, line.qty)} `; bindQtyControls(li, (id) => menu.find((m) => m.id === id) || (id === line.id ? line : null)); linesEl.appendChild(li); }); } }; document.querySelector("[data-cart-review]")?.addEventListener("click", () => { document.querySelector("[data-cart-dialog]")?.showModal(); }); document.querySelector("[data-cart-clear]")?.addEventListener("click", () => { clearCart(); document.querySelector("[data-cart-dialog]")?.close(); }); const goCheckout = () => { if (cartCount() <= 0) return; const service = params.get("service") || sessionStorage.getItem(SERVICE_KEY) || ""; const mode = params.get("mode") || sessionStorage.getItem(MODE_KEY) || "menu"; if (!serviceLabels[service]) { window.location.replace("./"); return; } document.querySelector("[data-cart-dialog]")?.close(); suppressUnloadWarn = true; window.location.href = `checkout.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}`; }; document.querySelectorAll("[data-cart-checkout]").forEach((btn) => { btn.addEventListener("click", (e) => { e.preventDefault(); goCheckout(); }); }); const pathLinks = document.querySelectorAll("[data-path-link]"); if (pathLinks.length) { const service = params.get("service") || ""; if (!serviceLabels[service]) { window.location.replace("./"); return; } try { sessionStorage.setItem(SERVICE_KEY, service); } catch (e) { /* ignore */ } const serviceEl = document.querySelector("[data-order-service]"); if (serviceEl) serviceEl.textContent = serviceLabels[service]; pathLinks.forEach((link) => { link.href = `order.html?service=${encodeURIComponent(service)}&mode=menu`; }); } const serviceEl = document.querySelector("[data-order-service]"); const panelMenu = document.querySelector("[data-panel-menu]"); if (serviceEl && panelMenu) { const service = params.get("service") || ""; let mode = params.get("mode") || "menu"; if (!modeLabels[mode]) mode = "menu"; if (!serviceLabels[service]) { window.location.replace("./"); return; } try { sessionStorage.setItem(SERVICE_KEY, service); sessionStorage.setItem(MODE_KEY, mode); } catch (e) { /* ignore */ } serviceEl.textContent = serviceLabels[service]; const back = document.querySelector("[data-back-path]"); if (back) back.href = "./"; panelMenu.hidden = false; initMenuPanel(); renderCartChrome(); } function initMenuPanel() { const menu = window.CornerCafeMenu || []; const filters = document.querySelector("[data-menu-filters]"); const list = document.querySelector("[data-menu-list]"); if (!filters || !list) return; const sections = ["Breakfast", "Lunch", "Tea", "Drinks", "Indian", "Kebab", "Pizza"].filter((s) => menu.some((item) => item.section === s) ); let active = sections[0] || "Breakfast"; const hoursEl = document.querySelector("[data-section-hours]"); const syncSectionHours = () => { if (!hoursEl) return; const h = window.SmOS_CC_Hours; if (active === "Indian" && h?.indian) { hoursEl.hidden = false; hoursEl.innerHTML = `Indian cuisine ${h.indian.display} · online cut-off ${h.indian.orderCutoff}.`; } else if (h?.cafe && ["Breakfast", "Lunch", "Tea", "Drinks"].includes(active)) { hoursEl.hidden = false; hoursEl.innerHTML = `Cafe ${h.cafe.display} · online cut-off ${h.cafe.orderCutoff}.`; } else { hoursEl.hidden = true; hoursEl.textContent = ""; } }; const findItem = (id) => menu.find((m) => m.id === id); const renderFilters = () => { filters.innerHTML = ""; sections.forEach((section) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "menu-filter" + (section === active ? " is-active" : ""); btn.textContent = section; btn.setAttribute("role", "tab"); btn.setAttribute("aria-selected", section === active ? "true" : "false"); btn.addEventListener("click", () => { active = section; renderFilters(); renderList(); syncSectionHours(); }); filters.appendChild(btn); }); }; const renderList = () => { list.innerHTML = ""; const items = menu.filter((item) => item.section === active); let lastGroup = ""; items.forEach((item) => { const group = item.group || ""; if (group && group !== lastGroup) { const h = document.createElement("h3"); h.className = "menu-group"; h.textContent = group; list.appendChild(h); lastGroup = group; } const row = document.createElement("div"); row.className = "menu-item" + (qtyOf(item.id) > 0 ? " is-in-basket" : ""); const qty = qtyOf(item.id); row.innerHTML = ` ${item.name} ${item.desc ? `${item.desc}` : ""} ${priceText(item)} ${qtyControlsHtml(item, qty)} `; bindQtyControls(row, findItem); list.appendChild(row); }); }; menuRenderList = renderList; renderFilters(); renderList(); syncSectionHours(); } function tokenize(text) { return String(text || "") .toLowerCase() .replace(/[^a-z0-9\s&']/g, " ") .split(/\s+/) .filter((t) => t.length > 1 && !["and", "the", "with", "for", "please", "want", "like", "some", "a", "an", "of", "my", "me"].includes(t)); } function scoreItem(item, tokens) { const hay = `${item.name} ${item.desc} ${(item.tags || []).join(" ")}`.toLowerCase(); let score = 0; tokens.forEach((token) => { if (hay.includes(token)) score += token.length > 4 ? 3 : 2; (item.tags || []).forEach((tag) => { if (tag === token) score += 4; else if (tag.startsWith(token) || token.startsWith(tag)) score += 2; }); if (item.name.toLowerCase().includes(token)) score += 2; }); return score; } function matchOrder(query) { const menu = window.CornerCafeMenu || []; const tokens = tokenize(query); if (!tokens.length) return []; const chunks = String(query) .split(/,| and |&|\+| then | also /i) .map((c) => c.trim()) .filter(Boolean); const results = []; const used = new Set(); const pushBest = (chunkTokens) => { let best = null; let bestScore = 0; menu.forEach((item) => { if (used.has(item.id)) return; const s = scoreItem(item, chunkTokens); if (s > bestScore) { bestScore = s; best = item; } }); if (best && bestScore >= 3) { used.add(best.id); results.push({ item: best, score: bestScore }); } }; if (chunks.length > 1) { chunks.forEach((chunk) => pushBest(tokenize(chunk))); } if (!results.length) { menu .map((item) => ({ item, score: scoreItem(item, tokens) })) .filter((r) => r.score >= 3) .sort((a, b) => b.score - a.score) .slice(0, 8) .forEach((r) => results.push(r)); } return results; } function initKnowPanel() { const form = document.querySelector("[data-know-form]"); const results = document.querySelector("[data-know-results]"); if (!form || !results) return; const findItem = (id) => (window.CornerCafeMenu || []).find((m) => m.id === id); const renderMatches = (matches) => { results.hidden = false; results.innerHTML = ""; const status = document.createElement("p"); status.className = "know-status"; if (!matches.length) { status.textContent = "No clear match — try a dish name from the menu."; results.appendChild(status); return; } status.textContent = `${matches.length} match${matches.length === 1 ? "" : "es"}`; results.appendChild(status); const list = document.createElement("div"); list.className = "know-match-list"; matches.forEach(({ item }) => { const row = document.createElement("div"); row.className = "know-match"; const qty = qtyOf(item.id); row.innerHTML = ` ${item.name} ${item.section}${item.desc ? ` · ${item.desc}` : ""} · ${priceText(item)} ${qtyControlsHtml(item, qty)} `; bindQtyControls(row, findItem); list.appendChild(row); }); results.appendChild(list); }; form.addEventListener("submit", (event) => { event.preventDefault(); const input = form.querySelector("[name=want]"); const query = (input?.value || "").trim(); renderMatches(matchOrder(query)); }); } function orderReference(service) { const prefixes = { delivery: "DEL", collection: "COL", "sitting-in": "SIT", }; const prefix = prefixes[service] || "ORD"; const storageKey = `corner-cafe-pay-ref-${service || "order"}`; let ref = ""; try { ref = sessionStorage.getItem(storageKey) || ""; } catch (e) { /* ignore */ } // Reject legacy generic CC- refs so service codes stay distinguishable. if (ref && !ref.startsWith(`${prefix}-`)) ref = ""; if (!ref) { const stamp = Date.now().toString(36).toUpperCase().slice(-5); const rand = Math.floor(Math.random() * 90 + 10); ref = `${prefix}-${stamp}${rand}`; try { sessionStorage.setItem(storageKey, ref); } catch (e) { /* ignore */ } } return ref; } function clearOrderReference(service) { try { sessionStorage.removeItem(`corner-cafe-pay-ref-${service || "order"}`); sessionStorage.removeItem("corner-cafe-pay-ref"); } catch (e) { /* ignore */ } } function loadSitTablesFromSession() { try { const raw = sessionStorage.getItem("corner-cafe-reservation"); if (!raw) return []; const res = JSON.parse(raw); return Array.isArray(res.tables) ? res.tables : []; } catch (e) { return []; } } async function fillTableSelect(selectEl, preferredIds) { if (!selectEl) return; const cfg = window.SmOS_CC_Reservations; const tables = Array.isArray(cfg?.tables) ? cfg.tables : []; const pref = new Set((preferredIds || []).map(String)); let occupied = new Set(); if (window.SmOS_API?.occupiedTables) { try { occupied = new Set(await window.SmOS_API.occupiedTables()); } catch (e) { occupied = new Set(); } } selectEl.innerHTML = ``; tables.forEach((t) => { const opt = document.createElement("option"); opt.value = t.id; const taken = occupied.has(String(t.id)) && !pref.has(String(t.id)); opt.textContent = `Table ${t.label}${t.seats ? ` · ${t.seats} seats` : ""}${ taken ? " · occupied" : "" }`; opt.disabled = taken; if (pref.has(String(t.id))) opt.selected = true; selectEl.appendChild(opt); }); if (!tables.length) { selectEl.innerHTML = ""; const opt = document.createElement("option"); opt.value = ""; opt.textContent = "Enter table below"; selectEl.appendChild(opt); } // If multiple reserved tables, keep first selected; user can change. if (pref.size && !selectEl.value) { const first = [...pref][0]; selectEl.value = first; } } function initAddressLookup(root) { const postcodeEl = root.querySelector("[data-addr-postcode]"); const findBtn = root.querySelector("[data-addr-find]"); const listEl = root.querySelector("[data-addr-list]"); const statusEl = root.querySelector("[data-addr-status]"); const valueEl = root.querySelector("[data-addr-value]"); const manualBtn = root.querySelector("[data-addr-manual]"); if (!postcodeEl || !findBtn || !listEl || !valueEl) return; let debounceTimer = null; let lastQuery = ""; const setStatus = (msg, show = true) => { if (!statusEl) return; statusEl.textContent = msg || ""; statusEl.hidden = !show || !msg; }; const showManual = (focus) => { valueEl.hidden = false; valueEl.required = true; if (manualBtn) manualBtn.hidden = true; if (focus) valueEl.focus(); }; const renderList = (addresses) => { listEl.innerHTML = ""; if (!addresses.length) { listEl.hidden = true; return; } addresses.forEach((addr, idx) => { const li = document.createElement("li"); li.className = "addr-lookup__item"; const btn = document.createElement("button"); btn.type = "button"; btn.className = "addr-lookup__option"; btn.setAttribute("role", "option"); btn.textContent = addr.label || ""; btn.addEventListener("click", () => { valueEl.value = addr.label || ""; valueEl.hidden = false; valueEl.required = true; listEl.hidden = true; listEl.querySelectorAll(".addr-lookup__option").forEach((el) => { el.removeAttribute("aria-selected"); }); btn.setAttribute("aria-selected", "true"); setStatus("Address selected. You can edit it below if needed.", true); if (manualBtn) manualBtn.hidden = true; }); li.appendChild(btn); listEl.appendChild(li); if (idx === 0) btn.focus({ preventScroll: true }); }); listEl.hidden = false; }; const runLookup = async () => { const postcode = String(postcodeEl.value || "").trim(); if (!postcode) { setStatus("Enter a postcode to find addresses."); return; } if (postcode === lastQuery && !listEl.hidden) return; lastQuery = postcode; findBtn.disabled = true; setStatus("Looking up addresses…"); listEl.hidden = true; listEl.innerHTML = ""; try { if (!window.SmOS_API?.lookupAddresses) { throw new Error("Address lookup unavailable. Enter the address manually."); } const data = await window.SmOS_API.lookupAddresses(postcode); const addresses = Array.isArray(data.addresses) ? data.addresses : []; if (!addresses.length) { setStatus("No addresses found. Enter the address manually."); showManual(false); if (manualBtn) manualBtn.hidden = true; return; } const demoNote = data.demo ? " Demo mode — add IDEAL_POSTCODES_API_KEY for live UK postcodes." : ""; setStatus(`${addresses.length} address${addresses.length === 1 ? "" : "es"} found. Select one.${demoNote}`); renderList(addresses); if (manualBtn) { manualBtn.hidden = false; manualBtn.textContent = "Enter address manually"; } valueEl.hidden = true; valueEl.value = ""; valueEl.required = false; } catch (err) { setStatus(err?.message || "Address lookup failed. Enter the address manually."); showManual(false); if (manualBtn) { manualBtn.hidden = false; manualBtn.textContent = "Enter address manually"; } } finally { findBtn.disabled = false; } }; findBtn.addEventListener("click", () => { clearTimeout(debounceTimer); runLookup(); }); postcodeEl.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); clearTimeout(debounceTimer); runLookup(); } }); postcodeEl.addEventListener("input", () => { clearTimeout(debounceTimer); const raw = String(postcodeEl.value || "").trim(); const compact = raw.replace(/\s+/g, ""); if (compact.length < 5) return; if (!/^[A-Z]{1,2}\d[A-Z\d]?\d[A-Z]{2}$/i.test(compact)) return; debounceTimer = setTimeout(runLookup, 450); }); manualBtn?.addEventListener("click", () => { listEl.hidden = true; showManual(true); setStatus("Type your full delivery address below.", true); }); } async function initCheckout() { const rootEl = document.querySelector("[data-checkout]"); if (!rootEl) return; if (window.SmOS_CC_PaymentsReady) { try { await window.SmOS_CC_PaymentsReady; } catch (e) { /* keep static defaults */ } } const mollieReturn = params.get("mollie_return") === "1"; const molliePaymentId = (params.get("payment_id") || "").trim(); if (mollieReturn && molliePaymentId) { suppressUnloadWarn = true; rootEl.querySelectorAll("[data-checkout-hide-on-done]").forEach((el) => { el.hidden = true; }); const done = document.querySelector("[data-checkout-done]"); const lead = document.querySelector("[data-done-lead]"); const donePay = document.querySelector("[data-done-pay]"); if (done) done.hidden = false; if (lead) lead.textContent = "Confirming Mollie payment…"; if (donePay) { donePay.innerHTML = `

Please wait while we confirm your payment.

`; } let pendingMeta = {}; try { pendingMeta = JSON.parse(sessionStorage.getItem("corner-cafe-mollie-pending") || "{}") || {}; } catch (_) { pendingMeta = {}; } const showMollieResult = (status, booking) => { const ref = booking?.ref || pendingMeta.ref || "—"; const name = booking?.customer_name || pendingMeta.name || ""; const total = booking?.total != null ? money(booking.total) : money(pendingMeta.total || 0); const svc = booking?.service || pendingMeta.service || "order"; const svcLabel = serviceLabels[svc] || svc; if (status === "paid" && booking) { orderPlaced = true; clearCart(); clearOrderReference(svc); try { sessionStorage.removeItem("corner-cafe-mollie-pending"); } catch (_) {} if (lead) { lead.textContent = `Thanks ${name || "there"}. ${ref} · ${svcLabel} · ${total} · paid via Mollie.`; } if (donePay) { donePay.innerHTML = `

Payment confirmed. We’re preparing your ${String(svcLabel).toLowerCase()} order ${ref}.

`; } const receiptBox = document.querySelector("[data-done-receipt]"); if (receiptBox) receiptBox.hidden = false; const historyOrder = { ref, service: svc, payment: "mollie", paymentName: booking.payment || "Mollie", paymentStatus: "paid", total: booking.total, items: booking.items || [], name, phone: booking.phone || pendingMeta.phone || "", email: booking.email || pendingMeta.email || "", confirmVia: pendingMeta.confirmVia || "none", wantReceipt: Boolean(pendingMeta.wantReceipt), address: booking.address || "", notes: booking.notes || "", placedAt: booking.created_at || new Date().toISOString(), }; try { const history = JSON.parse(sessionStorage.getItem("corner-cafe-orders") || "[]"); history.unshift(historyOrder); sessionStorage.setItem("corner-cafe-orders", JSON.stringify(history.slice(0, 20))); } catch (_) {} const receiptBtn = document.querySelector("[data-download-receipt]"); if (receiptBtn && window.SmOSReceipt?.openPrint) { receiptBtn.onclick = () => { window.SmOSReceipt.openPrint({ ...historyOrder, customer_name: name, payment_status: "paid", payment: historyOrder.paymentName, }); }; } return; } if (lead) { lead.textContent = status === "canceled" || status === "expired" || status === "failed" ? `Payment ${status}. Order ${ref} was not placed.` : `Payment status: ${status || "open"}. Order ${ref} is not confirmed yet.`; } if (donePay) { donePay.innerHTML = `

If you completed payment, wait a moment and refresh — or return to checkout and try again.

Back to checkout

`; } }; try { if (!window.SmOS_API?.mollieStatus) throw new Error("Mollie status API unavailable"); let result = await window.SmOS_API.mollieStatus(molliePaymentId); // Brief poll — webhook may lag; status endpoint also finalizes when paid. for (let i = 0; i < 4 && result && !result.finalized && result.mollie_status === "open"; i += 1) { await new Promise((r) => setTimeout(r, 900)); result = await window.SmOS_API.mollieStatus(molliePaymentId); } showMollieResult(result?.mollie_status, result?.booking); } catch (e) { if (lead) lead.textContent = e.message || "Could not confirm Mollie payment."; if (donePay) { donePay.innerHTML = `

Back to start

`; } } return; } const service = params.get("service") || ""; const mode = params.get("mode") || "menu"; if (!serviceLabels[service] || cartCount() <= 0) { window.location.replace(serviceLabels[service] ? `order.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}` : "./"); return; } try { sessionStorage.setItem(SERVICE_KEY, service); sessionStorage.setItem(MODE_KEY, mode); } catch (e) { /* ignore */ } const serviceEl = document.querySelector("[data-order-service]"); if (serviceEl) serviceEl.textContent = serviceLabels[service]; const back = document.querySelector("[data-back-order]"); if (back) back.href = `order.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}`; const addressField = document.querySelector("[data-field-address]"); if (addressField) { const needsAddress = service === "delivery"; addressField.hidden = !needsAddress; addressField.toggleAttribute("hidden", !needsAddress); const ta = addressField.querySelector("[data-addr-value], textarea[name=address]"); if (ta) { ta.required = false; ta.disabled = !needsAddress; if (!needsAddress) { ta.value = ""; ta.required = false; } } if (needsAddress) initAddressLookup(addressField); } const tableField = document.querySelector("[data-field-table]"); const tableSelect = document.querySelector("[name=table_id]"); const reservedTables = service === "sitting-in" ? loadSitTablesFromSession() : []; if (tableField) { const needsTable = service === "sitting-in"; tableField.hidden = !needsTable; tableField.toggleAttribute("hidden", !needsTable); if (tableSelect) { tableSelect.required = needsTable; tableSelect.disabled = !needsTable; if (needsTable) { await fillTableSelect( tableSelect, reservedTables.map((t) => t.id || t.label) ); } else { tableSelect.value = ""; } } } const linesEl = document.querySelector("[data-checkout-lines]"); const totalEl = document.querySelector("[data-checkout-total]"); const refEl = document.querySelector("[data-checkout-ref]"); const ref = orderReference(service); if (refEl) refEl.textContent = ref; if (totalEl) totalEl.textContent = money(cartTotal()); if (linesEl) { linesEl.innerHTML = ""; cart.forEach((line) => { const li = document.createElement("li"); li.className = "checkout-line"; li.innerHTML = `${line.qty}× ${line.name}${money(line.qty * line.price)}`; linesEl.appendChild(li); }); } const cfg = window.SmOS_CC_Payments || { options: [] }; const optionsRoot = document.querySelector("[data-pay-options]"); const detailEl = document.querySelector("[data-pay-detail]"); const confirmBox = document.querySelector("[data-pay-confirm]"); const confirmCheck = document.querySelector("[data-pay-confirmed]"); const placeBtn = document.querySelector("[data-place-order]"); const placeHint = document.querySelector("[data-place-hint]"); let selectedPay = null; const requiresPrepaid = () => { if (service === "delivery" || service === "collection") return true; if (!selectedPay) return false; return Boolean(selectedPay.requiresPaidConfirm); }; const isMollie = () => selectedPay && (selectedPay.id === "mollie" || selectedPay.provider === "mollie"); const available = (cfg.options || []).filter((opt) => (opt.availableFor || []).includes(service) ); const heroLead = document.querySelector("[data-checkout-lead]") || document.querySelector(".app-hero__lead"); const payBlock = document.querySelector("[data-checkout-pay]"); const payTitle = document.querySelector("[data-checkout-pay-title]"); const payIntro = document.querySelector("[data-checkout-pay-intro]"); if (heroLead) { if (service === "delivery" || service === "collection") { heroLead.textContent = "Pay first — order only places after payment is confirmed."; } else if (service === "sitting-in") { heroLead.textContent = "Add your details and table — you’re welcome to settle at the till before or after your meal."; } } if (service === "sitting-in" && payBlock) { payBlock.hidden = true; } else if (payTitle) { payTitle.textContent = "Payment"; } if (payIntro) { if (service === "delivery" || service === "collection") { payIntro.hidden = false; payIntro.textContent = cfg.mollieEnabled ? cfg.mollieMode === "test" ? "Pay by card, Apple Pay, or Google Pay (test mode)." : "Pay by card, Apple Pay, or Google Pay." : "Card payment is not available yet. Sit-in orders can still be placed and paid at the cafe."; } else { payIntro.hidden = true; } } const syncConfirmVia = () => { const via = document.querySelector('input[name="confirm_via"]:checked')?.value || "none"; const emailEl = document.querySelector("[data-confirm-email]"); const phoneEl = document.querySelector("[data-confirm-phone]"); const wantReceipt = Boolean(document.querySelector("[data-want-receipt]")?.checked); const receiptHint = document.querySelector("[data-receipt-contact-hint]"); if (emailEl) { const on = via === "email"; emailEl.required = on; emailEl.disabled = !on; emailEl.hidden = !on; if (!on) emailEl.value = ""; } if (phoneEl) { const on = via === "phone"; phoneEl.required = on; phoneEl.disabled = !on; phoneEl.hidden = !on; if (!on) phoneEl.value = ""; } if (receiptHint) { receiptHint.hidden = !wantReceipt || via === "email" || via === "phone"; } }; document.querySelectorAll("[data-confirm-via-opt]").forEach((input) => { input.addEventListener("change", syncConfirmVia); }); document.querySelector("[data-want-receipt]")?.addEventListener("change", syncConfirmVia); syncConfirmVia(); const paymentReady = () => { if (!selectedPay) return false; if (isMollie()) return Boolean(cfg.mollieEnabled); if (!requiresPrepaid()) return true; return Boolean(confirmCheck && confirmCheck.checked); }; const syncPlaceButton = () => { const mollie = isMollie(); const ready = paymentReady() && cartCount() > 0; if (placeBtn) { placeBtn.disabled = !ready; placeBtn.textContent = mollie ? "Pay now" : "Place order"; } if (placeHint) { if (mollie && !cfg.mollieEnabled) { placeHint.hidden = false; placeHint.textContent = "Card payment is not available yet."; } else { placeHint.hidden = true; placeHint.textContent = ""; } } if (confirmBox) confirmBox.hidden = true; }; const showDetail = (opt) => { if (!detailEl) return; detailEl.hidden = false; detailEl.innerHTML = `

${opt.summary}

`; syncPlaceButton(); }; if (optionsRoot) { optionsRoot.innerHTML = ""; if (service === "sitting-in") { selectedPay = available[0] || { id: "cash-sit-in", name: "Pay at the cafe", availableFor: ["sitting-in"], }; if (detailEl) detailEl.hidden = true; syncPlaceButton(); } else { available.forEach((opt, idx) => { const label = document.createElement("label"); label.className = "pay-option"; label.innerHTML = ` ${opt.name} ${opt.feeNote} `; const input = label.querySelector("input"); input.addEventListener("change", () => { if (!input.checked) return; selectedPay = opt; if (confirmCheck) confirmCheck.checked = false; showDetail(opt); }); optionsRoot.appendChild(label); }); if (available[0]) { selectedPay = available[0]; showDetail(available[0]); } else { syncPlaceButton(); } } } confirmCheck?.addEventListener("change", syncPlaceButton); syncPlaceButton(); document.querySelector("[data-checkout-form]")?.addEventListener("submit", async (event) => { event.preventDefault(); if (!selectedPay || cartCount() <= 0) return; const molliePay = isMollie(); if (!molliePay && requiresPrepaid() && !(confirmCheck && confirmCheck.checked)) { if (placeHint) placeHint.hidden = false; return; } const form = event.target; const data = new FormData(form); const tableId = String(data.get("table_id") || "").trim(); const cfgTables = window.SmOS_CC_Reservations?.tables || []; const tableMeta = cfgTables.find((t) => String(t.id) === tableId); const tables = service === "sitting-in" && tableId ? [ { id: tableId, label: tableMeta?.label || tableId, seats: tableMeta?.seats || null, }, ] : reservedTables.length ? reservedTables : null; const prepaid = requiresPrepaid() && !molliePay; const paymentStatus = molliePay ? "awaiting" : prepaid ? "paid" : "unpaid"; const confirmVia = String(data.get("confirm_via") || "none"); const email = String(data.get("email") || "").trim(); const phone = String(data.get("phone") || "").trim(); if (confirmVia === "email" && !email) { alert("Enter an email address for confirmation, or choose No confirmation."); return; } if (confirmVia === "phone" && !phone) { alert("Enter a phone number for confirmation, or choose No confirmation."); return; } const wantReceipt = Boolean(data.get("want_receipt")); if (wantReceipt && confirmVia !== "email" && confirmVia !== "phone") { alert("To receive a receipt, choose Email or Phone and enter your contact details."); document.querySelector("[data-receipt-contact-hint]")?.removeAttribute("hidden"); return; } if (wantReceipt && confirmVia === "email" && !email) { alert("Enter an email address to receive your receipt."); return; } if (wantReceipt && confirmVia === "phone" && !phone) { alert("Enter a phone number to receive your receipt."); return; } const deliveryAddress = String(data.get("address") || "").trim(); if (service === "delivery" && !deliveryAddress) { alert("Find your postcode and select a delivery address, or enter one manually."); document.querySelector("[data-addr-postcode]")?.focus(); return; } const order = { ref, service, mode, payment: selectedPay.id, paymentName: selectedPay.name, paymentStatus, paymentConfirmed: prepaid, total: cartTotal(), items: cart.slice(), name: String(data.get("name") || "").trim(), phone: confirmVia === "phone" ? phone : phone || "", email: confirmVia === "email" ? email : email || "", confirmVia, wantReceipt, address: service === "delivery" ? deliveryAddress : "", notes: String(data.get("notes") || "").trim(), tableId: tableId || null, tables, placedAt: new Date().toISOString(), }; if (placeBtn) placeBtn.disabled = true; if (molliePay) { if (!window.SmOS_API?.createMolliePayment) { if (placeBtn) placeBtn.disabled = false; alert("Mollie checkout is unavailable."); return; } try { const created = await window.SmOS_API.createMolliePayment({ kind: "order", service, ref: order.ref, customer_name: order.name, phone: order.phone || null, email: order.email || null, confirm_via: order.confirmVia === "none" ? null : order.confirmVia, address: order.address || null, notes: order.notes || null, total: order.total, items: order.items, tables: order.tables, payload: order, }); if (!created?.checkout_url) throw new Error("No Mollie checkout URL returned."); try { sessionStorage.setItem( "corner-cafe-mollie-pending", JSON.stringify({ paymentId: created.payment_id, ref: order.ref, service, total: order.total, name: order.name, wantReceipt: order.wantReceipt, confirmVia: order.confirmVia, email: order.email, phone: order.phone, }) ); } catch (_) {} suppressUnloadWarn = true; location.href = created.checkout_url; return; } catch (e) { if (placeBtn) placeBtn.disabled = false; alert(e.message || "Could not start Mollie checkout."); return; } } if (window.SmOS_API?.postBooking) { try { await window.SmOS_API.postBooking({ kind: "order", service, ref: order.ref, customer_name: order.name, phone: order.phone || null, email: order.email || null, confirm_via: order.confirmVia === "none" ? null : order.confirmVia, payment: order.paymentName, payment_status: paymentStatus, total: order.total, address: order.address || null, notes: order.notes || null, items: order.items, tables: order.tables, payload: order, }); } catch (e) { if (placeBtn) placeBtn.disabled = false; alert(e.message || "Could not place order. Confirm payment and try again."); return; } } try { const history = JSON.parse(sessionStorage.getItem("corner-cafe-orders") || "[]"); history.unshift(order); sessionStorage.setItem("corner-cafe-orders", JSON.stringify(history.slice(0, 20))); clearOrderReference(service); } catch (e) { /* ignore */ } orderPlaced = true; suppressUnloadWarn = true; clearCart(); rootEl.querySelectorAll("[data-checkout-hide-on-done]").forEach((el) => { el.hidden = true; }); const done = document.querySelector("[data-checkout-done]"); const lead = document.querySelector("[data-done-lead]"); const donePay = document.querySelector("[data-done-pay]"); if (done) done.hidden = false; if (lead) { lead.textContent = prepaid ? `Thanks ${order.name}. ${order.ref} · ${serviceLabels[service]} · ${money(order.total)} · paid & confirmed.` : `Thanks ${order.name}. Order ${order.ref} · ${serviceLabels[service]} · ${money(order.total)} · ${selectedPay.name}.`; } if (donePay) { if (prepaid) { donePay.innerHTML = `

Payment confirmed. We’re preparing your ${serviceLabels[service].toLowerCase()} order ${order.ref}.

`; } else if (service === "sitting-in") { donePay.innerHTML = `

Please settle ${money(order.total)} at the till whenever you’re ready — before or after your meal. Quote ${order.ref}${order.tableId ? ` · table ${order.tableId}` : ""}.

`; } else { donePay.innerHTML = `

Pay ${money(order.total)} at the cafe. Quote ${order.ref}.

`; } } const receiptBox = document.querySelector("[data-done-receipt]"); const receiptNote = document.querySelector("[data-receipt-send-note]"); const receiptBtn = document.querySelector("[data-download-receipt]"); if (receiptBox) { const showReceipt = Boolean(order.wantReceipt) || prepaid || service === "sitting-in"; receiptBox.hidden = !showReceipt; if (receiptNote) { if (order.wantReceipt && order.confirmVia === "email" && order.email) { receiptNote.hidden = false; receiptNote.textContent = `Receipt copy requested by email to ${order.email} (sent when messaging is connected).`; } else if (order.wantReceipt && order.confirmVia === "phone" && order.phone) { receiptNote.hidden = false; receiptNote.textContent = `Receipt copy requested by phone to ${order.phone} (sent when messaging is connected).`; } else if (order.wantReceipt) { receiptNote.hidden = false; receiptNote.textContent = "You asked for a receipt — download or print it below. Email/SMS delivery can be connected later."; } else if (service === "sitting-in") { receiptNote.hidden = false; receiptNote.textContent = "A till receipt can also be printed by your waiter after you pay."; } else { receiptNote.hidden = true; } } if (receiptBtn) { receiptBtn.onclick = () => { if (window.SmOSReceipt?.openPrint) { window.SmOSReceipt.openPrint({ ...order, customer_name: order.name, payment_status: order.paymentStatus, payment: order.paymentName, }); } else { alert("Receipt helper not loaded."); } }; } } if (heroLead) { heroLead.textContent = prepaid ? "Order placed — paid and confirmed." : service === "sitting-in" ? "Order placed — settle at the till when you’re ready." : "Order placed."; } }); } initCheckout(); })();