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
+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();