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:
qcigano
2026-08-02 19:26:12 +02:00
parent 1f433d31e3
commit 3c574ed0c4
9 changed files with 560 additions and 9 deletions
+18
View File
@@ -253,3 +253,21 @@ collections:
- { label: "Rabatt in Prozent (z.B. 10 für 10%)", name: prozent, widget: number, value_type: int, required: false }
- { label: "ODER: Rabatt als fester Betrag (€)", name: betrag, widget: number, value_type: float, step: 0.01, required: false }
- { label: "Gültig bis (leer = unbegrenzt)", name: bis, widget: datetime, format: "YYYY-MM-DD", date_format: "YYYY-MM-DD", time_format: false, required: false }
- name: bankverbindung
label: "🏦 Bankverbindung"
icon: account_balance
files:
- name: konto
label: "Bankverbindung für Vorkasse"
file: src/content/bankverbindung.json
description: >
Wird Kund:innen direkt im Checkout angezeigt, sobald sie "Banküberweisung (Vorkasse)"
als Zahlungsart wählen — sie überweisen dann selbst auf dieses Konto. Bitte einmal mit
deinen echten Kontodaten ausfüllen, sonst bekommen Kund:innen nur einen Platzhalter
angezeigt.
fields:
- { label: Kontoinhaber:in, name: kontoinhaber, widget: string, required: false }
- { label: IBAN, name: iban, widget: string, required: false, hint: "z.B. DE12 3456 7890 1234 5678 90" }
- { label: BIC, name: bic, widget: string, required: false }
- { label: Bank (Name), name: bank, widget: string, required: false }
+6
View File
@@ -0,0 +1,6 @@
{
"kontoinhaber": "",
"iban": "",
"bic": "",
"bank": ""
}
+20
View File
@@ -0,0 +1,20 @@
// Bankverbindung für die Zahlungsart "Vorkasse/Banküberweisung" — von VanVan im
// Adminbereich unter "🏦 Bankverbindung" eingetragen. Wird direkt (ohne Umweg über einen
// Zahlungsanbieter) im Checkout angezeigt, sobald diese Zahlungsart gewählt wird — das ist die
// einzige der vier Zahlungsarten, die ganz ohne externe Anbindung zu 100% "echt" funktioniert.
// @ts-ignore -- JSON-Import, zur Build-Zeit von Astro/Vite aufgelöst
import raw from "../content/bankverbindung.json";
export interface Bankverbindung {
kontoinhaber: string;
iban: string;
bic: string;
bank: string;
}
export const bankverbindung = raw as Bankverbindung;
export function bankverbindungVollstaendig(b: Bankverbindung): boolean {
return !!(b.kontoinhaber && b.iban);
}
+32
View File
@@ -164,6 +164,14 @@ export const ui = {
revocationPrefix: "Ich habe die", revocationLink: "Widerrufsbelehrung", revocationAnd: "und",
privacyLink: "Datenschutzerklärung", revocationSuffix: "zur Kenntnis genommen.",
bankTransfer: "Banküberweisung (Vorkasse)", orderButton: "Jetzt zahlungspflichtig bestellen", emptyCartAlert: "Dein Warenkorb ist leer.",
creditCard: "Kreditkarte", creditCardSub: "Visa & Mastercard",
payPalHint: "Bezahle sicher und schnell direkt mit deinem PayPal-Konto.",
klarnaHint: "Rechnung, Ratenkauf oder Sofortüberweisung wähle bei Klarna, wie du zahlen möchtest.",
klarnaConnect: "Weiter zu Klarna →", klarnaConnecting: "Verbinde mit Klarna …", klarnaConnected: "Verbindung zu Klarna hergestellt (Demo)",
cardHint: "Deine Kartendaten werden sicher übertragen.", cardNumber: "Kartennummer", cardExpiry: "Gültig bis", cardCvc: "CVC",
bankHintReal: "Bitte überweise den Rechnungsbetrag nach Bestellabschluss an folgendes Konto. Deine Ware wird nach Zahlungseingang verschickt.",
bankPlaceholder: "Die Bankverbindung wird dir nach Bestelleingang per E-Mail zugesendet.",
bankHolder: "Kontoinhaber:in", bankIban: "IBAN", bankBic: "BIC", bankBankName: "Bank",
shippingHintTitle: "Versandhinweis",
shippingHint: "Versand ausschließlich per DHL nach Deutschland, Österreich, Schweiz und Luxemburg. Lieferzeit 27 Werktage. Versandkostenfrei ab 75 €. Versand erst nach Zahlungseingang.",
shippingLink: "Mehr zu Versand & Zahlung →",
@@ -337,6 +345,14 @@ export const ui = {
revocationPrefix: "I have taken note of the", revocationLink: "withdrawal notice", revocationAnd: "and",
privacyLink: "privacy policy", revocationSuffix: ".",
bankTransfer: "Bank transfer (prepayment)", orderButton: "Order now, subject to payment", emptyCartAlert: "Your cart is empty.",
creditCard: "Credit card", creditCardSub: "Visa & Mastercard",
payPalHint: "Pay securely and instantly with your PayPal account.",
klarnaHint: "Invoice, installments or instant transfer choose how you'd like to pay with Klarna.",
klarnaConnect: "Continue to Klarna →", klarnaConnecting: "Connecting to Klarna …", klarnaConnected: "Connected to Klarna (demo)",
cardHint: "Your card details are transmitted securely.", cardNumber: "Card number", cardExpiry: "Expiry", cardCvc: "CVC",
bankHintReal: "Please transfer the invoice amount to the following account after completing your order. Your items ship once payment is received.",
bankPlaceholder: "Bank details will be sent to you by email after your order.",
bankHolder: "Account holder", bankIban: "IBAN", bankBic: "BIC", bankBankName: "Bank",
shippingHintTitle: "Shipping note",
shippingHint: "Shipped exclusively via DHL to Germany, Austria, Switzerland and Luxembourg. Delivery time 27 business days. Free shipping from €75. Shipping only starts after payment has been received.",
shippingLink: "More on shipping & payment →",
@@ -510,6 +526,14 @@ export const ui = {
revocationPrefix: "Ich ha d'", revocationLink: "Widerrufsbelehrig", revocationAnd: "und d'",
privacyLink: "Datenschutzerklärig", revocationSuffix: "zur Kenntnis gnoh.",
bankTransfer: "Banküberwysig (Vorusszahlig)", orderButton: "Jetzt zahligspflichtig bstelle", emptyCartAlert: "Din Warenchorb isch leer.",
creditCard: "Kreditkarte", creditCardSub: "Visa & Mastercard",
payPalHint: "Zahl sicher und schnäll dirräkt mit dim PayPal-Konto.",
klarnaHint: "Rächnig, Ratechauf oder Sofortiübrwysig wähl bi Klarna, wie du zahle wosch.",
klarnaConnect: "Wyter zu Klarna →", klarnaConnecting: "Verbindet mit Klarna …", klarnaConnected: "Verbindig zu Klarna hergstellt (Demo)",
cardHint: "Dini Kartedate werde sicher übertreit.", cardNumber: "Kartenummere", cardExpiry: "Gültig bis", cardCvc: "CVC",
bankHintReal: "Bitte überwys dr Rächnigsbetrag nach Bstellabschluss uf das Konto. Dini Ware wird nach Zahligsigang verschickt.",
bankPlaceholder: "D'Bankverbindig wird dir nach Bstelligang per E-Mail zuegschickt.",
bankHolder: "Kontoinhaber:in", bankIban: "IBAN", bankBic: "BIC", bankBankName: "Bank",
shippingHintTitle: "Versandhinwys",
shippingHint: "Versand nume per DHL nach Dütschland, Öschterrych, d'Schwiz und Luxemburg. Lieferzyt 27 Werktäg. Gratisversand ab 75 €. Dr Versand geit erscht los, wenn d'Zahlig igange isch.",
shippingLink: "Meh zu Versand & Zahlig →",
@@ -683,6 +707,14 @@ export const ui = {
revocationPrefix: "J'ai pris connaissance de la", revocationLink: "notice de rétractation", revocationAnd: "et de la",
privacyLink: "politique de confidentialité", revocationSuffix: ".",
bankTransfer: "Virement bancaire (paiement anticipé)", orderButton: "Commander avec obligation de paiement", emptyCartAlert: "Votre panier est vide.",
creditCard: "Carte bancaire", creditCardSub: "Visa & Mastercard",
payPalHint: "Payez en toute sécurité et instantanément avec votre compte PayPal.",
klarnaHint: "Facture, paiement en plusieurs fois ou virement instantané choisissez comment payer avec Klarna.",
klarnaConnect: "Continuer vers Klarna →", klarnaConnecting: "Connexion à Klarna …", klarnaConnected: "Connecté à Klarna (démo)",
cardHint: "Vos données de carte sont transmises en toute sécurité.", cardNumber: "Numéro de carte", cardExpiry: "Date d'expiration", cardCvc: "CVC",
bankHintReal: "Veuillez virer le montant de la facture sur le compte suivant après avoir finalisé votre commande. Vos articles sont expédiés dès réception du paiement.",
bankPlaceholder: "Les coordonnées bancaires vous seront envoyées par e-mail après votre commande.",
bankHolder: "Titulaire du compte", bankIban: "IBAN", bankBic: "BIC", bankBankName: "Banque",
shippingHintTitle: "Remarque sur la livraison",
shippingHint: "Livraison exclusivement par DHL en Allemagne, Autriche, Suisse et au Luxembourg. Délai de livraison 2 à 7 jours ouvrés. Livraison gratuite dès 75 €. L'expédition n'a lieu qu'après réception du paiement.",
shippingLink: "En savoir plus sur la livraison & le paiement →",
+111 -2
View File
@@ -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&currency=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();
+112 -3
View File
@@ -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&currency=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();
+111 -2
View File
@@ -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&currency=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();
+111 -2
View File
@@ -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&currency=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();
+39
View File
@@ -1057,6 +1057,45 @@ a:focus-visible, button:focus-visible {
background-blend-mode: overlay;
}
.pay-option .pay-icon.pay-icon-emoji { font-size: 1.7rem; font-style: normal; }
/* Kreditkarte: zwei überlappende Kreise (Visa/Mastercard-Anmutung) statt einem Buchstaben. */
.pay-icon-cc { position: relative; }
.pay-icon-cc .cc-dot { position: absolute; width: 24px; height: 24px; border-radius: 50%; top: 50%; transform: translateY(-50%); }
.pay-icon-cc .cc-dot-a { left: 8px; background: #eb5b3c; }
.pay-icon-cc .cc-dot-b { left: 20px; background: #f7b73c; mix-blend-mode: screen; }
.pay-option .pay-sub { display: block; font-weight: 400; font-size: 0.8rem; color: var(--c-text-muted); margin-top: 0.1em; }
/* Ausklappbarer "Verbinden"-Bereich unter der Zahlungsart-Auswahl: pro Methode ein Panel, nur
das zur ausgewählten Zahlungsart passende ist sichtbar. Macht die Auswahl sofort "aktionabel"
statt nur ein reines Auswahlfeld zu sein. */
.pay-connect {
margin-top: 1.1rem;
padding: 1.1rem 1.2rem;
border-radius: var(--radius-m);
background: color-mix(in srgb, var(--c-accent) 6%, var(--c-anthracite));
border: 1px solid color-mix(in srgb, var(--c-accent) 25%, transparent);
}
.pay-panel[hidden] { display: none; }
.pay-panel p { margin: 0 0 0.8em; color: var(--c-text-muted); font-size: 0.88rem; }
.pay-connect-btn { width: 100%; }
.pay-connect-btn[disabled] { opacity: 0.75; cursor: default; }
.pay-connect-status { margin: 0.7em 0 0; font-size: 0.88rem; color: var(--c-accent); font-weight: 600; }
/* Inline-Kartenformular (Kreditkarte) */
.card-fields { display: grid; gap: 0.7rem; }
.card-fields label { display: block; font-size: 0.8rem; color: var(--c-text-muted); margin-bottom: 0.3rem; }
.card-fields input { font-variant-numeric: tabular-nums; letter-spacing: 0.03em; }
/* Bankverbindung (Vorkasse) — echte, direkt aus dem Adminbereich gespeiste Kontodaten. */
.bank-details {
display: grid;
gap: 0.5rem;
font-size: 0.92rem;
}
.bank-details .bank-row { display: flex; justify-content: space-between; gap: 1em; padding: 0.4em 0; border-bottom: 1px dashed rgba(244, 241, 247, 0.14); }
.bank-details .bank-row:last-child { border-bottom: none; }
.bank-details .bank-row span:first-child { color: var(--c-text-muted); }
.bank-details .bank-row span:last-child { font-weight: 700; letter-spacing: 0.02em; }
.pay-option input[type="radio"] {
appearance: none;
width: 22px;