Shop C5: Muster-Rechtstexte (Widerrufsbelehrung, Formular, AGB), Versand/Zahlung, Gewaehrleistungshinweis, Normalisierung und Hash

Widerrufsbelehrung nach Anlage 1 (Stand 2026, mit Hinweis auf die Widerrufsfunktion), Formular nach Anlage 2,
Muster-AGB ohne OS-Plattform, erzeugter Versand-/Zahlungstext, Gewaehrleistungshinweis (Wortlaut zu pruefen),
Muster-Markdown fuer die Kanzlei und Tests inkl. Abgleich Markdown/Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 08:40:23 +00:00
1 parent bea9f3cfba
commit 0063c04d8a
3 files changed
+773

No files matched your search

+312
View File
@@ -0,0 +1,312 @@
import { createHash } from 'node:crypto'
import { euro } from './geld.js'
import { STANDARD_EINSTELLUNGEN } from './einstellungen.js'
/**
* Muster-Rechtstexte des Haendlers (Plan 2026-10-05-webshop.md, Task C5; Spec 4.6).
* Alles rein: Einstellungen rein, Klartext raus (Absaetze mit '\n\n' getrennt), Sie-Form fuer Verbraucher.
* Das sind MUSTER, keine Rechtsberatung. Der Haendler prueft, passt an und bestaetigt selbst (BESTAETIGUNG_TEXT).
* Quellen: Anlage 1 und 2 zu Art. 246a § 1 Abs. 2 S. 2 EGBGB (Stand 2026, mit Baustein zur Widerrufsfunktion, § 356a BGB),
* § 312g Abs. 2 BGB, § 36 VSBG, VO (EU) 2025/1960 Anhang I; siehe /root/reports/webshop-2026-10-05/recherche-recht-stripe.md
* (B.2, B.3, B.3a, B.8). Dieselben Texte mit Platzhaltern: docs/funktionen/shop-muster-rechtstexte.md.
*/
export const BESTAETIGUNG_TEXT =
'Ich habe die Texte geprüft. Ich bin als Verkäufer für Rechtstexte, Produktangaben und Preise verantwortlich. WEBklar stellt Muster bereit und berät nicht rechtlich.'
export const MAX_AGB = 30000
export const MAX_WIDERRUFSBELEHRUNG = 20000
export const MAX_WIDERRUFSFORMULAR = 5000
const MAX_URL = 500
const LANDNAMEN = { DE: 'Deutschland', AT: 'Österreich' }
const ZAHLUNGSARTEN_REIHENFOLGE = ['card', 'klarna', 'sepa_debit']
const ZAHLUNGSARTEN_TEXT = { card: 'Kreditkarte, Apple Pay, Google Pay', klarna: 'Klarna', sepa_debit: 'SEPA-Lastschrift' }
/** Einstellungen mit Standardwerten auffuellen (Unterobjekte eine Ebene tief), damit Teilobjekte nicht werfen. */
function mitStandard(e) {
const std = STANDARD_EINSTELLUNGEN
const roh = e && typeof e === 'object' ? e : {}
const sub = (k) => ({ ...std[k], ...(roh[k] && typeof roh[k] === 'object' && !Array.isArray(roh[k]) ? roh[k] : {}) })
return { ...std, ...roh, anbieter: sub('anbieter'), versand: sub('versand'), abholung: sub('abholung') }
}
const text = (s) => (typeof s === 'string' ? s.trim() : '')
function anschrift(a) {
const ort = [text(a.plz), text(a.ort)].filter(Boolean).join(' ')
const land = text(a.land) && a.land !== 'DE' ? (LANDNAMEN[a.land] ?? a.land) : ''
return [text(a.strasse), ort, land].filter(Boolean).join(', ') || '[Anschrift]'
}
function anbieterZeile(a) {
return [text(a.name) || '[Name]', anschrift(a), text(a.telefon) ? `Telefon: ${text(a.telefon)}` : '', `E-Mail: ${text(a.email) || '[E-Mail-Adresse]'}`]
.filter(Boolean)
.join(', ')
}
function laenderListe(e) {
const laender = Array.isArray(e.versand.laender) ? e.versand.laender : []
return laender.map((l) => LANDNAMEN[l] ?? l).join(', ')
}
function steuerSatz(e) {
return e.kleinunternehmer
? 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet und ausgewiesen.'
: 'Alle Preise enthalten die gesetzliche Umsatzsteuer.'
}
/** 'Kreditkarte, Apple Pay, Google Pay, Klarna': aktive Zahlungsarten in fester Reihenfolge. Auch fuer die Widget-Konfiguration. */
export function zahlungsartenText(e) {
const arten = Array.isArray(e?.zahlungsarten) ? e.zahlungsarten : []
return ZAHLUNGSARTEN_REIHENFOLGE.filter((a) => arten.includes(a))
.map((a) => ZAHLUNGSARTEN_TEXT[a])
.join(', ')
}
/**
* Widerrufsbelehrung fuer Waren: gesetzliches Muster (Anlage 1 zu Art. 246a § 1 Abs. 2 S. 2 EGBGB, Stand 2026) mit Baustein
* zur Widerrufsfunktion (§ 356a BGB).
* Gestaltungshinweise, so gewaehlt: Fristbeginn = Alternative "Kaufvertrag" (Waren in Besitz genommen) UND Alternative "mehrere
* Waren einer einheitlichen Bestellung, getrennt geliefert" (letzte Ware), beide als eigene Saetze, weil der Warenkorb beides
* erzeugen kann. Teilsendungen/regelmaessige Lieferung entfallen (kommt im Shop nicht vor). Nicht abgeholt: "Wir holen die Waren ab"
* entfaellt, dafuer die Rueckgabe-Alternative (zurueckzusenden oder zu uebergeben) samt Rueckzahlungsverweigerungs-Satz.
* Rueckkosten nach e.ruecksendekosten ('kaeufer' = Standard, 'haendler').
* @param {object} e Einstellungen
* @param {{ widerrufUrl?: string }} [opts] Adresse der Widerrufsfunktion im Shop
*/
export function musterWiderrufsbelehrung(e, { widerrufUrl = '' } = {}) {
e = mitStandard(e)
const a = e.anbieter
const kontakt = anbieterZeile(a)
const url = text(widerrufUrl) || '[Internetadresse der Widerrufsfunktion]'
const rueckadresse = `${text(a.name) || '[Name]'}, ${anschrift(a)}`
const kosten = e.ruecksendekosten === 'haendler' ? 'Wir tragen die Kosten der Rücksendung der Waren.' : 'Sie tragen die unmittelbaren Kosten der Rücksendung der Waren.'
const abholung = e.abholung.aktiv
? `Haben Sie die Waren bei uns abgeholt, übergeben Sie diese zur Rückgabe an unserer Anschrift: ${text(e.abholung.adresse) || rueckadresse}.`
: ''
return [
'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, an dem Sie oder ein von Ihnen benannter Dritter, der nicht der Beförderer ist, die Waren in Besitz genommen haben bzw. hat.',
'Haben Sie mehrere Waren im Rahmen einer einheitlichen Bestellung bestellt und werden diese getrennt geliefert, beträgt die Widerrufsfrist vierzehn Tage ab dem Tag, an dem Sie oder ein von Ihnen benannter Dritter, der nicht der Beförderer ist, die letzte Ware in Besitz genommen haben bzw. hat.',
`Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (${kontakt}) 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 ${url} 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.',
'Wir können die Rückzahlung verweigern, bis wir die Waren wieder zurückerhalten haben oder bis Sie den Nachweis erbracht haben, dass Sie die Waren zurückgesandt haben, je nachdem, welches der frühere Zeitpunkt ist.',
`Sie haben die Waren unverzüglich und in jedem Fall spätestens binnen vierzehn Tagen ab dem Tag, an dem Sie uns über den Widerruf dieses Vertrags unterrichten, an uns (${rueckadresse}) zurückzusenden oder zu übergeben. Die Frist ist gewahrt, wenn Sie die Waren vor Ablauf der Frist von vierzehn Tagen absenden.`,
...(abholung ? [abholung] : []),
kosten,
'Sie müssen für einen etwaigen Wertverlust der Waren nur aufkommen, wenn dieser Wertverlust auf einen zur Prüfung der Beschaffenheit, Eigenschaften und Funktionsweise der Waren nicht notwendigen Umgang mit ihnen zurückzuführen ist.',
'Ausschluss bzw. vorzeitiges Erlöschen des Widerrufsrechts',
[
'Das Widerrufsrecht besteht nicht bei Verträgen zur Lieferung von Waren, die nicht vorgefertigt sind und für deren Herstellung eine individuelle Auswahl oder Bestimmung durch den Verbraucher maßgeblich ist oder die eindeutig auf die persönlichen Bedürfnisse des Verbrauchers zugeschnitten sind (§ 312g Abs. 2 Nr. 1 BGB), z. B. Waren mit Wunschaufdruck oder Maßanfertigungen.',
'Das Widerrufsrecht besteht nicht bei Verträgen zur Lieferung von Waren, die schnell verderben können oder deren Verfallsdatum schnell überschritten würde (§ 312g Abs. 2 Nr. 2 BGB), z. B. frische Lebensmittel.',
'Das Widerrufsrecht erlischt vorzeitig bei Verträgen zur Lieferung versiegelter Waren, die aus Gründen des Gesundheitsschutzes oder der Hygiene nicht zur Rückgabe geeignet sind, wenn ihre Versiegelung nach der Lieferung entfernt wurde (§ 312g Abs. 2 Nr. 3 BGB).',
].join('\n'),
'Ende der Widerrufsbelehrung',
].join('\n\n')
}
/** Muster-Widerrufsformular (Anlage 2 zu Art. 246a § 1 Abs. 2 S. 1 Nr. 1 EGBGB), Unternehmer eingesetzt. */
export function musterWiderrufsformular(e) {
e = mitStandard(e)
const a = e.anbieter
return [
'Muster-Widerrufsformular',
'(Wenn Sie den Vertrag widerrufen wollen, dann füllen Sie bitte dieses Formular aus und senden Sie es zurück.)',
`An ${text(a.name) || '[Name]'}, ${anschrift(a)}, E-Mail: ${text(a.email) || '[E-Mail-Adresse]'}:`,
'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'].join('\n'),
'(*) Unzutreffendes streichen.',
].join('\n\n')
}
/** Kurze Muster-AGB fuer kleine Shops. Kopfzeile kennzeichnet sie als Muster; der Haendler loescht sie nach der Pruefung. */
export function musterAgb(e) {
e = mitStandard(e)
const a = e.anbieter
const name = text(a.name) || '[Name]'
const arten = Array.isArray(e.zahlungsarten) ? e.zahlungsarten : []
const zahlung = zahlungsartenText(e)
const abs = []
abs.push('Muster – bitte prüfen und anpassen')
abs.push('Allgemeine Geschäftsbedingungen')
abs.push(
`§ 1 Geltung und Anbieter\n\nDiese Allgemeinen Geschäftsbedingungen gelten für alle Bestellungen von Verbrauchern in unserem Online-Shop. Anbieter und Vertragspartner ist ${anbieterZeile(a)}${text(a.ustId) ? `, USt-IdNr.: ${text(a.ustId)}` : ''}.`
)
abs.push(
'§ 2 Vertragsschluss\n\nDie Darstellung der Waren im Shop ist kein verbindliches Angebot. Mit dem Klick auf die Schaltfläche „Zahlungspflichtig bestellen“ geben Sie eine verbindliche Bestellung ab. Wir bestätigen den Eingang der Bestellung unverzüglich per E-Mail. Der Vertrag kommt zustande, sobald wir Ihnen nach erfolgreicher Zahlung die Bestellbestätigung per E-Mail senden. Bestellungen, die nicht bezahlt werden, werden nach Ablauf der Zahlungsfrist verworfen.'
)
abs.push(
`§ 3 Preise und Versandkosten\n\nEs gelten die zum Zeitpunkt der Bestellung im Shop angegebenen Preise in Euro. ${steuerSatz(e)} Versandkosten und etwaige weitere Kosten werden vor Abgabe der Bestellung gesondert ausgewiesen und richten sich nach dem Zielland. Einzelheiten finden Sie unter „Versand & Zahlung“.`
)
let z = `§ 4 Zahlung\n\nDie Zahlung erfolgt über den Zahlungsdienstleister Stripe. Es stehen folgende Zahlungsarten zur Verfügung: ${zahlung || '[Zahlungsarten]'}.`
if (arten.includes('sepa_debit')) z += ' Bei SEPA-Lastschrift versenden wir die Ware erst nach Eingang der Zahlung.'
abs.push(z)
let l = '§ 5 Lieferung und Abholung\n\n'
const teile = []
if (e.versand.aktiv) teile.push(`Wir liefern in folgende Länder: ${laenderListe(e) || '[Länder]'}.`)
if (e.abholung.aktiv) teile.push(`Sie können die Waren auch bei uns abholen: ${text(e.abholung.adresse) || '[Abholadresse]'}.${text(e.abholung.hinweis) ? ` ${text(e.abholung.hinweis)}` : ''}`)
if (!teile.length) teile.push('Angaben zur Lieferung und Abholung: [bitte ergänzen].')
l += teile.join(' ')
l += ` Die Lieferzeit beträgt ${text(e.lieferzeit) || '[Lieferzeit]'}, gerechnet ab Zahlungseingang. Ist eine Ware nicht verfügbar, informieren wir Sie unverzüglich.`
abs.push(l)
abs.push(`§ 6 Eigentumsvorbehalt\n\nDie Waren bleiben bis zur vollständigen Bezahlung unser Eigentum.`)
abs.push('§ 7 Widerrufsrecht\n\nVerbrauchern steht ein gesetzliches Widerrufsrecht zu. Einzelheiten ergeben sich aus der Widerrufsbelehrung, die Sie unter „Widerrufsbelehrung“ im Shop finden.')
abs.push(
'§ 8 Gewährleistung\n\nEs gelten die gesetzlichen Mängelhaftungsrechte. Der Hinweis auf das gesetzliche Gewährleistungsrecht steht vor Abgabe der Bestellung im Shop.'
)
abs.push(
'§ 9 Vertragstext und Sprache\n\nWir speichern den Vertragstext nach Vertragsschluss nicht für Sie abrufbar. Ihre Bestelldaten, diese Allgemeinen Geschäftsbedingungen und die Widerrufsbelehrung erhalten Sie mit der Bestellbestätigung per E-Mail; bitte bewahren Sie diese E-Mail auf. Die Vertragssprache ist Deutsch.'
)
abs.push(
`§ 10 Streitbeilegung\n\n${name} ist nicht bereit und nicht verpflichtet, an einem Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen (§ 36 VSBG).`
)
return abs.join('\n\n')
}
/** Aus den Einstellungen erzeugter Text zu Versand und Zahlung (nicht frei editierbar, damit er stimmt). */
export function versandUndZahlung(e) {
e = mitStandard(e)
const abs = ['Versand und Zahlung']
if (e.versand.aktiv) {
abs.push(`Liefergebiet: ${laenderListe(e) || '–'}`)
const laender = Array.isArray(e.versand.laender) ? e.versand.laender : []
const zeilen = laender
.filter((l) => Number.isInteger(e.versand.kostenCent?.[l]))
.map((l) => `${LANDNAMEN[l] ?? l}: ${euro(e.versand.kostenCent[l])}`)
if (zeilen.length) abs.push(`Versandkosten:\n${zeilen.join('\n')}`)
if (Number.isInteger(e.versand.freiAbCent)) abs.push(`Versandkostenfrei ab einem Bestellwert von ${euro(e.versand.freiAbCent)}.`)
} else {
abs.push('Versand: Wir versenden derzeit nicht.')
}
if (e.abholung.aktiv) {
abs.push(`Abholung: ${text(e.abholung.adresse) || '–'}${text(e.abholung.hinweis) ? `\n${text(e.abholung.hinweis)}` : ''}`)
}
abs.push(`Lieferzeit: ${text(e.lieferzeit) || '–'}`)
abs.push(`Zahlungsarten: ${zahlungsartenText(e) || '–'}`)
if (Number.isInteger(e.mindestbestellCent)) abs.push(`Mindestbestellwert: ${euro(e.mindestbestellCent)}`)
abs.push(e.kleinunternehmer ? 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet und ausgewiesen.' : 'Alle Preise inkl. gesetzlicher USt.')
return abs.join('\n\n')
}
/**
* Harmonisierter Hinweis auf das gesetzliche Gewährleistungsrecht (Art. 246a § 1 Abs. 1 S. 1 Nr. 11 EGBGB, VO (EU) 2025/1960 Anhang I,
* seit 27.09.2026), in Kasse hervorgehoben.
* Wortlaut prüfen (Kanzlei): Der amtliche deutsche Wortlaut von Anhang I stand in der Recherche nicht vor (EUR-Lex nicht geladen);
* dies ist eine sinngemaesse Fassung (Mindestdauer zwei Jahre, wichtigste Elemente). Grafik/Symbol und Farbe kommen spaeter.
*/
export function gewaehrleistungsHinweis() {
return [
'Gesetzliches Gewährleistungsrecht',
'Für Waren besteht ein gesetzliches Gewährleistungsrecht von mindestens zwei Jahren ab Lieferung der Ware. Ist die Ware nicht vertragsgemäß, haben Sie zunächst Anspruch auf kostenlose Nachbesserung (Reparatur) oder Ersatzlieferung; unter den gesetzlichen Voraussetzungen können Sie stattdessen den Preis mindern oder vom Vertrag zurücktreten. Diese Rechte bestehen unabhängig von einer Garantie.',
].join('\n\n')
}
// --- Normalisierung --------------------------------------------------------------------------------------------------------
// Steuerzeichen (ausser Zeilenumbruch) und unsichtbare Formatzeichen raus
const STEUER = /[\p{Cc}\p{Cf}\u2028\u2029]/gu
function bereinigeText(s) {
return s
.replace(/\r\n?/g, '\n')
.replace(/\t/g, ' ')
.replace(STEUER, (z) => (z === '\n' ? z : ''))
.replace(/[ ]+\n/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.trim()
}
function pruefeUrl(wert) {
if (wert === undefined || wert === null) return { wert: '' }
if (typeof wert !== 'string') return { fehler: 'Bitte gib die Adresse als Text an.' }
const s = wert.trim()
if (s === '') return { wert: '' }
if (s.length > MAX_URL) return { fehler: `Die Adresse darf höchstens ${MAX_URL} Zeichen lang sein.` }
if (/[\s\p{Cc}\p{Cf}]/u.test(s)) return { fehler: 'Die Adresse darf keine Leerzeichen oder Sonderzeichen enthalten.' }
let u
try {
u = new URL(s)
} catch {
return { fehler: 'Bitte gib eine vollständige Adresse an, die mit https:// beginnt.' }
}
if (u.protocol !== 'https:' || !u.hostname || !/^https:\/\//i.test(s)) return { fehler: 'Die Adresse muss mit https:// beginnen.' }
if (u.username || u.password) return { fehler: 'Die Adresse darf keine Zugangsdaten enthalten.' }
return { wert: s }
}
/**
* Pruefen und Normalisieren der Haendler-Rechtstexte. Texte duerfen im Entwurf leer sein; URLs muessen https sein oder leer.
* @returns {{rechtstexte: {agb, widerrufsbelehrung, widerrufsformular, impressumUrl, datenschutzUrl}} | {fehler: Object<string,string>}}
*/
export function normalisiereRechtstexte(eingabe) {
if (!eingabe || typeof eingabe !== 'object' || Array.isArray(eingabe)) return { fehler: { rechtstexte: 'Die Rechtstexte fehlen oder sind unlesbar.' } }
const fehler = {}
const out = {}
const texte = [
['agb', 'AGB', MAX_AGB],
['widerrufsbelehrung', 'Widerrufsbelehrung', MAX_WIDERRUFSBELEHRUNG],
['widerrufsformular', 'Widerrufsformular', MAX_WIDERRUFSFORMULAR],
]
for (const [key, name, max] of texte) {
const w = Object.hasOwn(eingabe, key) ? eingabe[key] : undefined
if (w === undefined || w === null) {
out[key] = ''
continue
}
if (typeof w !== 'string') {
fehler[key] = `Bitte gib die ${name} als Text an.`
out[key] = ''
continue
}
const s = bereinigeText(w)
if ([...s].length > max) {
fehler[key] = `Die ${name} darf höchstens ${max} Zeichen lang sein.`
out[key] = ''
} else out[key] = s
}
for (const [key, name] of [['impressumUrl', 'Impressum'], ['datenschutzUrl', 'Datenschutzerklärung']]) {
const r = pruefeUrl(Object.hasOwn(eingabe, key) ? eingabe[key] : undefined)
if (r.fehler) {
fehler[key] = `${name}: ${r.fehler}`
out[key] = ''
} else out[key] = r.wert
}
if (Object.keys(fehler).length) return { fehler }
return {
rechtstexte: {
agb: out.agb,
widerrufsbelehrung: out.widerrufsbelehrung,
widerrufsformular: out.widerrufsformular,
impressumUrl: out.impressumUrl,
datenschutzUrl: out.datenschutzUrl,
},
}
}
/** Alle drei Texte gefuellt und beide Adressen gesetzt: Voraussetzung fuer die Bestaetigung. */
export function vollstaendig(rechtstexte) {
const r = rechtstexte && typeof rechtstexte === 'object' ? rechtstexte : {}
return ['agb', 'widerrufsbelehrung', 'widerrufsformular', 'impressumUrl', 'datenschutzUrl'].every((k) => typeof r[k] === 'string' && r[k].trim() !== '')
}
/** sha256 (hex) ueber kanonisches JSON der fuenf Felder plus erzeugtem Versand-/Zahlungstext und Gewaehrleistungshinweis. */
export function rechtstexteHash(rechtstexte, einstellungen) {
const r = rechtstexte && typeof rechtstexte === 'object' ? rechtstexte : {}
const s = (k) => (typeof r[k] === 'string' ? r[k] : '')
const kanonisch = JSON.stringify([
['agb', s('agb')],
['widerrufsbelehrung', s('widerrufsbelehrung')],
['widerrufsformular', s('widerrufsformular')],
['impressumUrl', s('impressumUrl')],
['datenschutzUrl', s('datenschutzUrl')],
['versandUndZahlung', versandUndZahlung(einstellungen)],
['gewaehrleistungsHinweis', gewaehrleistungsHinweis()],
])
return createHash('sha256').update(kanonisch).digest('hex')
}