Webshop A3: Register-Eintrag shop, Haken, Rechtstext-Satz und Ticket-Details

Registereintrag config.features.shop (Bestelltexte mit Fassung config.shop.fassung, Rechtsunterlagen-Getter,
Mailtexte, Ticket), STANDARD_HAKEN.shop (Schalter, Bestellpruefung ueber shop/anfrage.js, Unterlagen, Belehrung),
legalDocs-Satz shop, shopTicketDetails sowie Rumpf von anfrageOffenePunkte. Tests angepasst und um Shop-Faelle erweitert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 08:35:28 +00:00
1 parent 6782d99960
commit 0e74f1669c
12 files changed
+594 -23

No files matched your search

+131 -1
View File
@@ -233,7 +233,85 @@ function termineConsent() {
}
/**
* Register der kaufbaren Funktionen (News-Pinnwand, Terminplaner): Preis, Zustimmungen, Rechtsunterlagen,
* Rechtsunterlage des Webshops: wie termineRechtsunterlage, aber mit Fassung und Adresse aus config.shop.fassung (bei jedem
* Zugriff gelesen). Slugs wie in services/legalDocs.js (SAETZE.shop); test/legalDocs.test.js gleicht beide ab.
*/
function shopRechtsunterlage(slug, { nullIstWert = false } = {}) {
let ueberschrieben // undefined = Standard
const standard = () => ({ version: config.shop.fassung, url: `/rechtliches/${slug}-${config.shop.fassung}.html` })
return {
get: () => (ueberschrieben === undefined ? standard() : ueberschrieben),
set(wert) {
const std = standard()
const zurueck = wert === undefined || (wert === null && !nullIstWert) || (wert !== null && wert.version === std.version && wert.url === std.url)
ueberschrieben = zurueck ? undefined : wert
},
}
}
const shopLb = shopRechtsunterlage('leistungsbeschreibung-webshop')
const shopAvv = shopRechtsunterlage('avv-webshop')
const shopWiderruf = shopRechtsunterlage('widerrufsbelehrung-funktionen', { nullIstWert: true })
const SHOP_PREIS = '299 €'
const SHOP_DAUER = 'meist innerhalb von 2–3 Wochen'
/**
* Bestelltexte des Webshops: fuenf Haken (alle Besteller), ohne Adressen im Text (die versionierten Links zeigt die
* Bestellseite daneben aus bestellung.dokumente). Die Fassung (consent.version) ist config.shop.fassung; die dynamischen Teile
* (Fassung, Preis, Umsatzsteuer-Hinweis, Dauer) stehen fertig im Text, gespeichert wird immer dieser Text. Aufbau wie
* termineConsent (Haken je Fassung einmal gebaut; Tests duerfen einzelne Texte aendern und zurueckstellen). Bei Textaenderung
* neues Fassungsdatum und neuen Hash im Pin von test/featureConsentTexte.test.js. Zitat und Wertersatz-Satz von "beginn" und
* die Pflichtinformationen sind Entwurf bis zur Kanzlei.
*/
function shopConsent() {
let versionGesetzt // undefined = config.shop.fassung
let gebaut = null // { version, items }
const version = () => versionGesetzt ?? config.shop.fassung
const items = (v) => [
{
id: 'unterlagen',
haken: true,
text: `Ich stimme der Leistungsbeschreibung Webshop und dem Auftragsverarbeitungsvertrag Webshop in der Fassung ${v} zu.`,
},
{
id: 'verantwortung',
haken: true,
text: 'Ich verkaufe als Unternehmer in eigenem Namen. Für meine Produkte, Preise, Produktangaben und Rechtstexte (AGB, Widerrufsbelehrung, Impressum, Datenschutzerklärung) bin ich selbst verantwortlich; WEBklar stellt Muster bereit und berät nicht rechtlich. Ich verkaufe keine der in der Leistungsbeschreibung ausgeschlossenen Waren.',
},
{
id: 'stripe',
haken: true,
text: 'Die Zahlungen meiner Käufer laufen über mein eigenes Stripe-Konto zu den Bedingungen von Stripe. WEBklar erhält und verwahrt kein Geld meiner Käufer und erhebt keine Umsatzbeteiligung.',
},
{
id: 'beginn',
haken: true,
text: 'Ich verlange ausdrücklich, dass WEBklar vor Ablauf der Widerrufsfrist mit der Einrichtung des Webshops beginnt. Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Einrichtung erlischt (§ 356 Abs. 5 Nr. 2 BGB). Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).',
},
{
id: 'information',
haken: true,
text: `Ich habe die Pflichtinformationen zu dieser Bestellung gelesen: Leistung (Einrichtung des Webshops für diese Website: Einbau des Shop-Widgets, Verwaltung im Portal, Muster-Rechtstexte), Gesamtpreis ${SHOP_PREIS} einmalig (${ustHinweis}), voraussichtliche Dauer ${SHOP_DAUER}, keine laufende Gebühr von WEBklar; der Webshop läuft, solange WEBklar die Website betreibt. Die Widerrufsbelehrung mit Muster-Widerrufsformular habe ich gelesen.`,
},
]
return {
get version() {
return version()
},
set version(wert) {
versionGesetzt = wert === undefined || wert === null || wert === config.shop.fassung ? undefined : wert
},
knopf: `Zahlungspflichtig bestellen · ${SHOP_PREIS}`,
get items() {
const v = version()
if (gebaut?.version !== v) gebaut = { version: v, items: items(v) }
return gebaut.items
},
}
}
/**
* Register der kaufbaren Funktionen (News-Pinnwand, Terminplaner, Webshop): Preis, Zustimmungen, Rechtsunterlagen,
* Ticket- und Mail-Texte je Funktion; reine Daten, config.js importiert keine Dienste. Die Helfer (featureConfig,
* featureHaken, oeffentlicheAnsicht) stehen in server/services/featureRegistry.js. Haken (verfuegbar,
* bestellpruefung, unterlagenBereit, belehrung, freischaltbar) gehoeren nicht hierher: wer sie in Tests auf dem
@@ -360,6 +438,58 @@ const features = {
orderedConflictMessage: 'Der Terminplaner ist für dieses Projekt schon bestellt.',
unavailableMessage: 'Der Terminplaner ist noch nicht verfügbar.',
},
shop: {
key: 'shop',
product: 'shopSetup',
label: { name: 'Webshop', kurz: 'Shop', keine: 'keinen Webshop' },
priceLabel: SHOP_PREIS,
setupEstimate: SHOP_DAUER,
consent: shopConsent(),
// Fassung und Adresse folgen config.shop.fassung bei jedem Zugriff (Slugs wie in services/legalDocs.js; config.js
// importiert keine Dienste, test/legalDocs.test.js gleicht beide ab)
get leistungsbeschreibung() {
return shopLb.get()
},
set leistungsbeschreibung(wert) {
shopLb.set(wert)
},
get avv() {
return shopAvv.get()
},
set avv(wert) {
shopAvv.set(wert)
},
get widerruf() {
return shopWiderruf.get()
},
set widerruf(wert) {
shopWiderruf.set(wert)
},
returnHash: 'webshop',
returnProjekt: true,
ticket: {
topic: (project) => `Webshop einbauen – ${ticketProjektName(project)}`,
// Dienst erst beim Aufruf laden: config.js importiert keine Dienste statisch
details: async (ctx) => {
const { shopTicketDetails } = await import('./services/shop/ticketDetails.js')
return shopTicketDetails(ctx)
},
},
mails: {
funktion: 'Webshop',
formen: { dein: 'Dein Webshop', nom: 'der Webshop', akk: 'den Webshop', gen: 'des Webshops', pron: 'ihn' },
knopf: 'Webshop öffnen',
danach: 'Danach kannst Du im Portal Artikel anlegen, Dein Stripe-Konto verbinden und den Shop öffnen.',
bereit: 'Dein Webshop ist eingebaut. Sobald Stripe-Konto, Artikel und Rechtstexte fertig sind, kannst Du ihn im Portal öffnen.',
preisHinweis: `einmalig, ${ustHinweis}; keine laufende Gebühr von WEBklar, Stripe-Gebühren zahlst Du direkt an Stripe`,
anleitung: false,
},
requiresWartungsAbo: false,
ticketFreischaltung: true,
disabledMessage: 'Der Webshop ist für diese Website abgeschaltet. Schreib uns im Chat, wenn Du ihn wieder möchtest.',
orderedConflictMessage: 'Der Webshop ist für dieses Projekt schon bestellt.',
unavailableMessage: 'Der Webshop ist noch nicht verfügbar.',
},
}
export const config = {