350 lines
27 KiB
Markdown
350 lines
27 KiB
Markdown
# Webshop (Funktion `shop`): Design
|
||
|
||
Stand 2026-10-05. Basis: Portal-Zweig `feature/terminplaner-tp2` af63b69 (Register `config.features`, generischer Checkout,
|
||
Zustimmungsnachweis `featureConsents`, versionierte Rechtsunterlagen). Arbeitszweig `feature/webshop`, Worktree `dev-webshop`.
|
||
Pfade: **P** = Portal-Repo (`kundenbereich.webklar.com`), **W** = WOMS-Repo (`ticket.webklar.com`).
|
||
Fakten aus dem Code: `/root/reports/webshop-2026-10-05/fakten-*.md`; Recht und Stripe: `recherche-recht-stripe.md`.
|
||
|
||
Auftrag des Inhabers (2026-10-05, `/goal`): „Online-Webshop-Integration, auf Anfrage, einmalige Kosten, der Kunde
|
||
konfiguriert alle Artikel, Stripe-Anbindung (PayPal optional, nur Stripe ist ok), keine Rückfragen, Recht beachten.“
|
||
Alle Entscheidungen unten (W1–W30) hat Claude ohne Rückfrage getroffen; sie stehen gesammelt in Abschnitt 12.
|
||
|
||
---
|
||
|
||
## 1. Ziel und Abgrenzung
|
||
|
||
**Der Webshop liefert:** Ein Kunde fragt im Portal einen Webshop für eine seiner Websites an. Das Team prüft die Anfrage
|
||
(was wird verkauft, ist es erlaubt, passt es) und gibt sie frei. Danach bestellt der Kunde die Einrichtung (einmalig
|
||
299 €, Stripe-Checkout wie bei den anderen Funktionen). Es entsteht ein Einrichtungs-Ticket, das Team baut den Shop in
|
||
die Website ein. Der Kunde verbindet sein **eigenes** Stripe-Konto (Stripe Connect), pflegt Artikel, Versand, Abholung
|
||
und seine Rechtstexte im Portal und öffnet den Shop. Endkunden kaufen im Widget auf seiner Website und bezahlen über
|
||
Stripe Checkout direkt an den Kunden. Bestellungen, Versand, Erstattung und Widerrufe verwaltet der Kunde im Portal.
|
||
|
||
**Nicht Teil (V1):** digitale Produkte/Downloads, Abos/wiederkehrende Zahlungen, Gutscheine/Rabattcodes, Versand
|
||
außerhalb Deutschlands und Österreichs, Lagerverwaltung über mehrere Lager, Rechnungserstellung durch WEBklar (die
|
||
Stripe-Quittung und die Bestellbestätigung gehen an den Käufer, Rechnungen schreibt der Händler selbst), Anbindung
|
||
eines schon bestehenden Stripe-Kontos per OAuth (später), alkoholische Getränke, Tabak, Arzneimittel, Waffen,
|
||
Erotik, Lebende Tiere und alles auf Stripes Liste verbotener Geschäfte (Bestellgate + Leistungsbeschreibung).
|
||
|
||
**Warum nur Stripe (W1):** Stripe läuft schon für Abo und Funktionen. Stripe Checkout bietet Karte, Apple Pay, Google Pay,
|
||
Klarna und SEPA-Lastschrift im selben Checkout an. **PayPal geht über Stripe bei Connect-Direct-Charges nicht** (Stripe-Doku,
|
||
Recherche A3); eine eigene PayPal-Anbindung wäre eine zweite Integration samt eigenem Webhook, eigener Erstattung und eigenem
|
||
Konto-Onboarding je Händler. Der Inhaber hat „nur Stripe“ ausdrücklich erlaubt; PayPal bleibt ein späterer Ausbau.
|
||
|
||
**Warum Stripe Connect mit eigenem Konto des Kunden (W2):** Das Geld fließt direkt vom Käufer an den Händler (Direct
|
||
Charges auf dem verbundenen Konto). WEBklar nimmt nie fremdes Geld an, behält keine Gebühr ein und haftet nicht für
|
||
Rückbuchungen. Damit ist WEBklar kein Zahlungsdienst im Sinne des ZAG (Stripe ist das lizenzierte Institut), und der
|
||
Händler hat sein volles Stripe-Dashboard (Auszahlungen, Erstattungen, Belege). Konten werden mit den
|
||
`controller`-Eigenschaften angelegt: Stripe-Dashboard voll, Gebühren zahlt das Konto, Verluste trägt Stripe/Konto,
|
||
Anforderungen sammelt Stripe (Gegenstück zum früheren `type: 'standard'`).
|
||
|
||
**Pilot und Schalter (W3):** `SHOP_ENABLED=off|pilot|on` wie `TERMINE_ENABLED`; `pilot` nur für `SHOP_PILOT_EMAILS`.
|
||
Pilotkunde ist das Testkonto (kenso.gri@gmail.com, Projekt `test-website`).
|
||
|
||
---
|
||
|
||
## 2. Ablauf aus Kundensicht
|
||
|
||
| Zustand | Kachel (Funktionen-Tab) | Ansicht `#webshop` | Mail |
|
||
|---|---|---|---|
|
||
| nichts angefragt | „Webshop anfragen“ | Anfrageformular | – |
|
||
| Anfrage offen | „Anfrage wird geprüft“ | Anfrage lesen, zurückziehen | T21 an Team |
|
||
| Anfrage abgelehnt | „Anfrage abgelehnt“ + Begründung, neue Anfrage möglich | Begründung | K24 |
|
||
| freigegeben (`off`) | „Webshop bestellen · 299 €“ | Bestellseite (generisch) | K24 |
|
||
| `ordered` | „Zahlung offen“ | Statuskarte, „Zahlung fortsetzen“ | – |
|
||
| `setup` | „In Einrichtung“ | Statuskarte + **alle Reiter nutzbar** (Artikel, Stripe, Rechtliches) | K15, T16 |
|
||
| `active` | „Webshop öffnen“ | Verwaltung | K16 |
|
||
| `disabled` | „Webshop ansehen“ | lesen, CSV-Export | – |
|
||
|
||
**Auf Anfrage (W4):** Der Inhaber will die Funktion „auf Anfrage“. Umsetzung als vorgeschaltete Freigabe: Die Kachel ist
|
||
für jeden Kunden mit Website-Projekt sichtbar (wenn der Schalter es erlaubt), bestellbar wird sie erst nach Freigabe
|
||
durch das Team. Die Anfrage fragt: Was wird verkauft (Freitext + Warenart: Waren/Lebensmittel/Kleidung/Kunst/Sonstiges),
|
||
ungefähre Artikelzahl, Versand und/oder Abholung, Händler ist Unternehmer (Pflicht-Haken), keine der ausgeschlossenen
|
||
Waren (Pflicht-Haken), Nachricht ans Team. Das Team sieht die Anfrage im WOMS (Kunde → Funktionen) und nimmt sie an oder
|
||
lehnt sie mit Begründung ab. Der Preis bleibt fest (299 €); Sonderfälle (sehr viele Artikel, Sonderdesign) bespricht das
|
||
Team im Chat und rechnet Mehraufwand nach Stunden ab, wie beim Terminplaner (Leistungsbeschreibung).
|
||
|
||
**Einmalige Kosten (W5):** 299 € einmalig (Kleinunternehmer, ohne USt gemäß § 19 UStG), kein Wartungs-Abo nötig, keine
|
||
laufende Gebühr von WEBklar, keine Umsatzbeteiligung. Der Shop läuft, solange WEBklar die Website betreibt (Hosting oder
|
||
Wartung). Stripe-Gebühren zahlt der Händler direkt an Stripe.
|
||
|
||
**Ab `setup` nutzbar (W6):** Anders als beim Terminplaner (Einstellung ab Bestellung gesperrt) pflegt der Kunde ab
|
||
`setup` Artikel, Stripe-Konto und Rechtstexte, damit der Shop zum Einbau fertig ist. Die Website zeigt den Shop erst,
|
||
wenn alles erfüllt ist (Abschnitt 4.6, „Shop geöffnet“).
|
||
|
||
---
|
||
|
||
## 3. Datenmodell (Appwrite, `scripts/shop-setup.mjs`, nur Server-Key)
|
||
|
||
Alle Collections ohne Client-Rechte (wie `termineConfig`); alles läuft über den Portal-Server.
|
||
|
||
- **`shopConfig`** (Dokument-ID = ID des `portalFeatures`-Dokuments; eins je Projekt):
|
||
`customerId`, `projectId`, `siteKey` (Kopie aus portalFeatures, Index), `anfrage` (JSON-Text ≤ 4000),
|
||
`anfrageStatus` (`''|offen|angenommen|abgelehnt|zurueckgezogen`), `anfrageAt`, `anfrageEntschiedenAt`,
|
||
`anfrageEntschiedenVon`, `anfrageBegruendung` (≤ 1000), `stripeAccountId`, `stripeStatus` (JSON ≤ 4000:
|
||
charges_enabled, payouts_enabled, details_submitted, requirements.currently_due, disabled_reason, geprüft am),
|
||
`einstellungen` (JSON ≤ 20000), `rechtstexte` (JSON ≤ 100000), `rechtstexteBestaetigtAt`, `rechtstexteBestaetigtVon`,
|
||
`rechtstexteFassung` (Hash), `geoeffnet` (bool), `geoeffnetAt`.
|
||
- **`shopProducts`**: `shopId` (= shopConfig-ID, Index), `customerId`, `aktiv` (bool), `sort` (int), `name` (≤ 120),
|
||
`daten` (JSON ≤ 30000: Kurz-/Langbeschreibung, Preis in Cent, Grundpreis-Menge+Einheit, Steuersatz, Bestand,
|
||
Varianten, Bilder, Lieferart, Widerrufsausschluss, Produktsicherheit, Lebensmittelangaben, Kategorie).
|
||
- **`shopOrders`**: `shopId` (Index), `customerId`, `bestellnummer` (eindeutig je Shop, Index), `status` (Index),
|
||
`tokenHash`, `stripeSessionId` (Index), `paymentIntentId`, `summeCent`, `erstelltAt`, `ablaufAt`, `bezahltAt`,
|
||
`kaeuferEmail` (≤ 320), `daten` (JSON ≤ 60000: Positionen als Schnappschuss, Lieferart, Adressen, Versandkosten,
|
||
Steuerausweis, Fassung der Rechtstexte, Verlauf), `anonymisiertAt`.
|
||
- **`shopWiderrufe`**: `shopId` (Index), `customerId`, `bestellnummer`, `name`, `email`, `nachricht` (≤ 2000),
|
||
`eingangAt`, `bestaetigungGesendetAt`, `bearbeitetAt`.
|
||
- **Bilder** auf der Platte unter `upload-data/shop/<siteKey>/<id>.<ext>` (gitignored, wie News-Fotos), ausgeliefert über
|
||
die öffentliche API.
|
||
|
||
Bestellnummer (W7): `<JJ>-<6 Zeichen base32>` (z. B. `26-K7QM2X`), zufällig, bei Kollision im Shop neu ziehen. Keine
|
||
fortlaufende Zählung nötig (kein Rechnungsnummernkreis, Rechnungen schreibt der Händler).
|
||
|
||
---
|
||
|
||
## 4. Shop-Verwaltung im Portal (`#webshop`)
|
||
|
||
Reiter: **Übersicht · Artikel · Bestellungen · Einstellungen · Rechtliches**. Projektwahl wie beim Terminplaner (Shop je
|
||
Website-Projekt).
|
||
|
||
### 4.1 Übersicht
|
||
Checkliste „Bereit zum Öffnen“ mit Haken: Einbau fertig (`active`), Stripe verbunden und Zahlungen freigeschaltet
|
||
(`charges_enabled`), Rechtstexte vollständig und bestätigt, mindestens ein aktiver Artikel, Versand oder Abholung
|
||
eingerichtet. Schalter „Shop geöffnet“ (nur wenn alle Haken grün; serverseitig erneut geprüft). Kennzahlen: offene
|
||
Bestellungen, Bestellungen 30 Tage, Umsatz 30 Tage (nur Anzeige). Knopf „Shop-Vorschau“ (Standalone-Seite, Abschnitt 5.4).
|
||
|
||
### 4.2 Stripe-Konto
|
||
Knopf „Stripe-Konto einrichten“: Server legt das verbundene Konto an (Land DE, E-Mail und Website des Kunden
|
||
vorbefüllt) und holt einen Onboarding-Link (Account Link), Weiterleitung zu Stripe; Rücksprung `#webshop?stripe=zurueck`
|
||
liest den Status neu. Danach: Status (Zahlungen aktiv / Auszahlungen aktiv / fehlende Angaben) und Knöpfe
|
||
„Angaben ergänzen“ (neuer Account Link) und „Stripe-Dashboard öffnen“ (dashboard.stripe.com). Status wird zusätzlich
|
||
per Connect-Webhook `account.updated` aktuell gehalten.
|
||
|
||
### 4.3 Artikel
|
||
Liste (sortierbar mit Hoch/Runter, aktiv/inaktiv, Bestand) und Editor:
|
||
- Name, Kategorie (frei, gruppiert im Shop), Kurzbeschreibung (≤ 300), Beschreibung (≤ 5000, Absätze, kein HTML).
|
||
- Preis brutto in € (PAngV: Gesamtpreis), Steuersatz (19 / 7 / 0 % – bei Kleinunternehmer ausgeblendet).
|
||
- Grundpreis (PAngV § 4): Menge + Einheit (g, kg, ml, l, m, m², Stück-los) → Anzeige „x € / 1 kg“ bzw. „/ 100 g“ nach
|
||
§ 5 PAngV (bei ≤ 250 g/ml darf 100 g/ml verwendet werden; V1 rechnet immer auf 1 kg / 1 l / 1 m / 1 m²).
|
||
- Bestand (leer = unbegrenzt) und „ausverkauft anzeigen“ statt ausblenden.
|
||
- Varianten (optional, eine Dimension, z. B. „Größe“): je Wert eigener Preis und Bestand (max 30).
|
||
- Bilder: bis 4, JPEG/PNG/WebP ≤ 8 MB, serverseitig geprüft (Signatur), erstes = Titelbild.
|
||
- Lieferart: Versand und Abholung / nur Abholung.
|
||
- Widerruf (§ 312g Abs. 2 BGB): „gesetzliches Widerrufsrecht“ (Standard) oder Ausschluss mit Grund: schnell
|
||
verderblich, nach Kundenwunsch angefertigt, versiegelt aus Hygienegründen. Der Shop zeigt den Hinweis am Artikel und in
|
||
der Bestellübersicht, die Bestellbestätigung nennt ihn.
|
||
- Produktsicherheit (GPSR Art. 19): Hersteller (Name, Anschrift, E-Mail/Website; Vorgabe „ich selbst“ = Anbieterdaten),
|
||
bei Herstellern außerhalb der EU die verantwortliche Person in der EU, Warn- und Sicherheitshinweise (Freitext).
|
||
- Lebensmittel (LMIV Art. 14): Schalter „ist ein Lebensmittel“ → Pflichtfeld „Pflichtangaben“ (Bezeichnung, Zutaten,
|
||
Allergene hervorgehoben, Menge, ggf. Hersteller), wird vor dem Kauf am Artikel gezeigt.
|
||
|
||
### 4.4 Bestellungen
|
||
Liste (Status-Filter, Suche nach Nummer/Name), Detail mit Positionen, Lieferart, Adresse, Zahlung, Verlauf. Aktionen:
|
||
„Versendet“ (optional Sendungsnummer + Versanddienst → Mail G10), „Abholbereit“ (→ G11), „Abgeschlossen“, „Erstatten“
|
||
(voll oder Teilbetrag, Grund; Stripe-Refund auf dem verbundenen Konto → G13), „Stornieren“ (nur unbezahlt). Widerrufe
|
||
erscheinen an der Bestellung und als eigene Liste. CSV-Export (Bestellungen eines Zeitraums).
|
||
|
||
### 4.5 Einstellungen
|
||
Shopname, Anbieter (Firma/Name, Anschrift, E-Mail, Telefon, optional USt-IdNr.), Steuer (Kleinunternehmer ja/nein),
|
||
Versand (an/aus, Länder DE / DE+AT, Versandkosten je Land, versandkostenfrei ab), Abholung (an/aus, Adresse, Hinweis z. B.
|
||
Öffnungszeiten), Lieferzeit (Pflicht, z. B. „2–4 Werktage“), Zahlungsarten (Karte inkl. Apple Pay/Google Pay immer;
|
||
Klarna und SEPA-Lastschrift wählbar – nur einschalten, wenn im eigenen Stripe-Konto aktiviert; SEPA mit Warnung, weil die
|
||
Zahlung erst nach Tagen sicher ist; der Shop zeigt genau diese Liste),
|
||
Akzentfarbe, Mindestbestellwert (optional).
|
||
|
||
### 4.6 Rechtliches (Händler-Rechtstexte) und „Shop geöffnet“
|
||
Der Händler ist Verkäufer und verantwortlich. WEBklar liefert Muster, keine Rechtsberatung (W8, RDG):
|
||
- **Widerrufsbelehrung**: vorbefüllt mit dem gesetzlichen Muster (Anlage 1 zu Art. 246a § 1 Abs. 2 EGBGB) inkl. der
|
||
Hinweise auf die elektronische Widerrufsfunktion, Name/Anschrift/E-Mail eingesetzt; Rücksendekosten trägt der Käufer
|
||
(Standard, umschaltbar); Muster-Widerrufsformular (Anlage 2). Bearbeitbar; „Auf Muster zurücksetzen“.
|
||
- **AGB**: Muster-AGB für kleine Shops (Vertragsschluss, Preise, Versand, Zahlung über Stripe, Eigentumsvorbehalt,
|
||
Gewährleistung gesetzlich, Streitbeilegung VSBG-Hinweis), deutlich als Muster gekennzeichnet; bearbeitbar.
|
||
- **Versand- und Zahlungsinformationen**: aus den Einstellungen erzeugt (nicht frei editierbar, damit sie stimmen).
|
||
- **Impressum-URL** und **Datenschutz-URL** (Pflicht, https).
|
||
- Bestätigung: Haken „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.“ (Zeit, Person, Hash der Texte gespeichert;
|
||
jede Änderung der Texte hebt die Bestätigung auf und schließt den Shop nicht, verlangt aber neue Bestätigung vor der
|
||
nächsten Öffnung — Festlegung: geöffnet bleibt geöffnet, Banner „bitte neu bestätigen“; neue Bestellungen werden mit
|
||
der zuletzt bestätigten Fassung geschlossen, siehe 5.2).
|
||
|
||
**Shop geöffnet** (serverseitig bei jeder Bestellung geprüft): Funktion `active` · `geoeffnet` · Stripe `charges_enabled` ·
|
||
bestätigte Rechtstexte vorhanden · Artikel lieferbar. Fällt ein Punkt weg, zeigt das Widget „Der Shop ist gerade
|
||
geschlossen“ und nimmt keine Bestellungen an.
|
||
|
||
---
|
||
|
||
## 5. Shop auf der Website (Widget `P/public/embed/shop.js`)
|
||
|
||
### 5.1 Einbau
|
||
`<div data-webklar-shop="<siteKey>"></div>` + `<script src="https://project.webklar.com/embed/shop.js" defer></script>`
|
||
(Muster News/Termine). Eigene Styles mit Präfix/Shadow-DOM nach Vorbild Termine, Akzentfarbe aus den Einstellungen.
|
||
Ansichten: Kategorien + Artikelraster, Artikeldetail (Bilder, Beschreibung, Preis, Grundpreis, Varianten, Lieferhinweis,
|
||
Widerrufsausschluss, Produktsicherheit, Lebensmittelangaben), Warenkorb (im `sessionStorage` des Besuchers, nur Artikel-IDs
|
||
und Mengen; Begründung: technisch notwendig, § 25 Abs. 2 Nr. 2 TDDDG), Kasse, Danke-Seite, Widerrufsformular. Ganz oben
|
||
im Widget (Beginn des Bestellvorgangs, § 312j Abs. 1 BGB): Lieferländer und akzeptierte Zahlungsarten. Fußzeile immer:
|
||
AGB · Widerrufsbelehrung · Versand & Zahlung · **Vertrag widerrufen** · Impressum · Datenschutz.
|
||
|
||
### 5.2 Kasse und Vertragsschluss (W9)
|
||
Die verbindliche Bestellung geschieht **im Widget** mit dem Knopf **„Zahlungspflichtig bestellen“** (§ 312j Abs. 3 BGB);
|
||
Stripe Checkout ist danach nur noch die Zahlung. Unmittelbar über dem Knopf (§ 312j Abs. 2, Art. 246a § 1 EGBGB):
|
||
Positionen mit wesentlichen Merkmalen, Einzel- und Gesamtpreis inkl. USt (bzw. Kleinunternehmer-Hinweis), Versandkosten,
|
||
Gesamtsumme, Lieferart, Lieferzeit, Zahlungsarten, Hinweis auf Widerrufsrecht bzw. dessen Ausschluss je Artikel, Link
|
||
AGB und Widerrufsbelehrung, Anbieter-Identität. Lieferart wählt der Käufer im Widget; Name, E-Mail, Telefon (optional)
|
||
und Lieferadresse sammelt Stripe Checkout (`shipping_address_collection` nur bei Versand, erlaubte Länder aus den
|
||
Einstellungen). Korrektur von Eingabefehlern (§ 312i Abs. 1 Nr. 1): Warenkorb und Lieferart sind bis zum Knopf änderbar,
|
||
Adresse in Stripe bis zur Zahlung.
|
||
|
||
Server (`POST /api/public/shop/:siteKey/bestellung`): prüft „Shop geöffnet“, rechnet alle Preise aus der Datenbank neu
|
||
(nie aus dem Browser), prüft und **reserviert** Bestand (offene Bestellung zählt bis `ablaufAt`), legt `shopOrders` mit
|
||
Status `offen` an (Schnappschuss der Positionen und der bestätigten Rechtstexte-Fassung), erstellt die Checkout-Session
|
||
**auf dem verbundenen Konto** (`stripeAccount`): `mode: payment`, `price_data` je Position + Versandkosten als
|
||
`shipping_options` (fester Betrag aus dem Server), `payment_method_types` aus den Einstellungen, `locale: 'de'`,
|
||
`expires_at` = jetzt + 30 min, `client_reference_id` = Bestell-ID, `metadata` {shopId, orderId, bestellnummer},
|
||
`customer_creation: 'if_required'`, `phone_number_collection`. `success_url` = Seite des Kunden mit
|
||
`?wk_bestellung=<id>&wk_t=<token>` (nur erlaubte Hosts des Projekts, sonst Standalone-Seite), `cancel_url` = Warenkorb.
|
||
Unter einer Sperre je Shop (`withLocks`), damit zwei gleichzeitige Käufe das letzte Stück nicht doppelt reservieren.
|
||
|
||
### 5.3 Zahlung, Bestätigung, Verlauf
|
||
- **Connect-Webhook** `POST /api/shop/stripe-webhook` (eigenes Signatur-Geheimnis `STRIPE_CONNECT_WEBHOOK_SECRET`,
|
||
Endpunkt mit `connect: true`): `checkout.session.completed` (bei `payment_status: paid`),
|
||
`checkout.session.async_payment_succeeded` → `bezahlt`, Bestand endgültig abziehen, Käuferdaten aus der Session
|
||
speichern, **G09 Bestellbestätigung** an den Käufer, **K25 Neue Bestellung** an den Händler.
|
||
`async_payment_failed` / `expired` → `storniert`, Reservierung frei (G14 nur bei fehlgeschlagener Zahlung).
|
||
`account.updated` → `stripeStatus`. `charge.refunded` → Erstattung aus dem Stripe-Dashboard nachtragen.
|
||
Alles idempotent (Status-Übergänge nur vorwärts, Sperre je Bestellung).
|
||
- **Danke-Seite** fragt `GET /api/public/shop/:siteKey/bestellung/:id?t=<token>`; ist die Bestellung noch `offen`, holt der
|
||
Server die Session bei Stripe und schließt selbst ab (gleicher Code wie der Webhook) — fällt der Webhook aus, geht
|
||
trotzdem nichts verloren.
|
||
- **G09 Bestellbestätigung** (dauerhafter Datenträger, § 312f Abs. 2 BGB): Anbieter, Bestellnummer, Positionen,
|
||
Preise, Versand, Gesamt, Lieferart/-zeit, Lieferadresse, Zahlungsart, Hinweis auf Widerrufsausschlüsse, vollständige
|
||
AGB, vollständige Widerrufsbelehrung und Muster-Widerrufsformular (im Mailtext), Link „Vertrag widerrufen“.
|
||
Absender: WEBklar-Versandadresse mit Anzeigename „<Shopname>“, Reply-To = E-Mail des Händlers.
|
||
|
||
### 5.4 Standalone-Seite und Widerrufsfunktion (W10, § 356a BGB)
|
||
- `https://project.webklar.com/shop/<siteKey>` zeigt das Widget auf einer neutralen Seite (Vorschau, Rücksprung-Ersatz).
|
||
- `https://project.webklar.com/shop/<siteKey>/widerruf` und die Widget-Ansicht „Vertrag widerrufen“: Schaltfläche
|
||
„Vertrag widerrufen“ führt zu einem Formular (Name, E-Mail, Bestellnummer, optional Erklärung/Artikel), danach
|
||
Bestätigungsschaltfläche **„Widerruf bestätigen“**. Eingang wird gespeichert, **G12 Eingangsbestätigung** mit Inhalt,
|
||
Datum und Uhrzeit sofort an den Käufer (dauerhafter Datenträger), **K26** an den Händler. Erreichbar ohne Login, auch
|
||
ohne passende Bestellung (der Händler klärt), Missbrauchsschutz per Rate-Limit.
|
||
|
||
---
|
||
|
||
## 6. Bestellung der Funktion bei WEBklar (Register-Eintrag `shop`)
|
||
|
||
Registereintrag nach Muster `termine`: `product: 'shopSetup'` (29900 Cent, Lookup-Key `webklar_shop_einrichtung_29900`),
|
||
`priceLabel '299 €'`, `setupEstimate 'meist innerhalb von 2–3 Wochen'`, `requiresWartungsAbo: false`,
|
||
`ticketFreischaltung: true`, `returnHash 'webshop'`, `returnProjekt: true`, Haken:
|
||
- `verfuegbar(email)`: Schalter `SHOP_ENABLED` / Pilotliste.
|
||
- `bestellpruefung`: Anfrage des Projekts ist `angenommen` (sonst offener Punkt „Anfrage noch nicht freigegeben“).
|
||
- `unterlagenBereit`: Leistungsbeschreibung + AVV Webshop der aktuellen Fassung abrufbar (HTML + PDF, sha256).
|
||
- `belehrung`: die gemeinsame Widerrufsbelehrung der Funktionen.
|
||
- `freischaltbar(doc)`: wie News (Ticket geschlossen genügt).
|
||
|
||
Zustimmungen (alle Haken Pflicht, Fassung = `config.shop.fassung`):
|
||
1. **unterlagen**: Leistungsbeschreibung Webshop und AVV Webshop in der Fassung X.
|
||
2. **verantwortung**: „Ich verkaufe als Unternehmer in eigenem Namen. Für Produkte, Preise, Produktangaben und meine
|
||
Rechtstexte bin ich verantwortlich. Ich verkaufe keine ausgeschlossenen Waren (Leistungsbeschreibung Nr. 3).“
|
||
3. **stripe**: „Zahlungen laufen über mein eigenes Stripe-Konto zu den Bedingungen von Stripe. WEBklar erhält kein Geld
|
||
meiner Käufer.“
|
||
4. **beginn**: vorzeitiger Beginn, Erlöschen (§ 356 Abs. 5 Nr. 2 BGB), Wertersatz (§ 357a Abs. 2 BGB) – Wortlaut wie
|
||
Terminplaner.
|
||
5. **information**: Pflichtinformationen (Leistung, 299 € einmalig ohne USt nach § 19 UStG, Dauer, Laufzeit „solange die
|
||
Website bei WEBklar läuft“), Widerrufsbelehrung mit Muster-Formular gelesen.
|
||
|
||
Ticket: Betreff „Webshop einbauen – <Projekt>“, Details: siteKey, Snippet, Domain, Anfrage (Warenart, Versand/Abholung,
|
||
Artikelzahl), Schritte (Shop-Seite anlegen, Snippet einsetzen, Menüpunkt, Fußzeilen-Link „Vertrag widerrufen“ auf allen
|
||
Seiten, Datenschutz-Absatz Webshop einfügen, Impressum prüfen, Testkauf im Stripe-Testmodus entfällt → Vorschau prüfen),
|
||
Verweis WOMS. Einbauanleitung `docs/funktionen/shop-einbau.md`.
|
||
|
||
---
|
||
|
||
## 7. WOMS
|
||
|
||
Funktionen-Panel des Kunden: Webshop-Zeile mit Anfrage (Inhalt, Zeit) und Knöpfen **„Anfrage annehmen“** /
|
||
**„Ablehnen“** (Begründung Pflicht) → Portal-Admin-Route; Zustand, Ticket, Stripe-Status (nur lesen), Kennzahlen.
|
||
Aktivieren/Abschalten über die vorhandenen generischen Aktionen. „Rechtliches“: Karten Leistungsbeschreibung Webshop und
|
||
AVV Webshop.
|
||
|
||
---
|
||
|
||
## 8. Mails
|
||
|
||
Neue IDs (nächste freie laut Test-Liste; Zählung im Plan geprüft): **K24** „Webshop-Anfrage: Entscheidung“ (Varianten
|
||
angenommen/abgelehnt), **K25** „Neue Bestellung im Webshop“ (an Händler), **K26** „Widerruf im Webshop eingegangen“ (an
|
||
Händler), **T21** „Neue Webshop-Anfrage“ (Team). Käufer-Mails **G09** Bestellbestätigung, **G10** versendet, **G11**
|
||
abholbereit, **G12** Eingangsbestätigung Widerruf, **G13** Erstattung, **G14** Zahlung fehlgeschlagen — eigene Vorlagen
|
||
im Stil der G-Vorlagen des Terminplaners (Absender WEBklar, Anzeigename Shop, Reply-To Händler), über die vorhandene
|
||
Gast-Mail-Warteschlange, falls wiederverwendbar (Plan), sonst eigene kleine Warteschlange mit Wiederholung.
|
||
K15/K16/T16 bekommen ihre Texte aus dem Register (vorhanden).
|
||
|
||
---
|
||
|
||
## 9. Datenschutz und Aufbewahrung
|
||
|
||
- Rollen: Händler = Verantwortlicher für Käuferdaten; WEBklar = Auftragsverarbeiter (AVV Webshop, Hetzner als
|
||
Unterauftragsverarbeiter, Mailversand über Hetzner); Stripe = eigener Vertragspartner des Händlers (nicht
|
||
Unterauftragsverarbeiter von WEBklar). Gespeichert werden nur Bestelldaten, die für Abwicklung und Nachweis nötig sind.
|
||
- Aufbewahrung (W11): personenbezogene Käuferdaten (Name, E-Mail, Telefon, Adressen) einer Bestellung werden **3 Jahre
|
||
nach Ende des Bestelljahres** anonymisiert (Gewährleistung 2 Jahre + Puffer); Beträge, Positionen, Bestellnummer
|
||
bleiben für die Buchhaltung des Händlers (dessen Aufbewahrungspflicht deckt der Händler über Stripe-Belege und eigene
|
||
Unterlagen; er kann jederzeit CSV exportieren). Unbezahlte/stornierte Bestellungen: Käuferdaten nach 30 Tagen weg.
|
||
Widerrufe: wie die Bestellung. Wird der Shop abgeschaltet: 90 Tage Lesen + Export, dann Löschung aller Shopdaten
|
||
(Bilder eingeschlossen). Job im bestehenden Löschlauf (`retention.js`).
|
||
- Portal-Datenschutzerklärung: neuer Abschnitt „Webshop“ (Stripe Connect: Weitergabe von E-Mail/Website des Kunden an
|
||
Stripe zur Kontoeröffnung, Abruf des Kontostatus; Verarbeitung als Auftragsverarbeiter).
|
||
- Muster-Datenschutzabsatz für die Website des Händlers (`docs/funktionen/shop-datenschutz-absatz.md`): Bestellabwicklung,
|
||
WEBklar als Auftragsverarbeiter, Stripe (inkl. PayPal/Klarna über Stripe), Warenkorb im Browser-Speicher.
|
||
|
||
---
|
||
|
||
## 10. Rechtstexte von WEBklar (neu, Fassung `2026-10-05`)
|
||
|
||
1. **Leistungsbeschreibung Webshop** (`public/rechtliches/leistungsbeschreibung-webshop-2026-10-05.html` + PDF):
|
||
Leistung (Anfrage, Einrichtung, Einbau, Betrieb des Widgets, Verwaltung im Portal), ausgeschlossene Waren,
|
||
Voraussetzungen (eigenes Stripe-Konto, Unternehmer, eigene Rechtstexte), keine Rechtsberatung/Muster ohne Gewähr,
|
||
Verfügbarkeit (keine Zusage einer bestimmten Verfügbarkeit über das Hosting hinaus), Haftung (Begrenzung wie
|
||
bestehende LB; keine Haftung für Inhalte, Preise und Rechtstexte des Händlers), Preis 299 € einmalig, Laufzeit/Ende
|
||
(mit dem Website-Betrieb; Abschalten, 90 Tage Export, Löschung), Änderungen.
|
||
2. **AVV Webshop** (Geschwister AVV Terminplaner): Gegenstand, Art der Daten (Käufer: Name, E-Mail, Telefon, Liefer- und
|
||
Rechnungsadresse, Bestellung, Widerrufe), Betroffene (Käufer), Unterauftragsverarbeiter Hetzner, Löschfristen genau wie
|
||
gebaut, TOM-Anlage, Weisungen über das Portal.
|
||
3. Gemeinsame **Widerrufsbelehrung Funktionen** bleibt (bestehende Datei der aktuellen Fassung).
|
||
4. **Kanzlei-Fragen** `docs/rechtliches/webshop-kanzlei-paket.md` (Muster-AGB, Muster-Widerrufsbelehrung inkl. 356a,
|
||
Zustimmungstexte, LB-Haftung, Rolle Plattform/ZAG) – vor `on`, nicht vor `pilot`.
|
||
|
||
---
|
||
|
||
## 11. Livegang (je Schritt mit Freigabe des Inhabers; erst nach TP2-Livegang)
|
||
|
||
1. Stripe-Dashboard WEBklar: **Connect aktivieren** (Plattformprofil: „Plattform, Verkäufer verkaufen eigene Waren,
|
||
Direct Charges, keine Gebühr“), Branding (Name/Icon/Farbe für das Onboarding).
|
||
2. `node scripts/shop-setup.mjs` (Collections) und `scripts/stripe-products.mjs --anlegen` (shopSetup).
|
||
3. Connect-Webhook anlegen (`scripts/shop-setup.mjs --webhook` oder Dashboard, Ereignisse aus 5.3), Geheimnis als
|
||
`STRIPE_CONNECT_WEBHOOK_SECRET` in `/home/webklar/apps/.env`, Compose-Zeilen `SHOP_ENABLED`, `SHOP_PILOT_EMAILS`.
|
||
4. Push `feature/webshop` → `main` (nach TP2), WOMS-Zweig → `test`.
|
||
5. Pilot mit Testkonto: Anfrage, Freigabe, Bestellung (Test-Coupon), Einbau auf `test-website`, Stripe-Konto im
|
||
Testmodus? (Live-Konto des Pilots; Testkauf 1 € mit Erstattung), Widerruf testen.
|
||
6. Kanzlei-Paket → Befund einarbeiten → `SHOP_ENABLED=on`.
|
||
|
||
---
|
||
|
||
## 12. Entscheidungen (Claude, 2026-10-05, ohne Rückfrage auf Wunsch des Inhabers)
|
||
|
||
| Nr. | Entscheidung | Begründung |
|
||
|---|---|---|
|
||
| W1 | Nur Stripe; PayPal/Klarna/Wallets über Stripe Checkout | eine Integration, gleiche Zahlungsarten |
|
||
| W2 | Stripe Connect, eigenes Konto des Händlers, Direct Charges, keine Plattformgebühr | kein Fremdgeld bei WEBklar (ZAG), Händler hat volles Dashboard |
|
||
| W3 | Schalter `SHOP_ENABLED` off/pilot/on | wie Terminplaner |
|
||
| W4 | „Auf Anfrage“ = Anfrage → Team-Freigabe → Bestellung | Prüfung erlaubter Waren, Wunsch des Inhabers |
|
||
| W5 | 299 € einmalig, kein Abo nötig, keine laufende Gebühr | „einmalige Kostensache“ |
|
||
| W6 | Verwaltung ab `setup` nutzbar | Shop ist zum Einbau fertig befüllt |
|
||
| W7 | Bestellnummer zufällig `JJ-XXXXXX` | keine Zählung/Sperre nötig |
|
||
| W8 | Händler-Rechtstexte als Muster + Pflichtbestätigung, keine Rechtsberatung | RDG, Verantwortung beim Verkäufer |
|
||
| W9 | Vertragsschluss im Widget mit „Zahlungspflichtig bestellen“, Stripe nur Zahlung | § 312j Abs. 3 BGB sicher erfüllt |
|
||
| W10 | Widerrufsfunktion (§ 356a BGB) im Widget + Standalone-Seite | seit 19.06.2026 Pflicht |
|
||
| W11 | Käuferdaten 3 Jahre nach Bestelljahr anonymisieren, Shopdaten 90 Tage nach Abschalten löschen | Datensparsamkeit, Gewährleistung |
|
||
| W13 | Hinweis auf die gesetzliche Gewährleistung (VO (EU) 2025/1960) in Kasse und Bestätigung | Pflicht seit 27.09.2026 |
|
||
| W14 | Stripe Accounts v1 mit `controller` statt v2 | v1 weiter unterstützt, passt zu stripe-node 19, kein Plattform-Umbau |
|
||
| W12 | Versand nur DE/AT, keine digitalen Produkte, keine Gutscheine | Umfang V1, Recht (digitale Inhalte, Mehrzweckgutscheine) |
|