Files
Webklar-Kundenbereich/server/services/aboStatus.js
T
ClaudeandClaude Sonnet 5.5 d568a3798d 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>
2026-10-04 12:12:09 +00:00

58 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)),
}
}