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>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 21:45:15 +00:00
1 parent 8bb02f3949
commit 140275022d
5 files changed
+38 -2

No files matched your search

+4 -1
View File
@@ -9,7 +9,7 @@
import { api } from './api.js'
import { h, leeren } from './dom.js'
import { K, ladeAbo, sichereAdresse, meldeTitel, fmtEuro, fmtDatum, ABO_NICHT_GELADEN } from './ansicht-hilfen.js'
import { K, ladeAbo, sichereAdresse, meldeTitel, beiRueckkehr, fmtEuro, fmtDatum, ABO_NICHT_GELADEN } from './ansicht-hilfen.js'
import { baueBlock, baueEinzelheiten, baueKundenartZeile, baueLinkzeile, hatWortlaut, kundenartVon, leistungLink } from './bestell-bausteine.js'
const laeufe = new WeakMap() // Wurzel → Nummer des jüngsten Aufrufs
@@ -114,6 +114,9 @@ function bestellung(daten, productKey, { viewAs, kundenart }) {
baueKundenartZeile(kundenart, T.kundenart, { id: 'abo-kundenart' }),
einzelheiten)
// Zurück von der Zahlungsseite (Bfcache): der Knopf ist wieder bedienbar (E-Fix M6, wie ansicht-wechsel.js)
beiRueckkehr(abschnitt, () => { kaufen.disabled = viewAs; kaufen.textContent = T.knopf || KNOPF })
// Das Häkchen schaltet die Zeilen „Fällig“ und „Beginn“ um
haken?.addEventListener('change', () => {
const neu = faelligBeginn({ lage: haken.checked ? 'wunsch' : 'frist', erste, frist })
+15
View File
@@ -146,6 +146,21 @@ export function meldeTitel(el, text) {
el.dispatchEvent(new CustomEvent('kn:titel', { bubbles: true, detail: { text } }))
}
/**
* Zurück aus dem Bfcache (E-Fix M6): kommt der Kunde von der Zahlungsseite von Stripe per Wischgeste zurück, stellt Safari die Seite so wieder her,
* wie sie war (Knopf gesperrt, "Weiter zur Zahlung…"). fn läuft bei pageshow mit persisted, solange `el` im Dokument steht; ist es ersetzt worden,
* entfernt sich der Hörer. Ohne Fenster tut es nichts.
*/
export function beiRueckkehr(el, fn) {
const win = globalThis.window
if (!win || typeof win.addEventListener !== 'function') return
const hoerer = (ev) => {
if (!el.isConnected) { win.removeEventListener('pageshow', hoerer); return }
if (ev.persisted) fn()
}
win.addEventListener('pageshow', hoerer)
}
/** Zähler neu laden lassen: was = 'chat' | 'vertraege' | 'alle'. Ohne Dokument tut es nichts. */
export function meldeZaehlerNeu(was) {
if (typeof document === 'undefined' || typeof document.dispatchEvent !== 'function' || typeof CustomEvent === 'undefined') return
+2 -1
View File
@@ -3,7 +3,7 @@
import { api } from './api.js'
import { h, leeren } from './dom.js'
import { K, zuAnsicht, fmtEuro, fmtDatum, fmtTagMonat, sichereAdresse } from './ansicht-hilfen.js'
import { K, zuAnsicht, fmtEuro, fmtDatum, fmtTagMonat, sichereAdresse, beiRueckkehr } from './ansicht-hilfen.js'
const laeufe = new WeakMap() // Reiter-Inhalt → Nummer des jüngsten Aufrufs
const TEXT_UST = 'Gemäß § 19 UStG ohne Umsatzsteuer.'
@@ -70,6 +70,7 @@ function zahlungsKnopf(viewAs) {
knopf.disabled = false
}
})
beiRueckkehr(knopf, () => { knopf.disabled = viewAs }) // zurück von Stripe (Bfcache): wieder bedienbar (E-Fix M6)
return [knopf, viewAs ? h('span', { class: K.meldung }, TEXT_ANSICHTSMODUS) : null, fehler] // ohne Umhüllung: der Knopf ist das Element mit diesem Text
}
+2
View File
@@ -26,6 +26,8 @@ test('Zahlungsproblem: Knopf öffnet das Stripe-Portal; Widerruf nur in der Fris
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)
}))
+15
View File
@@ -99,6 +99,21 @@ test('Privat und leer: ein Häkchen aus dem Wortlaut, ungehakt nach der Frist, g
}
}))
test('M6: zurück von der Zahlungsseite (Wischgeste, Bfcache) ist der Knopf wieder bedienbar; ein später entfernter Knopf bekommt nichts mehr', mit({ 'POST /api/abo/checkout': { url: 'https://checkout.stripe.test/x' } }, async (fx) => {
const w = await zeige(fx, aboNeu(), meMit('privat'))
const knopf = fx.finde(w, '#abo-buy')
await fx.klick(knopf); await fx.ruhe()
assert.deepEqual([knopf.hasAttribute('disabled'), knopf.textContent], [true, 'Weiter zur Zahlung…'])
fx.window.dispatchEvent({ type: 'pageshow', persisted: false }); assert.equal(knopf.hasAttribute('disabled'), true, 'ein normales Laden (nicht persisted) ändert nichts')
fx.window.dispatchEvent({ type: 'pageshow', persisted: true })
assert.deepEqual([knopf.hasAttribute('disabled'), knopf.textContent], [false, 'Zahlungspflichtig bestellen'])
// der Knopf kann wieder bestellen
await fx.klick(knopf); await fx.ruhe(); assert.equal(fx.fetchLog.filter((a) => a.url === '/api/abo/checkout').length, 2)
// die Seite wurde ersetzt: der Hörer entfernt sich und fasst den alten Knopf nicht mehr an
w.replaceChildren(); knopf.disabled = true
fx.window.dispatchEvent({ type: 'pageshow', persisted: true }); assert.equal(knopf.hasAttribute('disabled'), true)
}))
test('Der Body enthält nie kundenart oder startChoice', mit({ 'POST /api/abo/checkout': { url: 'https://checkout.stripe.test/x' } }, async (fx) => {
for (const m of [meMit('unternehmen'), meMit('privat'), meMit('')]) {
const w = await zeige(fx, aboNeu(), m)