Abo-Baustein (Kernversion) und Platzhalter fuer die Bestellpruefung

aboStatus.js: laeuft(customerId) (Wartungs-Abo, productKey || 'wartung', Status in
ACTIVE_STATUSES), bestellbar(customerId) mit den Gruenden kein_abo, gekuendigt,
nicht_aktiv in dieser Pruefreihenfolge und aboMemo() (je Aufruf und Kunde nur eine
Lesung). Fail-closed: ein Lesefehler wirft, nie false oder ok:false.

termine/fragebogen.js: Platzhalter bestellpruefung() mit der endgueltigen Signatur,
liefert []; Block C ersetzt ihn.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 12:12:09 +00:00
1 parent 786df1230e
commit d568a3798d
4 files changed
+258

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
// KERNVERSION (Block B) – Block E baut aus
import { ACTIVE_STATUSES, getCurrentSubscription } from './stripeBilling.js'
// Abo-Baustein fuer Funktionen, die ein laufendes Wartungs-Abo voraussetzen (Terminplaner; die Analyse zieht in
// Block E nach). Fail-closed: ein Lesefehler wirft, er wird nie zu false bzw. ok:false. Wer die Fehler abfaengt,
// entscheidet selbst, was dann gilt (Bestellung ablehnen, Kunde im Listenlauf ueberspringen, nie abschalten).
// Wartungs-Abo: Altbestand ohne productKey gilt als Wartung (wie routes/abo.js productKeyOf), ein anderer
// Schluessel (hosting, mailStorage, unbekannt) nicht. Status wie im Portal: active, trialing, past_due.
function laeuftAus(sub) {
return Boolean(sub) && (sub.productKey || 'wartung') === 'wartung' && ACTIVE_STATUSES.has(sub.status)
}
// Die Gruende gelten in dieser Reihenfolge fuer alle Bloecke: kein laufendes Wartungs-Abo => kein_abo; Kuendigung
// vorgemerkt (cancelAtPeriodEnd oder cancelAt, auch bei trialing und past_due) => gekuendigt; Status nicht active
// (trialing, past_due) => nicht_aktiv.
function bestellbarAus(sub) {
if (!laeuftAus(sub)) return { ok: false, grund: 'kein_abo' }
if (sub.cancelAtPeriodEnd || sub.cancelAt) return { ok: false, grund: 'gekuendigt' }
if (sub.status !== 'active') return { ok: false, grund: 'nicht_aktiv' }
return { ok: true }
}
/** Hat der Kunde ein laufendes Wartungs-Abo? Wirft bei Lesefehler. */
export async function laeuft(customerId) {
return laeuftAus(await getCurrentSubscription(customerId))
}
/** Darf der Kunde jetzt bestellen? { ok: true } oder { ok: false, grund: 'kein_abo' | 'gekuendigt' | 'nicht_aktiv' }. Wirft bei Lesefehler. */
export async function bestellbar(customerId) {
return bestellbarAus(await getCurrentSubscription(customerId))
}
/**
* Zwischenspeicher fuer einen Lauf (eine Anfrage, ein Listenlauf): { laeuft, bestellbar } mit denselben Antworten
* wie oben, aber das Abo jedes Kunden wird je aboMemo()-Aufruf nur einmal gelesen (auch bei gleichzeitigen Fragen,
* und fuer laeuft und bestellbar gemeinsam). Ein Lesefehler wird nicht gemerkt: der naechste Aufruf liest neu.
*/
export function aboMemo() {
const gelesen = new Map()
const lade = (customerId) => {
if (!gelesen.has(customerId)) {
gelesen.set(
customerId,
getCurrentSubscription(customerId).catch((err) => {
gelesen.delete(customerId)
throw err
})
)
}
return gelesen.get(customerId)
}
return {
laeuft: async (customerId) => laeuftAus(await lade(customerId)),
bestellbar: async (customerId) => bestellbarAus(await lade(customerId)),
}
}
+11
View File
@@ -0,0 +1,11 @@
// STUB (Block B) – ersetzt Task C2 (bestellpruefung); Task C1 legt in dieser Datei den Rest des Fragebogen-Dienstes an
/**
* Bestellpruefung des Terminplaners: Liste der offenen Punkte `[{ schritt, text }]`, leer = bestellbar.
* Die Signatur ist endgueltig (Block C ersetzt nur den Rumpf): `mitAbo !== false` schaltet den Abo-Punkt (h)
* dazu, den nur die Vorschau (GET /api/termine/einstellungen) mit `abo` anfordert; der Checkout ruft mit
* `mitAbo: false` und ohne `abo`. Der Platzhalter meldet nichts offen.
*/
export function bestellpruefung({ einstellung, fragebogen, feature, abo, mitAbo = true } = {}) {
return []
}