diff --git a/docs/rechtliches/widerrufsbelehrung-funktionen.md b/docs/rechtliches/widerrufsbelehrung-funktionen.md new file mode 100644 index 0000000..a635114 --- /dev/null +++ b/docs/rechtliches/widerrufsbelehrung-funktionen.md @@ -0,0 +1,54 @@ +# Widerrufsbelehrung und Muster-Widerrufsformular (Einrichtung von Funktionen) + +**Fassung 2026-10-05** + +Das Widerrufsrecht gilt nur für Verbraucher. Wir zeigen die Belehrung und das Muster-Widerrufsformular trotzdem allen, die eine Funktion bestellen. Die Belehrung gilt für die Bestellung der Einrichtung einer Funktion im Kundenportal, zum Beispiel des Terminplaners. Hinweise dazu stehen nach dem Muster-Widerrufsformular. + +## Widerrufsbelehrung + +### Widerrufsrecht + +Sie haben das Recht, binnen vierzehn Tagen ohne Angabe von Gründen diesen Vertrag zu widerrufen. + +Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag des Vertragsabschlusses. + +Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, Telefon: 0176 23726355, E-Mail: support@webklar.com) mittels einer eindeutigen Erklärung (z. B. ein mit der Post versandter Brief oder eine E-Mail) über Ihren Entschluss, diesen Vertrag zu widerrufen, informieren. Sie können dafür das beigefügte Muster-Widerrufsformular verwenden, das jedoch nicht vorgeschrieben ist. + +Sie können Ihr Widerrufsrecht auch online unter https://project.webklar.com/widerrufen.html ausüben. Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs. + +Zur Wahrung der Widerrufsfrist reicht es aus, dass Sie die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absenden. + +### Folgen des Widerrufs + +Wenn Sie diesen Vertrag widerrufen, haben wir Ihnen alle Zahlungen, die wir von Ihnen erhalten haben, einschließlich der Lieferkosten (mit Ausnahme der zusätzlichen Kosten, die sich daraus ergeben, dass Sie eine andere Art der Lieferung als die von uns angebotene, günstigste Standardlieferung gewählt haben), unverzüglich und spätestens binnen vierzehn Tagen ab dem Tag zurückzuzahlen, an dem die Mitteilung über Ihren Widerruf dieses Vertrags bei uns eingegangen ist. Für diese Rückzahlung verwenden wir dasselbe Zahlungsmittel, das Sie bei der ursprünglichen Transaktion eingesetzt haben, es sei denn, mit Ihnen wurde ausdrücklich etwas anderes vereinbart; in keinem Fall werden Ihnen wegen dieser Rückzahlung Entgelte berechnet. + +Haben Sie verlangt, dass die Dienstleistungen während der Widerrufsfrist beginnen soll, so haben Sie uns einen angemessenen Betrag zu zahlen, der dem Anteil der bis zu dem Zeitpunkt, zu dem Sie uns von der Ausübung des Widerrufsrechts hinsichtlich dieses Vertrags unterrichten, bereits erbrachten Dienstleistungen im Vergleich zum Gesamtumfang der im Vertrag vorgesehenen Dienstleistungen entspricht. + +## Muster-Widerrufsformular + +(Wenn Sie den Vertrag widerrufen wollen, dann füllen Sie bitte dieses Formular aus und senden Sie es zurück.) + +– An WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, E-Mail: support@webklar.com: + +– Hiermit widerrufe(n) ich/wir (*) den von mir/uns (*) abgeschlossenen Vertrag über den Kauf der folgenden Waren (*)/die Erbringung der folgenden Dienstleistung (*) + +– Bestellt am (*)/erhalten am (*) + +– Name des/der Verbraucher(s) + +– Anschrift des/der Verbraucher(s) + +– Unterschrift des/der Verbraucher(s) (nur bei Mitteilung auf Papier) + +– Datum + +(*) Unzutreffendes streichen. + +## Hinweise zum Widerruf + +Diese Hinweise gehören nicht zur gesetzlichen Widerrufsbelehrung. Sie erklären, wie sich das Widerrufsrecht auf die Bestellung einer Funktion auswirkt. + +- Das Widerrufsrecht gilt für jede Bestellung einer Funktion gesondert, also für jedes Website-Projekt, für das der Kunde eine Funktion bestellt. Die Frist von vierzehn Tagen beginnt jeweils mit dem Vertragsschluss. +- Widerruft der Kunde die Einrichtung einer Funktion, endet diese Funktion. Das Wartungs-Abo bleibt davon unberührt. +- Hat WEBklar auf ausdrücklichen Wunsch des Kunden vor Ablauf der Widerrufsfrist mit der Einrichtung begonnen, schuldet der Kunde bei einem Widerruf einen angemessenen Betrag für die bis dahin erbrachte Leistung. Der Betrag bemisst sich als Anteil am Gesamtpreis; Maß ist der im Arbeitsblatt erfasste Aufwand für die Einrichtung. +- Die Rückzahlung erfolgt über das ursprüngliche Zahlungsmittel; die Zahlung lief über Stripe. diff --git a/server/services/termine/rechtstexte.js b/server/services/termine/rechtstexte.js index 378d8b5..3e04c1a 100644 --- a/server/services/termine/rechtstexte.js +++ b/server/services/termine/rechtstexte.js @@ -58,12 +58,18 @@ const verbinde = (teile) => teile.filter(Boolean).join(', ') const FRIST = 'des Vertragsabschlusses.' // Gestaltungshinweis a): Dienstleistungsvertrag // Widerrufsfunktion: Satzrahmen des Gesetzes wörtlich („Sie können Ihr Widerrufsrecht auch online unter … ausüben.“); in die -// Lücke „… [Internetadresse oder anderen geeigneten Hinweis …]“ kommt ein geeigneter Hinweis statt einer Internetadresse. +// Lücke „… [Internetadresse oder anderen geeigneten Hinweis …]“ kommt bei den Gästen ein geeigneter Hinweis statt einer +// Internetadresse, bei der Bestellung einer Funktion (WEBklar als Unternehmer) die Adresse der Widerrufsseite. +const EINGANGSBESTAETIGUNG = + 'Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) ' + + 'unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs.' + const WIDERRUFSFUNKTION = 'Sie können Ihr Widerrufsrecht auch online unter „Vertrag widerrufen“ (Schaltfläche im Terminbereich auf unserer Website ' + 'sowie Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail) ausüben. ' - + 'Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) ' - + 'unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs.' + + EINGANGSBESTAETIGUNG + +const WIDERRUFSFUNKTION_WEBKLAR = `Sie können Ihr Widerrufsrecht auch online unter https://project.webklar.com/widerrufen.html ausüben. ${EINGANGSBESTAETIGUNG}` // Wertersatz bei Dienstleistungen; „oder Lieferung von Wasser/Gas/Strom/Fernwärme [Unzutreffendes streichen]“ ist gestrichen. const WERTERSATZ = @@ -76,7 +82,7 @@ function kontakt({ name, adresse, telefon, email }) { return verbinde([name, adresse, `Telefon: ${telefon}`, `E-Mail: ${email}`]) } -function belehrungsAbsaetze(daten) { +function belehrungsAbsaetze(daten, widerrufsfunktion = WIDERRUFSFUNKTION) { return [ 'Widerrufsbelehrung', 'Widerrufsrecht', @@ -85,7 +91,7 @@ function belehrungsAbsaetze(daten) { `Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (${kontakt(daten)}) mittels einer eindeutigen Erklärung ` + '(z. B. ein mit der Post versandter Brief oder eine E-Mail) über Ihren Entschluss, diesen Vertrag zu widerrufen, informieren. ' + 'Sie können dafür das beigefügte Muster-Widerrufsformular verwenden, das jedoch nicht vorgeschrieben ist.', - WIDERRUFSFUNKTION, + widerrufsfunktion, 'Zur Wahrung der Widerrufsfrist reicht es aus, dass Sie die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absenden.', 'Folgen des Widerrufs', 'Wenn Sie diesen Vertrag widerrufen, haben wir Ihnen alle Zahlungen, die wir von Ihnen erhalten haben, einschließlich der Lieferkosten ' @@ -98,9 +104,9 @@ function belehrungsAbsaetze(daten) { ] } -function formularAbsaetze({ name, adresse, email }) { +function formularAbsaetze({ name, adresse, email }, anZeile = `An ${verbinde([name, adresse, email])}:`) { const zeilen = [ - `An ${verbinde([name, adresse, email])}:`, + anZeile, 'Hiermit widerrufe(n) ich/wir (*) den von mir/uns (*) abgeschlossenen Vertrag über den Kauf der folgenden Waren (*)/die Erbringung der folgenden Dienstleistung (*)', 'Bestellt am (*)/erhalten am (*)', 'Name des/der Verbraucher(s)', @@ -127,6 +133,28 @@ export function widerrufsbelehrung(betrieb) { return [...belehrungsAbsaetze(daten), ...formularAbsaetze(daten)].join(ABSATZ) } +/** WEBklar als Unternehmer (Bestellung einer Funktion im Kundenportal): Kontaktangaben der Widerrufsbelehrung. */ +export const WEBKLAR_KONTAKT = Object.freeze({ + name: 'WEBklar GbR', + adresse: 'Am Schwimmbad 10, 67722 Winnweiler', + telefon: '0176 23726355', + email: 'support@webklar.com', +}) + +/** + * Widerrufsbelehrung und Muster-Widerrufsformular fuer die Bestellung der Einrichtung einer Funktion (Terminplaner, News): + * WEBklar ist der Unternehmer, Sie-Form, Text mit Zeilenumbruechen wie widerrufsbelehrung. Gleiche Bausteine wie bei den + * Gaesten (Frist „des Vertragsabschlusses.“, Wertersatz fuer Dienstleistungen), aber die Widerrufsfunktion nennt die Adresse + * der Widerrufsseite (Wortlaut wie in den Abo-Bedingungen, Abschnitt Widerrufsbelehrung) und die „An“-Zeile des Formulars nennt + * die E-Mail-Adresse mit „E-Mail:“. Quelle des Dokuments docs/rechtliches/widerrufsbelehrung-funktionen.md; test/termineRechtstexte.test.js + * haelt beide gleich und gleicht die Belehrung mit public/abo-bedingungen.html ab. + */ +export function widerrufsbelehrungFunktion() { + const daten = betriebsdaten(WEBKLAR_KONTAKT, { vollstaendig: true }) + const an = `An ${verbinde([daten.name, daten.adresse, `E-Mail: ${daten.email}`])}:` + return [...belehrungsAbsaetze(daten, WIDERRUFSFUNKTION_WEBKLAR), ...formularAbsaetze(daten, an)].join(ABSATZ) +} + // ---------------------------------------------------------------- Hinweis unter dem Buchungsformular (recherche-recht.md 11.2) const LOESCHFRIST_ABGESAGT_TAGE = 30 diff --git a/test/termineRechtstexte.test.js b/test/termineRechtstexte.test.js index b5c3e28..cb9fe54 100644 --- a/test/termineRechtstexte.test.js +++ b/test/termineRechtstexte.test.js @@ -1,10 +1,14 @@ import { test } from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' +import { createHash } from 'node:crypto' +import { config } from '../server/config.js' import { RECHTSTEXTE_VERSION, RECHTSTEXTE_ENTWURF, + WEBKLAR_KONTAKT, widerrufsbelehrung, + widerrufsbelehrungFunktion, formularHinweis, datenschutzAbsatz, ZUSTIMMUNG_VORZEITIG, @@ -481,3 +485,178 @@ test('6c-Entwurf: nur als Entwurfsdatei, public/datenschutz.html bleibt unverän const html = lies('public/datenschutz.html') assert.ok(!/6c\./.test(html) && !html.includes('id="termine"'), '6c darf vor TP2 nicht in public/datenschutz.html stehen') }) + +// ---------------------------------------------------------------- Widerrufsbelehrung der Funktionen (WEBklar als Unternehmer, Task F3) + +const KONTAKT_WEBKLAR = 'WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, Telefon: 0176 23726355, E-Mail: support@webklar.com' +const AN_ZEILE_WEBKLAR = 'An WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, E-Mail: support@webklar.com:' +const WIDERRUFEN_URL = 'https://project.webklar.com/widerrufen.html' +const funktion = () => widerrufsbelehrungFunktion() + +test('Gäste-Belehrung bleibt byte-gleich (Hash der Ausgabe vor Task F3)', () => { + const text = widerrufsbelehrung(BETRIEB) + assert.equal(createHash('sha256').update(text).digest('hex'), 'a4b3549b7b4ba0bdeeca438bc823d07eede5e4aa35e21e335fb5304ff36a1ff6') + assert.equal(text.length, 2983) +}) + +test('WEBKLAR_KONTAKT: WEBklar GbR als Unternehmer', () => { + assert.deepEqual(WEBKLAR_KONTAKT, { + name: 'WEBklar GbR', + adresse: 'Am Schwimmbad 10, 67722 Winnweiler', + telefon: '0176 23726355', + email: 'support@webklar.com', + }) +}) + +test('belehrung_funktion_muster_woertlich: jeder Absatz der Anlage 1 für Dienstleistungen steht wörtlich darin, nur die Lücken sind gefüllt', () => { + const text = funktion() + const muster = musterZeilen() + assert.equal(muster.length, 8) + assert.equal(fristBaustein(), 'des Vertragsabschlusses.') + for (const zeile of muster) { + const erwartet = zeile + .replace('ab dem Tag .', `ab dem Tag ${fristBaustein()}`) + .replace('uns ( )', `uns (${KONTAKT_WEBKLAR})`) + assert.ok(text.includes(erwartet), `Absatz fehlt oder ist verändert: ${erwartet}`) + } + // Fristbeginn und Kontakt stehen genau einmal + assert.equal(text.split('ab dem Tag des Vertragsabschlusses.').length - 1, 1) + assert.equal(text.split(KONTAKT_WEBKLAR).length - 1, 1) +}) + +test('belehrung_funktion_widerrufsfunktion: Satzrahmen der Anlage 1 wörtlich, die Lücke ist die Adresse der Widerrufsfunktion', () => { + const text = funktion() + const m = /„(Sie können Ihr Widerrufsrecht auch online unter) … \[[^\]]+\] (ausüben\.) (Wenn Sie diese Online-Funktion nutzen, [^“]+?)\s*“/.exec(ANLAGE_1) + assert.ok(m, 'Gestaltungshinweis zur Widerrufsfunktion fehlt oder hat sich geändert in anlage-1.txt') + const absatz = `${m[1]} ${WIDERRUFEN_URL} ${m[2]} ${m[3]}` + assert.ok(text.includes(absatz), 'Absatz zur Widerrufsfunktion fehlt oder weicht vom Satzrahmen der Anlage ab') + assert.equal(text.split(WIDERRUFEN_URL).length - 1, 1) + // kein Hinweis auf eine Schaltfläche der Gäste-Fassung + assert.ok(!text.includes('Termin verwalten') && !text.includes('Schaltfläche')) + // der Wertersatz für Dienstleistungen steht wie in der Gäste-Fassung + const w = /Haben Sie verlangt, dass die Dienstleistungen oder Lieferung von Wasser\/Gas\/Strom\/Fernwärme \[Unzutreffendes streichen\] (während[^“]+?)\s*“/.exec(ANLAGE_1) + assert.ok(w) + assert.ok(text.includes(`Haben Sie verlangt, dass die Dienstleistungen ${w[1]}`)) +}) + +test('belehrung_funktion_formular: Muster-Widerrufsformular der Anlage 2 wörtlich, „An“-Zeile mit WEBklar', () => { + const text = funktion() + const z = zeilen(ANLAGE_2) + const start = z.indexOf('Muster-Widerrufsformular') + assert.ok(start >= 0) + const inhalt = z.slice(start).filter((s) => s && s !== '–') + let anZeile = 0 + for (const zeile of inhalt) { + if (zeile.startsWith('An [hier ist')) { + anZeile += 1 + assert.ok(text.includes(`– ${AN_ZEILE_WEBKLAR}`), '„An“-Zeile mit WEBklar fehlt') + } else { + assert.ok(text.includes(zeile), `Formularzeile fehlt oder ist verändert: ${zeile}`) + } + } + assert.equal(anZeile, 1) + assert.equal(text.split(AN_ZEILE_WEBKLAR).length - 1, 1) +}) + +test('Belehrung der Funktionen: reiner Text ohne Platzhalter, deterministisch, Sie-Form, nichts zu Waren', () => { + const text = funktion() + assert.equal(typeof text, 'string') + assert.ok(text.startsWith('Widerrufsbelehrung\n\nWiderrufsrecht\n')) + assert.ok(text.includes('\n') && !text.includes('\r')) + assert.ok(!/\{\{|\}\}|\[|\]|…|\.\.\.|\(\s*\)/.test(text), 'offener Platzhalter') + assert.ok(!/undefined|null|NaN/.test(text)) + assert.ok(!/Rücksendung|zurückzusenden|Besitz genommen|Wir holen die Waren|Sie haben die Waren|elektronisch ausfüllen|Wir können die Rückzahlung verweigern/.test(text)) + assert.ok(!DU_WOERTER.test(text)) + assert.ok(!/&|<[a-z/]/i.test(text)) + assert.equal(funktion(), funktion()) + // Gäste-Fassung und Funktionen-Fassung unterscheiden sich nur in Kontakt, Widerrufsfunktion und „An“-Zeile + const gast = widerrufsbelehrung(WEBKLAR_KONTAKT) + assert.notEqual(gast, text) + const ohneUnterschied = (t) => t.replace(/uns \([^)]*\)/, 'uns (K)').replace(/Sie können Ihr Widerrufsrecht auch online unter .*? ausüben\./, 'WF').replace(/– An [^\n]*/, '– An A') + assert.equal(ohneUnterschied(gast), ohneUnterschied(text)) +}) + +/** Text eines HTML-Ausschnitts: Tags weg, Entitäten der Seite aufgelöst, Leerraum zu einem Leerzeichen. */ +const klartext = (html) => + html + .replace(/<[^>]+>/g, ' ') + .replace(/&/g, '&') + .replace(/ /g, ' ') + .replace(/\s+/g, ' ') + .trim() + +test('belehrung_gleich_abo: Belehrung der Funktionen = Belehrung in den Abo-Bedingungen (Drift-Wächter), Muster-Formular ebenso', () => { + const html = lies('public/abo-bedingungen.html') + const von = html.indexOf('