Files
vans-diy-bastelbedarf/src/pages/fr/checkout/index.astro
T
qciganoandClaude Sonnet 5 c9d80c9ac0 PayPal-Zahlungskarte im Checkout: eigens gestaltete, mittig geteilte Kachel
Auf ausdrücklichen Wunsch: die PayPal-Zahlungsoption ist jetzt eine "besondere" Kachel statt
der schlichten Icon-Zeile — exakt in der Mitte horizontal geteilt, obere Hälfte PayPal-
Markenverlauf mit Logo + zweifarbiger Wortmarke, untere Hälfte die Visa-/Mastercard-Hinweise.
Animierter Verlaufsrand, leuchtender Mittelsteg, Glanzeffekt und Glitzer-Akzente — dieselbe
Formsprache wie die anderen "besonderen" Karten der Seite (z.B. .account-head). Alle 4 Sprachen.

Feste Höhe (statt min-height) auf der Kachel, damit die Trennlinie IMMER exakt mittig sitzt,
unabhängig vom Inhalt jeder Hälfte — mit auto-Höhe hätte der Flex-Container sich sonst am
längeren Inhalt orientiert und die Linie wäre verrutscht (nachgemessen: vorher 106,6px oben vs.
141px unten, jetzt 109px/109px exakt).

Dabei zwei echte Bugs gefunden und behoben:
1. Eine generische Farb-Regel (".pay-card:has(input:checked) .pay-option span:not(.pay-icon)")
   hätte die neue zweifarbige Wortmarke und den Kartenhinweis auf die einfarbige Markenfarbe
   umgefärbt und auf dem blauen Verlaufshintergrund fast unlesbar gemacht — mit gezielt
   spezifischeren Regeln überschrieben.
2. Die Radio-Markierung überlappte bei bestimmten Fensterbreiten (ca. 800–1000px) die
   Mastercard-Karte, weil zwei VERSCHACHTELTE Zweispalten-Layouts (.checkout-layout und
   .checkout-split) bei ZU ÄHNLICHEN Umbruchpunkten (800px bzw. 760px) gleichzeitig aktiv
   blieben und die Zahlungsspalte auf ca. 90px zusammenquetschten — ein vorbestehender
   Layout-Fehler, der die ganze Zahlungsspalte betraf, nicht nur die neue Kachel. Behoben,
   indem die innere Spalte jetzt deutlich früher umbricht (1024px statt 760px), lange bevor
   die äußere Spalte eng wird.

Getestet (Browser, alle 4 Sprachen): Kontrastprüfung im markierten Zustand, Geometrie der
Trennlinie, Breiten-Sweep von 390px bis 1600px ohne Überlappung mehr, Mobil-Ansicht.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-04 11:11:25 +02:00

408 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
import Layout from "../../../layouts/Layout.astro";
import type { Locale } from "../../../i18n/config";
import { useTranslations } from "../../../i18n/ui";
const lang: Locale = "fr";
const t = useTranslations(lang);
---
<Layout title="Paiement" description="Finaliser la commande chez Van's DIY & Bastelbedarf." lang={lang} path="/checkout/">
<section class="section-tight">
<div class="container">
<span class="eyebrow">{t.checkout.eyebrow}</span>
<h1>{t.checkout.title}</h1>
</div>
</section>
<section class="section-tight">
<div class="container checkout-layout">
<form class="frm card" id="checkout-form">
<div class="checkout-split">
<div class="checkout-split-col">
<h3>{t.checkout.step2}</h3>
{/* Nur noch zwei Zahlungsarten: Klarna raus (Bonitätsprüfungs-Pflichten), keine eigene
Kreditkarten-Eingabemaske (PCI-DSS-pflichtig) — Kartenzahlung läuft stattdessen über
PayPals eigenen, PCI-zertifizierten Gast-Checkout. */}
<div class="payment-options">
<div class="pay-card" style="--brand-color:#009cde; --brand-bg:#003087;">
<label class="pay-option pay-option-paypal-split">
{/* Speziell gestaltete Karte auf ausdrücklichen Wunsch: exakt in der Mitte geteilt —
obere Hälfte PayPal-Markenfarben/Wortmarke, untere Hälfte Visa/Mastercard. Eigener
Premium-Stil (Verlaufsrand, Glanz, Sternchen) statt der schlichten Icon-Zeile der
anderen Zahlungsart, siehe .pp-tile im CSS. */}
<span class="pp-tile">
<span class="pp-tile-shine" aria-hidden="true"></span>
<span class="pp-sparkle pp-sparkle-1" aria-hidden="true">✨</span>
<span class="pp-sparkle pp-sparkle-2" aria-hidden="true">✨</span>
<span class="pp-half pp-half-top">
<span class="pp-badge" aria-hidden="true">
<svg viewBox="0 0 24 24" width="26" height="26" fill="#fff" aria-hidden="true"><path d="M15.607 4.653H8.941L6.645 19.251H1.82L4.862 0h7.995c3.754 0 6.375 2.294 6.473 5.513-.648-.478-2.105-.86-3.722-.86m6.57 5.546c0 3.41-3.01 6.853-6.958 6.853h-2.493L11.595 24H6.74l1.845-11.538h3.592c4.208 0 7.346-3.634 7.153-6.949a5.24 5.24 0 0 1 2.848 4.686M9.653 5.546h6.408c.907 0 1.942.222 2.363.541-.195 2.741-2.655 5.483-6.441 5.483H8.714Z"></path></svg>
</span>
<span class="pp-wordmark"><span class="pp-word-pay">Pay</span><span class="pp-word-pal">Pal</span></span>
</span>
<span class="pp-divider" aria-hidden="true"></span>
<span class="pp-half pp-half-bottom">
<span class="pp-cards-caption">{t.checkout.payPalSub}</span>
<span class="pp-cards-row" aria-hidden="true">
<span class="mini-card mini-card-visa mini-card-lg"><svg viewBox="0 0 24 24" fill="#1434CB" aria-hidden="true"><path d="M9.112 8.262L5.97 15.758H3.92L2.374 9.775c-.094-.368-.175-.503-.461-.658C1.447 8.864.677 8.627 0 8.479l.046-.217h3.3a.904.904 0 01.894.764l.817 4.338 2.018-5.102zm8.033 5.049c.008-1.979-2.736-2.088-2.717-2.972.006-.269.262-.555.822-.628a3.66 3.66 0 011.913.336l.34-1.59a5.207 5.207 0 00-1.814-.333c-1.917 0-3.266 1.02-3.278 2.479-.012 1.079.963 1.68 1.698 2.04.756.367 1.01.603 1.006.931-.005.504-.602.725-1.16.734-.975.015-1.54-.263-1.992-.473l-.351 1.642c.453.208 1.289.39 2.156.398 2.037 0 3.37-1.006 3.377-2.564m5.061 2.447H24l-1.565-7.496h-1.656a.883.883 0 00-.826.55l-2.909 6.946h2.036l.405-1.12h2.488zm-2.163-2.656l1.02-2.815.588 2.815zm-8.16-4.84l-1.603 7.496H8.34l1.605-7.496z"></path></svg></span>
<span class="mini-card mini-card-mastercard mini-card-lg"><svg viewBox="0 0 32 20" aria-hidden="true"><circle cx="13" cy="10" r="7.2" fill="#EB001B"></circle><circle cx="19" cy="10" r="7.2" fill="#F79E1B"></circle><path d="M16 4.4a7.18 7.18 0 010 11.2 7.18 7.18 0 010-11.2z" fill="#FF5F00"></path></svg></span>
</span>
</span>
</span>
<input type="radio" name="pay" value="paypal" checked />
</label>
<div class="pay-panel-inline">
<p>{t.checkout.payPalHint}</p>
<div id="paypal-button-container"></div>
<p class="pay-connect-status" id="paypal-error" style="display:none; color: var(--c-sale);"></p>
</div>
</div>
<div class="pay-card" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
<label class="pay-option">
<span class="pay-icon" style="background:linear-gradient(160deg, #4d7599, #2c4258);">
<svg viewBox="0 0 24 24" width="27" height="27" fill="none" stroke="#fff" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 9.5 12 4l10 5.5"></path><path d="M4 9.5V19M9 9.5V19M15 9.5V19M20 9.5V19"></path><path d="M2.5 19h19"></path></svg>
</span>
<span>{t.checkout.bankTransfer}</span>
<input type="radio" name="pay" value="ueberweisung" />
</label>
<div class="pay-panel-inline">
<p>{t.checkout.bankHintReal}</p>
<div class="bank-details" id="bank-details"></div>
</div>
</div>
</div>
</div>
<div class="checkout-split-divider" aria-hidden="true"><span>🧵</span></div>
<div class="checkout-split-col">
<h3>{t.checkout.step1}</h3>
<div><label for="email">{t.checkout.email}</label><input id="email" type="email" required /></div>
<div class="grid grid-2">
<div><label for="vorname">{t.checkout.firstName}</label><input id="vorname" type="text" required /></div>
<div><label for="nachname">{t.checkout.lastName}</label><input id="nachname" type="text" required /></div>
</div>
<div><label for="strasse">{t.checkout.street}</label><input id="strasse" type="text" placeholder="1 Rue de lExemple" required /></div>
<div class="grid grid-2">
<div><label for="plz">{t.checkout.zip}</label><input id="plz" type="text" inputmode="numeric" maxlength="5" placeholder="75001" required /></div>
<div><label for="ort">{t.checkout.city}</label><input id="ort" type="text" placeholder="Paris" required /></div>
</div>
<div>
<label for="land">{t.checkout.country}</label>
<select id="land">
<option value="de">Allemagne</option>
<option value="at">Autriche</option>
<option value="ch">Suisse</option>
<option value="lu">Luxembourg</option>
</select>
</div>
<hr class="divider" />
<h3>{t.checkout.step3}</h3>
<div id="checkout-summary" class="checkout-summary"></div>
<p class="small">{t.common.vatNote}</p>
<div class="checkbox-row">
<input id="agb" type="checkbox" required />
<label for="agb">{t.checkout.agbPrefix} <a href="/agb/">{t.checkout.agbLink}</a> {t.checkout.agbSuffix}</label>
</div>
<div class="checkbox-row">
<input id="widerruf" type="checkbox" required />
<label for="widerruf">{t.checkout.revocationPrefix} <a href="/widerruf/">{t.checkout.revocationLink}</a> {t.checkout.revocationAnd} <a href="/datenschutz/">{t.checkout.privacyLink}</a> {t.checkout.revocationSuffix}</label>
</div>
</div>
</div>
<p id="order-submit-hint">{t.checkout.payPalCompleteHint}</p>
<button class="btn btn-primary btn-block" type="submit" id="order-submit-btn">{t.checkout.orderButton}</button>
</form>
<aside class="card checkout-aside">
<div class="coupon-row">
<label for="coupon-input">{t.cart.couponLabel}</label>
<div class="coupon-input-group">
<input type="text" id="coupon-input" placeholder={t.cart.couponPlaceholder} />
<button type="button" id="coupon-apply" class="btn btn-outline btn-sm">{t.cart.couponApply}</button>
</div>
<p class="small" id="coupon-status"></p>
</div>
<hr class="divider stitched" />
<div class="coupon-row">
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
<div class="coupon-input-group">
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
</div>
<p class="small" id="partner-code-status"></p>
</div>
<hr class="divider stitched" />
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
<p class="small">{t.checkout.shippingHint}</p>
<a class="small" href="/fr/versand-zahlung/">{t.checkout.shippingLink}</a>
</aside>
</div>
</section>
<section class="section-tight">
<div class="container">
<p class="todo-note">{t.checkout.todoNote}</p>
</div>
</section>
</Layout>
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/fr/checkout/danke/", checkoutLang: lang, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, bankHolderLabel: t.checkout.bankHolder, bankIbanLabel: t.checkout.bankIban, bankBicLabel: t.checkout.bankBic, bankBankNameLabel: t.checkout.bankBankName, bankPlaceholderLabel: t.checkout.bankPlaceholder, partnerDiscountLabel: t.cart.partnerDiscount, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove, loyaltyDiscountLabel: t.cart.loyaltyDiscount, aboDiscountLabel: t.cart.aboDiscount, gratisPraemieLabel: t.cart.gratisPraemie, payPalCompleteHintLabel: t.checkout.payPalCompleteHint }}>
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltyDiscountLabel, aboDiscountLabel, gratisPraemieLabel, payPalCompleteHintLabel };
</script>
<script type="module">
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, treuebonusDiscount, aboDiscount, istAusgewaehlt, zeilenpreisFuer } from "../../../scripts/cart";
import { formatPrice } from "../../../i18n/format";
import { products, getProduct } from "../../../data/products";
import { berechneVersandkosten } from "../../../data/versand";
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
import { registriereAbgeschlosseneBestellung, treuebonusEinloesen } from "../../../scripts/account";
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltyDiscountLabel, aboDiscountLabel, gratisPraemieLabel, payPalCompleteHintLabel } = window.__checkoutVars;
// TODO (VanVan/qciga) : "sb" est l'identifiant sandbox gratuit de PayPal — fonctionne
// immédiatement sans compte, mais aucun argent réel ne circule. Remplacer par le vrai
// identifiant client PayPal Business de VanVan une fois prêt (developer.paypal.com).
const PAYPAL_CLIENT_ID = "sb";
const summary = document.getElementById("checkout-summary");
const landSelect = document.getElementById("land");
const cart = getCart().filter(istAusgewaehlt);
const subtotal = cartTotal();
let currentTotal = 0;
let currentShipping = 0;
let currentRabattGesamt = 0;
function renderSummary() {
const land = landSelect.value;
const shipping = cart.length === 0 ? 0 : berechneVersandkosten(land, cart.map((i) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
const coupon = appliedCoupon();
const discount = couponDiscount(subtotal);
const partnerCode = appliedPartnerCode();
const partnerDiscount = partnerCodeDiscount(subtotal);
const loyaltyDiscount = treuebonusDiscount();
const aboDiscountBetrag = aboDiscount(subtotal);
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
currentTotal = total;
currentShipping = shipping;
currentRabattGesamt = totalDiscount(subtotal);
summary.innerHTML = `
${cart.map((i) => {
const zeilenpreis = zeilenpreisFuer(i);
return `<div class="row"><span>${i.menge}× ${i.name}${i.gratis ? ` <span class="small">(${gratisPraemieLabel})</span>` : ""}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
}).join("")}
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
${partnerCode && partnerDiscount > 0 ? `<div class="row"><span>${partnerDiscountLabel}</span><span>-${formatPrice(partnerDiscount, checkoutLang)}</span></div>` : ""}
${loyaltyDiscount > 0 ? `<div class="row"><span>${loyaltyDiscountLabel}</span><span>-${formatPrice(loyaltyDiscount, checkoutLang)}</span></div>` : ""}
${aboDiscountBetrag > 0 ? `<div class="row"><span>${aboDiscountLabel}</span><span>-${formatPrice(aboDiscountBetrag, checkoutLang)}</span></div>` : ""}
<div class="row"><span>${shippingLabel}</span><span>${shipping === 0 ? freeLabel : formatPrice(shipping, checkoutLang)}</span></div>
<div class="row total"><span>${totalLabel}</span><span>${formatPrice(total, checkoutLang)}</span></div>
`;
const couponStatus = document.getElementById("coupon-status");
const couponInput = document.getElementById("coupon-input");
if (coupon && discount > 0) {
couponStatus.innerHTML = `✅ ${couponAppliedTemplate.replace("__C__", coupon.code)} <a href="#" id="coupon-clear">✕ ${couponRemoveLabel}</a>`;
couponInput.value = coupon.code;
const clearLink = document.getElementById("coupon-clear");
if (clearLink) clearLink.addEventListener("click", (e) => { e.preventDefault(); clearCoupon(); renderSummary(); });
} else {
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
}
const partnerStatus = document.getElementById("partner-code-status");
const partnerInput = document.getElementById("partner-code-input");
if (partnerCode && partnerDiscount > 0) {
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
partnerInput.value = partnerCode.code;
const partnerClearLink = document.getElementById("partner-code-clear");
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); renderSummary(); });
} else {
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
}
}
renderSummary();
landSelect.addEventListener("change", renderSummary);
function baueBestellPayload() {
return {
land: landSelect.value,
kunde: {
name: `${document.getElementById("vorname").value.trim()} ${document.getElementById("nachname").value.trim()}`.trim(),
email: document.getElementById("email").value.trim(),
strasse: document.getElementById("strasse").value.trim(),
plz: document.getElementById("plz").value.trim(),
ort: document.getElementById("ort").value.trim(),
},
artikel: cart.map((i) => {
const produkt = getProduct(i.slug);
const einzelpreis = i.gratis ? 0 : zeilenpreisFuer(i) / i.menge;
return { slug: i.slug, name: i.name, kategorie: produkt?.kategorie ?? "", menge: i.menge, preis: einzelpreis, gratis: !!i.gratis };
}),
zwischensumme: subtotal,
versandkosten: currentShipping,
rabattGesamt: currentRabattGesamt,
summe: currentTotal,
treuebonusVerwendet: treuebonusDiscount() > 0,
aboRabattVerwendet: aboDiscount(subtotal) > 0,
gutscheinCode: appliedCoupon()?.code ?? null,
};
}
function nachBestellErfolg() {
if (treuebonusDiscount() > 0) {
treuebonusEinloesen();
} else {
registriereAbgeschlosseneBestellung();
}
location.href = thankYouPath;
}
let paypalLoaded = false;
const paypalErrorEl = document.getElementById("paypal-error");
function zeigePaypalFehler(msg) {
if (!paypalErrorEl) return;
paypalErrorEl.textContent = "⚠️ " + msg;
paypalErrorEl.style.display = "block";
}
function loadPayPalButtons() {
if (paypalLoaded || !document.getElementById("paypal-button-container")) return;
paypalLoaded = true;
const script = document.createElement("script");
script.src = `https://www.paypal.com/sdk/js?client-id=${PAYPAL_CLIENT_ID}&currency=EUR&intent=capture`;
script.onload = () => {
if (!window.paypal) return;
window.paypal.Buttons({
style: { layout: "vertical", color: "blue", shape: "pill", label: "paypal" },
createOrder: async () => {
if (cart.length === 0) {
alert(emptyCartAlert);
throw new Error("Panier vide");
}
if (paypalErrorEl) paypalErrorEl.style.display = "none";
const res = await fetch("/api/paypal/create-order", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ summe: currentTotal }),
});
const data = await res.json().catch(() => null);
if (!res.ok || !data?.ok) {
zeigePaypalFehler(data?.error || "Impossible de créer la commande PayPal.");
throw new Error(data?.error || "Échec de la commande PayPal.");
}
return data.id;
},
onApprove: async (data) => {
try {
const res = await fetch("/api/paypal/capture-order", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ paypalOrderId: data.orderID, ...baueBestellPayload() }),
});
const result = await res.json().catch(() => null);
if (!res.ok || !result?.ok) {
zeigePaypalFehler(result?.error || "Le paiement n'a pas pu être finalisé.");
return;
}
nachBestellErfolg();
} catch (err) {
zeigePaypalFehler(err instanceof Error ? err.message : "Erreur de paiement inconnue.");
}
},
onError: () => {
zeigePaypalFehler("PayPal a signalé une erreur. Réessayez ou choisissez le virement bancaire.");
},
onCancel: () => {
if (paypalErrorEl) paypalErrorEl.style.display = "none";
},
}).render("#paypal-button-container");
};
document.head.appendChild(script);
}
loadPayPalButtons();
const bankDetailsEl = document.getElementById("bank-details");
if (bankDetailsEl) {
if (bankverbindungVollstaendig(bankverbindung)) {
bankDetailsEl.innerHTML = `
<div class="bank-row"><span>${bankHolderLabel}</span><span>${bankverbindung.kontoinhaber}</span></div>
<div class="bank-row"><span>${bankIbanLabel}</span><span>${bankverbindung.iban}</span></div>
${bankverbindung.bic ? `<div class="bank-row"><span>${bankBicLabel}</span><span>${bankverbindung.bic}</span></div>` : ""}
${bankverbindung.bank ? `<div class="bank-row"><span>${bankBankNameLabel}</span><span>${bankverbindung.bank}</span></div>` : ""}
`;
} else {
bankDetailsEl.innerHTML = `<p class="small">${bankPlaceholderLabel}</p>`;
}
}
document.getElementById("coupon-apply").addEventListener("click", () => {
const input = document.getElementById("coupon-input");
const code = input.value.trim();
if (!code) { clearCoupon(); renderSummary(); return; }
setCouponCode(code);
input.dataset.invalid = appliedCoupon() ? "0" : "1";
renderSummary();
});
document.getElementById("partner-code-apply").addEventListener("click", () => {
const input = document.getElementById("partner-code-input");
const code = input.value.trim();
if (!code) { clearPartnerCode(); renderSummary(); return; }
setPartnerCode(code);
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
renderSummary();
});
const orderForm = document.getElementById("checkout-form");
const submitBtn = document.getElementById("order-submit-btn");
const orderErrorEl = document.createElement("p");
orderErrorEl.className = "small";
orderErrorEl.style.color = "var(--c-sale)";
orderErrorEl.style.display = "none";
submitBtn.insertAdjacentElement("beforebegin", orderErrorEl);
orderForm.addEventListener("submit", async (e) => {
e.preventDefault();
if (cart.length === 0) {
alert(emptyCartAlert);
return;
}
const zahlungsart = document.querySelector('input[name="pay"]:checked')?.value || "";
if (zahlungsart === "paypal") {
orderErrorEl.textContent = "⚠️ " + payPalCompleteHintLabel;
orderErrorEl.style.display = "block";
return;
}
orderErrorEl.style.display = "none";
submitBtn.disabled = true;
const urspruenglicherBtnText = submitBtn.textContent;
submitBtn.textContent = "…";
try {
const res = await fetch("/api/orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...baueBestellPayload(), zahlungsart }),
});
const data = await res.json().catch(() => null);
if (!res.ok || !data?.ok) {
throw new Error(data?.error || "Bestellung konnte nicht gespeichert werden.");
}
nachBestellErfolg();
} catch (err) {
orderErrorEl.textContent = "⚠️ " + (err instanceof Error ? err.message : "Bestellung konnte nicht gespeichert werden. Bitte nochmal versuchen.");
orderErrorEl.style.display = "block";
submitBtn.disabled = false;
submitBtn.textContent = urspruenglicherBtnText;
}
});
</script>