Mails: Produktname statt fest Wartungs-Abo, neue Mails für Wechsel und Zusatzspeicher, UptimeRobot-Hinweis

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Fable 5.1 committed 2026-09-29 10:38:43 +00:00
1 parent 0d2254ca20
commit 19f9c747a2
24 files changed
+407 -100

No files matched your search

@@ -3,17 +3,18 @@
<head>
<meta charset="utf-8">
<!--
K01 · Vertragsbestätigung Wartungs-Abo
Betreff: Ihr WEBklar Wartungs-Abo – Vertragsbestätigung
K01 · Vertragsbestätigung (Hosting-Abo oder Wartungs-Abo)
Betreff: Ihr WEBklar {{produkt}} – Vertragsbestätigung
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail aus der Bestellung) · BCC intern
Platzhalter: {{name}}, {{angebot_text}}, {{vertragsschluss}}, {{leistungsbeginn}}, {{mindestlaufzeit_bis}}, {{bedingungen_version}}, {{zustimmung_zeitpunkt}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): angebot, sofortbeginn, beginn_nach_widerrufsfrist
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).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Ihr WEBklar Wartungs-Abo – Vertragsbestätigung</title>
<title>Ihr WEBklar {{produkt}} – Vertragsbestätigung</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@@ -22,7 +23,7 @@ a{color:#111111;}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">Ihr Wartungs-Abo ist abgeschlossen. Alle Vertragsunterlagen finden Sie im Anhang.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">Ihr {{produkt}} ist abgeschlossen. Alle Vertragsunterlagen finden Sie im Anhang.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
@@ -33,16 +34,28 @@ a{color:#111111;}
<tr><td class="card" style="background:#ffffff;border-radius:16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#e4f2e6;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#1d5b2c;">&#10003;&nbsp; Abo abgeschlossen</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;">Ihr Wartungs-Abo ist bestätigt.</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag {{name}},<br>vielen Dank für Ihre Bestellung. Hiermit bestätigen wir den Abschluss Ihres Wartungs-Abos.</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="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;">50,00 €<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;">gemäß § 19 UStG ohne Umsatzsteuer</div>
<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;">Ihr {{produkt}} ist bestätigt.</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag {{name}},<br>vielen Dank für Ihre Bestellung. Hiermit bestätigen wir den Abschluss Ihres {{produkt}}s.</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="mut" style="font-size:12px;text-transform:uppercase;letter-spacing:1px;color:#6b6964;">WEBklar {{produkt}}</div><div class="ink" style="font-size:30px;line-height:38px;font-weight:bold;letter-spacing:-0.5px;color:#111111;padding-top:4px;">{{preis}}</div><div class="mut" style="font-size:12px;line-height:18px;color:#6b6964;">gemäß § 19 UStG ohne Umsatzsteuer</div>
<!-- WENN angebot -->
<div class="ink" style="margin-top:14px;padding-top:12px;border-top:1px solid #e8e6e0;color:#111111;"><strong>Ihr Angebot:</strong> {{angebot_text}} (wie vereinbart)</div>
<!-- ENDE angebot -->
</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;">Vertragsschluss</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{vertragsschluss}}</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;">Leistungsbeginn</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{leistungsbeginn}}</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; 1 E-Mail-Postfach<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;">Vertragsschluss</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{vertragsschluss}}</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;">Leistungsbeginn</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{leistungsbeginn}}</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;">
<!-- WENN hosting -->
&#8226;&nbsp; Bereitstellung Ihrer statischen Website auf unserem Server in Deutschland <span class="mut" style="color:#6b6964;">(SSL, tägliche Datensicherung, Überwachung der Erreichbarkeit, Updates von Betriebssystem und Server-Software)</span><br>&#8226;&nbsp; 1 Domain<br>&#8226;&nbsp; 1 E-Mail-Postfach <span class="mut" style="color:#6b6964;">(5 GB; mehr über den Zusatzspeicher)</span><br>&#8226;&nbsp; Keine Pflege der Website; Änderungen nur nach Freigabe, 100 € pro Stunde in 15-Minuten-Schritten <span class="mut" style="color:#6b6964;">(gemäß § 19 UStG ohne Umsatzsteuer)</span>
<!-- ENDE hosting -->
<!-- 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 -->
</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 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>
<!-- WENN sofortbeginn -->
@@ -4,16 +4,17 @@
<meta charset="utf-8">
<!--
K02 · Kündigungsbestätigung (direkt bei Stripe)
Betreff: Bestätigung Ihrer Kündigung – WEBklar Wartungs-Abo
Betreff: Bestätigung Ihrer Kündigung – WEBklar {{produkt}}
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail am Abo) · BCC intern
Platzhalter: {{name}}, {{eingang_zeitpunkt}}, {{vertragsende}}
Platzhalter: {{name}}, {{produkt}}, {{eingang_zeitpunkt}}, {{vertragsende}}, {{vertragsende_paragraph}}
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 zum Stundenkontingent.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Bestätigung Ihrer Kündigung – WEBklar Wartungs-Abo</title>
<title>Bestätigung Ihrer Kündigung – WEBklar {{produkt}}</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@@ -22,7 +23,7 @@ a{color:#111111;}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">Ihr Wartungs-Abo endet zum {{vertragsende}}.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">Ihr {{produkt}} endet zum {{vertragsende}}.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
@@ -34,10 +35,10 @@ a{color:#111111;}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<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;">Kündigung bestätigt</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;">Ihre Kündigung ist bestätigt.</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag {{name}},<br>wir bestätigen Ihre Kündigung des WEBklar Wartungs-Abos vom {{eingang_zeitpunkt}} Uhr.</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag {{name}},<br>wir bestätigen Ihre Kündigung des WEBklar {{produkt}}s vom {{eingang_zeitpunkt}} Uhr.</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="mut" style="font-size:12px;text-transform:uppercase;letter-spacing:1px;color:#6b6964;">Ihr Abo endet zum</div><div class="ink" style="font-size:30px;line-height:38px;font-weight:bold;letter-spacing:-0.5px;color:#111111;padding-top:4px;">{{vertragsende}}</div></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;">Bis dahin bleiben alle Leistungen verfügbar, danach erfolgen keine weiteren Abbuchungen. Nicht genutzte Zeit aus dem Stundenkontingent entfällt mit Vertragsende.</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Ihre Daten, den Domain-AuthCode und Ihr Postfach stellen wir Ihnen auf Wunsch gemäß § 9 der Vertragsbedingungen bereit.</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;">Bis dahin bleiben alle Leistungen verfügbar, danach erfolgen keine weiteren Abbuchungen. Gebuchter E-Mail-Zusatzspeicher endet zusammen mit dem Abo.<!-- WENN wartung --> Nicht genutzte Zeit aus dem Stundenkontingent entfällt mit Vertragsende.<!-- ENDE wartung --></td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Ihre Daten, den Domain-AuthCode und die Inhalte Ihrer Postfächer stellen wir Ihnen auf Wunsch gemäß {{vertragsende_paragraph}} der Vertragsbedingungen bereit.</td></tr>
<tr><td class="px" style="padding:28px 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;">Sie haben das nicht veranlasst?</div><div class="mut" style="color:#4a4843;">Dann melden Sie sich bitte umgehend bei uns: <a class="mut" href="mailto:support@webklar.com" style="color:#4a4843;">support@webklar.com</a> oder 0176 23726355.</div></td></tr></table></td></tr>
<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>
@@ -4,16 +4,17 @@
<meta charset="utf-8">
<!--
K03 · Eingangsbestätigung Kündigung (Kündigungsseite)
Betreff: Eingangsbestätigung Ihrer Kündigung – WEBklar Wartungs-Abo
Betreff: Eingangsbestätigung Ihrer Kündigung – WEBklar {{produkt}}
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: E-Mail aus dem Formular · BCC intern
Platzhalter: {{name}}, {{kuendigungsart}}, {{eingang_zeitpunkt}}, {{email}}, {{kundennummer}}, {{vertrag}}, {{wunschtermin}}, {{grund}}, {{vertragsende}}, {{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, speicher_angaben, speicher_kind_pruefung
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.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Eingangsbestätigung Ihrer Kündigung – WEBklar Wartungs-Abo</title>
<title>Eingangsbestätigung Ihrer Kündigung – WEBklar {{produkt}}</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@@ -77,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;">Ein bestehendes Stundenguthaben verfällt mit Vertragsende. Ihre Daten, den Domain-AuthCode und Ihr Postfach stellen wir Ihnen auf Wunsch gemäß § 9 der Vertragsbedingungen bereit.</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 -->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>
<!-- 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>
@@ -4,16 +4,17 @@
<meta charset="utf-8">
<!--
K04 · Eingangsbestätigung Widerruf
Betreff: Eingangsbestätigung Ihres Widerrufs – WEBklar Wartungs-Abo
Betreff: Eingangsbestätigung Ihres Widerrufs – WEBklar {{produkt}}
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: E-Mail aus dem Formular · BCC intern
Platzhalter: {{name}}, {{bestellung}}, {{eingang_zeitpunkt}}, {{email}}, {{kundennummer}}
Platzhalter: {{name}}, {{produkt}}, {{bestellung}}, {{eingang_zeitpunkt}}, {{email}}, {{kundennummer}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): kundennummer, abschluss, wechsel, zusatzspeicher, pruefen
produkt = Katalogname ohne „WEBklar “ („Abo“ ohne gefundenes Abo). Folgen je Bestellung wie in den Hinweisen zum Widerruf (Fassung 4): abschluss (Wertersatz bei Sofortbeginn, Zusatzspeicher voll erstattet), wechsel (Hosting-Abo läuft weiter, Mehrbetrag abzüglich Wertersatz), zusatzspeicher (volle Erstattung, kein Wertersatz), pruefen (keine offene Frist).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Eingangsbestätigung Ihres Widerrufs – WEBklar Wartungs-Abo</title>
<title>Eingangsbestätigung Ihres Widerrufs – WEBklar {{produkt}}</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@@ -41,13 +42,13 @@ a{color:#111111;}
<!-- ENDE kundennummer -->
</table></td></tr>
<!-- WENN abschluss -->
<tr><td class="px" style="padding:28px 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;">Wie es weitergeht</div><div class="mut" style="color:#4a4843;">Es erfolgen keine weiteren Abbuchungen. Bereits gezahlte Beträge erstatten wir innerhalb von 14 Tagen über das ursprüngliche Zahlungsmittel, abzüglich eines anteiligen Wertersatzes für die bis zum Widerruf erbrachten Leistungen (§ 357a Abs. 2 BGB). Sie erhalten dazu eine gesonderte Abrechnung.</div></td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 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;">Wie es weitergeht</div><div class="mut" style="color:#4a4843;">Es erfolgen keine weiteren Abbuchungen. Bereits gezahlte Beträge erstatten wir innerhalb von 14 Tagen über das ursprüngliche Zahlungsmittel; haben Sie den sofortigen Beginn verlangt, ziehen wir einen anteiligen Wertersatz für die bis zum Widerruf erbrachten Leistungen ab (§ 357a Abs. 2 BGB). Mit dem Abschluss endet auch dazu bestellter E-Mail-Zusatzspeicher; was Sie dafür gezahlt haben, erstatten wir vollständig; Wertersatz verlangen wir dafür nicht. Sie erhalten dazu eine gesonderte Abrechnung.</div></td></tr></table></td></tr>
<!-- ENDE abschluss -->
<!-- WENN wechsel -->
<tr><td class="px" style="padding:28px 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;">Wie es weitergeht</div><div class="mut" style="color:#4a4843;">Wir setzen Ihr Abo auf das Hosting-Abo mit dem bisherigen Preis und der bisherigen Mindestlaufzeit zurück. Den für den Wechsel gezahlten Betrag erstatten wir innerhalb von 14 Tagen über das ursprüngliche Zahlungsmittel, abzüglich eines anteiligen Wertersatzes für die bis zum Widerruf erbrachten Leistungen (§ 357a Abs. 2 BGB). Sie erhalten dazu eine gesonderte Abrechnung.</div></td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 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;">Wie es weitergeht</div><div class="mut" style="color:#4a4843;">Ihr Hosting-Abo läuft zu den bisherigen Bedingungen weiter, als hätte es den Wechsel nicht gegeben, einschließlich seiner ursprünglichen Mindestlaufzeit; den Preis stellen wir entsprechend zurück. Den für das Wartungs-Abo gezahlten Mehrbetrag erstatten wir innerhalb von 14 Tagen über das ursprüngliche Zahlungsmittel, abzüglich des Wertersatzes für die bis zum Widerruf erbrachte Leistung (§ 357a Abs. 2 BGB): den anteiligen Mehrpreis des Wartungs-Abos gegenüber dem Hosting-Abo für die Tage vom Beginn des Wartungs-Abos bis zum Widerruf. Sie erhalten dazu eine gesonderte Abrechnung.</div></td></tr></table></td></tr>
<!-- ENDE wechsel -->
<!-- WENN zusatzspeicher -->
<tr><td class="px" style="padding:28px 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;">Wie es weitergeht</div><div class="mut" style="color:#4a4843;">Wir setzen Ihren E-Mail-Zusatzspeicher auf die Menge vor der Bestellung zurück. Den dafür gezahlten Betrag erstatten wir innerhalb von 14 Tagen über das ursprüngliche Zahlungsmittel, abzüglich eines anteiligen Wertersatzes für die bis zum Widerruf erbrachten Leistungen (§ 357a Abs. 2 BGB). Sie erhalten dazu eine gesonderte Abrechnung.</div></td></tr></table></td></tr>
<tr><td class="px" style="padding:28px 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;">Wie es weitergeht</div><div class="mut" style="color:#4a4843;">Wir setzen Ihren E-Mail-Zusatzspeicher auf die Menge vor der Bestellung zurück. Was Sie dafür gezahlt haben, erstatten wir vollständig innerhalb von 14 Tagen über das ursprüngliche Zahlungsmittel; Wertersatz verlangen wir nicht. Passen die gespeicherten E-Mails danach nicht mehr in den verbleibenden Speicher, nimmt das betroffene Postfach keine neuen E-Mails mehr an, bis wieder Platz ist.</div></td></tr></table></td></tr>
<!-- ENDE zusatzspeicher -->
<!-- WENN pruefen -->
<tr><td class="px" style="padding:28px 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;">Wie es weitergeht</div><div class="mut" style="color:#4a4843;">Wir prüfen, ob für diese Bestellung eine Widerrufsfrist läuft, und melden uns umgehend bei Ihnen.</div></td></tr></table></td></tr>
@@ -8,8 +8,8 @@
Absender: „WEBklar“ <Portal-Postfach>
Empfänger: Kunde (E-Mail des Kundenkontos) · BCC intern · Anhang: Vertragsunterlagen der Fassung 4 (PDF)
Platzhalter: {{name}}, {{wechseltag}}, {{betrag_heute}}, {{monatspreis}}, {{mindestlaufzeit_bis}}, {{bedingungen_version}}, {{zustimmung_zeitpunkt}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): zahlung_offen, lastschrift
Stand Aufgabe 9 (Rechtsrunde 28.09.2026): zahlung_offen bei Karte mit offener Zahlung, lastschrift bei SEPA; Endfassung der Texte in Aufgabe 12.
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): zahlung_offen, lastschrift, zusatzspeicher
zahlung_offen bei Karte mit zurückgehaltener Zahlung, lastschrift bei SEPA (Bedingungen § 9 Abs. 3); zusatzspeicher, wenn der Kunde Zusatzspeicher gebucht hat – monatspreis ist dann die Monatsgebühr mit Zusatzspeicher (Spec 3.5).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -37,17 +37,17 @@ a{color:#111111;}
<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;">Ihr Wechsel ins Wartungs-Abo</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag {{name}},<br>vielen Dank für Ihre Bestellung. Hiermit bestätigen wir Ihren Wechsel vom Hosting-Abo ins Wartungs-Abo.</td></tr>
<!-- WENN zahlung_offen -->
<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:#fdf6e3;border:1px solid #efdcaa;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 noch offen</div><div class="mut" style="color:#4a4843;">Der Wechsel wird wirksam, sobald die Zahlung bestätigt ist. Andernfalls läuft Ihr Hosting-Abo mit Preis und Mindestlaufzeit wie bisher weiter.<br>Die Zahlung schließen Sie im Kundenportal unter „Verträge“ ab.</div></td></tr></table></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:#fdf6e3;border:1px solid #efdcaa;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 noch offen</div><div class="mut" style="color:#4a4843;">Der Wechsel wird wirksam, sobald die Zahlung bestätigt ist. Andernfalls läuft Ihr Hosting-Abo mit Preis und Mindestlaufzeit wie bisher weiter.<br>Die Zahlung schließen Sie im Kundenportal unter „Verträge“ ab. Bleibt sie aus, verfällt die Wechselrechnung, und wir informieren Sie per E-Mail.</div></td></tr></table></td></tr>
<!-- ENDE zahlung_offen -->
<!-- WENN lastschrift -->
<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;">gemäß § 19 UStG ohne Umsatzsteuer</div></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="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 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>
<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>Sie haben ausdrücklich verlangt, dass wir vor Ablauf der Widerrufsfrist mit dem Wartungs-Abo beginnen, und wurden darüber informiert, dass Sie bei einem Widerruf des Wechsels Wertersatz für die bis dahin erbrachte Leistung zahlen: den anteiligen Mehrpreis des Wartungs-Abos gegenüber dem Hosting-Abo. Widerrufen Sie den Wechsel, läuft Ihr Hosting-Abo zu den bisherigen Bedingungen weiter.</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}}) einschließlich des Auftragsverarbeitungsvertrags haben wir am {{zustimmung_zeitpunkt}} Uhr erhalten.<br><br>Sie haben ausdrücklich verlangt, dass wir vor Ablauf der Widerrufsfrist mit dem Wartungs-Abo beginnen, und wurden darüber informiert, dass Sie bei einem Widerruf des Wechsels Wertersatz für die bis dahin erbrachte Leistung zahlen: den anteiligen Mehrpreis des Wartungs-Abos gegenüber dem Hosting-Abo für die Tage bis zum Widerruf. Widerrufen Sie den Wechsel, läuft Ihr Hosting-Abo zu den bisherigen Bedingungen weiter, als hätte es den Wechsel nicht gegeben, einschließlich seiner ursprünglichen Mindestlaufzeit; den gezahlten Mehrbetrag erstatten wir abzüglich dieses Wertersatzes.</td></tr>
<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>
</td></tr>
@@ -9,7 +9,7 @@
Empfänger: Kunde (E-Mail des Kundenkontos) · BCC intern · Anhang bei einer Bestellung (Plus): Vertragsunterlagen der zugestimmten Fassung (PDF)
Platzhalter: {{name}}, {{pakete}}, {{gesamt_gb}}, {{monatspreis_zusatz}}, {{wirksam_ab}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): plus, plus_bezahlt, bestellung, minus
Stand Aufgabe 8 (Fix 2): plus = Bestellung über die schon bezahlte Menge (Einrichtung, anteilige Berechnung), plus_bezahlt = Bestellung bis zur schon bezahlten Menge (keine Kosten, nichts einzurichten), bestellung = Unterlagen und Widerrufshinweis (bei beiden Bestellungen), minus = Verringerung zum Ende des Abrechnungsmonats (Spec 3.4); Endfassung der Texte in Aufgabe 12.
plus = Bestellung über die schon bezahlte Menge (Einrichtung, anteilige Berechnung), plus_bezahlt = Bestellung bis zur schon bezahlten Menge (keine Kosten, nichts einzurichten), bestellung = Unterlagen und Widerrufshinweis (bei beiden Bestellungen), minus = Verringerung zum Ende des Abrechnungsmonats (Spec 3.4) (Bedingungen § 7 Abs. 3, § 10).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -55,7 +55,7 @@ a{color:#111111;}
<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 bestellung -->
<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 den Regeln zum E-Mail-Zusatzspeicher<br>&#128206;&nbsp; Widerrufsbelehrung mit Muster-Widerrufsformular</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;">Sie können diese Bestellung innerhalb von 14 Tagen widerrufen; die Widerrufsbelehrung finden Sie in den beigefügten Vertragsunterlagen. Widerrufen Sie, nehmen wir die Pakete zurück und erstatten alles, was Sie dafür gezahlt haben.</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;">Sie können diese Bestellung innerhalb von 14 Tagen widerrufen; die Widerrufsbelehrung finden Sie in den beigefügten Vertragsunterlagen. Widerrufen Sie, nehmen wir die Pakete zurück und erstatten alles, was Sie dafür gezahlt haben. Wertersatz verlangen wir nicht. Passen die gespeicherten E-Mails danach nicht mehr in den verbleibenden Speicher, nimmt das betroffene Postfach keine neuen E-Mails mehr an, bis wieder Platz ist.</td></tr>
<!-- ENDE bestellung -->
<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>
@@ -9,7 +9,7 @@
Empfänger: Kunde (E-Mail des Kundenkontos) · BCC intern
Platzhalter: {{name}}, {{grund}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): lastschrift
Stand Aufgabe 9 (Rechtsrunde 28.09.2026, Bedingungen § 9 Abs. 3): lastschrift bei SEPA-Lastschrift; Endfassung der Texte in Aufgabe 12.
lastschrift bei SEPA-Lastschrift (Bedingungen § 9 Abs. 3: der Abbuchungstag bleibt der Wechseltag); grund aus SWITCH_FAILED_REASONS (planSwitch.js).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -3,17 +3,17 @@
<head>
<meta charset="utf-8">
<!--
T02 · Neues Wartungs-Abo
Betreff: [Abo] Neues Wartungs-Abo: {{email}}
T02 · Neues Abo (Hosting-Abo oder Wartungs-Abo)
Betreff: [Abo] Neues {{produkt}}: {{email}}
Absender: „WEBklar Kundenportal“ <Portal-Postfach>
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{kunde_name}}, {{appwrite_id}}, {{stripe_abo_id}}, {{angebot_text}}
Platzhalter: {{produkt}}, {{email}}, {{kunde_name}}, {{appwrite_id}}, {{stripe_abo_id}}, {{angebot_text}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): angebot, angebot_fehler, nachweis_fehlt
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>[Abo] Neues Wartungs-Abo: {{email}}</title>
<title>[Abo] Neues {{produkt}}: {{email}}</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@@ -22,7 +22,7 @@ a{color:#111111;}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">{{kunde_name}} hat das Wartungs-Abo abgeschlossen.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<span style="display:none;max-height:0;overflow:hidden;opacity:0;mso-hide:all;font-size:1px;line-height:1px;color:#f1f0ec;">{{kunde_name}} hat das {{produkt}} abgeschlossen.&#8199;&#847;&#8199;&#847;&#8199;&#847;</span>
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
@@ -33,8 +33,8 @@ a{color:#111111;}
<tr><td class="card" style="background:#ffffff;border-radius:16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#e4f2e6;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#1d5b2c;">&#10003;&nbsp; Neues Abo</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;">Neues Wartungs-Abo abgeschlossen</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;">{{kunde_name}}</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;">E-Mail</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;">Appwrite-Kunde</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{appwrite_id}}</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="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;">Neues {{produkt}} abgeschlossen</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;">{{kunde_name}}</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;">E-Mail</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;">Appwrite-Kunde</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{appwrite_id}}</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>
<!-- WENN angebot -->
<tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;color:#6b6964;">Angebot</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{angebot_text}}</td></tr>
<!-- ENDE angebot -->
@@ -48,6 +48,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:#fdf0ef;border:1px solid #f1c9c5;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;">Nachweis nicht gespeichert</div><div class="mut" style="color:#4a4843;">Das Vertragsereignis „Abschluss“ (Nachweis der Zustimmung, Beginn der Widerrufsfrist) konnte nicht in Appwrite gespeichert werden. Bitte prüfen, ob die Collection contractEvents mit allen Attributen angelegt ist (scripts/setup-abo-collections.mjs), und den Nachweis zu diesem Abo nachtragen. Vertragsbestätigung und Angebot sind davon nicht betroffen.</div></td></tr></table></td></tr>
<!-- ENDE nachweis_fehlt -->
<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 als Monitor in UptimeRobot anlegen.</div></td></tr></table></td></tr>
<tr><td style="padding:0 0 44px 0;font-size:0;line-height:0;">&nbsp;</td></tr>
</table>
</td></tr>
@@ -7,8 +7,8 @@
Betreff: [Abo] Kündigung (Stripe): {{email}}
Absender: „WEBklar Kundenportal“ <Portal-Postfach>
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{stripe_abo_id}}, {{vertragsende}}
Platzhalter: {{email}}, {{produkt}}, {{stripe_abo_id}}, {{vertragsende}}, {{vertragsende_paragraph}}
produkt = Katalogname ohne „WEBklar “; vertragsende_paragraph = Paragraph zum Vertragsende der Fassung des Kunden (v3 § 9, v4 § 11).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -34,9 +34,9 @@ a{color:#111111;}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<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;">Kündigung</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 gekündigt</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;">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;">Vertragsende</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{vertragsende}}</td></tr></table></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;">Vertragsende</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{vertragsende}}</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;">Die Bestätigung an den Kunden ist raus.</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;">Hosting, Domain und Postfach gemäß § 9 der Vertragsbedingungen abwickeln (Export, AuthCode).</div></td></tr></table></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;">Zum Vertragsende Website, Domain und Postfächer gemäß {{vertragsende_paragraph}} der Vertragsbedingungen abwickeln (Export, AuthCode); gebuchter Zusatzspeicher endet mit dem Abo.</div></td></tr></table></td></tr>
<tr><td style="padding:0 0 44px 0;font-size:0;line-height:0;">&nbsp;</td></tr>
</table>
</td></tr>
@@ -7,8 +7,8 @@
Betreff: [Abo] Kündigung zurückgenommen: {{email}}
Absender: „WEBklar Kundenportal“ <Portal-Postfach>
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{stripe_abo_id}}
Platzhalter: {{email}}, {{produkt}}, {{stripe_abo_id}}
produkt = Katalogname ohne „WEBklar “.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -34,7 +34,7 @@ a{color:#111111;}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#e4f2e6;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#1d5b2c;">&#10003;&nbsp; Kündigung aufgehoben</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;">Kündigung zurückgenommen</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;color:#6b6964;">Stripe-Abo</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{stripe_abo_id}}</td></tr></table></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;color:#6b6964;">Stripe-Abo</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{stripe_abo_id}}</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;">Das Abo läuft wieder normal weiter. Der Kunde bekommt dazu keine Mail.</td></tr>
<tr><td style="padding:0 0 44px 0;font-size:0;line-height:0;">&nbsp;</td></tr>
</table>
+5 -5
View File
@@ -7,8 +7,8 @@
Betreff: [Abo] Abo beendet: {{email}}
Absender: „WEBklar Kundenportal“ <Portal-Postfach>
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{stripe_abo_id}}, {{status}}
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).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -34,9 +34,9 @@ a{color:#111111;}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<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;">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 verfällt.</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;">Hosting, Domain und Postfach gemäß § 9 der Vertragsbedingungen abwickeln (Export, AuthCode).</div></td></tr></table></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" 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>
</td></tr>
@@ -7,7 +7,7 @@
Betreff: [Abo] Kündigung eingegangen: {{email}}
Absender: „WEBklar Kundenportal“ <Portal-Postfach>
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{name}}, {{kundennummer}}, {{vertrag}}, {{kuendigungsart}}, {{wunschtermin}}, {{grund}}, {{vertragsende}}, {{stripe_abo_id}}, {{pakete}}, {{pakete_angabe}}, {{fehler}}
Platzhalter: {{email}}, {{name}}, {{produkt}}, {{kundennummer}}, {{vertrag}}, {{kuendigungsart}}, {{wunschtermin}}, {{grund}}, {{vertragsende}}, {{stripe_abo_id}}, {{pakete}}, {{pakete_angabe}}, {{fehler}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): kundennummer, grund, automatisch_gekuendigt, sonderkuendigung, ausserordentlich, kein_abo_gefunden, zusatzspeicher, kein_speicher, wechsel_ausstehend, speicher_fehlgeschlagen, nachweis_fehlt, speicher_angaben, speicher_kind_pruefung
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -34,7 +34,7 @@ a{color:#111111;}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#fbefd5;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#7a4b00;">Kündigung eingegangen</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;">Kündigung über die Kündigungsseite</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;">Name</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{name}}</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;">E-Mail</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="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;">Name</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{name}}</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;">E-Mail</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>
<!-- WENN kundennummer -->
<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;">Kundennummer</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{kundennummer}}</td></tr>
<!-- ENDE kundennummer -->
@@ -7,7 +7,7 @@
Betreff: [Abo] WIDERRUF eingegangen: {{email}}
Absender: „WEBklar Kundenportal“ <Portal-Postfach>
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{name}}, {{kundennummer}}, {{bestellung}}, {{stripe_abo_id}}, {{abo_beginn}}, {{fristende}}, {{rechtzeitig}}, {{ereignisse}}, {{pruefhinweis}}
Platzhalter: {{email}}, {{name}}, {{produkt}}, {{kundennummer}}, {{bestellung}}, {{stripe_abo_id}}, {{abo_beginn}}, {{fristende}}, {{rechtzeitig}}, {{ereignisse}}, {{pruefhinweis}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): kundennummer, abo_gefunden, auf_kuendigung_gesetzt, kein_abo_gefunden, kuendigung_fehlgeschlagen, abschluss, wechsel, zusatzspeicher, keine_frist, nachweis_fehlt, wechsel_offen, abschluss_verspaetet, nicht_pruefbar, bereits_widerrufen
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -34,7 +34,7 @@ a{color:#111111;}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px" style="padding:44px 40px 8px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#fbe3e1;border-radius:100px;padding:6px 12px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;color:#8a1f17;">Widerruf</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;">Widerruf eingegangen</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;">Name</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{name}}</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;">E-Mail</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;">Bestellung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{bestellung}}</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;">Name</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{name}}</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;">E-Mail</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;">Bestellung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{bestellung}}</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>
<!-- WENN kundennummer -->
<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;">Kundennummer</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{kundennummer}}</td></tr>
<!-- ENDE kundennummer -->
@@ -52,13 +52,13 @@ a{color:#111111;}
<!-- ENDE kuendigung_fehlgeschlagen -->
<!-- WENN abschluss -->
<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:#fdf0ef;border:1px solid #f1c9c5;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;">Achtung</div><div class="mut" style="color:#4a4843;">Abo in Stripe sofort beenden, Wertersatz berechnen (anteilige Tage) und den Rest innerhalb von 14 Tagen erstatten (Stripe → Rückerstattung).</div></td></tr></table></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:#fdf0ef;border:1px solid #f1c9c5;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;">Achtung</div><div class="mut" style="color:#4a4843;">Abo in Stripe sofort beenden, Wertersatz berechnen (anteilige Tage, nur bei Sofortbeginn) und den Rest innerhalb von 14 Tagen erstatten (Stripe → Rückerstattung). Dazu bestellter Zusatzspeicher endet mit; was der Kunde dafür gezahlt hat, vollständig erstatten (kein Wertersatz, Hinweise zum Widerruf Nr. 2).</div></td></tr></table></td></tr>
<!-- ENDE abschluss -->
<!-- WENN wechsel -->
<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:#fdf0ef;border:1px solid #f1c9c5;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;">Wechsel ins Wartungs-Abo widerrufen: in Stripe den Preis zurück auf das Hosting-Abo setzen und die Metadaten des Wechsels zurücknehmen (termStartAt, productKey, switchedAt – wie in scripts/wechsel-gescheitert.mjs), die Wechselrechnung erstatten (Wertersatz für die Tage im Wartungs-Abo abziehen) innerhalb von 14 Tagen. Widerrufen:<br>{{ereignisse}}</div></td></tr></table></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:#fdf0ef;border:1px solid #f1c9c5;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;">Wechsel ins Wartungs-Abo widerrufen: in Stripe den Preis zurück auf das Hosting-Abo setzen und die Metadaten des Wechsels zurücknehmen (termStartAt, productKey, switchedAt – wie in scripts/wechsel-gescheitert.mjs), den Mehrbetrag innerhalb von 14 Tagen erstatten: was der Kunde seit dem Wechsel mehr gezahlt hat als im Hosting-Abo, abzüglich Wertersatz (anteiliger Mehrpreis für die Tage im Wartungs-Abo bis zum Widerruf, Hinweise zum Widerruf Nr. 3). Widerrufen:<br>{{ereignisse}}</div></td></tr></table></td></tr>
<!-- ENDE wechsel -->
<!-- WENN zusatzspeicher -->
<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:#fdf0ef;border:1px solid #f1c9c5;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;">Zusatzspeicher-Bestellung widerrufen: in Stripe die Menge zurück auf den Stand vor der Bestellung setzen, konsoleH anpassen und den Betrag erstatten (Wertersatz für die Tage abziehen) innerhalb von 14 Tagen. Widerrufen:<br>{{ereignisse}}</div></td></tr></table></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:#fdf0ef;border:1px solid #f1c9c5;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;">Zusatzspeicher-Bestellung widerrufen: in Stripe die Menge zurück auf den Stand vor der Bestellung setzen, konsoleH anpassen und den Betrag innerhalb von 14 Tagen vollständig erstatten (kein Wertersatz, Hinweise zum Widerruf Nr. 4). Widerrufen:<br>{{ereignisse}}</div></td></tr></table></td></tr>
<!-- ENDE zusatzspeicher -->
<!-- WENN keine_frist -->
<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:#fdf0ef;border:1px solid #f1c9c5;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;">Achtung</div><div class="mut" style="color:#4a4843;">Nichts automatisch geändert – keine offene Frist gefunden: zu dieser Bestellung läuft keine Widerrufsfrist (mehr) oder es gibt keine Bestellung dieser Art. Bitte prüfen, bevor der Widerruf abgelehnt wird; der Kunde hat „wir prüfen“ bekommen.</div></td></tr></table></td></tr>
+2 -2
View File
@@ -9,7 +9,7 @@
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{email}}, {{name}}, {{stripe_abo_id}}, {{wechseltag}}, {{betrag_heute}}, {{monatspreis}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): wirksam, lastschrift, gescheitert, lastschrift_gescheitert, nachweis_fehlt
Stand Aufgabe 9: wirksam (Wechsel gilt), lastschrift (SEPA, noch nicht eingelöst), gescheitert (K14 verschickt), lastschrift_gescheitert (Handgriffe), nachweis_fehlt; Endfassung in Aufgabe 12.
wirksam (Wechsel gilt), lastschrift (SEPA, noch nicht eingelöst), gescheitert (K14 verschickt), lastschrift_gescheitert (Handgriffe), nachweis_fehlt; monatspreis = Monatsgebühr des Wartungs-Abos mit gebuchtem Zusatzspeicher (Spec 3.5).
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -35,7 +35,7 @@ a{color:#111111;}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<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;">Wechsel</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;">Wechsel ins Wartungs-Abo</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;">{{name}}</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;">E-Mail</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;">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;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;">Wechselrechnung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{betrag_heute}}</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;color:#6b6964;">Monatspreis Wartung</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{monatspreis}}</td></tr></table></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;">{{name}}</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;">E-Mail</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;">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;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;">Wechselrechnung</td><td class="val ink rowline" valign="top" style="padding:12px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{betrag_heute}}</td></tr><tr><td class="lbl mut rowline" width="170" valign="top" style="width:170px;padding:12px 12px 12px 0;color:#6b6964;">Monatspreis ab Wechsel</td><td class="val ink rowline" valign="top" style="padding:12px 0;color:#111111;">{{monatspreis}} <span class="mut" style="color:#6b6964;">(Wartungs-Abo, ggf. mit gebuchtem Zusatzspeicher)</span></td></tr></table></td></tr>
<!-- WENN wirksam -->
<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:#eef6ef;border:1px solid #c9e3cd;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;">Der Wechsel ist wirksam</div><div class="mut" style="color:#4a4843;">Das Abo läuft ab dem Wechseltag als Wartungs-Abo, mit neuer Mindestlaufzeit von 12 Monaten. Die erste Stunde Arbeitszeit bucht das Portal, sobald die Wechselrechnung bezahlt ist.<br>Weitere Postfächer erst einrichten, wenn die Wechselrechnung bezahlt ist (Vertragsbedingungen § 9 Abs. 3).</div></td></tr></table></td></tr>
@@ -9,7 +9,7 @@
Empfänger: kenso@webklar.com, andrej@webklar.com
Platzhalter: {{aktion}}, {{email}}, {{name}}, {{stripe_abo_id}}, {{postfaecher_hinweis}}, {{neue_groesse_gb}}, {{pakete}}, {{faellig}}
Optionale Blöcke (zwischen WENN-/ENDE-Kommentaren; bei Nichtzutreffen samt Inhalt entfernen): einrichten, periodenende, teilweise, nachweis_fehlt
Stand Aufgabe 8 (Fix 1): aktion ist „einrichten“ oder „reduzieren zum TT.MM.JJJJ“; einrichten (Plus über die bezahlte Menge, sofort), periodenende (Minus oder Plus bis zur bezahlten Menge, zum Periodenende), teilweise (Schritt 2 bei Stripe fehlgeschlagen), nachweis_fehlt; Endfassung in Aufgabe 12.
aktion ist „einrichten“ oder „reduzieren zum TT.MM.JJJJ“; einrichten (Plus über die bezahlte Menge, sofort), periodenende (Minus oder Plus bis zur bezahlten Menge, zum Periodenende), teilweise (Schritt 2 bei Stripe fehlgeschlagen), nachweis_fehlt.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
+36 -13
View File
@@ -1,6 +1,4 @@
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { Router } from 'express'
import { config } from '../config.js'
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
@@ -20,7 +18,7 @@ import {
reconcilePendingSwitch,
switchToWartung,
} from '../services/planSwitch.js'
import { PRODUCT_KEYS, describeItems, productView, termsDocFor } from '../services/products.js'
import { PRODUCT_KEYS, contractAttachmentFor, describeItems, productLabel, productView, termsDocFor } from '../services/products.js'
import {
ACTIVE_STATUSES,
cancelAtPeriodEnd,
@@ -38,7 +36,6 @@ import {
} from '../services/stripeBilling.js'
const router = Router()
const publicDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'public')
const fmtDate = (iso) =>
iso
@@ -68,6 +65,12 @@ function minTermEndOf(sub) {
// Alte Dokumente ohne productKey sind Wartungs-Abos (Spec 6)
const productKeyOf = (sub) => sub?.productKey || 'wartung'
const isWartung = (sub) => Boolean(sub) && productKeyOf(sub) === 'wartung'
// {{produkt}} in den Mails: Katalogname ohne "WEBklar " (F33), ohne gefundenes Abo neutral "Abo"
const productLabelOf = (sub) => (sub ? productLabel(productKeyOf(sub)) : 'Abo')
// {{vertragsende_paragraph}}: Paragraph zum Vertragsende in der Fassung des Kunden (v3 § 9, v4 § 11); ohne
// gefundenes Abo die aktuelle Fassung (F08)
const endParagraphOf = (sub) => termsDocFor(sub ? sub.termsVersion : config.abo.termsVersion).endParagraph
/**
* Abo fuers Portal. events: Vertragsereignisse des Kunden (listContractEvents); es zaehlen die
@@ -411,15 +414,26 @@ async function customerNameOf(sub, fallback = '') {
return customer?.name || 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.
*/
async function sendContractConfirmation(subDoc, fallbackName = '') {
if (!subDoc || subDoc.confirmationSentAt || !subDoc.email) return
const pdfPath = path.join(publicDir, 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf')
const pdf = await readFile(pdfPath).catch(() => null)
const attachment = contractAttachmentFor(subDoc.termsVersion)
const pdf = await readFile(attachment.path).catch((err) => {
console.error(`[abo] Vertragsunterlagen ${attachment.filename} nicht lesbar:`, err.message)
return null
})
const lateStart = subDoc.startChoice === 'nach_frist'
const productKey = productKeyOf(subDoc)
await sendCustomerMail(subDoc.email, {
template: 'K01',
values: {
name: await customerNameOf(subDoc, fallbackName),
produkt: productLabel(productKey),
preis: `${fmtEuro(config.products[productKey].amountCents)} / Monat`,
angebot_text: subDoc.offerText || '',
vertragsschluss: fmtDate(subDoc.startedAt),
leistungsbeginn: fmtDate(lateStart ? subDoc.trialEnd : subDoc.startedAt),
@@ -430,12 +444,12 @@ async function sendContractConfirmation(subDoc, fallbackName = '') {
},
blocks: {
angebot: Boolean(subDoc.offerText),
hosting: productKey === 'hosting',
wartung: productKey === 'wartung',
sofortbeginn: !lateStart,
beginn_nach_widerrufsfrist: lateStart,
},
attachments: pdf
? [{ filename: 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf', content: pdf, contentType: 'application/pdf' }]
: [],
attachments: pdf ? [{ filename: attachment.filename, content: pdf, contentType: 'application/pdf' }] : [],
})
await updateDocument(config.collections.subscriptions, subDoc.$id, {
confirmationSentAt: new Date().toISOString(),
@@ -450,16 +464,19 @@ async function sendPortalCancellationConfirmation(sub, createdUnix) {
template: 'K02',
values: {
name: await customerNameOf(sub),
produkt: productLabelOf(sub),
eingang_zeitpunkt: fmtDateTime(receivedAt),
vertragsende: fmtDate(endsAt),
vertragsende_paragraph: endParagraphOf(sub),
},
blocks: { wartung: isWartung(sub) },
})
await updateDocument(config.collections.subscriptions, sub.$id, {
cancelRequestedAt: receivedAt.toISOString(),
}).catch(() => {})
await sendAdminNotice({
template: 'T05',
values: { email: sub.email, stripe_abo_id: sub.stripeSubscriptionId, vertragsende: fmtDate(endsAt) },
values: { email: sub.email, produkt: productLabelOf(sub), stripe_abo_id: sub.stripeSubscriptionId, vertragsende: fmtDate(endsAt), vertragsende_paragraph: endParagraphOf(sub) },
})
}
@@ -525,6 +542,7 @@ async function handleStripeEvent(event) {
await sendAdminNotice({
template: 'T02',
values: {
produkt: productLabel(sub ? productKeyOf(sub) : obj.metadata?.productKey),
email: sub?.email || obj.customer_details?.email || '',
kunde_name: obj.customer_details?.name || obj.customer_details?.email || '',
appwrite_id: sub?.customerId || obj.client_reference_id || '',
@@ -599,7 +617,7 @@ async function handleStripeEvent(event) {
)
}
if (reactivated && sub) {
await sendAdminNotice({ template: 'T06', values: { email: sub.email, stripe_abo_id: obj.id } })
await sendAdminNotice({ template: 'T06', values: { email: sub.email, produkt: productLabelOf(sub), stripe_abo_id: obj.id } })
}
return
}
@@ -607,7 +625,7 @@ async function handleStripeEvent(event) {
const sub = await syncSubscription(obj.id)
await sendAdminNotice({
template: 'T07',
values: { email: sub?.email || '', stripe_abo_id: obj.id, status: obj.status },
values: { email: sub?.email || '', produkt: productLabelOf(sub), stripe_abo_id: obj.id, status: obj.status, vertragsende_paragraph: endParagraphOf(sub) },
})
return
}
@@ -888,6 +906,7 @@ router.post('/public/cancel', async (req, res) => {
email: d.email,
kundennummer: d.customerNumber,
vertrag: contractLabel(d.contract, sub),
produkt: productLabelOf(sub),
// Eine Verringerung des Speichers wirkt immer zum Ende des Abrechnungsmonats
wunschtermin: storage ? 'Ende des Abrechnungsmonats' : d.effective ? fmtDate(`${d.effective}T12:00:00Z`) : 'nächstmöglicher Zeitpunkt',
grund: d.reason,
@@ -903,11 +922,13 @@ router.post('/public/cancel', async (req, res) => {
await sendCustomerMail(d.email, {
template: 'K03',
values: { ...common, pakete_neu: reduced ? result.details.toUnits : '', kuendigungsart: kindLabel, eingang_zeitpunkt: fmtDateTime(receivedAt) },
values: { ...common, pakete_neu: reduced ? result.details.toUnits : '', kuendigungsart: kindLabel, eingang_zeitpunkt: fmtDateTime(receivedAt), vertragsende_paragraph: endParagraphOf(sub) },
blocks: {
kundennummer: Boolean(d.customerNumber),
grund: Boolean(d.reason),
abo: !storage,
// Stundenguthaben 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',
ausserordentlich: !storage && !effectiveAt && d.kind === 'ausserordentlich',
@@ -1070,6 +1091,7 @@ router.post('/public/withdraw', async (req, res) => {
values: {
name: d.name,
bestellung: orderLabel(d.order, sub, { customer: true }),
produkt: productLabelOf(sub),
eingang_zeitpunkt: fmtDateTime(receivedAt),
email: d.email,
kundennummer: d.customerNumber,
@@ -1091,6 +1113,7 @@ router.post('/public/withdraw', async (req, res) => {
name: d.name,
kundennummer: d.customerNumber,
bestellung: orderLabel(d.order, sub),
produkt: productLabelOf(sub),
stripe_abo_id: sub?.stripeSubscriptionId || '',
abo_beginn: fmtDate(sub?.startedAt),
fristende: result.deadline ? `${fmtDate(result.deadline)}, 24 Uhr` : 'keine Frist gefunden',
+21 -7
View File
@@ -197,6 +197,7 @@ async function stripeContext(doc, customer) {
stripeSub,
wartungPriceId,
aboItemId: items.aboItemId,
storageUnits: items.storageUnits,
// Beginn des laufenden Abrechnungsmonats (seit API 2025-03 am Posten)
periodStart: aboItem?.current_period_start ?? stripeSub.current_period_start ?? null,
paymentMethod: paymentMethodTypeOf(stripeSub),
@@ -297,7 +298,7 @@ async function sendSwitchConfirmation(contact, consent, facts, blocks) {
name: contact.name,
wechseltag: fmtDate(facts.switchedAt),
betrag_heute: fmtEuro(facts.amountDueCents),
monatspreis: fmtEuro(config.products.wartung.amountCents),
monatspreis: fmtEuro(monthlyCentsOf(facts)),
mindestlaufzeit_bis: fmtDate(addMonths(new Date(facts.switchedAt), 12)),
bedingungen_version: consent.termsVersion,
zustimmung_zeitpunkt: fmtDateTime(consent.consentAt),
@@ -317,13 +318,19 @@ async function sendSwitchNotice(contact, subscriptionId, facts, blocks) {
stripe_abo_id: subscriptionId,
wechseltag: fmtDate(facts.switchedAt),
betrag_heute: fmtEuro(facts.amountDueCents),
monatspreis: fmtEuro(config.products.wartung.amountCents),
monatspreis: fmtEuro(monthlyCentsOf(facts)),
},
blocks,
})
}
const factsOf = (event) => ({ switchedAt: event.details?.switchedAt || event.consentAt, amountDueCents: event.details?.amountDueCents })
const factsOf = (event) => ({
switchedAt: event.details?.switchedAt || event.consentAt,
amountDueCents: event.details?.amountDueCents,
monthlyCents: event.details?.monthlyCents,
})
// Monatsgebuehr fuer {{monatspreis}}; Ereignisse von vor Task 12 kennen nur den Katalogpreis des Wartungs-Abos
const monthlyCentsOf = (facts) => facts.monthlyCents ?? config.products.wartung.amountCents
// Ein Wechsel je Abo zur selben Zeit (Doppelklick): der zweite wartet nicht, sondern bekommt 409
const switchesInFlight = new Set()
@@ -370,6 +377,8 @@ async function performSwitch(customerId, current, consent, prorationDate, expect
}
const sepa = ctx.paymentMethod === 'sepa_debit'
const switchedAt = new Date().toISOString()
// Spec 3.5: Monatsgebuehr des Wartungs-Abos mit gebuchtem Zusatzspeicher ({{monatspreis}} in K12 und T12)
const monthlyCents = config.products.wartung.amountCents + ctx.storageUnits * config.mailStorage.amountCents
// Stripe-Metadaten am Abo (max. 500 Zeichen je Wert): Produkt, neue Mindestlaufzeit, Nachweis der Zustimmung
const metadata = {
productKey: 'wartung',
@@ -421,6 +430,7 @@ async function performSwitch(customerId, current, consent, prorationDate, expect
details: {
from: 'hosting',
amountDueCents,
monthlyCents,
prorationDate,
switchedAt,
paymentMethod: ctx.paymentMethod,
@@ -440,10 +450,10 @@ async function performSwitch(customerId, current, consent, prorationDate, expect
})
const contact = { name: customer?.name || '', email: customer?.email || doc.email || '' }
const facts = { switchedAt, amountDueCents }
const facts = { switchedAt, amountDueCents, monthlyCents }
const lastschrift = sepa && effective && !paid
// K12 sofort; zahlung_offen nur bei zurueckgehaltener Aenderung (Karte), lastschrift bei SEPA (F37)
await sendSwitchConfirmation(contact, consent, facts, { zahlung_offen: pending, lastschrift })
await sendSwitchConfirmation(contact, consent, facts, { zahlung_offen: pending, lastschrift, zusatzspeicher: ctx.storageUnits > 0 })
// T12 erst, wenn der Wechsel wirksam ist - ohne Nachweis sofort, damit das Team ihn von Hand verfolgt
if (effective || !event) {
await sendSwitchNotice(contact, doc.$id, facts, { wirksam: effective, lastschrift, nachweis_fehlt: !event })
@@ -623,7 +633,7 @@ export async function handleSwitchPaymentFailed(invoice) {
if (d.invoiceId && d.invoiceId !== invoice.id) return { skipped: 'nicht die Wechselrechnung' }
if (d.lastschriftFailedAt) return { skipped: 'schon gemeldet' }
await updateContractEvent(event.$id, { details: { lastschriftFailedAt: new Date().toISOString(), invoiceId: invoice.id } })
await sendSwitchNotice(contact, subscriptionId, { switchedAt: d.switchedAt, amountDueCents: invoice.amount_due }, { lastschrift_gescheitert: true })
await sendSwitchNotice(contact, subscriptionId, { switchedAt: d.switchedAt, amountDueCents: invoice.amount_due, monthlyCents: d.monthlyCents }, { lastschrift_gescheitert: true })
return { notified: true, eventId: event.$id }
})
}
@@ -636,7 +646,11 @@ export async function handleSwitchPaymentFailed(invoice) {
await sendSwitchNotice(
contact,
subscriptionId,
{ switchedAt: new Date(switchedAt).toISOString(), amountDueCents: invoice.amount_due },
{
switchedAt: new Date(switchedAt).toISOString(),
amountDueCents: invoice.amount_due,
monthlyCents: config.products.wartung.amountCents + describeItems(stripeSub.items?.data).storageUnits * config.mailStorage.amountCents,
},
{ lastschrift_gescheitert: true, nachweis_fehlt: true }
)
return { notified: true, eventId: null }
+9 -2
View File
@@ -28,12 +28,19 @@ export function getProduct(key) {
/**
* 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.
* 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' }
return { url: '/abo-bedingungen.html', pdfFile: 'WEBklar-Abo-Vertragsunterlagen.pdf', endParagraph: '§ 11' }
}
return { url: '/vertragsunterlagen/v3/abo-bedingungen.html', pdfFile: 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf' }
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". */
export function productLabel(key) {
return PRODUCT_KEYS.includes(key) ? config.products[key].name.replace(/^WEBklar\s+/, '') : 'Abo'
}
const PUBLIC_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'public')
+51 -1
View File
@@ -27,9 +27,17 @@ const { recordContractEvent, listContractEvents, eventIdFor } = await import('..
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
const { withdrawalDeadline } = await import('../server/services/deadlines.js')
const { offerText } = await import('../server/services/offers.js')
const { setMailTransportForTests } = await import('../server/services/mailer.js')
const { mailSink } = await import('./support/mailSink.js')
const app = await startTestApp({ '/api/abo': aboRoutes })
after(() => Promise.all([app.close(), aw.close(), st.close()]))
// F25: Mails landen in einer Senke statt bei SMTP, damit K01 (Anhang je Fassung) und T02 pruefbar sind (Task 12)
const mails = mailSink()
setMailTransportForTests(mails)
after(() => {
setMailTransportForTests(null)
return Promise.all([app.close(), aw.close(), st.close()])
})
seedFixtures(aw)
const DAY_MS = 86400000
@@ -399,6 +407,48 @@ test('Webhook checkout.session.completed: Ereignis abschluss ab Vertragsschluss,
}
})
// Task 12: K01 je Produkt (Katalogname, Preis, Liste „Enthalten“) mit dem PDF der Fassung, der der Kunde zugestimmt
// hat (§ 312f BGB, contractAttachmentFor); T02 nennt das Produkt und die Aufgabe fuer UptimeRobot
test('Webhook checkout.session.completed: K01 je Produkt mit PDF der zugestimmten Fassung, T02 mit UptimeRobot', async () => {
const subH = 'sub_1SD6y2SxAbCdEfGhIjKlMnOs'
st.subscriptions.set(subH, stripeSubscription({ id: subH, customerId: 'c3', productKey: 'hosting' }))
try {
mails.sent.length = 0
const res = await postWebhook(checkoutCompleted(subH))
assert.equal(res.status, 200, JSON.stringify(res.body))
const k01 = mails.sent.find((m) => m.subject.includes('Vertragsbestätigung'))
assert.ok(k01, 'K01 verschickt')
assert.equal(k01.subject, 'Ihr WEBklar Hosting-Abo – Vertragsbestätigung')
assert.deepEqual(k01.attachments.map((a) => [a.filename, a.contentType]), [['WEBklar-Abo-Vertragsunterlagen.pdf', 'application/pdf']])
assert.ok(k01.attachments[0].content.length > 1000, 'PDF-Inhalt angehaengt')
for (const text of ['15,00 € / Monat', 'gemäß § 19 UStG ohne Umsatzsteuer', '1 E-Mail-Postfach', `(Version ${config.abo.termsVersion})`]) {
assert.ok(k01.text.includes(text), `${text}\n---\n${k01.text}`)
}
assert.ok(!k01.text.includes('Stunde Arbeitszeit') && !k01.text.includes('Stundenkontingent'), k01.text)
assert.ok(aw.docs('subscriptions').get(subH).confirmationSentAt)
const t02 = mails.sent.find((m) => m.subject.startsWith('[Abo] Neues'))
assert.ok(t02.subject.startsWith('[Abo] Neues Hosting-Abo: '), t02.subject)
assert.ok(t02.text.includes('Website als Monitor in UptimeRobot anlegen'), t02.text)
} finally {
await cleanupWebhook(subH)
}
// Wartungs-Abo nach Fassung 3 (Altbestand): das Archiv-PDF der Fassung 3, Stunde und beliebig viele Postfaecher
const subW = 'sub_1SD6z3TxAbCdEfGhIjKlMnOt'
st.subscriptions.set(subW, stripeSubscription({ id: subW, customerId: 'c3', productKey: 'wartung', metadata: { termsVersion: '2026-09-25-v3' } }))
try {
mails.sent.length = 0
assert.equal((await postWebhook(checkoutCompleted(subW, { 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-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)']) {
assert.ok(k01.text.includes(text), `${text}\n---\n${k01.text}`)
}
} finally {
await cleanupWebhook(subW)
}
})
// Fix 1, Important: ein dauerhafter Fehler beim Nachweis (4xx, z. B. Collection oder Attribut fehlt) darf
// Angebot, T02 und K01 nicht blockieren; ein voruebergehender (5xx, Netz) gibt 500, Stripe wiederholt
test('Webhook: Nachweis dauerhaft nicht speicherbar (400) → 200, Angebot trotzdem aktiviert', async () => {
+148 -7
View File
@@ -1,11 +1,12 @@
import assert from 'node:assert/strict'
import { readFileSync, readdirSync } from 'node:fs'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { test } from 'node:test'
import { escapeHtml, htmlToText, loadTemplate, renderMail, templateIds } from '../server/services/mailTemplates.js'
import { contractAttachmentFor, termsDocFor } from '../server/services/products.js'
const DIR = new URL('../server/mail-templates/', import.meta.url)
// K12, K14 und T12 (Wechsel ins Wartungs-Abo) aus Task 9, K13 und T13 (Zusatzspeicher) aus Task 8 (Fix 1);
// die Endfassung der Texte folgt in Task 12
// Endfassung der Texte in Task 12 (T14 „Löschfristen“ kommt mit Task 21 dazu)
const EXPECTED = [
...Array.from({ length: 11 }, (_, i) => `K${String(i + 1).padStart(2, '0')}`),
'K12',
@@ -67,7 +68,7 @@ test('Werte werden HTML-sicher eingesetzt, Zeilenumbrüche werden <br>', () => {
})
test('optionale Blöcke: nur eingeschaltete erscheinen', () => {
const values = { name: 'Max', angebot_text: 'Erste 2 Monate je 25,00 €', vertragsschluss: '01.10.2026', leistungsbeginn: '15.10.2026', mindestlaufzeit_bis: '15.10.2027', bedingungen_version: 'v3', zustimmung_zeitpunkt: '01.10.2026, 10:00' }
const values = { name: 'Max', produkt: 'Wartungs-Abo', preis: '50,00 € / Monat', angebot_text: 'Erste 2 Monate je 25,00 €', vertragsschluss: '01.10.2026', leistungsbeginn: '15.10.2026', mindestlaufzeit_bis: '15.10.2027', bedingungen_version: 'v3', zustimmung_zeitpunkt: '01.10.2026, 10:00' }
const early = renderMail('K01', values, { angebot: false, sofortbeginn: true, beginn_nach_widerrufsfrist: false })
assert.doesNotMatch(early.html, /Ihr Angebot:/)
assert.match(early.html, /Sie haben ausdrücklich verlangt/)
@@ -102,11 +103,13 @@ test('Anrede ohne Namen, fehlende und unbekannte Werte werden gemeldet', () => {
})
test('Textfassung K01: alle Vertragsangaben lesbar', () => {
// F34: Produkt und Preis kommen aus dem Katalog, der Produktblock (hier wartung) bringt die Liste „Enthalten“
const mail = renderMail(
'K01',
{ name: 'Erika Musterfrau', 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' },
{ sofortbeginn: true }
{ name: 'Erika Musterfrau', 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' },
{ sofortbeginn: true, wartung: true }
)
assert.deepEqual(mail.problems, [])
const t = mail.text
for (const line of [
'Guten Tag Erika Musterfrau,',
@@ -266,11 +269,15 @@ test('K03, K04, T08, T09: Vertrag bzw. Bestellung, Pakete und die Blöcke der Au
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.match(renderMail('K03', k03, { pruefen: true }).text, /bestätigen Ihnen den Zeitpunkt, zu dem sie wirkt, gesondert per E-Mail/)
assert.match(renderMail('K03', k03, { abo: true }).text, /Stundenguthaben verfällt/)
// 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/)
assert.match(renderMail('K03', k03, { abo: true }).text, /Domain-AuthCode/)
const k04 = { ...sampleValues(loadTemplate('K04')), bestellung: 'Wechsel ins Wartungs-Abo' }
assert.match(renderMail('K04', k04, { wechsel: true }).text, /Widerruf der Bestellung „Wechsel ins Wartungs-Abo“/)
assert.match(renderMail('K04', k04, { wechsel: true }).text, /bisherigen Preis/)
// Task 12: Wortlaut der Hinweise zum Widerruf (Fassung 4)
assert.match(renderMail('K04', k04, { wechsel: true }).text, /zu den bisherigen Bedingungen weiter/)
assert.match(renderMail('K04', k04, { zusatzspeicher: true }).text, /Menge vor der Bestellung/)
assert.match(renderMail('K04', k04, { abschluss: true }).text, /keine weiteren Abbuchungen/)
assert.match(renderMail('K04', k04, { pruefen: true }).text, /prüfen, ob für diese Bestellung eine Widerrufsfrist läuft/)
@@ -304,3 +311,137 @@ test('K03, K04, T08, T09: Vertrag bzw. Bestellung, Pakete und die Blöcke der Au
assert.match(renderMail('T09', t09b, { abo_gefunden: true, abschluss_verspaetet: true }).text, /als ordentliche Kündigung behandeln/)
assert.match(renderMail('T09', t09b, { abo_gefunden: true, bereits_widerrufen: true }).text, /schon widerrufen/)
})
// ---------------------------------------------------------------------------
// Task 12: Mails je Produkt (Hosting-Abo, Wartungs-Abo), Vertragsunterlagen der zugestimmten Fassung,
// Paragraph zum Vertragsende je Fassung, Folgen des Widerrufs wie in den Hinweisen der Fassung 4, UptimeRobot
// ---------------------------------------------------------------------------
test('Neue Vorlagen vorhanden', () => {
for (const id of ['K12', 'K13', 'K14', 'T12', 'T13']) assert.ok(templateIds().includes(id), id)
})
test('K01 Hosting: 1 Postfach, keine Arbeitsstunde; Wartung: beliebig viele Postfächer', () => {
const base = { name: 'K', produkt: 'Hosting-Abo', preis: '15,00 € / Monat', vertragsschluss: '01.10.2026', leistungsbeginn: '01.10.2026',
mindestlaufzeit_bis: '01.10.2027', bedingungen_version: '2026-10-05-v4', zustimmung_zeitpunkt: '01.10.2026, 10:00', angebot_text: '' }
const h = renderMail('K01', base, { hosting: true, sofortbeginn: true })
assert.equal(h.subject, 'Ihr WEBklar Hosting-Abo – Vertragsbestätigung')
assert.ok(h.text.includes('1 E-Mail-Postfach') && !h.text.includes('Stunde Arbeitszeit'), h.text)
assert.ok(h.text.includes('15,00 € / Monat') && h.text.includes('gemäß § 19 UStG ohne Umsatzsteuer'))
assert.ok(h.text.includes('Ihr Hosting-Abo ist bestätigt.') && h.text.includes('Abschluss Ihres Hosting-Abos'))
assert.doesNotMatch(h.text, /Stundenkontingent/)
assert.deepEqual(h.problems, [])
const w = renderMail('K01', { ...base, produkt: 'Wartungs-Abo', preis: '50,00 € / Monat' }, { wartung: true, sofortbeginn: true })
assert.ok(w.text.includes('beliebig viele Postfächer'), w.text)
assert.ok(w.text.includes('1 Stunde Arbeitszeit je bezahltem Monat') && w.text.includes('Stundenkontingent'))
assert.doesNotMatch(w.text, /1 E-Mail-Postfach\n/)
assert.deepEqual(w.problems, [])
})
test('T02 und T07 erinnern an UptimeRobot', () => {
// F38: echte Platzhalter, keine Unstimmigkeiten
const t02 = renderMail('T02', { produkt: 'Hosting-Abo', email: 'k@example.invalid', kunde_name: 'K', appwrite_id: 'c1', stripe_abo_id: 'sub_1', angebot_text: '' }, {})
assert.deepEqual(t02.problems, [])
assert.ok(t02.subject.startsWith('[Abo] Neues Hosting-Abo') && t02.text.includes('Website als Monitor in UptimeRobot anlegen'), t02.text)
assert.equal(loadTemplate('T02').file, 'T02 Neues Abo.html')
assert.match(renderMail('T02', { produkt: 'Hosting-Abo', email: 'k@example.invalid', kunde_name: 'K', appwrite_id: 'c1', stripe_abo_id: 'sub_1', angebot_text: '' }, { nachweis_fehlt: true }).text, /Nachweis nicht gespeichert/)
const t07 = renderMail('T07', { produkt: 'Hosting-Abo', email: 'k@example.invalid', stripe_abo_id: 'sub_1', status: 'canceled', vertragsende_paragraph: '§ 11' }, {})
assert.deepEqual(t07.problems, [])
assert.ok(t07.text.includes('Monitor in UptimeRobot entfernen'), t07.text)
})
test('Keine Vorlage nennt fest „Wartungs-Abo“ im Betreff außer K12, K14 und T12', () => {
for (const id of templateIds()) if (!['K12', 'K14', 'T12'].includes(id)) assert.ok(!loadTemplate(id).subject.includes('Wartungs-Abo'), id)
})
test('K01-Anhang: Fassung 3 für v3-Abos, Fassung 4 sonst', () => {
// contractAttachmentFor(termsVersion) liefert { filename, path } und wird von sendContractConfirmation benutzt
assert.equal(contractAttachmentFor('2026-09-25-v3').filename, 'WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf')
assert.equal(contractAttachmentFor('2026-10-05-v4').filename, 'WEBklar-Abo-Vertragsunterlagen.pdf')
assert.ok(existsSync(contractAttachmentFor('2026-10-05-v4').path))
assert.ok(existsSync(contractAttachmentFor('2026-09-25-v3').path))
})
// Rechtsrunde: In Fassung 4 ist § 9 der Wechsel, das Vertragsende steht in § 11 (F08)
test('K02, K03, T05, T07: Paragraph zum Vertragsende aus der Fassung des Kunden', () => {
assert.equal(termsDocFor('2026-09-25-v3').endParagraph, '§ 9')
assert.equal(termsDocFor(null).endParagraph, '§ 9')
assert.equal(termsDocFor('2026-10-05-v4').endParagraph, '§ 11')
for (const id of ['K02', 'K03', 'T05', 'T07']) {
const tpl = loadTemplate(id)
assert.ok(tpl.placeholders.includes('vertragsende_paragraph'), id)
const v4 = renderMail(id, { ...sampleValues(tpl), vertragsende_paragraph: '§ 11' }, allBlocks(tpl, true))
assert.deepEqual(v4.problems, [], id)
assert.doesNotMatch(v4.text, /§ 9 der Vertragsbedingungen/, id)
assert.match(v4.text, /§ 11 der Vertragsbedingungen/, id)
assert.match(renderMail(id, { ...sampleValues(tpl), vertragsende_paragraph: '§ 9' }, allBlocks(tpl, true)).text, /§ 9 der Vertragsbedingungen/, id)
}
})
test('K02–K06, T02, T05–T09: Produkt aus dem Katalog statt fest Wartungs-Abo', () => {
for (const id of ['K02', 'K03', 'K04', 'K05', 'K06', 'T02', 'T05', 'T06', 'T07', 'T08', 'T09']) {
assert.ok(loadTemplate(id).placeholders.includes('produkt'), id)
}
const values = { name: 'K', produkt: 'Hosting-Abo', eingang_zeitpunkt: '01.10.2026, 10:00', vertragsende: '01.10.2027', vertragsende_paragraph: '§ 11' }
const k02 = renderMail('K02', values, {})
assert.deepEqual(k02.problems, [])
assert.equal(k02.subject, 'Bestätigung Ihrer Kündigung – WEBklar Hosting-Abo')
assert.match(k02.text, /Kündigung des WEBklar Hosting-Abos vom 01\.10\.2026, 10:00 Uhr/)
assert.doesNotMatch(k02.text, /Stundenkontingent|Abo verwalten|Wartungs-Abo/)
const k02w = renderMail('K02', { ...values, produkt: 'Wartungs-Abo' }, { wartung: true })
assert.match(k02w.text, /Nicht genutzte Zeit aus dem Stundenkontingent entfällt mit Vertragsende\./)
assert.equal(renderMail('K03', { ...sampleValues(loadTemplate('K03')), produkt: 'Wartungs-Abo' }, {}).subject, 'Eingangsbestätigung Ihrer Kündigung – WEBklar Wartungs-Abo')
assert.equal(renderMail('K04', { ...sampleValues(loadTemplate('K04')), produkt: 'Hosting-Abo' }, {}).subject, 'Eingangsbestätigung Ihres Widerrufs – WEBklar Hosting-Abo')
for (const id of ['T05', 'T06', 'T07', 'T08', 'T09']) {
const tpl = loadTemplate(id)
assert.match(renderMail(id, { ...sampleValues(tpl), produkt: 'Hosting-Abo' }, allBlocks(tpl, true)).text, /^Abo: Hosting-Abo$/m, id)
}
})
// F36: die Folgen des Widerrufs je Bestellung wie in den Hinweisen zum Widerruf der Fassung 4
test('K04: Folgen des Widerrufs je Bestellung (Abschluss, Wechsel, Zusatzspeicher, wir prüfen)', () => {
const values = { name: 'K', produkt: 'Hosting-Abo', bestellung: 'Abschluss Ihres Abos', eingang_zeitpunkt: '01.10.2026, 10:00', email: 'k@example.invalid', kundennummer: '' }
const abschluss = renderMail('K04', values, { abschluss: true })
assert.deepEqual(abschluss.problems, [])
assert.match(abschluss.text, /keine weiteren Abbuchungen/)
assert.match(abschluss.text, /Wertersatz/)
assert.match(abschluss.text, /Zusatzspeicher[^.]*erstatten wir vollständig; Wertersatz verlangen wir dafür nicht/)
const wechsel = renderMail('K04', { ...values, bestellung: 'Wechsel ins Wartungs-Abo' }, { wechsel: true }).text
assert.match(wechsel, /Hosting-Abo läuft zu den bisherigen Bedingungen weiter, als hätte es den Wechsel nicht gegeben, einschließlich seiner ursprünglichen Mindestlaufzeit/)
assert.match(wechsel, /Mehrbetrag/)
assert.match(wechsel, /abzüglich des Wertersatzes/)
assert.match(wechsel, /§ 357a Abs\. 2 BGB/)
const speicher = renderMail('K04', { ...values, bestellung: 'E-Mail-Zusatzspeicher' }, { zusatzspeicher: true }).text
assert.match(speicher, /Menge vor der Bestellung/)
assert.match(speicher, /erstatten wir vollständig/)
assert.match(speicher, /Wertersatz verlangen wir nicht/)
assert.doesNotMatch(speicher, /abzüglich/)
assert.match(renderMail('K04', values, { pruefen: true }).text, /prüfen, ob für diese Bestellung eine Widerrufsfrist läuft/)
})
test('K12: Monatspreis mit gebuchtem Zusatzspeicher (Block), Widerruf des Wechsels wie in Fassung 4', () => {
const values = sampleValues(loadTemplate('K12'))
const mit = renderMail('K12', values, { zusatzspeicher: true })
assert.deepEqual(mit.problems, [])
assert.match(mit.text, /einschließlich Ihres gebuchten E-Mail-Zusatzspeichers/)
const ohne = renderMail('K12', values, {})
assert.doesNotMatch(ohne.text, /einschließlich Ihres gebuchten/)
assert.match(ohne.text, /Widerrufen Sie den Wechsel, läuft Ihr Hosting-Abo zu den bisherigen Bedingungen weiter/)
assert.match(ohne.text, /Mehrbetrag/)
// K13: Widerruf des Zusatzspeichers ohne Wertersatz
const k13 = renderMail('K13', sampleValues(loadTemplate('K13')), { plus: true, bestellung: true })
assert.match(k13.text, /erstatten alles, was Sie dafür gezahlt haben\. Wertersatz verlangen wir nicht\./)
})
// T09: die Handgriffe je Bestellung passen zu den Hinweisen zum Widerruf (Zusatzspeicher ohne Wertersatz)
test('T09: Handgriffe je Bestellung ohne Wertersatz beim Zusatzspeicher', () => {
const t09 = { ...sampleValues(loadTemplate('T09')), produkt: 'Hosting-Abo' }
const speicher = renderMail('T09', t09, { abo_gefunden: true, zusatzspeicher: true }).text
assert.match(speicher, /vollständig erstatten/)
assert.doesNotMatch(speicher, /Wertersatz für die Tage abziehen/)
const wechsel = renderMail('T09', t09, { abo_gefunden: true, wechsel: true }).text
assert.match(wechsel, /Mehrbetrag/)
assert.match(wechsel, /Wertersatz/)
const abschluss = renderMail('T09', t09, { abo_gefunden: true, abschluss: true }).text
assert.match(abschluss, /Zusatzspeicher[^.]*vollständig/)
})
+26
View File
@@ -301,6 +301,32 @@ test('Wechsel: Stripe-Aufruf mit Anker jetzt, Metadaten, Ereignis', async () =>
assert.deepEqual(logs.warn.filter((w) => /Vorlage (K12|T12)/.test(w)), [])
})
// Task 12 (Spec 3.5): {{monatspreis}} in K12 und T12 ist die Monatsgebühr des Wartungs-Abos mit gebuchtem
// Zusatzspeicher; K12 sagt es im Block zusatzspeicher. Auch nach einem gescheiterten Wechsel (T12 aus dem Ereignis)
test('K12/T12: Monatspreis mit gebuchtem Zusatzspeicher, auch im Hinweis zum gescheiterten Wechsel', async () => {
await resetHosting({ stripe: { storageUnits: 2 } })
st.nextSubscriptionUpdate = { pending_update: pendingUpdate(), latest_invoice: switchInvoice({ id: 'in_speicher', status: 'open', amount_due: 4300, hosted_invoice_url: 'https://pay.example/inv2' }) }
await switchToWartung('c1', consent, nowUnix())
const [k12] = mailsWithSubject(K12_SUBJECT)
assert.ok(k12.text.includes('52,00 € / Monat'), k12.text)
assert.ok(k12.text.includes('einschließlich Ihres gebuchten E-Mail-Zusatzspeichers'), k12.text)
assert.ok(k12.text.includes('43,00 €'))
assert.deepEqual(mailsWithSubject(t12Subject('k@example.invalid')), [])
const [ev] = await listContractEvents('c1')
assert.equal(ev.details.monthlyCents, 5200)
await markSwitchFailed(ev.$id, SWITCH_FAILED_REASONS.abgelaufen)
const [t12] = mailsWithSubject(t12Subject('k@example.invalid'))
assert.ok(t12.text.includes('52,00 €'), t12.text)
assert.ok(t12.text.includes('43,00 €'))
// Ohne Zusatzspeicher: 50,00 € und kein Block
await resetHosting()
st.nextSubscriptionUpdate = { latest_invoice: switchInvoice() }
await switchToWartung('c1', consent, nowUnix())
const [plain] = mailsWithSubject(K12_SUBJECT)
assert.ok(plain.text.includes('50,00 € / Monat'), plain.text)
assert.ok(!plain.text.includes('einschließlich Ihres gebuchten'))
})
test('Offene Rechnung liefert paymentUrl', async () => {
await resetHosting({ id: 'sub_h2', customerId: 'c2', mirror: hosting2Doc })
// Karte, Zahlung nicht sofort erfolgreich (3-D Secure): Stripe haelt die Aenderung in pending_update zurueck
+4 -1
View File
@@ -115,14 +115,17 @@ test('priceIdsOfInvoice: eindeutige Preis-IDs, ohne Lücken', () => {
// Fix round 1 / B9: feste Form statt Einzelfelder
test('Vertragsunterlagen je Version', () => {
assert.deepEqual(termsDocFor('2026-10-05-v4'), { url: '/abo-bedingungen.html', pdfFile: 'WEBklar-Abo-Vertragsunterlagen.pdf' })
// 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' })
assert.deepEqual(termsDocFor('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',
})
})
+27 -1
View File
@@ -118,6 +118,32 @@ test('Kündigung nur Zusatzspeicher: Menge 0 ohne Erstattung, Ereignis', async (
assert.equal(aw.docs('subscriptions').get('sub_h').cancelRequestedAt, undefined)
})
// 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
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(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
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)
// 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)
const k03u = mails.sent.find((m) => m.subject.startsWith('Eingangsbestätigung Ihrer Kündigung'))
assert.equal(k03u.subject, 'Eingangsbestätigung Ihrer Kündigung – WEBklar Abo')
assert.ok(k03u.text.includes('§ 11 der Vertragsbedingungen'), k03u.text)
})
test('2 von 3 Paketen ergibt Menge 1', async () => {
await resetSubH({ units: 3 })
const res = await cancel({ contract: 'zusatzspeicher', packages: '2' })
@@ -362,7 +388,7 @@ test('Widerruf Wechsel in der Frist: Ereignis widerrufen, kein automatischer Pre
assert.equal(aw.docs('subscriptions').get('sub_h').withdrawalRequestedAt, undefined)
// K04 nennt die Bestellung, T09 die Handgriffe und die Frist
assert.match(k04().text, /Wechsel ins Wartungs-Abo/)
assert.match(k04().text, /bisherigen Preis/)
assert.match(k04().text, /zu den bisherigen Bedingungen weiter, als hätte es den Wechsel nicht gegeben/)
assert.doesNotMatch(k04().text, /keine weiteren Abbuchungen/)
assert.match(t09().text, /RECHTZEITIG/)
assert.match(t09().text, /Preis .*zurück/)
+1 -1
View File
@@ -218,7 +218,7 @@ test('Mail-Senke: Kunden- und Team-Mails landen in der Senke, null schaltet zur
values: { name: 'K', antrag_datum: '01.10.2026', antrag_nr: 'A1', begruendung: 'Nachweis fehlt' },
attachments: [{ filename: 'a.pdf', content: Buffer.from('PDF'), contentType: 'application/pdf' }],
})
assert.deepEqual(await sendAdminNotice({ template: 'T06', values: { email: 'k@example.invalid', stripe_abo_id: 'sub_1' } }), { sent: true })
assert.deepEqual(await sendAdminNotice({ template: 'T06', values: { email: 'k@example.invalid', produkt: 'Hosting-Abo', stripe_abo_id: 'sub_1' } }), { sent: true })
assert.equal(sink.sent.length, 2)
const [customerMail, notice] = sink.sent
assert.deepEqual(customerMail.to.map((t) => t.address), ['k@example.invalid'])