Shop Review B I-1: Eingangsmail G15 bei SEPA, G09 nimmt die Bestellung ausdruecklich an

G15 "Wir haben Ihre Bestellung erhalten" geht einmal raus, sobald Stripe die Sitzung
abgeschlossen, die Zahlung aber noch nicht gemeldet hat (meldeEingang, Marker mailsEingangAt,
aus dem Webhook und aus pruefeOffene). G09 sagt ausdruecklich, dass der Kaufvertrag mit der
Mail geschlossen ist, trennt Bestell- und Zahlungsdatum und nennt die Telefonnummer des
Anbieters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 09:44:48 +00:00
1 parent 1b7aa9ebe3
commit 77fb8c8f44
11 files changed
+238 -27

No files matched your search

+22 -5
View File
@@ -8,10 +8,10 @@ const DIR = new URL('../server/mail-templates/', import.meta.url)
// G01–G08 Gästemails des Terminplaners (Task 9, neutrales Layout im Auftrag eines Betriebs), K20 und K21 Hinweise an den Betrieb;
// K12, K14 und T12 (Wechsel ins Wartungs-Abo) aus Task 9, K13 und T13 (Zusatzspeicher) aus Task 8 (Fix 1); Endfassung der Texte in Task 12; T14 „Löschfristen“ aus Task 21 (F39); T15 Push-Zeitmail; K15, K16, T16 Funktionen (News-Pinnwand, Terminplaner; K15, K16 und T16 gelten je Funktion, Terminplaner TP2 B9a und B9b); T17 Repo zuordnen; K17 Chat-Hinweis an Kunden; K18, K19 Website-Analyse; K22, K23 Terminplaner beendet/läuft wieder (Terminplaner TP2 E3); T18 Bankzugang erneuern (Finanzen); T19 Erstattung fällig und T20 Terminplaner Betriebsmeldung (Terminplaner TP2, Team-Hinweise; T16 gilt je Funktion, T19 aus B9b); T21 Neue Webshop-Anfrage (Webshop B1, Team-Hinweis); K24 Webshop-Anfrage entschieden (Webshop B2, Kunde); G09–G14 Käufermails des Webshops (Bestellbestätigung, versendet, abholbereit, Eingang Widerruf, Erstattung, Zahlung fehlgeschlagen; Webshop E4, eigener Fuß statt Terminplaner-Fuß, Sie-Form) und K25, K26 an den Händler (neue Bestellung, Widerruf; Webshop E4); K04 und T09 gelten beim Widerruf einer Funktion je Funktion (produkt, T09 zusätzlich preis; Terminplaner TP2 F9)
const ids = (prefix, count) => Array.from({ length: count }, (_, i) => `${prefix}${String(i + 1).padStart(2, '0')}`)
const EXPECTED = [...ids('G', 14), ...ids('K', 26), ...ids('T', 19), 'T20', 'T21']
const EXPECTED = [...ids('G', 15), ...ids('K', 26), ...ids('T', 19), 'T20', 'T21']
// GUEST = Gästemails des Terminplaners (gemeinsamer Fuß, Anrede-Blöcke); SHOP_GUEST = Käufermails des Webshops (G09–G14, siehe unten)
const GUEST = ids('G', 8)
const SHOP_GUEST = ['G09', 'G10', 'G11', 'G12', 'G13', 'G14']
const SHOP_GUEST = ['G09', 'G10', 'G11', 'G12', 'G13', 'G14', 'G15']
const sampleValues = (tpl) => Object.fromEntries(tpl.placeholders.map((p) => [p, `Wert-${p}`]))
const allBlocks = (tpl, on) => Object.fromEntries(tpl.blocks.map((b) => [b, on]))
@@ -1083,11 +1083,11 @@ test('K24 abgelehnt: nennt die Begründung (maskiert, Zeilenumbruch), keinen Pre
const SHOP_GEMEIN = ['farbe', 'betrieb_name', 'anbieter_zeile', 'impressum_url', 'datenschutz_url', 'widerruf_url']
test('G09–G14: Platzhalter, Blöcke und Betreff wie im Plan (G-Vorlagen des Webshops haben keine Anrede-Blöcke)', () => {
test('G09–G15: Platzhalter, Blöcke und Betreff wie im Plan (G-Vorlagen des Webshops haben keine Anrede-Blöcke)', () => {
const erwartet = {
G09: {
s: 'Ihre Bestellung {{bestellnummer}} bei {{betrieb_name}}',
p: ['bestellnummer', 'datum', 'positionen', 'zwischensumme', 'versand', 'gesamt', 'steuer_hinweis', 'lieferart_text', 'lieferadresse', 'abholort', 'lieferzeit', 'zahlungsart', 'ausschluss_text', 'agb', 'widerrufsbelehrung', 'widerrufsformular', 'gewaehrleistung'],
p: ['bestellnummer', 'datum', 'zahlungsdatum', 'positionen', 'zwischensumme', 'versand', 'gesamt', 'steuer_hinweis', 'lieferart_text', 'lieferadresse', 'abholort', 'lieferzeit', 'zahlungsart', 'ausschluss_text', 'agb', 'widerrufsbelehrung', 'widerrufsformular', 'gewaehrleistung'],
b: ['versand', 'abholung', 'ausschluss'],
},
G10: { s: 'Ihre Bestellung {{bestellnummer}} wurde versendet', p: ['bestellnummer', 'sendungsnummer', 'versanddienst'], b: ['sendung'] },
@@ -1095,6 +1095,7 @@ test('G09–G14: Platzhalter, Blöcke und Betreff wie im Plan (G-Vorlagen des We
G12: { s: 'Eingangsbestätigung Ihres Widerrufs bei {{betrieb_name}}', p: ['name', 'bestellnummer', 'eingang', 'nachricht'], b: ['nachricht'] },
G13: { s: 'Erstattung zu Ihrer Bestellung {{bestellnummer}}', p: ['bestellnummer', 'betrag', 'grund'], b: ['grund'] },
G14: { s: 'Ihre Bestellung {{bestellnummer}} wurde nicht abgeschlossen', p: ['bestellnummer'], b: [] },
G15: { s: 'Wir haben Ihre Bestellung {{bestellnummer}} erhalten', p: ['bestellnummer', 'datum', 'positionen', 'gesamt'], b: [] },
}
assert.deepEqual(Object.keys(erwartet), SHOP_GUEST)
for (const [id, { s, p, b }] of Object.entries(erwartet)) {
@@ -1105,7 +1106,7 @@ test('G09–G14: Platzhalter, Blöcke und Betreff wie im Plan (G-Vorlagen des We
}
})
test('G09–G14: neutrales Layout in der Shop-Farbe, kein Logo, Fuß mit Anbieter, Impressum, Datenschutz und Widerrufsfunktion', () => {
test('G09–G15: neutrales Layout in der Shop-Farbe, kein Logo, Fuß mit Anbieter, Impressum, Datenschutz und Widerrufsfunktion', () => {
for (const id of SHOP_GUEST) {
const tpl = loadTemplate(id)
const source = sourceOf(id)
@@ -1131,6 +1132,7 @@ const g = (id, werte, blocks = {}) => renderMail(id, { ...G_WERTE, ...werte }, b
const G09_WERTE = {
datum: '05.10.2026',
zahlungsdatum: '07.10.2026',
positionen: '2 × Tasse (blau) – 24,00 €\n1 × Teller – 12,50 €',
zwischensumme: '36,50 €',
versand: '4,90 €',
@@ -1158,6 +1160,10 @@ test('G09 Versand: Betreff mit Bestellnummer und Shop, Positionen, Summen, Liefe
for (const teil of ['26-K7QM2X', '05.10.2026', '36,50 €', '4,90 €', '41,40 €', '§ 19 UStG', 'Versand nach Deutschland', '2–4 Werktage', 'Kreditkarte', 'Vom Widerruf ausgeschlossen: nach Kundenwunsch angefertigt', 'AGB-Text Zeile 2', 'Widerrufsbelehrung Zeile 1', 'Hiermit widerrufe ich', 'GESETZLICHE GEWÄHRLEISTUNG', 'Hinweis zur gesetzlichen Gewährleistung', 'Muster-Widerrufsformular', 'Allgemeine Geschäftsbedingungen']) {
assert.ok(mail.text.includes(teil), teil)
}
// I-1 b: ausdrückliche Annahme, Bestelldatum und Zahlungsdatum getrennt
assert.match(mail.text, /Mit dieser E-Mail nehmen wir Ihre Bestellung an\. Der Kaufvertrag ist damit geschlossen\./)
assert.match(mail.text, /Bestelldatum: 05\.10\.2026/)
assert.match(mail.text, /Zahlungsdatum: 07\.10\.2026/)
assert.match(mail.text, /Lieferadresse: Erika Beispiel/)
assert.doesNotMatch(mail.text, /Abholort|Werkstatt, Hauptstr/)
assert.match(mail.html, /href="https:\/\/project\.webklar\.com\/shop\/sk\/widerruf"/)
@@ -1171,6 +1177,17 @@ test('G09 Abholung ohne Ausschluss: Abholort statt Lieferadresse, kein Widerrufs
assert.match(mail.text, /Abholung – kostenlos/)
})
test('G15: Eingang ohne Vertragsschluss - Lastschrift in Bearbeitung, Bestellbestätigung folgt, Positionen und Gesamt', () => {
const mail = g('G15', { datum: '05.10.2026', positionen: '2 × Tasse (blau) – 24,00 €', gesamt: '41,40 €' })
assert.deepEqual(mail.problems, [])
assert.equal(mail.subject, 'Wir haben Ihre Bestellung 26-K7QM2X erhalten')
assert.equal(loadTemplate('G15').file, 'G15 Bestellung eingegangen.html')
for (const teil of ['26-K7QM2X', '05.10.2026', '2 × Tasse (blau) – 24,00 €', '41,40 €', 'Ihre Zahlung per Lastschrift ist noch in Bearbeitung. Sobald sie bei uns eingegangen ist, erhalten Sie die Bestellbestätigung; erst damit kommt der Kaufvertrag zustande.']) {
assert.ok(mail.text.includes(teil), teil)
}
assert.doesNotMatch(mail.text, /Kaufvertrag ist damit geschlossen|Widerrufsbelehrung|Allgemeine Geschäftsbedingungen/)
})
test('G10: Sendungsnummer und Versanddienst nur im Block sendung', () => {
const mit = g('G10', { sendungsnummer: '0034<1>', versanddienst: 'DHL' }, { sendung: true })
assert.deepEqual(mit.problems, [])