Mails: Vertragsbestätigung je Fassung, Zustimmung zum AVV, kleinere Textkorrekturen
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
63d3bb11bf
commit
12a24514a4
10 files changed
+172
-36
No files matched your search
+59
-2
@@ -432,7 +432,8 @@ test('Webhook checkout.session.completed: K01 je Produkt mit PDF der zugestimmte
|
||||
} finally {
|
||||
await cleanupWebhook(subH)
|
||||
}
|
||||
// Wartungs-Abo nach Fassung 3 (Altbestand): das Archiv-PDF der Fassung 3, Stunde und beliebig viele Postfaecher
|
||||
// Wartungs-Abo nach Fassung 3 (Altbestand): das Archiv-PDF der Fassung 3 und die Zeile „Enthalten“ der Fassung 3
|
||||
// (1 E-Mail-Postfach, 1 Stunde Arbeitszeit pro Monat; Block wartung_v3, Fix 1)
|
||||
const subW = 'sub_1SD6z3TxAbCdEfGhIjKlMnOt'
|
||||
st.subscriptions.set(subW, stripeSubscription({ id: subW, customerId: 'c3', productKey: 'wartung', metadata: { termsVersion: '2026-09-25-v3' } }))
|
||||
try {
|
||||
@@ -441,12 +442,68 @@ test('Webhook checkout.session.completed: K01 je Produkt mit PDF der zugestimmte
|
||||
const k01 = mails.sent.find((m) => m.subject.includes('Vertragsbestätigung'))
|
||||
assert.equal(k01.subject, 'Ihr WEBklar Wartungs-Abo – Vertragsbestätigung')
|
||||
assert.deepEqual(k01.attachments.map((a) => a.filename), ['WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf'])
|
||||
for (const text of ['50,00 € / Monat', 'beliebig viele Postfächer', '1 Stunde Arbeitszeit je bezahltem Monat', '(Version 2026-09-25-v3)']) {
|
||||
for (const text of ['50,00 € / Monat', '• 1 E-Mail-Postfach', '• 1 Stunde Arbeitszeit pro Monat', 'Ihr aktuelles Stundenkontingent', '(Version 2026-09-25-v3) einschließlich des Auftragsverarbeitungsvertrags']) {
|
||||
assert.ok(k01.text.includes(text), `${text}\n---\n${k01.text}`)
|
||||
}
|
||||
assert.ok(!k01.text.includes('beliebig viele') && !k01.text.includes('je bezahltem Monat'), k01.text)
|
||||
} finally {
|
||||
await cleanupWebhook(subW)
|
||||
}
|
||||
// Gegenprobe: Wartungs-Abo nach Fassung 4 - aktuelles PDF, beliebig viele Postfaecher, Stunde je bezahltem Monat
|
||||
const subW4 = 'sub_1SD6z4UxAbCdEfGhIjKlMnOu'
|
||||
st.subscriptions.set(subW4, stripeSubscription({ id: subW4, customerId: 'c3', productKey: 'wartung' }))
|
||||
try {
|
||||
mails.sent.length = 0
|
||||
assert.equal((await postWebhook(checkoutCompleted(subW4, { productKey: 'wartung' }))).status, 200)
|
||||
const k01 = mails.sent.find((m) => m.subject.includes('Vertragsbestätigung'))
|
||||
assert.equal(k01.subject, 'Ihr WEBklar Wartungs-Abo – Vertragsbestätigung')
|
||||
assert.deepEqual(k01.attachments.map((a) => a.filename), ['WEBklar-Abo-Vertragsunterlagen.pdf'])
|
||||
for (const text of ['50,00 € / Monat', '• beliebig viele Postfächer', '1 Stunde Arbeitszeit je bezahltem Monat', `(Version ${config.abo.termsVersion}) einschließlich des Auftragsverarbeitungsvertrags`]) {
|
||||
assert.ok(k01.text.includes(text), `${text}\n---\n${k01.text}`)
|
||||
}
|
||||
assert.ok(!k01.text.includes('1 E-Mail-Postfach') && !k01.text.includes('pro Monat'), k01.text)
|
||||
} finally {
|
||||
await cleanupWebhook(subW4)
|
||||
}
|
||||
// Fix 1 (3): ohne gespeicherte Fassung kommen Versionsangabe und PDF aus derselben Quelle - beide Fassung 3
|
||||
const subW0 = 'sub_1SD6z5VxAbCdEfGhIjKlMnOv'
|
||||
st.subscriptions.set(subW0, stripeSubscription({ id: subW0, customerId: 'c3', productKey: 'wartung', metadata: { termsVersion: null } }))
|
||||
try {
|
||||
mails.sent.length = 0
|
||||
assert.equal((await postWebhook(checkoutCompleted(subW0, { productKey: 'wartung' }))).status, 200)
|
||||
const k01 = mails.sent.find((m) => m.subject.includes('Vertragsbestätigung'))
|
||||
assert.ok(k01, 'K01 verschickt')
|
||||
assert.deepEqual(k01.attachments.map((a) => a.filename), ['WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf'])
|
||||
assert.ok(k01.text.includes('(Version 2026-09-25-v3)'), k01.text)
|
||||
assert.ok(!k01.text.includes(config.abo.termsVersion), k01.text)
|
||||
assert.ok(k01.text.includes('• 1 E-Mail-Postfach'), k01.text)
|
||||
} finally {
|
||||
await cleanupWebhook(subW0)
|
||||
}
|
||||
})
|
||||
|
||||
// Fix 1 (5): T07 nennt die nicht genutzte Arbeitszeit nur beim Wartungs-Abo
|
||||
test('Webhook customer.subscription.deleted: T07 mit Arbeitszeit-Hinweis nur bei Wartung', async () => {
|
||||
const deleted = (id) => ({ id: `evt_del_${id}`, object: 'event', type: 'customer.subscription.deleted', created: Math.floor(Date.now() / 1000), data: { object: { id, object: 'subscription', status: 'canceled' } } })
|
||||
const subW = 'sub_1SD6z6WxAbCdEfGhIjKlMnOw'
|
||||
const subH = 'sub_1SD6z7XxAbCdEfGhIjKlMnOx'
|
||||
st.subscriptions.set(subW, stripeSubscription({ id: subW, customerId: 'c3', productKey: 'wartung', status: 'canceled' }))
|
||||
st.subscriptions.set(subH, stripeSubscription({ id: subH, customerId: 'c3', productKey: 'hosting', status: 'canceled' }))
|
||||
try {
|
||||
mails.sent.length = 0
|
||||
assert.equal((await postWebhook(deleted(subW))).status, 200)
|
||||
const t07w = mails.sent.find((m) => m.subject.startsWith('[Abo] Abo beendet'))
|
||||
assert.ok(t07w, 'T07 verschickt')
|
||||
assert.ok(t07w.text.includes('Abo: Wartungs-Abo') && t07w.text.includes('Nicht genutzte Arbeitszeit aus dem Stundenkontingent verfällt mit dem Vertragsende.'), t07w.text)
|
||||
mails.sent.length = 0
|
||||
assert.equal((await postWebhook(deleted(subH))).status, 200)
|
||||
const t07h = mails.sent.find((m) => m.subject.startsWith('[Abo] Abo beendet'))
|
||||
assert.ok(t07h.text.includes('Abo: Hosting-Abo') && t07h.text.includes('Gebuchter Zusatzspeicher endet mit dem Abo.'), t07h.text)
|
||||
assert.ok(!t07h.text.includes('Arbeitszeit') && !t07h.text.includes('Stundenkontingent'), t07h.text)
|
||||
} finally {
|
||||
await cleanupWebhook(subW)
|
||||
await cleanupWebhook(subH)
|
||||
}
|
||||
})
|
||||
|
||||
// Fix 1, Important: ein dauerhafter Fehler beim Nachweis (4xx, z. B. Collection oder Attribut fehlt) darf
|
||||
|
||||
@@ -121,7 +121,7 @@ test('Textfassung K01: alle Vertragsangaben lesbar', () => {
|
||||
'• 1 Domain',
|
||||
'Zum Kundenportal: https://project.webklar.com/dashboard.html',
|
||||
'📎 Widerrufsbelehrung mit Muster-Widerrufsformular',
|
||||
'Ihre Zustimmung zu den Vertragsbedingungen (Version 2026-09-25-v3) haben wir am 01.10.2026, 10:07 Uhr erhalten.',
|
||||
'Ihre Zustimmung zu den Vertragsbedingungen (Version 2026-09-25-v3) einschließlich des Auftragsverarbeitungsvertrags haben wir am 01.10.2026, 10:07 Uhr erhalten.',
|
||||
'Vertrag widerrufen (https://project.webklar.com/widerrufen.html)',
|
||||
'Vertrag kündigen (https://project.webklar.com/kuendigen.html)',
|
||||
'Telefon 0176 23726355 · support@webklar.com',
|
||||
@@ -267,11 +267,13 @@ test('K03, K04, T08, T09: Vertrag bzw. Bestellung, Pakete und die Blöcke der Au
|
||||
assert.match(speicher.text, /Vertrag: E-Mail-Zusatzspeicher zum WEBklar Hosting-Abo/)
|
||||
assert.match(speicher.text, /Ihr Zusatzspeicher verringert sich zum\n31\.10\.2026/)
|
||||
assert.match(speicher.text, /um 2 Paket\(e\) gekündigt; danach bleiben 1 Paket\(e\)/)
|
||||
assert.doesNotMatch(speicher.text, /Wartungs-Abo\n|Stundenguthaben|Wir prüfen Ihre Kündigung/)
|
||||
assert.doesNotMatch(speicher.text, /Wartungs-Abo\n|Stundenkontingent|Stundenguthaben|Wir prüfen Ihre Kündigung/)
|
||||
assert.match(renderMail('K03', k03, { pruefen: true }).text, /bestätigen Ihnen den Zeitpunkt, zu dem sie wirkt, gesondert per E-Mail/)
|
||||
// Task 12: das Stundenguthaben gibt es nur im Wartungs-Abo (Block wartung innerhalb von abo)
|
||||
assert.match(renderMail('K03', k03, { abo: true, wartung: true }).text, /Stundenguthaben verfällt/)
|
||||
assert.doesNotMatch(renderMail('K03', k03, { abo: true }).text, /Stundenguthaben/)
|
||||
// Task 12 (Fix 1, 6): das Stundenkontingent gibt es nur im Wartungs-Abo (Block wartung innerhalb von abo); es ist
|
||||
// kein Geldguthaben (v4 § 4 Abs. 1), darum „nicht genutzte Arbeitszeit“ statt „Stundenguthaben“
|
||||
assert.match(renderMail('K03', k03, { abo: true, wartung: true }).text, /Nicht genutzte Arbeitszeit aus dem Stundenkontingent verfällt mit Vertragsende\./)
|
||||
assert.doesNotMatch(renderMail('K03', k03, { abo: true }).text, /Stundenkontingent|Stundenguthaben/)
|
||||
assert.doesNotMatch(renderMail('K03', k03, { abo: true, wartung: true }).text, /Stundenguthaben/)
|
||||
assert.match(renderMail('K03', k03, { abo: true }).text, /Domain-AuthCode/)
|
||||
|
||||
const k04 = { ...sampleValues(loadTemplate('K04')), bestellung: 'Wechsel ins Wartungs-Abo' }
|
||||
@@ -445,3 +447,44 @@ test('T09: Handgriffe je Bestellung ohne Wertersatz beim Zusatzspeicher', () =>
|
||||
const abschluss = renderMail('T09', t09, { abo_gefunden: true, abschluss: true }).text
|
||||
assert.match(abschluss, /Zusatzspeicher[^.]*vollständig/)
|
||||
})
|
||||
|
||||
// Task 12, Fix round 1: K01 je Fassung (v3-Zeile „1 E-Mail-Postfach · 1 Stunde Arbeitszeit pro Monat“ im Block wartung_v3),
|
||||
// Zustimmung einschließlich AVV; K12 „Heute fällig“ nennt den Zusatzspeicher nur im Block; T07 nennt die Arbeitszeit nur bei Wartung
|
||||
test('K01: Block wartung_v3 mit der Zeile der Fassung 3, wartung mit der Fassung 4, Zustimmung einschließlich AVV', () => {
|
||||
const base = { name: 'K', produkt: 'Wartungs-Abo', preis: '50,00 € / Monat', angebot_text: '', vertragsschluss: '01.10.2026', leistungsbeginn: '01.10.2026',
|
||||
mindestlaufzeit_bis: '01.10.2027', bedingungen_version: '2026-09-25-v3', zustimmung_zeitpunkt: '01.10.2026, 10:07' }
|
||||
assert.ok(loadTemplate('K01').blocks.includes('wartung_v3'), 'Block wartung_v3 deklariert')
|
||||
const v3 = renderMail('K01', base, { wartung_v3: true, sofortbeginn: true })
|
||||
assert.deepEqual(v3.problems, [])
|
||||
assert.match(v3.text, /• 1 E-Mail-Postfach/)
|
||||
assert.match(v3.text, /• 1 Stunde Arbeitszeit pro Monat/)
|
||||
assert.match(v3.text, /Dort sehen Sie jederzeit Ihr aktuelles Stundenkontingent\./)
|
||||
assert.doesNotMatch(v3.text, /beliebig viele|je bezahltem Monat|Zusatzspeicher/)
|
||||
assert.match(v3.text, /Ihre Zustimmung zu den Vertragsbedingungen \(Version 2026-09-25-v3\) einschließlich des Auftragsverarbeitungsvertrags haben wir am 01\.10\.2026, 10:07 Uhr erhalten\./)
|
||||
const v4 = renderMail('K01', { ...base, bedingungen_version: '2026-10-05-v4' }, { wartung: true, sofortbeginn: true })
|
||||
assert.match(v4.text, /• beliebig viele Postfächer/)
|
||||
assert.match(v4.text, /1 Stunde Arbeitszeit je bezahltem Monat/)
|
||||
assert.doesNotMatch(v4.text, /1 E-Mail-Postfach|pro Monat/)
|
||||
const h = renderMail('K01', { ...base, produkt: 'Hosting-Abo', preis: '15,00 € / Monat' }, { hosting: true, sofortbeginn: true })
|
||||
assert.doesNotMatch(h.text, /Stundenkontingent|Stunde Arbeitszeit/)
|
||||
})
|
||||
|
||||
test('K12: „Heute fällig“ nennt den Zusatzspeicher nur im Block zusatzspeicher', () => {
|
||||
const values = sampleValues(loadTemplate('K12'))
|
||||
assert.match(renderMail('K12', values, { zusatzspeicher: true }).text, /Monatsgebühr des Wartungs-Abos mit gebuchtem Zusatzspeicher, abzüglich der Gutschrift/)
|
||||
const ohne = renderMail('K12', values, {}).text
|
||||
assert.match(ohne, /Monatsgebühr des Wartungs-Abos, abzüglich der Gutschrift/)
|
||||
assert.doesNotMatch(ohne, /mit gebuchtem Zusatzspeicher/)
|
||||
})
|
||||
|
||||
test('T07: Hinweis auf nicht genutzte Arbeitszeit nur im Block wartung', () => {
|
||||
const values = { produkt: 'Wartungs-Abo', email: 'k@example.invalid', stripe_abo_id: 'sub_1', status: 'canceled', vertragsende_paragraph: '§ 11' }
|
||||
assert.ok(loadTemplate('T07').blocks.includes('wartung'), 'Block wartung deklariert')
|
||||
const w = renderMail('T07', values, { wartung: true })
|
||||
assert.deepEqual(w.problems, [])
|
||||
assert.match(w.text, /Nicht genutzte Arbeitszeit aus dem Stundenkontingent verfällt mit dem Vertragsende\. Gebuchter Zusatzspeicher endet mit dem Abo\./)
|
||||
const h = renderMail('T07', { ...values, produkt: 'Hosting-Abo' }, {})
|
||||
assert.deepEqual(h.problems, [])
|
||||
assert.match(h.text, /Gebuchter Zusatzspeicher endet mit dem Abo\./)
|
||||
assert.doesNotMatch(h.text, /Arbeitszeit|Stundenkontingent|Restguthaben/)
|
||||
})
|
||||
+13
-7
@@ -115,18 +115,24 @@ test('priceIdsOfInvoice: eindeutige Preis-IDs, ohne Lücken', () => {
|
||||
|
||||
// Fix round 1 / B9: feste Form statt Einzelfelder
|
||||
test('Vertragsunterlagen je Version', () => {
|
||||
// F08 (Task 12): endParagraph = Paragraph zum Vertragsende der Fassung (v4: § 9 ist der Wechsel, § 11 das Vertragsende)
|
||||
assert.deepEqual(termsDocFor('2026-10-05-v4'), { url: '/abo-bedingungen.html', pdfFile: 'WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph: '§ 11' })
|
||||
// F08 (Task 12): endParagraph = Paragraph zum Vertragsende der Fassung (v4: § 9 ist der Wechsel, § 11 das Vertragsende);
|
||||
// Fix 1 (3): version = die Fassung, die K01 nennt - ohne Angabe die archivierte Fassung 3, passend zum PDF
|
||||
assert.deepEqual(termsDocFor('2026-10-05-v4'), { version: '2026-10-05-v4', url: '/abo-bedingungen.html', pdfFile: 'WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph: '§ 11' })
|
||||
assert.deepEqual(termsDocFor('2026-09-25-v3'), {
|
||||
version: '2026-09-25-v3',
|
||||
url: '/vertragsunterlagen/v3/abo-bedingungen.html',
|
||||
pdfFile: 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf',
|
||||
endParagraph: '§ 9',
|
||||
})
|
||||
assert.deepEqual(termsDocFor(null), {
|
||||
url: '/vertragsunterlagen/v3/abo-bedingungen.html',
|
||||
pdfFile: 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf',
|
||||
endParagraph: '§ 9',
|
||||
})
|
||||
for (const none of [null, undefined, '']) {
|
||||
assert.deepEqual(termsDocFor(none), {
|
||||
version: '2026-09-25-v3',
|
||||
url: '/vertragsunterlagen/v3/abo-bedingungen.html',
|
||||
pdfFile: 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf',
|
||||
endParagraph: '§ 9',
|
||||
}, String(none))
|
||||
}
|
||||
assert.equal(termsDocFor('2026-09-25-v2').version, '2026-09-25-v2', 'aeltere Fassung bleibt benannt')
|
||||
})
|
||||
|
||||
test('Produktansicht enthält Leistungen ohne 1-Postfach-Regel bei Wartung', () => {
|
||||
|
||||
@@ -119,23 +119,23 @@ test('Kündigung nur Zusatzspeicher: Menge 0 ohne Erstattung, Ereignis', async (
|
||||
})
|
||||
|
||||
// Task 12: Betreff und Text nennen das Abo des Kunden (Katalogname ohne „WEBklar “, F33); das Vertragsende steht
|
||||
// in der Fassung 4 in § 11, nicht in § 9 (F08); das Stundenguthaben gibt es nur im Wartungs-Abo
|
||||
// in der Fassung 4 in § 11, nicht in § 9 (F08); das Stundenkontingent gibt es nur im Wartungs-Abo (kein Geldguthaben, Fix 1)
|
||||
test('K03/K04/T08/T09: Produkt des Kunden, Vertragsende nach § 11 der Fassung 4', async () => {
|
||||
await resetSubH()
|
||||
assert.equal((await cancel({ contract: 'abo', kind: 'ordentlich' })).status, 200)
|
||||
assert.equal(k03().subject, 'Eingangsbestätigung Ihrer Kündigung – WEBklar Hosting-Abo')
|
||||
assert.ok(k03().text.includes('§ 11 der Vertragsbedingungen'), k03().text)
|
||||
assert.ok(!k03().text.includes('§ 9 der Vertragsbedingungen') && !k03().text.includes('Stundenguthaben'))
|
||||
assert.ok(!k03().text.includes('§ 9 der Vertragsbedingungen') && !k03().text.includes('Stundenkontingent') && !k03().text.includes('Stundenguthaben'))
|
||||
assert.ok(t08().text.includes('Abo: Hosting-Abo'), t08().text)
|
||||
await resetSubH()
|
||||
assert.equal((await withdraw({ order: 'abschluss' })).status, 200)
|
||||
assert.equal(k04().subject, 'Eingangsbestätigung Ihres Widerrufs – WEBklar Hosting-Abo')
|
||||
assert.ok(t09().text.includes('Abo: Hosting-Abo'), t09().text)
|
||||
// Wartungs-Abo nach Fassung 3 (Altbestand): § 9, Stundenguthaben
|
||||
// Wartungs-Abo nach Fassung 3 (Altbestand): § 9, nicht genutzte Arbeitszeit aus dem Stundenkontingent
|
||||
await resetSubH({ patch: { productKey: 'wartung', termsVersion: '2026-09-25-v3' }, stripe: { productKey: 'wartung', metadata: { termsVersion: '2026-09-25-v3' } } })
|
||||
assert.equal((await cancel({ contract: 'abo', kind: 'ordentlich' })).status, 200)
|
||||
assert.equal(k03().subject, 'Eingangsbestätigung Ihrer Kündigung – WEBklar Wartungs-Abo')
|
||||
assert.ok(k03().text.includes('§ 9 der Vertragsbedingungen') && k03().text.includes('Stundenguthaben'), k03().text)
|
||||
assert.ok(k03().text.includes('§ 9 der Vertragsbedingungen') && k03().text.includes('Nicht genutzte Arbeitszeit aus dem Stundenkontingent verfällt mit Vertragsende.'), k03().text)
|
||||
// Unbekannte E-Mail: kein Abo, neutrale Anrede „Abo“, Paragraph der aktuellen Fassung (F08)
|
||||
await resetSubH()
|
||||
assert.equal((await cancel({ contract: 'abo', kind: 'ordentlich', email: 'niemand@example.invalid' })).status, 200)
|
||||
|
||||
Reference in new issue
Block a user