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:
ClaudeandClaude Fable 5.1 committed 2026-09-29 14:46:41 +00:00
1 parent 63d3bb11bf
commit 12a24514a4
10 files changed
+172 -36

No files matched your search

@@ -8,8 +8,10 @@
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail aus der Bestellung) · BCC intern · Anhang: Vertragsunterlagen der zugestimmten Fassung (PDF, contractAttachmentFor)
Platzhalter: {{name}}, {{produkt}}, {{preis}}, {{angebot_text}}, {{vertragsschluss}}, {{leistungsbeginn}}, {{mindestlaufzeit_bis}}, {{bedingungen_version}}, {{zustimmung_zeitpunkt}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): angebot, hosting, wartung, sofortbeginn, beginn_nach_widerrufsfrist
produkt = Katalogname ohne „WEBklar “ („Hosting-Abo“, „Wartungs-Abo“), preis z. B. „15,00 € / Monat“; genau einer der Blöcke hosting/wartung bringt die Liste „Enthalten“ (Spec 3.1).
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): angebot, hosting, wartung, wartung_v3, sofortbeginn, beginn_nach_widerrufsfrist
produkt = Katalogname ohne „WEBklar “ („Hosting-Abo“, „Wartungs-Abo“), preis z. B. „15,00 € / Monat“; genau einer der Blöcke hosting/wartung/wartung_v3 bringt die Liste „Enthalten“:
hosting und wartung nach Fassung 4 (Spec 3.1), wartung_v3 für ein Wartungs-Abo nach Fassung 3 (termsDocFor nicht v4: „1 E-Mail-Postfach · 1 Stunde Arbeitszeit pro Monat“ wie in den Bedingungen v3 § 3).
bedingungen_version und das PDF im Anhang kommen aus derselben Quelle (termsDocFor(subDoc.termsVersion).version): ohne gespeicherte Fassung beides Fassung 3.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -48,16 +50,22 @@ a{color:#111111;}
<!-- WENN wartung -->
&#8226;&nbsp; Betrieb und Pflege Ihrer Website bzw. Web-Anwendung<br>&#8226;&nbsp; 1 Domain<br>&#8226;&nbsp; beliebig viele Postfächer <span class="mut" style="color:#6b6964;">(zusammen 5 GB; mehr über den Zusatzspeicher)</span><br>&#8226;&nbsp; 1 Stunde Arbeitszeit je bezahltem Monat <span class="mut" style="color:#6b6964;">(12 Monate nutzbar)</span>
<!-- ENDE wartung -->
<!-- WENN wartung_v3 -->
&#8226;&nbsp; Betrieb und Pflege Ihrer Website bzw. Web-Anwendung<br>&#8226;&nbsp; 1 Domain<br>&#8226;&nbsp; 1 E-Mail-Postfach <span class="mut" style="color:#6b6964;">(mindestens 5 GB)</span><br>&#8226;&nbsp; 1 Stunde Arbeitszeit pro Monat <span class="mut" style="color:#6b6964;">(nicht genutzte Zeit bleibt 12 Monate verfügbar)</span>
<!-- ENDE wartung_v3 -->
</td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 40px 0 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td class="btn" bgcolor="#111111" style="background:#111111;border-radius:10px;"><a href="https://project.webklar.com/dashboard.html" style="display:block;padding:14px 26px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;color:#ffffff;text-decoration:none;">Zum Kundenportal &rarr;</a></td></tr></table></td></tr>
<!-- WENN wartung -->
<tr><td class="px mut" style="padding:10px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;">Dort sehen Sie jederzeit Ihr aktuelles Stundenkontingent.</td></tr>
<!-- ENDE wartung -->
<!-- WENN wartung_v3 -->
<tr><td class="px mut" style="padding:10px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;">Dort sehen Sie jederzeit Ihr aktuelles Stundenkontingent.</td></tr>
<!-- ENDE wartung_v3 -->
<!-- WENN hosting -->
<tr><td class="px mut" style="padding:10px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;">Dort sehen Sie jederzeit Ihre Verträge und Rechnungen.</td></tr>
<!-- ENDE hosting -->
<tr><td class="px" style="padding:32px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:20px 22px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#111111;"><div class="ink" style="font-weight:bold;color:#111111;padding-bottom:6px;">Ihre Vertragsunterlagen im Anhang (PDF)</div><div class="mut" style="color:#4a4843;">&#128206;&nbsp; Vertragsbedingungen mit Leistungsbeschreibung<br>&#128206;&nbsp; Widerrufsbelehrung mit Muster-Widerrufsformular<br>&#128206;&nbsp; Auftragsverarbeitungsvertrag</div></td></tr></table></td></tr>
<tr><td class="px mut" style="padding:28px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;">Ihre Zustimmung zu den Vertragsbedingungen (Version {{bedingungen_version}}) haben wir am {{zustimmung_zeitpunkt}} Uhr erhalten.<br><br>
<tr><td class="px mut" style="padding:28px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;">Ihre Zustimmung zu den Vertragsbedingungen (Version {{bedingungen_version}}) einschließlich des Auftragsverarbeitungsvertrags haben wir am {{zustimmung_zeitpunkt}} Uhr erhalten.<br><br>
<!-- WENN sofortbeginn -->
Sie haben ausdrücklich verlangt, dass wir vor Ablauf der Widerrufsfrist mit der Leistung beginnen, und wurden darüber informiert, dass Sie bei einem Widerruf einen angemessenen Betrag für die bis dahin erbrachten Leistungen zahlen.
<!-- ENDE sofortbeginn -->
@@ -9,7 +9,7 @@
Empfänger: E-Mail aus dem Formular · BCC intern
Platzhalter: {{name}}, {{produkt}}, {{kuendigungsart}}, {{eingang_zeitpunkt}}, {{email}}, {{kundennummer}}, {{vertrag}}, {{wunschtermin}}, {{grund}}, {{vertragsende}}, {{vertragsende_paragraph}}, {{pakete}}, {{pakete_neu}}, {{pakete_angabe}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): kundennummer, grund, automatisch_gekuendigt, sonderkuendigung, ausserordentlich, kein_abo_gefunden, zusatzspeicher, pruefen, abo, wartung, speicher_angaben, speicher_kind_pruefung
produkt = Katalogname ohne „WEBklar “ („Abo“ ohne gefundenes Abo); vertragsende_paragraph = Paragraph zum Vertragsende der Fassung des Kunden (v3 § 9, v4 § 11); wartung (innerhalb von abo) = Stundenguthaben.
produkt = Katalogname ohne „WEBklar “ („Abo“ ohne gefundenes Abo); vertragsende_paragraph = Paragraph zum Vertragsende der Fassung des Kunden (v3 § 9, v4 § 11); wartung (innerhalb von abo) = Hinweis auf nicht genutzte Arbeitszeit aus dem Stundenkontingent (kein Geldguthaben, v4 § 4 Abs. 1).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -78,7 +78,7 @@ a{color:#111111;}
<!-- ENDE speicher_kind_pruefung -->
<!-- WENN abo -->
<tr><td class="px mut" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;"><!-- WENN wartung -->Ein bestehendes Stundenguthaben verfällt mit Vertragsende. <!-- ENDE wartung -->Ihre Daten, den Domain-AuthCode und die Inhalte Ihrer Postfächer stellen wir Ihnen auf Wunsch gemäß {{vertragsende_paragraph}} der Vertragsbedingungen bereit. Gebuchter E-Mail-Zusatzspeicher endet zusammen mit dem Abo.</td></tr>
<tr><td class="px mut" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;"><!-- WENN wartung -->Nicht genutzte Arbeitszeit aus dem Stundenkontingent verfällt mit Vertragsende. <!-- ENDE wartung -->Ihre Daten, den Domain-AuthCode und die Inhalte Ihrer Postfächer stellen wir Ihnen auf Wunsch gemäß {{vertragsende_paragraph}} der Vertragsbedingungen bereit. Gebuchter E-Mail-Zusatzspeicher endet zusammen mit dem Abo.</td></tr>
<!-- ENDE abo -->
<tr><td class="px ink" style="padding:28px 40px 44px 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:24px;color:#111111;">Viele Grüße<br><strong>Ihr WEBklar-Team</strong></td></tr>
</table>
@@ -43,7 +43,7 @@ a{color:#111111;}
<tr><td class="px" style="padding:24px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:20px 22px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#111111;"><div class="ink" style="font-weight:bold;color:#111111;padding-bottom:6px;">Zahlung per SEPA-Lastschrift</div><div class="mut" style="color:#4a4843;">Ihr Wartungs-Abo beginnt schon mit der Bestellung. Wird die Lastschrift nicht eingelöst, gilt der Wechsel als nicht erfolgt: Ihr Hosting-Abo läuft dann mit Preis und Mindestlaufzeit wie vor dem Wechsel weiter, Ihr Abbuchungstag bleibt der Wechseltag, und wir informieren Sie per E-Mail.</div></td></tr></table></td></tr>
<!-- ENDE lastschrift -->
<tr><td class="px" style="padding:32px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:20px 22px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#111111;"><div class="mut" style="font-size:12px;text-transform:uppercase;letter-spacing:1px;color:#6b6964;">WEBklar Wartungs-Abo</div><div class="ink" style="font-size:30px;line-height:38px;font-weight:bold;letter-spacing:-0.5px;color:#111111;padding-top:4px;">{{monatspreis}}<span class="mut" style="font-size:15px;font-weight:normal;letter-spacing:0;color:#6b6964;">&nbsp;/ Monat</span></div><div class="mut" style="font-size:12px;line-height:18px;color:#6b6964;">Monatsgebühr des Wartungs-Abos<!-- WENN zusatzspeicher --> einschließlich Ihres gebuchten E-Mail-Zusatzspeichers<!-- ENDE zusatzspeicher -->, gemäß § 19 UStG ohne Umsatzsteuer</div></td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 40px 0 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;mso-line-height-rule:exactly;"><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Wechseltag</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{wechseltag}}</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Heute fällig</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{betrag_heute}}</strong><br><span class="mut" style="color:#6b6964;">Monatsgebühr des Wartungs-Abos mit gebuchtem Zusatzspeicher, abzüglich der Gutschrift für die nicht genutzten Tage Ihres Hosting-Abos</span></td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Abbuchung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">monatlich am Kalendertag des Wechsels</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Laufzeit</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>Mindestlaufzeit 12 Monate bis {{mindestlaufzeit_bis}}.</strong> Kündigung zu diesem Termin bis einen Monat vorher. Danach läuft das Abo unbefristet und ist jederzeit zum Ende des Abrechnungsmonats kündbar.</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;color:#6b6964;">Enthalten</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">&#8226;&nbsp; Betrieb und Pflege Ihrer Website bzw. Web-Anwendung<br>&#8226;&nbsp; 1 Domain<br>&#8226;&nbsp; beliebig viele E-Mail-Postfächer (zusammen 5 GB)<br>&#8226;&nbsp; 1 Stunde Arbeitszeit je bezahltem Monat <span class="mut" style="color:#6b6964;">(12 Monate nutzbar)</span></td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 40px 0 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;mso-line-height-rule:exactly;"><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Wechseltag</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{wechseltag}}</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Heute fällig</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{betrag_heute}}</strong><br><span class="mut" style="color:#6b6964;">Monatsgebühr des Wartungs-Abos<!-- WENN zusatzspeicher --> mit gebuchtem Zusatzspeicher<!-- ENDE zusatzspeicher -->, abzüglich der Gutschrift für die nicht genutzten Tage Ihres Hosting-Abos</span></td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Abbuchung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">monatlich am Kalendertag des Wechsels</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Laufzeit</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>Mindestlaufzeit 12 Monate bis {{mindestlaufzeit_bis}}.</strong> Kündigung zu diesem Termin bis einen Monat vorher. Danach läuft das Abo unbefristet und ist jederzeit zum Ende des Abrechnungsmonats kündbar.</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;color:#6b6964;">Enthalten</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">&#8226;&nbsp; Betrieb und Pflege Ihrer Website bzw. Web-Anwendung<br>&#8226;&nbsp; 1 Domain<br>&#8226;&nbsp; beliebig viele E-Mail-Postfächer (zusammen 5 GB)<br>&#8226;&nbsp; 1 Stunde Arbeitszeit je bezahltem Monat <span class="mut" style="color:#6b6964;">(12 Monate nutzbar)</span></td></tr></table></td></tr>
<tr><td class="px mut" style="padding:20px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;mso-line-height-rule:exactly;color:#4a4843;">Ihre Postfächer und gebuchter Zusatzspeicher bleiben unverändert. Weitere Postfächer richten wir ein, sobald die Wechselrechnung bezahlt ist; dann steht auch Ihre erste Stunde Arbeitszeit bereit.</td></tr>
<tr><td class="px" style="padding:28px 40px 0 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td class="btn" bgcolor="#111111" style="background:#111111;border-radius:10px;"><a href="https://project.webklar.com/dashboard.html" style="display:block;padding:14px 26px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;color:#ffffff;text-decoration:none;">Zum Kundenportal &rarr;</a></td></tr></table></td></tr>
<tr><td class="px" style="padding:32px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:20px 22px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#111111;"><div class="ink" style="font-weight:bold;color:#111111;padding-bottom:6px;">Ihre Vertragsunterlagen im Anhang (PDF)</div><div class="mut" style="color:#4a4843;">&#128206;&nbsp; Vertragsbedingungen mit Leistungsbeschreibung<br>&#128206;&nbsp; Widerrufsbelehrung mit Muster-Widerrufsformular<br>&#128206;&nbsp; Auftragsverarbeitungsvertrag</div></td></tr></table></td></tr>
+3 -2
View File
@@ -8,7 +8,8 @@
Absender: „WEBklar Kundenportal“ <Portal-Postfach>
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{produkt}}, {{stripe_abo_id}}, {{status}}, {{vertragsende_paragraph}}
produkt = Katalogname ohne „WEBklar “; vertragsende_paragraph = Paragraph zum Vertragsende der Fassung des Kunden (v3 § 9, v4 § 11).
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): wartung
produkt = Katalogname ohne „WEBklar “; vertragsende_paragraph = Paragraph zum Vertragsende der Fassung des Kunden (v3 § 9, v4 § 11); wartung = Hinweis auf nicht genutzte Arbeitszeit (nur Wartungs-Abo).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -35,7 +36,7 @@ a{color:#111111;}
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#ecebe6;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#4a4843;">Abo beendet</td></tr></table></td></tr>
<tr><td class="px ink" style="padding:16px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:28px;line-height:34px;mso-line-height-rule:exactly;font-weight:bold;letter-spacing:-0.5px;color:#111111;">Abo beendet</td></tr>
<tr><td class="px" style="padding:24px 40px 0 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;mso-line-height-rule:exactly;"><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Kunde</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{email}}</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Abo</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{produkt}}</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Stripe-Abo</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{stripe_abo_id}}</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;color:#6b6964;">Status</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{status}}</td></tr></table></td></tr>
<tr><td class="px mut" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Ein Restguthaben an Arbeitszeit verfällt mit dem Vertragsende; gebuchter Zusatzspeicher endet mit dem Abo.</td></tr>
<tr><td class="px mut" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;"><!-- WENN wartung -->Nicht genutzte Arbeitszeit aus dem Stundenkontingent verfällt mit dem Vertragsende. <!-- ENDE wartung -->Gebuchter Zusatzspeicher endet mit dem Abo.</td></tr>
<tr><td class="px" style="padding:24px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#fdf6e7;border:1px solid #f0dcb0;border-radius:12px;"><tr><td class="ink" style="padding:20px 22px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#111111;"><div class="ink" style="font-weight:bold;color:#111111;padding-bottom:6px;">Zu tun</div><div class="mut" style="color:#4a4843;">Website, Domain und Postfächer gemäß {{vertragsende_paragraph}} der Vertragsbedingungen abwickeln (Export bis 30 Tage nach Vertragsende, AuthCode).<br>Monitor in UptimeRobot entfernen.</div></td></tr></table></td></tr>
<tr><td style="padding:0 0 44px 0;font-size:0;line-height:0;">&nbsp;</td></tr>
</table>
+13 -7
View File
@@ -18,7 +18,7 @@ import {
reconcilePendingSwitch,
switchToWartung,
} from '../services/planSwitch.js'
import { PRODUCT_KEYS, contractAttachmentFor, describeItems, productLabel, productView, termsDocFor } from '../services/products.js'
import { PRODUCT_KEYS, contractAttachmentFor, describeItems, isTermsV4, productLabel, productView, termsDocFor } from '../services/products.js'
import {
ACTIVE_STATUSES,
cancelAtPeriodEnd,
@@ -416,12 +416,15 @@ async function customerNameOf(sub, fallback = '') {
/**
* K01 an den Kunden: Vertragsbestaetigung je Produkt (Katalogname, Preis, Liste "Enthalten" ueber die Bloecke
* hosting/wartung) mit den Vertragsunterlagen der Fassung, der der Kunde zugestimmt hat (§ 312f BGB,
* contractAttachmentFor: v3 Archiv-PDF, v4 aktuelles PDF). Ohne lesbare Datei geht die Mail ohne Anhang raus.
* hosting/wartung, fuer ein Wartungs-Abo nach Fassung 3 wartung_v3 mit der Zeile der Fassung 3) mit den
* Vertragsunterlagen der Fassung, der der Kunde zugestimmt hat (§ 312f BGB, contractAttachmentFor: v3 Archiv-PDF,
* v4 aktuelles PDF). Versionsangabe und Anhang kommen aus derselben Quelle (termsDocFor.version): ohne gespeicherte
* Fassung beides Fassung 3. Ohne lesbare Datei geht die Mail ohne Anhang raus.
*/
async function sendContractConfirmation(subDoc, fallbackName = '') {
if (!subDoc || subDoc.confirmationSentAt || !subDoc.email) return
const attachment = contractAttachmentFor(subDoc.termsVersion)
const terms = termsDocFor(subDoc.termsVersion)
const attachment = contractAttachmentFor(terms.version)
const pdf = await readFile(attachment.path).catch((err) => {
console.error(`[abo] Vertragsunterlagen ${attachment.filename} nicht lesbar:`, err.message)
return null
@@ -439,13 +442,14 @@ async function sendContractConfirmation(subDoc, fallbackName = '') {
leistungsbeginn: fmtDate(lateStart ? subDoc.trialEnd : subDoc.startedAt),
// Abos nach Bedingungen ohne Mindestlaufzeit (vor v3) entstehen nicht mehr; sie hätten hier "–"
mindestlaufzeit_bis: fmtDate(minTermEndOf(subDoc)),
bedingungen_version: subDoc.termsVersion || config.abo.termsVersion,
bedingungen_version: terms.version,
zustimmung_zeitpunkt: fmtDateTime(subDoc.termsAcceptedAt || Date.now()),
},
blocks: {
angebot: Boolean(subDoc.offerText),
hosting: productKey === 'hosting',
wartung: productKey === 'wartung',
wartung: productKey === 'wartung' && isTermsV4(terms.version),
wartung_v3: productKey === 'wartung' && !isTermsV4(terms.version),
sofortbeginn: !lateStart,
beginn_nach_widerrufsfrist: lateStart,
},
@@ -626,6 +630,8 @@ async function handleStripeEvent(event) {
await sendAdminNotice({
template: 'T07',
values: { email: sub?.email || '', produkt: productLabelOf(sub), stripe_abo_id: obj.id, status: obj.status, vertragsende_paragraph: endParagraphOf(sub) },
// Hinweis auf nicht genutzte Arbeitszeit nur beim Wartungs-Abo (Fix 1)
blocks: { wartung: isWartung(sub) },
})
return
}
@@ -927,7 +933,7 @@ router.post('/public/cancel', async (req, res) => {
kundennummer: Boolean(d.customerNumber),
grund: Boolean(d.reason),
abo: !storage,
// Stundenguthaben nur im Wartungs-Abo (Block innerhalb von abo)
// Hinweis auf nicht genutzte Arbeitszeit nur im Wartungs-Abo (Block innerhalb von abo)
wartung: !storage && isWartung(sub),
automatisch_gekuendigt: !storage && Boolean(effectiveAt) && d.kind !== 'geschaeftsaufgabe',
sonderkuendigung: !storage && Boolean(effectiveAt) && d.kind === 'geschaeftsaufgabe',
+18 -3
View File
@@ -25,17 +25,32 @@ export function getProduct(key) {
return product
}
/** Die archivierte Fassung 3 (public/vertragsunterlagen/v3): gilt fuer jede Zustimmung ohne Fassung "-v4". */
export const TERMS_VERSION_V3 = '2026-09-25-v3'
/** Ist die Fassung die aktuelle Version 4 (Hosting- und Wartungs-Abo)? Aeltere und fehlende Fassungen: nein. */
export function isTermsV4(termsVersion) {
return typeof termsVersion === 'string' && termsVersion.endsWith('-v4')
}
/**
* Vertragsunterlagen zur zugestimmten Fassung: Version 4 (Suffix "-v4") zeigt die aktuellen
* Bedingungen samt PDF, jede aeltere Version (auch unbekannt/keine) das Archiv der Version 3.
* version: die Fassung, die eine Mail nennt - die gespeicherte, ohne gespeicherte die archivierte Fassung 3,
* damit Versionsangabe und PDF-Anhang immer zusammenpassen (Task 12 Fix 1).
* endParagraph: der Paragraph zum Vertragsende (Daten, AuthCode, Loeschung) in dieser Fassung, fuer die
* Mails K02/K03/T05/T07 (F08) - in Fassung 4 ist § 9 der Wechsel zwischen den Abos, § 11 das Vertragsende.
*/
export function termsDocFor(termsVersion) {
if (typeof termsVersion === 'string' && termsVersion.endsWith('-v4')) {
return { url: '/abo-bedingungen.html', pdfFile: 'WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph: '§ 11' }
if (isTermsV4(termsVersion)) {
return { version: termsVersion, url: '/abo-bedingungen.html', pdfFile: 'WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph: '§ 11' }
}
return {
version: typeof termsVersion === 'string' && termsVersion ? termsVersion : TERMS_VERSION_V3,
url: '/vertragsunterlagen/v3/abo-bedingungen.html',
pdfFile: 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf',
endParagraph: '§ 9',
}
return { url: '/vertragsunterlagen/v3/abo-bedingungen.html', pdfFile: 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf', endParagraph: '§ 9' }
}
/** Katalogname ohne "WEBklar " (z. B. "Hosting-Abo") fuer {{produkt}} in den Mails (F33); ohne Schluessel "Abo". */
+59 -2
View File
@@ -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
+48 -5
View File
@@ -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
View File
@@ -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', () => {
+4 -4
View File
@@ -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)