Zahlungsarten: echte PayPal-Verbindung, Kreditkarte hinzugefügt, echte Bankverbindung für Vorkasse
Standard-Set für deutsche Online-Shops recherchiert und umgesetzt: PayPal,
Klarna, Kreditkarte (Visa & Mastercard, neu) und Banküberweisung (Vorkasse).
- PayPal: echte PayPal Smart Buttons über das offizielle JS-SDK (Sandbox-
Client-ID, kostenlos & ohne Konto testbar). Klick öffnet den echten
PayPal-Login-Popup und schließt eine (Test-)Zahlung wirklich ab. Für den
Live-Betrieb genügt später der Tausch gegen VanVans echte (kostenlose)
PayPal-Business-Client-ID.
- Banküberweisung (Vorkasse): braucht gar keine externe Anbindung — zeigt
jetzt VanVans echte Kontodaten an, die sie im neuen Adminbereich-Feld
"🏦 Bankverbindung" selbst einträgt. Damit ist diese Zahlungsart schon
zu 100% fertig, ganz ohne Drittanbieter.
- Klarna & Kreditkarte: echte Live-Anbindung braucht ein Händlerkonto bzw.
einen Zahlungsdienstleister (Phase 2, Cloudflare Worker). Bis dahin ein
sauber gestalteter, funktionierender Verbindungs-/Eingabe-Ablauf, der
1:1 austauschbar ist, sobald die echten Zugänge da sind.
- Auswahl einer Zahlungsart klappt jetzt direkt das passende Panel auf
statt nur eine Radiobox zu markieren.
Verifiziert per Puppeteer-Funktionstest gegen den echten Build: PayPal-
SDK lädt und rendert einen echten Button (iframe), Kreditkartenfelder,
Klarna-Demo-Verbindung und echte Bankdaten-Anzeige funktionieren
fehlerfrei, Adminbereich lädt weiterhin ohne Konfigurationsfehler.
This commit is contained in:
@@ -32,6 +32,13 @@ const t = useTranslations(lang);
|
||||
<span>Klarna</span>
|
||||
<input type="radio" name="pay" value="klarna" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#ff9838; --brand-bg:#241f3d;">
|
||||
<span class="pay-icon pay-icon-cc" style="background:linear-gradient(160deg, #2a2a3d, #14141f);">
|
||||
<span class="cc-dot cc-dot-a"></span><span class="cc-dot cc-dot-b"></span>
|
||||
</span>
|
||||
<span>{t.checkout.creditCard}<span class="pay-sub">{t.checkout.creditCardSub}</span></span>
|
||||
<input type="radio" name="pay" value="kreditkarte" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
|
||||
<span class="pay-icon pay-icon-emoji" style="background:linear-gradient(160deg, #4d7599, #2c4258);">🏦</span>
|
||||
<span>{t.checkout.bankTransfer}</span>
|
||||
@@ -39,6 +46,35 @@ const t = useTranslations(lang);
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pay-connect">
|
||||
<div class="pay-panel" data-panel="paypal">
|
||||
<p>{t.checkout.payPalHint}</p>
|
||||
<div id="paypal-button-container"></div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="klarna" hidden>
|
||||
<p>{t.checkout.klarnaHint}</p>
|
||||
<button type="button" class="btn btn-outline pay-connect-btn" id="klarna-connect">{t.checkout.klarnaConnect}</button>
|
||||
<p class="pay-connect-status" id="klarna-status"></p>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="kreditkarte" hidden>
|
||||
<p>{t.checkout.cardHint}</p>
|
||||
<div class="card-fields">
|
||||
<div>
|
||||
<label for="cc-number">{t.checkout.cardNumber}</label>
|
||||
<input type="text" id="cc-number" inputmode="numeric" placeholder="1234 5678 9012 3456" maxlength="19" />
|
||||
</div>
|
||||
<div class="grid grid-2">
|
||||
<div><label for="cc-expiry">{t.checkout.cardExpiry}</label><input type="text" id="cc-expiry" placeholder="MM/YY" maxlength="5" /></div>
|
||||
<div><label for="cc-cvc">{t.checkout.cardCvc}</label><input type="text" id="cc-cvc" inputmode="numeric" placeholder="123" maxlength="4" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="ueberweisung" hidden>
|
||||
<p>{t.checkout.bankHintReal}</p>
|
||||
<div class="bank-details" id="bank-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="checkout-split-divider" aria-hidden="true"><span>🧵</span></div>
|
||||
<div class="checkout-split-col">
|
||||
@@ -103,7 +139,7 @@ const t = useTranslations(lang);
|
||||
|
||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/ch/checkout/danke/", checkoutLang: lang }}>
|
||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel };
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
||||
</script>
|
||||
<script type="module">
|
||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon } from "../../../scripts/cart";
|
||||
@@ -111,13 +147,15 @@ const t = useTranslations(lang);
|
||||
import { products } from "../../../data/products";
|
||||
import { berechneVersandkosten } from "../../../data/versand";
|
||||
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
||||
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel } = window.__checkoutVars;
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
||||
|
||||
const summary = document.getElementById("checkout-summary");
|
||||
const landSelect = document.getElementById("land");
|
||||
const cart = getCart();
|
||||
const subtotal = cartTotal();
|
||||
let currentTotal = 0;
|
||||
|
||||
function renderSummary() {
|
||||
const land = landSelect.value;
|
||||
@@ -125,6 +163,7 @@ import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
const coupon = appliedCoupon();
|
||||
const discount = couponDiscount(subtotal);
|
||||
const total = Math.max(0, subtotal - discount) + shipping;
|
||||
currentTotal = total;
|
||||
|
||||
summary.innerHTML = `
|
||||
${cart.map((i) => {
|
||||
@@ -152,6 +191,76 @@ import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
renderSummary();
|
||||
landSelect.addEventListener("change", renderSummary);
|
||||
|
||||
// Zahlungsart-Auswahl: pro Methode ein Panel, nur die gewählte ist sichtbar. Macht die
|
||||
// Auswahl sofort "aktionabel" statt ein reines Formularfeld zu sein.
|
||||
const payPanels = document.querySelectorAll(".pay-panel");
|
||||
function showPayPanel(method) {
|
||||
payPanels.forEach((p) => { p.hidden = p.dataset.panel !== method; });
|
||||
}
|
||||
document.querySelectorAll('input[name="pay"]').forEach((radio) => {
|
||||
radio.addEventListener("change", () => showPayPanel(radio.value));
|
||||
});
|
||||
const initialPay = document.querySelector('input[name="pay"]:checked');
|
||||
if (initialPay) showPayPanel(initialPay.value);
|
||||
|
||||
// PayPal: echte PayPal Smart Buttons über das offizielle JS-SDK (Sandbox-Client-ID "sb" —
|
||||
// kostenlos, ohne eigenes Konto testbar). Klick öffnet den echten PayPal-Login-Popup. Für den
|
||||
// Live-Betrieb muss hier nur "sb" durch VanVans echte (kostenlose) PayPal-Business-Client-ID
|
||||
// ersetzt werden, siehe developer.paypal.com.
|
||||
let paypalLoaded = false;
|
||||
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=sb¤cy=EUR&intent=capture";
|
||||
script.onload = () => {
|
||||
if (!window.paypal) return;
|
||||
window.paypal.Buttons({
|
||||
style: { layout: "vertical", color: "blue", shape: "pill", label: "paypal" },
|
||||
createOrder: (data, actions) => actions.order.create({
|
||||
purchase_units: [{ amount: { value: Math.max(0.01, currentTotal).toFixed(2), currency_code: "EUR" } }],
|
||||
}),
|
||||
onApprove: (data, actions) => actions.order.capture().then(() => { location.href = thankYouPath; }),
|
||||
}).render("#paypal-button-container");
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
loadPayPalButtons();
|
||||
|
||||
// Klarna: echtes "Verbinden" braucht eine serverseitige Session mit VanVans Klarna-Händlerkonto
|
||||
// (Phase 2, Cloudflare Worker). Bis dahin ein sauber gestalteter Demo-Verbindungsablauf, damit
|
||||
// die Oberfläche schon fertig aussieht und 1:1 austauschbar ist, sobald die Anbindung steht.
|
||||
const klarnaBtn = document.getElementById("klarna-connect");
|
||||
const klarnaStatus = document.getElementById("klarna-status");
|
||||
if (klarnaBtn) {
|
||||
klarnaBtn.addEventListener("click", () => {
|
||||
klarnaBtn.disabled = true;
|
||||
klarnaBtn.textContent = klarnaConnectingLabel;
|
||||
setTimeout(() => {
|
||||
klarnaBtn.textContent = klarnaConnectLabel;
|
||||
klarnaBtn.disabled = false;
|
||||
klarnaStatus.textContent = "✅ " + klarnaConnectedLabel;
|
||||
}, 1100);
|
||||
});
|
||||
}
|
||||
|
||||
// Banküberweisung (Vorkasse): braucht KEINE externe Anbindung — zeigt einfach VanVans echte,
|
||||
// im Adminbereich hinterlegte Kontodaten an. Das ist die einzige der vier Zahlungsarten, die
|
||||
// schon zu 100% "fertig" funktioniert.
|
||||
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();
|
||||
|
||||
@@ -32,6 +32,13 @@ const t = useTranslations(lang);
|
||||
<span>Klarna</span>
|
||||
<input type="radio" name="pay" value="klarna" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#ff9838; --brand-bg:#241f3d;">
|
||||
<span class="pay-icon pay-icon-cc" style="background:linear-gradient(160deg, #2a2a3d, #14141f);">
|
||||
<span class="cc-dot cc-dot-a"></span><span class="cc-dot cc-dot-b"></span>
|
||||
</span>
|
||||
<span>{t.checkout.creditCard}<span class="pay-sub">{t.checkout.creditCardSub}</span></span>
|
||||
<input type="radio" name="pay" value="kreditkarte" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
|
||||
<span class="pay-icon pay-icon-emoji" style="background:linear-gradient(160deg, #4d7599, #2c4258);">🏦</span>
|
||||
<span>{t.checkout.bankTransfer}</span>
|
||||
@@ -39,6 +46,35 @@ const t = useTranslations(lang);
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pay-connect">
|
||||
<div class="pay-panel" data-panel="paypal">
|
||||
<p>{t.checkout.payPalHint}</p>
|
||||
<div id="paypal-button-container"></div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="klarna" hidden>
|
||||
<p>{t.checkout.klarnaHint}</p>
|
||||
<button type="button" class="btn btn-outline pay-connect-btn" id="klarna-connect">{t.checkout.klarnaConnect}</button>
|
||||
<p class="pay-connect-status" id="klarna-status"></p>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="kreditkarte" hidden>
|
||||
<p>{t.checkout.cardHint}</p>
|
||||
<div class="card-fields">
|
||||
<div>
|
||||
<label for="cc-number">{t.checkout.cardNumber}</label>
|
||||
<input type="text" id="cc-number" inputmode="numeric" placeholder="1234 5678 9012 3456" maxlength="19" />
|
||||
</div>
|
||||
<div class="grid grid-2">
|
||||
<div><label for="cc-expiry">{t.checkout.cardExpiry}</label><input type="text" id="cc-expiry" placeholder="MM/YY" maxlength="5" /></div>
|
||||
<div><label for="cc-cvc">{t.checkout.cardCvc}</label><input type="text" id="cc-cvc" inputmode="numeric" placeholder="123" maxlength="4" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="ueberweisung" hidden>
|
||||
<p>{t.checkout.bankHintReal}</p>
|
||||
<div class="bank-details" id="bank-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="checkout-split-divider" aria-hidden="true"><span>🧵</span></div>
|
||||
<div class="checkout-split-col">
|
||||
@@ -101,12 +137,12 @@ const t = useTranslations(lang);
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/checkout/danke/", checkoutLang: lang, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove }}>
|
||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/checkout/danke/", checkoutLang: lang, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, klarnaConnectLabel: t.checkout.klarnaConnect, klarnaConnectingLabel: t.checkout.klarnaConnecting, klarnaConnectedLabel: t.checkout.klarnaConnected, bankHolderLabel: t.checkout.bankHolder, bankIbanLabel: t.checkout.bankIban, bankBicLabel: t.checkout.bankBic, bankBankNameLabel: t.checkout.bankBankName, bankPlaceholderLabel: t.checkout.bankPlaceholder }}>
|
||||
// WICHTIG: define:vars-Skripte werden von Astro in ein IIFE gepackt (kein ES-Modul) — echte
|
||||
// `import`-Anweisungen würden hier zur Laufzeit mit "Cannot use import statement outside a
|
||||
// module" fehlschlagen. Deshalb hier nur die vom Server gerenderten Werte auf window ablegen,
|
||||
// die eigentliche Logik läuft im separaten <script type="module"> darunter.
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel };
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
||||
</script>
|
||||
<script type="module">
|
||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon } from "../../scripts/cart";
|
||||
@@ -114,13 +150,15 @@ const t = useTranslations(lang);
|
||||
import { products } from "../../data/products";
|
||||
import { berechneVersandkosten } from "../../data/versand";
|
||||
import { effektiverZeilenpreis } from "../../data/rabatt";
|
||||
import { bankverbindung, bankverbindungVollstaendig } from "../../data/bankverbindung";
|
||||
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel } = window.__checkoutVars;
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
||||
|
||||
const summary = document.getElementById("checkout-summary");
|
||||
const landSelect = document.getElementById("land");
|
||||
const cart = getCart();
|
||||
const subtotal = cartTotal();
|
||||
let currentTotal = 0;
|
||||
|
||||
function renderSummary() {
|
||||
const land = landSelect.value;
|
||||
@@ -128,6 +166,7 @@ import { effektiverZeilenpreis } from "../../data/rabatt";
|
||||
const coupon = appliedCoupon();
|
||||
const discount = couponDiscount(subtotal);
|
||||
const total = Math.max(0, subtotal - discount) + shipping;
|
||||
currentTotal = total;
|
||||
|
||||
summary.innerHTML = `
|
||||
${cart.map((i) => {
|
||||
@@ -155,6 +194,76 @@ import { effektiverZeilenpreis } from "../../data/rabatt";
|
||||
renderSummary();
|
||||
landSelect.addEventListener("change", renderSummary);
|
||||
|
||||
// Zahlungsart-Auswahl: pro Methode ein Panel, nur die gewählte ist sichtbar. Macht die
|
||||
// Auswahl sofort "aktionabel" statt ein reines Formularfeld zu sein.
|
||||
const payPanels = document.querySelectorAll(".pay-panel");
|
||||
function showPayPanel(method) {
|
||||
payPanels.forEach((p) => { p.hidden = p.dataset.panel !== method; });
|
||||
}
|
||||
document.querySelectorAll('input[name="pay"]').forEach((radio) => {
|
||||
radio.addEventListener("change", () => showPayPanel(radio.value));
|
||||
});
|
||||
const initialPay = document.querySelector('input[name="pay"]:checked');
|
||||
if (initialPay) showPayPanel(initialPay.value);
|
||||
|
||||
// PayPal: echte PayPal Smart Buttons über das offizielle JS-SDK (Sandbox-Client-ID "sb" —
|
||||
// kostenlos, ohne eigenes Konto testbar). Klick öffnet den echten PayPal-Login-Popup. Für den
|
||||
// Live-Betrieb muss hier nur "sb" durch VanVans echte (kostenlose) PayPal-Business-Client-ID
|
||||
// ersetzt werden, siehe developer.paypal.com.
|
||||
let paypalLoaded = false;
|
||||
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=sb¤cy=EUR&intent=capture";
|
||||
script.onload = () => {
|
||||
if (!window.paypal) return;
|
||||
window.paypal.Buttons({
|
||||
style: { layout: "vertical", color: "blue", shape: "pill", label: "paypal" },
|
||||
createOrder: (data, actions) => actions.order.create({
|
||||
purchase_units: [{ amount: { value: Math.max(0.01, currentTotal).toFixed(2), currency_code: "EUR" } }],
|
||||
}),
|
||||
onApprove: (data, actions) => actions.order.capture().then(() => { location.href = thankYouPath; }),
|
||||
}).render("#paypal-button-container");
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
loadPayPalButtons();
|
||||
|
||||
// Klarna: echtes "Verbinden" braucht eine serverseitige Session mit VanVans Klarna-Händlerkonto
|
||||
// (Phase 2, Cloudflare Worker). Bis dahin ein sauber gestalteter Demo-Verbindungsablauf, damit
|
||||
// die Oberfläche schon fertig aussieht und 1:1 austauschbar ist, sobald die Anbindung steht.
|
||||
const klarnaBtn = document.getElementById("klarna-connect");
|
||||
const klarnaStatus = document.getElementById("klarna-status");
|
||||
if (klarnaBtn) {
|
||||
klarnaBtn.addEventListener("click", () => {
|
||||
klarnaBtn.disabled = true;
|
||||
klarnaBtn.textContent = klarnaConnectingLabel;
|
||||
setTimeout(() => {
|
||||
klarnaBtn.textContent = klarnaConnectLabel;
|
||||
klarnaBtn.disabled = false;
|
||||
klarnaStatus.textContent = "✅ " + klarnaConnectedLabel;
|
||||
}, 1100);
|
||||
});
|
||||
}
|
||||
|
||||
// Banküberweisung (Vorkasse): braucht KEINE externe Anbindung — zeigt einfach VanVans echte,
|
||||
// im Adminbereich hinterlegte Kontodaten an. Das ist die einzige der vier Zahlungsarten, die
|
||||
// schon zu 100% "fertig" funktioniert.
|
||||
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();
|
||||
|
||||
@@ -32,6 +32,13 @@ const t = useTranslations(lang);
|
||||
<span>Klarna</span>
|
||||
<input type="radio" name="pay" value="klarna" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#ff9838; --brand-bg:#241f3d;">
|
||||
<span class="pay-icon pay-icon-cc" style="background:linear-gradient(160deg, #2a2a3d, #14141f);">
|
||||
<span class="cc-dot cc-dot-a"></span><span class="cc-dot cc-dot-b"></span>
|
||||
</span>
|
||||
<span>{t.checkout.creditCard}<span class="pay-sub">{t.checkout.creditCardSub}</span></span>
|
||||
<input type="radio" name="pay" value="kreditkarte" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
|
||||
<span class="pay-icon pay-icon-emoji" style="background:linear-gradient(160deg, #4d7599, #2c4258);">🏦</span>
|
||||
<span>{t.checkout.bankTransfer}</span>
|
||||
@@ -39,6 +46,35 @@ const t = useTranslations(lang);
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pay-connect">
|
||||
<div class="pay-panel" data-panel="paypal">
|
||||
<p>{t.checkout.payPalHint}</p>
|
||||
<div id="paypal-button-container"></div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="klarna" hidden>
|
||||
<p>{t.checkout.klarnaHint}</p>
|
||||
<button type="button" class="btn btn-outline pay-connect-btn" id="klarna-connect">{t.checkout.klarnaConnect}</button>
|
||||
<p class="pay-connect-status" id="klarna-status"></p>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="kreditkarte" hidden>
|
||||
<p>{t.checkout.cardHint}</p>
|
||||
<div class="card-fields">
|
||||
<div>
|
||||
<label for="cc-number">{t.checkout.cardNumber}</label>
|
||||
<input type="text" id="cc-number" inputmode="numeric" placeholder="1234 5678 9012 3456" maxlength="19" />
|
||||
</div>
|
||||
<div class="grid grid-2">
|
||||
<div><label for="cc-expiry">{t.checkout.cardExpiry}</label><input type="text" id="cc-expiry" placeholder="MM/YY" maxlength="5" /></div>
|
||||
<div><label for="cc-cvc">{t.checkout.cardCvc}</label><input type="text" id="cc-cvc" inputmode="numeric" placeholder="123" maxlength="4" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="ueberweisung" hidden>
|
||||
<p>{t.checkout.bankHintReal}</p>
|
||||
<div class="bank-details" id="bank-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="checkout-split-divider" aria-hidden="true"><span>🧵</span></div>
|
||||
<div class="checkout-split-col">
|
||||
@@ -103,7 +139,7 @@ const t = useTranslations(lang);
|
||||
|
||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/en/checkout/danke/", checkoutLang: lang }}>
|
||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel };
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
||||
</script>
|
||||
<script type="module">
|
||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon } from "../../../scripts/cart";
|
||||
@@ -111,13 +147,15 @@ const t = useTranslations(lang);
|
||||
import { products } from "../../../data/products";
|
||||
import { berechneVersandkosten } from "../../../data/versand";
|
||||
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
||||
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel } = window.__checkoutVars;
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
||||
|
||||
const summary = document.getElementById("checkout-summary");
|
||||
const landSelect = document.getElementById("land");
|
||||
const cart = getCart();
|
||||
const subtotal = cartTotal();
|
||||
let currentTotal = 0;
|
||||
|
||||
function renderSummary() {
|
||||
const land = landSelect.value;
|
||||
@@ -125,6 +163,7 @@ import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
const coupon = appliedCoupon();
|
||||
const discount = couponDiscount(subtotal);
|
||||
const total = Math.max(0, subtotal - discount) + shipping;
|
||||
currentTotal = total;
|
||||
|
||||
summary.innerHTML = `
|
||||
${cart.map((i) => {
|
||||
@@ -152,6 +191,76 @@ import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
renderSummary();
|
||||
landSelect.addEventListener("change", renderSummary);
|
||||
|
||||
// Zahlungsart-Auswahl: pro Methode ein Panel, nur die gewählte ist sichtbar. Macht die
|
||||
// Auswahl sofort "aktionabel" statt ein reines Formularfeld zu sein.
|
||||
const payPanels = document.querySelectorAll(".pay-panel");
|
||||
function showPayPanel(method) {
|
||||
payPanels.forEach((p) => { p.hidden = p.dataset.panel !== method; });
|
||||
}
|
||||
document.querySelectorAll('input[name="pay"]').forEach((radio) => {
|
||||
radio.addEventListener("change", () => showPayPanel(radio.value));
|
||||
});
|
||||
const initialPay = document.querySelector('input[name="pay"]:checked');
|
||||
if (initialPay) showPayPanel(initialPay.value);
|
||||
|
||||
// PayPal: echte PayPal Smart Buttons über das offizielle JS-SDK (Sandbox-Client-ID "sb" —
|
||||
// kostenlos, ohne eigenes Konto testbar). Klick öffnet den echten PayPal-Login-Popup. Für den
|
||||
// Live-Betrieb muss hier nur "sb" durch VanVans echte (kostenlose) PayPal-Business-Client-ID
|
||||
// ersetzt werden, siehe developer.paypal.com.
|
||||
let paypalLoaded = false;
|
||||
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=sb¤cy=EUR&intent=capture";
|
||||
script.onload = () => {
|
||||
if (!window.paypal) return;
|
||||
window.paypal.Buttons({
|
||||
style: { layout: "vertical", color: "blue", shape: "pill", label: "paypal" },
|
||||
createOrder: (data, actions) => actions.order.create({
|
||||
purchase_units: [{ amount: { value: Math.max(0.01, currentTotal).toFixed(2), currency_code: "EUR" } }],
|
||||
}),
|
||||
onApprove: (data, actions) => actions.order.capture().then(() => { location.href = thankYouPath; }),
|
||||
}).render("#paypal-button-container");
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
loadPayPalButtons();
|
||||
|
||||
// Klarna: echtes "Verbinden" braucht eine serverseitige Session mit VanVans Klarna-Händlerkonto
|
||||
// (Phase 2, Cloudflare Worker). Bis dahin ein sauber gestalteter Demo-Verbindungsablauf, damit
|
||||
// die Oberfläche schon fertig aussieht und 1:1 austauschbar ist, sobald die Anbindung steht.
|
||||
const klarnaBtn = document.getElementById("klarna-connect");
|
||||
const klarnaStatus = document.getElementById("klarna-status");
|
||||
if (klarnaBtn) {
|
||||
klarnaBtn.addEventListener("click", () => {
|
||||
klarnaBtn.disabled = true;
|
||||
klarnaBtn.textContent = klarnaConnectingLabel;
|
||||
setTimeout(() => {
|
||||
klarnaBtn.textContent = klarnaConnectLabel;
|
||||
klarnaBtn.disabled = false;
|
||||
klarnaStatus.textContent = "✅ " + klarnaConnectedLabel;
|
||||
}, 1100);
|
||||
});
|
||||
}
|
||||
|
||||
// Banküberweisung (Vorkasse): braucht KEINE externe Anbindung — zeigt einfach VanVans echte,
|
||||
// im Adminbereich hinterlegte Kontodaten an. Das ist die einzige der vier Zahlungsarten, die
|
||||
// schon zu 100% "fertig" funktioniert.
|
||||
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();
|
||||
|
||||
@@ -32,6 +32,13 @@ const t = useTranslations(lang);
|
||||
<span>Klarna</span>
|
||||
<input type="radio" name="pay" value="klarna" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#ff9838; --brand-bg:#241f3d;">
|
||||
<span class="pay-icon pay-icon-cc" style="background:linear-gradient(160deg, #2a2a3d, #14141f);">
|
||||
<span class="cc-dot cc-dot-a"></span><span class="cc-dot cc-dot-b"></span>
|
||||
</span>
|
||||
<span>{t.checkout.creditCard}<span class="pay-sub">{t.checkout.creditCardSub}</span></span>
|
||||
<input type="radio" name="pay" value="kreditkarte" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
|
||||
<span class="pay-icon pay-icon-emoji" style="background:linear-gradient(160deg, #4d7599, #2c4258);">🏦</span>
|
||||
<span>{t.checkout.bankTransfer}</span>
|
||||
@@ -39,6 +46,35 @@ const t = useTranslations(lang);
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pay-connect">
|
||||
<div class="pay-panel" data-panel="paypal">
|
||||
<p>{t.checkout.payPalHint}</p>
|
||||
<div id="paypal-button-container"></div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="klarna" hidden>
|
||||
<p>{t.checkout.klarnaHint}</p>
|
||||
<button type="button" class="btn btn-outline pay-connect-btn" id="klarna-connect">{t.checkout.klarnaConnect}</button>
|
||||
<p class="pay-connect-status" id="klarna-status"></p>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="kreditkarte" hidden>
|
||||
<p>{t.checkout.cardHint}</p>
|
||||
<div class="card-fields">
|
||||
<div>
|
||||
<label for="cc-number">{t.checkout.cardNumber}</label>
|
||||
<input type="text" id="cc-number" inputmode="numeric" placeholder="1234 5678 9012 3456" maxlength="19" />
|
||||
</div>
|
||||
<div class="grid grid-2">
|
||||
<div><label for="cc-expiry">{t.checkout.cardExpiry}</label><input type="text" id="cc-expiry" placeholder="MM/YY" maxlength="5" /></div>
|
||||
<div><label for="cc-cvc">{t.checkout.cardCvc}</label><input type="text" id="cc-cvc" inputmode="numeric" placeholder="123" maxlength="4" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pay-panel" data-panel="ueberweisung" hidden>
|
||||
<p>{t.checkout.bankHintReal}</p>
|
||||
<div class="bank-details" id="bank-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="checkout-split-divider" aria-hidden="true"><span>🧵</span></div>
|
||||
<div class="checkout-split-col">
|
||||
@@ -103,7 +139,7 @@ const t = useTranslations(lang);
|
||||
|
||||
<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 }}>
|
||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel };
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
||||
</script>
|
||||
<script type="module">
|
||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon } from "../../../scripts/cart";
|
||||
@@ -111,13 +147,15 @@ const t = useTranslations(lang);
|
||||
import { products } from "../../../data/products";
|
||||
import { berechneVersandkosten } from "../../../data/versand";
|
||||
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
||||
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel } = window.__checkoutVars;
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
||||
|
||||
const summary = document.getElementById("checkout-summary");
|
||||
const landSelect = document.getElementById("land");
|
||||
const cart = getCart();
|
||||
const subtotal = cartTotal();
|
||||
let currentTotal = 0;
|
||||
|
||||
function renderSummary() {
|
||||
const land = landSelect.value;
|
||||
@@ -125,6 +163,7 @@ import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
const coupon = appliedCoupon();
|
||||
const discount = couponDiscount(subtotal);
|
||||
const total = Math.max(0, subtotal - discount) + shipping;
|
||||
currentTotal = total;
|
||||
|
||||
summary.innerHTML = `
|
||||
${cart.map((i) => {
|
||||
@@ -152,6 +191,76 @@ import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
renderSummary();
|
||||
landSelect.addEventListener("change", renderSummary);
|
||||
|
||||
// Zahlungsart-Auswahl: pro Methode ein Panel, nur die gewählte ist sichtbar. Macht die
|
||||
// Auswahl sofort "aktionabel" statt ein reines Formularfeld zu sein.
|
||||
const payPanels = document.querySelectorAll(".pay-panel");
|
||||
function showPayPanel(method) {
|
||||
payPanels.forEach((p) => { p.hidden = p.dataset.panel !== method; });
|
||||
}
|
||||
document.querySelectorAll('input[name="pay"]').forEach((radio) => {
|
||||
radio.addEventListener("change", () => showPayPanel(radio.value));
|
||||
});
|
||||
const initialPay = document.querySelector('input[name="pay"]:checked');
|
||||
if (initialPay) showPayPanel(initialPay.value);
|
||||
|
||||
// PayPal: echte PayPal Smart Buttons über das offizielle JS-SDK (Sandbox-Client-ID "sb" —
|
||||
// kostenlos, ohne eigenes Konto testbar). Klick öffnet den echten PayPal-Login-Popup. Für den
|
||||
// Live-Betrieb muss hier nur "sb" durch VanVans echte (kostenlose) PayPal-Business-Client-ID
|
||||
// ersetzt werden, siehe developer.paypal.com.
|
||||
let paypalLoaded = false;
|
||||
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=sb¤cy=EUR&intent=capture";
|
||||
script.onload = () => {
|
||||
if (!window.paypal) return;
|
||||
window.paypal.Buttons({
|
||||
style: { layout: "vertical", color: "blue", shape: "pill", label: "paypal" },
|
||||
createOrder: (data, actions) => actions.order.create({
|
||||
purchase_units: [{ amount: { value: Math.max(0.01, currentTotal).toFixed(2), currency_code: "EUR" } }],
|
||||
}),
|
||||
onApprove: (data, actions) => actions.order.capture().then(() => { location.href = thankYouPath; }),
|
||||
}).render("#paypal-button-container");
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
loadPayPalButtons();
|
||||
|
||||
// Klarna: echtes "Verbinden" braucht eine serverseitige Session mit VanVans Klarna-Händlerkonto
|
||||
// (Phase 2, Cloudflare Worker). Bis dahin ein sauber gestalteter Demo-Verbindungsablauf, damit
|
||||
// die Oberfläche schon fertig aussieht und 1:1 austauschbar ist, sobald die Anbindung steht.
|
||||
const klarnaBtn = document.getElementById("klarna-connect");
|
||||
const klarnaStatus = document.getElementById("klarna-status");
|
||||
if (klarnaBtn) {
|
||||
klarnaBtn.addEventListener("click", () => {
|
||||
klarnaBtn.disabled = true;
|
||||
klarnaBtn.textContent = klarnaConnectingLabel;
|
||||
setTimeout(() => {
|
||||
klarnaBtn.textContent = klarnaConnectLabel;
|
||||
klarnaBtn.disabled = false;
|
||||
klarnaStatus.textContent = "✅ " + klarnaConnectedLabel;
|
||||
}, 1100);
|
||||
});
|
||||
}
|
||||
|
||||
// Banküberweisung (Vorkasse): braucht KEINE externe Anbindung — zeigt einfach VanVans echte,
|
||||
// im Adminbereich hinterlegte Kontodaten an. Das ist die einzige der vier Zahlungsarten, die
|
||||
// schon zu 100% "fertig" funktioniert.
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user