Shop Review B M-2: Widerrufsfunktion laeuft auch bei SHOP_ENABLED=off und ausserhalb des Pilotkreises
GET /:siteKey liefert dann die Widerrufsseite (nurWiderruf), POST /:siteKey/widerruf samt Preflight geht durch, solange die Funktion active oder disabled ist. Bestellen, Pruefen, Status und Bilder bleiben gesperrt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
08d837d690
commit
b2072e4e7c
3 files changed
+84
-24
No files matched your search
@@ -26,9 +26,11 @@ import { darfAnfrage, ipSchluessel, zaehle } from '../services/termine/limits.js
|
||||
* Fehlern verlassen den Server nie (unbekannter Fehler => 503 "Gerade nicht erreichbar").
|
||||
* - Antworten enthalten nie customerId, projectId, featureId, $id, Stripe-IDs oder E-Mail-Adressen ausser der oeffentlichen
|
||||
* Anbieter-Adresse des Haendlers.
|
||||
* - Unbekannter siteKey, Modus off, Pilotmodus fuer einen Kunden ausserhalb der Pilotliste oder eine Funktion, die nicht active
|
||||
* oder disabled ist: immer 200 { aktiv: false } (kein Orakel). Eine abgeschaltete Funktion (disabled) antwortet mit
|
||||
* { aktiv: true, offen: false, nurWiderruf: true, ... }, damit Kaeufer den Widerruf weiter erklaeren koennen.
|
||||
* - Unbekannter siteKey oder eine Funktion, die nicht active oder disabled ist: immer 200 { aktiv: false } (kein Orakel). Bestellen,
|
||||
* Pruefen, Status und Bilder zusaetzlich nur bei Modus on bzw. Pilotmodus mit Besitzer auf der Pilotliste. Eine abgeschaltete
|
||||
* Funktion (disabled), Modus off und ein Besitzer ausserhalb der Pilotliste antworten beim Lesen mit
|
||||
* { aktiv: true, offen: false, nurWiderruf: true, ... }, und POST /widerruf (samt Preflight) geht weiter: die Widerrufsfunktion
|
||||
* ist gesetzliche Pflicht (§ 356a BGB), solange es Bestellungen geben kann.
|
||||
* - CORS: GET fuer alle (*). POST und Preflight nur von Seiten des Betriebs (analyseDomains des Projekts), des Portals
|
||||
* (config.publicBaseUrl) und gleicher Herkunft (Host-Header), Origin in kanonischer Schreibweise; sonst 403. Meldungen ohne
|
||||
* Daten (429, 503, aktiv:false) tragen `*`, bei geprueftem POST die Origin.
|
||||
@@ -234,6 +236,20 @@ async function erreichbarerShop(siteKey, now) {
|
||||
return ctx
|
||||
}
|
||||
|
||||
/**
|
||||
* Zugang fuer die Widerrufsfunktion (§ 356a BGB): sie bleibt erreichbar, solange die Funktion active oder disabled ist, auch bei
|
||||
* SHOP_ENABLED=off und wenn der Besitzer nicht (mehr) zum Pilotkreis gehoert; die gesetzliche Pflicht laeuft mit den Bestellungen
|
||||
* weiter. Bestellen bleibt gesperrt (`bestellbar`). Bei off wird die Pilotliste gar nicht erst gelesen.
|
||||
* @returns {Promise<{ ctx: object|null, bestellbar: boolean }>}
|
||||
*/
|
||||
async function widerrufZugang(siteKey, now) {
|
||||
const ctx = await shopPerSiteKey(siteKey, { now })
|
||||
if (!ctx) return { ctx: null, bestellbar: false }
|
||||
const modus = shopModus()
|
||||
const bestellbar = modus === 'on' || (modus === 'pilot' && shopFuerKunde(await besitzerEmail(ctx)))
|
||||
return { ctx, bestellbar }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Konfiguration
|
||||
|
||||
const text = (wert) => (typeof wert === 'string' ? wert.trim() : '')
|
||||
@@ -298,7 +314,7 @@ router.get(
|
||||
route(async (req, now) => {
|
||||
const { siteKey } = req.params
|
||||
if (!darfAnfrage(req.ip, now)) throw zuViele(60)
|
||||
const ctx = await erreichbarerShop(siteKey, now)
|
||||
const { ctx, bestellbar } = await widerrufZugang(siteKey, now)
|
||||
if (!ctx) return aktivFalse(req)
|
||||
if (!zaehle(`shop:${siteKey}`, SHOP_JE_MINUTE, MINUTE_MS, now)) throw zuViele(60)
|
||||
|
||||
@@ -306,8 +322,8 @@ router.get(
|
||||
const widerrufUrl = `${basisUrl()}/shop/${siteKey}/widerruf`
|
||||
const token = formToken(siteKey, now)
|
||||
|
||||
// Abgeschaltet: nichts mehr zu kaufen, aber der Widerruf bleibt erreichbar
|
||||
if (ctx.doc.state === 'disabled') {
|
||||
// Abgeschaltet (Funktion disabled, SHOP_ENABLED=off oder ausserhalb des Pilotkreises): nichts mehr zu kaufen, aber der Widerruf bleibt erreichbar
|
||||
if (ctx.doc.state === 'disabled' || !bestellbar) {
|
||||
return {
|
||||
body: {
|
||||
aktiv: true,
|
||||
@@ -404,7 +420,8 @@ router.options(
|
||||
route(async (req, now, cors) => {
|
||||
if (!POST_ROUTEN.has(req.params.was)) throw nichtGefunden()
|
||||
if (!darfAnfrage(req.ip, now)) throw zuViele(60)
|
||||
const ctx = await erreichbarerShop(req.params.siteKey, now)
|
||||
// Widerruf: auch ohne Bestellmodus erreichbar (siehe widerrufZugang); alle anderen Routen nur bei erreichbarem Shop
|
||||
const ctx = req.params.was === 'widerruf' ? (await widerrufZugang(req.params.siteKey, now)).ctx : await erreichbarerShop(req.params.siteKey, now)
|
||||
if (!ctx) return vorabantwort('*')
|
||||
pruefeHerkunft(req, cors, ctx.project)
|
||||
return vorabantwort(cors.erlaubt)
|
||||
@@ -433,7 +450,7 @@ router.post(
|
||||
async (req, now, cors) => {
|
||||
const { siteKey } = req.params
|
||||
if (!darfAnfrage(req.ip, now)) throw zuViele(60)
|
||||
const ctx = await erreichbarerShop(siteKey, now) // active und disabled: der Widerruf bleibt moeglich
|
||||
const { ctx } = await widerrufZugang(siteKey, now) // active und disabled, unabhaengig von SHOP_ENABLED und Pilotliste: der Widerruf bleibt moeglich
|
||||
if (!ctx) return aktivFalse(req)
|
||||
pruefeHerkunft(req, cors, ctx.project)
|
||||
const b = req.body
|
||||
|
||||
Reference in new issue
Block a user