Files
ClaudeandClaude Sonnet 5.5 140275022d Kundenansicht neu E-Fix M6: Zahlungsknöpfe sind nach der Rückkehr von Stripe wieder bedienbar
Nach "Zahlungspflichtig bestellen" setzt die Seite den Knopf auf "Weiter zur Zahlung…" (gesperrt) und leitet zu Stripe. Bricht der Kunde per
Zurück-Wischgeste ab, stellt Safari die Seite aus dem Bfcache her: der Knopf blieb gesperrt, ein neuer Versuch ging nur nach Neuladen. Nur
ansicht-wechsel.js hatte einen pageshow-Handler. Neuer Helfer beiRueckkehr(el, fn) in ansicht-hilfen.js (pageshow mit persisted, entfernt sich,
sobald das Element nicht mehr im Dokument steht); genutzt von der Bestellseite und von "Zahlungsart aktualisieren" (Verträge).
Speicher und Gutschrift leiten nicht zu Stripe, dort ist nichts zu tun.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 21:45:15 +00:00

57 lines
6.9 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { zeigeAbo, aboZustand, aboZeile, laufzeitText } from '../public/neu/vertraege-abo.js'
import { mit, me, abo, aboAntwort, angebot, neueWurzel } from './support/knDaten.js'
const WARTUNG = aboAntwort({ subscription: abo({ priceAmount: 5000, currentPeriodEnd: '2026-11-16T00:00:00.000Z', minTermEnd: '2099-06-01T00:00:00.000Z', termsUrl: '/abo-bedingungen.html', termsPdfUrl: '/WEBklar-Abo-Vertragsunterlagen.pdf' }) })
const zeige = async (fx, daten, m = me()) => { const b = neueWurzel(fx); await zeigeAbo(b, { daten, me: m }); await fx.ruhe(); return b }
const SRV = { 'GET /api/abo/takeover-credits': { requests: [{ status: 'rueckfrage' }, { status: 'offen' }], limits: {} } }
test('aboZustand', () => {
assert.deepEqual([aboAntwort(), aboAntwort({ subscription: abo({ active: false }) }), WARTUNG, aboAntwort({ subscription: abo({ cancelAtPeriodEnd: true }) })].map(aboZustand), ['ohne', 'beendet', 'laeuft', 'gekuendigt'])
})
test('Abo Wartung: Vertrag, Preis, Laufzeit, Bedingungen, Gutschrift-Einstieg mit Rückfragen', mit(SRV, async (fx) => {
const b = await zeige(fx, WARTUNG); const t = fx.text(b)
for (const s of ['Mein Vertrag', 'aktiv', 'Wartungs-Abo', 'Gemäß § 19 UStG ohne Umsatzsteuer.', 'nächste Abbuchung 16.11.2026', 'Mindestlaufzeit bis 01.06.2099. Kündigung frühestens zu diesem Termin, mit einem Monat Frist.']) assert.ok(t.includes(s), s)
assert.equal(fx.finde(b, 'a[href="/WEBklar-Abo-Vertragsunterlagen.pdf"]').textContent, 'als PDF')
assert.equal(fx.finde(b, 'a[href="#gutschrift"][data-view="takeover"] [data-badge]').textContent, '1')
}))
test('Abo Hosting: Wechsel-Einstieg; wartende Zahlung nur mit sicherer Adresse; gekündigt ohne Wechsel-Karte', mit(SRV, async (fx) => {
const hosting = (o) => aboAntwort({ subscription: abo({ produkt: 'hosting', canSwitch: true, ...o }) })
const w = fx.finde(await zeige(fx, hosting({})), 'a[data-view="wechsel"]'); assert.deepEqual([w.textContent, w.getAttribute('href')], ['Zum Wartungs-Abo wechseln', '#wechsel'])
const sicher = await zeige(fx, hosting({ switchPending: { since: 'x', paymentUrl: 'https://pay.stripe.com/x' } })); assert.equal(fx.finde(sicher, 'a[href="https://pay.stripe.com/x"]').textContent, 'Jetzt bezahlen')
const unsicher = await zeige(fx, hosting({ switchPending: { since: 'x', paymentUrl: 'javascript:alert(1)' } })); assert.match(fx.text(unsicher), /Schreib uns gern im Chat, wenn du Hilfe brauchst\./)
const gekuendigt = await zeige(fx, hosting({ cancelAtPeriodEnd: true })); assert.equal(fx.finde(gekuendigt, 'a[data-view="wechsel"]'), null); assert.match(fx.text(gekuendigt), /Dein Abo ist gekündigt\. Bitte schreib uns im Chat, wenn du zum Wartungs-Abo wechseln möchtest\./)
}))
test('Zahlungsproblem: Knopf öffnet das Stripe-Portal; Widerruf nur in der Frist; Ansichtsmodus sperrt', mit({ ...SRV, 'POST /api/abo/portal': { url: 'https://billing.stripe.com/x' } }, async (fx) => {
const daten = aboAntwort({ subscription: abo({ paymentProblem: true, withdrawalOpen: true, withdrawalUntil: '2026-10-17T21:59:00.000Z' }) })
const b = await zeige(fx, daten); assert.match(fx.text(b), /Bitte aktualisiere deine Zahlungsart, damit deine Website online bleibt\./); assert.match(fx.finde(b, 'a[href="/widerrufen.html"]').textContent, /^Vertrag widerrufen · bis 17\.10\./)
await fx.klick(fx.finde(b, (e) => e.textContent === 'Zahlungsart aktualisieren')); assert.equal(fx.window.location.href, 'https://billing.stripe.com/x')
const portal = fx.finde(b, (e) => e.textContent === 'Zahlungsart aktualisieren'); assert.equal(portal.hasAttribute('disabled'), true, 'während der Weiterleitung gesperrt')
fx.window.dispatchEvent({ type: 'pageshow', persisted: true }); assert.equal(portal.hasAttribute('disabled'), false, 'M6: zurück von Stripe wieder bedienbar')
const ansicht = await zeige(fx, daten, me({ viewAs: true })); assert.equal(fx.finde(ansicht, (e) => e.textContent === 'Zahlungsart aktualisieren').hasAttribute('disabled'), true)
assert.equal(fx.finde(await zeige(fx, WARTUNG), 'a[href="/widerrufen.html"]'), null)
}))
test('Ohne Abo: zwei Produkte mit Link zur Bestellung, nicht verfügbar sperrt, beendet sagt es', mit(SRV, async (fx) => {
const b = await zeige(fx, aboAntwort()); assert.deepEqual(['hosting', 'wartung'].map((k) => fx.finde(b, `[data-produkt="${k}"] a`).getAttribute('href')), ['#bestellen-hosting', '#bestellen-wartung'])
assert.match(fx.text(b), /20,00 €/)
const aus = await zeige(fx, aboAntwort({ available: false })); assert.equal(fx.finde(aus, '[data-produkt="hosting"] a').getAttribute('aria-disabled'), 'true'); assert.match(fx.text(aus), /schreib uns gern im Chat\./)
assert.match(fx.text(await zeige(fx, aboAntwort({ subscription: abo({ active: false }) }))), /Dein bisheriges Abo ist beendet\. Du kannst jederzeit ein neues abschließen\./)
}))
test('Abo: Ausfall der Rückfragen-Abfrage lässt den Einstieg stehen', mit({ 'GET /api/abo/takeover-credits': () => ({ status: 500, body: { error: 'x' } }) }, async (fx) => {
const b = await zeige(fx, WARTUNG); assert.ok(fx.finde(b, 'a[href="#gutschrift"]')); assert.equal(fx.finde(b, '[data-badge]'), null)
}))
test('Texte aus app.js: Zeile und Laufzeit', () => {
assert.equal(aboZeile(abo({ cancelAtPeriodEnd: true, cancelAt: '2026-12-01T00:00:00.000Z' })), 'Leistungen bis 01.12.2026')
assert.equal(aboZeile(abo({ serviceStartsAt: '2026-10-30T00:00:00.000Z' })), 'Beginn und erste Abbuchung am 30.10.2026')
assert.equal(laufzeitText(abo({ cancelAtPeriodEnd: true, currentPeriodEnd: '2026-12-01T00:00:00.000Z' })), 'Gekündigt zum 01.12.2026. Bis dahin bleiben alle Leistungen verfügbar.')
assert.equal(laufzeitText(abo({ minTermEnd: '2000-01-01T00:00:00.000Z' })), 'Monatlich kündbar zum Ende des Abrechnungsmonats.')
})
test('Ohne Abo: Angebot mit Etikett und Gültigkeit; Fußsatz mit Link; Zusatzspeicher und Beginn nach Frist beim laufenden Abo', mit(SRV, async (fx) => {
const b = await zeige(fx, aboAntwort({ offers: { hosting: null, wartung: angebot({ text: 'Erste 2 Monate je 25,00 €', validUntil: '2026-11-30T00:00:00.000Z' }) } }))
assert.match(fx.text(fx.finde(b, '[data-produkt="wartung"]')), /Dein persönliches AngebotErste 2 Monate je 25,00 € · gültig bis 30\.11\.2026/)
assert.doesNotMatch(fx.text(fx.finde(b, '[data-produkt="hosting"]')), /Dein persönliches Angebot/)
assert.equal(fx.finde(b, 'a[href="/abo-bedingungen.html"]').textContent, 'Vertragsbedingungen'); assert.match(fx.text(b), /Alle Preise gemäß § 19 UStG ohne Umsatzsteuer\. Es gelten die Vertragsbedingungen\./)
const l = await zeige(fx, aboAntwort({ subscription: abo({ storage: { units: 2, pendingSetup: false, pendingReduction: null }, serviceStartsAt: '2026-10-30T00:00:00.000Z' }) }))
assert.match(fx.text(l), /E-Mail-Speicher15 GB \(2 Zusatzpakete\)/); assert.match(fx.text(l), /Wie gewählt beginnen wir nach Ablauf der Widerrufsfrist am 30\.10\.2026\. Dann wird auch zum ersten Mal abgebucht\./)
}))