32 KiB
module, title, phase, status, last_updated, session_source, related_lps, spec_style
| module | title | phase | status | last_updated | session_source | related_lps | spec_style | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 28 | Subscriptions / Billing | 1 | detail-spec-draft | 2026-05-08 | brainstorming-session-2026-05-07-2206.md |
|
requirements-pur |
Modul 28 — Subscriptions / Billing
Detail-Spec gemäß LP-9. Subscription-Engine und Billing-Anbindung an Stripe. Pricing-Modell selbst ist geparkt — die Engine wird so flexibel gebaut, dass konkrete Plans später konfigurativ definiert werden können (LP-7 + Brainstorming „Geparkt für später"). Apple-Compliance (LP-8): keinerlei Pricing-/Upgrade-UI in der App selbst — Self-Service-Signup ausschließlich über Marketing-Site
hivegrid.de.
Themenblöcke
A. Datenmodell + Pricing-Konfiguration
B. Subscription-Lifecycle + Status-Übergänge
C. Self-Service-Signup-Flow + Stripe-Checkout
D. Seat-Modell (Büro vs. Monteur)
E. Trial-Phase
F. Dunning + Past-Due
G. Downgrade-Read-Only-Middleware
H. Frozen-Modus (User-Pause)
I. Service-Metering (Trigger-Volumen, AI-Token)
J. AI-Credits-System
K. Setup-Fee-Handling
L. Demo-Buchung (extern)
M. Routes + In-App-Sicht
N. Permissions-Anwendung
O. Klartext-Hinweise/-Fehler
P. Phase 2 / Backlog
Block A — Datenmodell + Pricing-Konfiguration
Grundprinzip
Engine ist konfigurations-getrieben. Plattform-Admin definiert Produkte, Preise, Features, Usage-Tiers über Datenstrukturen — Code kennt die konkreten Plans nicht. Damit kann das Pricing-Modell jederzeit angepasst werden, ohne Code-Releases.
Entitäten
SubscriptionProduct
Ein Plan-Produkt (z.B. Cloud, Cloud+, Enterprise — Namen werden später festgelegt).
id(UUID),created_at,updated_atcode(technischer Schlüssel, unique, snake_case — z.B.cloud_basic)display_name(User-sichtbar, locale-abhängig — DE Pflicht, weitere via Modul 30)description(Markdown)tier_order(Integer — für Vergleich „höher/niedriger" bei Up-/Downgrade)is_active(Boolean — neue Tenants können nur aktive Produkte wählen)is_self_service_eligible(Boolean —Enterprisez.B. nur per Plattform-Admin zuweisbar)setup_fee_required(Boolean — siehe Block K)trial_default_days(Integer, 5-7 — siehe Block E; null = kein Trial)requires_manual_activation(Boolean — Vertrieb gibt Trial frei, siehe Block E)
SubscriptionPrice
Konkreter Preispunkt (Stripe-Price-Mapping).
id,subscription_product_id(FK)stripe_price_id(string, eindeutig)currency(ISO 4217 —EURDefault)interval(Enum:month | year | one_time—one_timefür Setup-Fee)interval_count(Integer, default 1)unit_amount_cents(Integer)tax_behavior(Enum:inclusive | exclusive— Stripe-konform)is_activevalid_from,valid_until(für preisliche Übergangsphasen)
SubscriptionFeature
Ein technisches Feature-Flag (siehe M27 Block F.1 für Loader-Convention).
id,code(unique, snake_case — z.B.kwp_integration,automation_advanced_branches,email_marketing,ai_assistant)display_name,descriptioncategory(Enum:module | sub_feature | integration | ai)
SubscriptionProductFeature (M:N)
Welche Features welcher Plan enthält.
subscription_product_id,subscription_feature_idquota_value(JSON, optional — z.B.{"max_users": 10}odernullfür unbegrenzt)
SubscriptionUsageBasedPrice
Definition eines Verbrauchs-basierten Preisteils (Trigger-Volumen, AI-Token).
id,subscription_product_idmetric_code(Enum:automation_trigger | ai_token | sms_outbound | other)display_nameincluded_amount(Integer — im Plan inklusive)overage_pricing_strategy(Enum:tier | per_unit | block)unit_label(z.B.1 Trigger,1.000 Token)
SubscriptionUsageBasedTier
Stufen-Tarife für Überschreitungen.
id,subscription_usage_based_price_idfrom_amount,to_amount(Integer —to_amountnull = bis unendlich)unit_amount_centsflat_amount_cents(Pauschale für Block-Strategie)
TenantSubscription
Aktive Subscription pro Tenant.
id,tenant_id(unique, M:1)status(Enum:trial | active | past_due | cancelled | frozen— siehe Block B)subscription_product_id(FK)stripe_customer_id,stripe_subscription_idstarted_at,current_period_start,current_period_endtrial_start,trial_endcancel_at_period_end(Boolean)cancelled_at,cancellation_reason(optional)frozen_since(Timestamp — siehe Block H)setup_fee_paid_at(Timestamp, nullable)last_dunning_attempt_at,dunning_attempt_countcreated_by_user_id,created_via(Enum:self_service | platform_admin | api)
TenantSubscriptionProduct
Snapshot der gewählten Produkt-Konfiguration zum Zeitpunkt der Buchung (für Audit + Preisstabilität).
id,tenant_subscription_idsubscription_product_id(FK auf das Produkt-Original)snapshot_data(JSONB — kompletter Stand bei Buchung)valid_from,valid_until
TenantSubscriptionProductPrice
Welche SubscriptionPrice-Einträge dem Tenant zugeordnet sind (Plan-Preis + Setup-Fee + ggf. Add-Ons).
id,tenant_subscription_id,subscription_price_idquantity(Integer — z.B. Seat-Anzahl)is_metered(Boolean — siehe Usage-Records)valid_from,valid_until
TenantSubscriptionUsageRecord
Verbrauchs-Datensatz für metered Pricing.
id,tenant_subscription_id,metric_code,period_start,period_endusage_amount(Integer)reported_to_stripe_at(Timestamp, nullable)created_at- Indexe auf
(tenant_subscription_id, metric_code, period_start)
CheckoutSessionStatus
Tracking des Stripe-Checkout-Flows pro Tenant-Anlage.
id,tenant_id(FK),stripe_checkout_session_idstatus(Enum:pending | completed | expired | failed)intent(Enum:initial_signup | plan_change | reactivate_from_frozen | reactivate_from_past_due)created_at,completed_at,expires_aterror_message(string)redirect_url_success,redirect_url_cancel
Credit
AI-Credit-System (siehe Block J). Token-Quota mit Carry-Over-Regeln.
id,tenant_idcredit_type(Enum:ai_token | automation_trigger_bonus | other)amount(Integer — positive = Guthaben, negative = Verbrauch)source(Enum:plan_inclusion | manual_grant | promo_code | overage_purchase | refund)valid_from,valid_until(optional — Plan-Inclusion verfällt am Period-Ende)balance_after(Integer — gerechnete Saldo nach diesem Eintrag)created_at,created_by_user_id(NULL für System)notes(Klartext für Audit)
Indizes (Performance)
TenantSubscription: unique auftenant_id, Index aufstatus,current_period_end,dunning_attempt_countTenantSubscriptionUsageRecord: Composite-Index auf(tenant_subscription_id, metric_code, period_start)Credit: Index auf(tenant_id, credit_type, created_at DESC)für Saldo-Berechnung
Sonderfälle / Constraints
TenantSubscriptionist nicht RLS-gegated (App-Code permission-gegated, da Plattform-Admin alle Tenants sehen muss — siehe M27 A.4)SubscriptionProduct/Price/Featuresind global (keintenant_id) — sie sind Plattform-KonfigurationCreditist tenant-scoped mit RLS- Soft-Delete für
SubscriptionProductüberis_active=false(alte Tenants behalten Snapshot viaTenantSubscriptionProduct)
Block B — Subscription-Lifecycle + Status-Übergänge
Status-Werte (entsprechen M27 A.5 Tenant-Status-Werten — TenantSubscription.status und Tenant.status sind synchronisiert):
trial → Self-Service-Signup-Phase, Vertrieb-freigegeben für 5-7 Tage
active → Bezahlend, voller Funktionsumfang
past_due → Stripe-Zahlung gescheitert, Read-Only (siehe Block G)
cancelled → User-Kündigung wirkt zum Period-Ende oder sofort
frozen → User pausiert Workspace, reduzierte Gebühr, Read-Only (siehe Block H)
Übergangs-Tabelle
| Von | Nach | Trigger |
|---|---|---|
trial |
active |
Trial-Konversion: Stripe-Checkout-Setup erfolgreich abgeschlossen |
trial |
cancelled |
Trial läuft ab ohne Konversion → Tenant wird zu deletion_pending (M27) |
active |
past_due |
Stripe-Webhook invoice.payment_failed |
past_due |
active |
Nachzahlung erfolgreich (Stripe-Webhook invoice.payment_succeeded) |
past_due |
cancelled |
Nach 14 Tagen + 3 Dunning-Versuchen erfolglos → Stripe gibt subscription.deleted |
active |
frozen |
User klickt „Workspace pausieren" + bestätigt reduzierten Plan |
frozen |
active |
User klickt „Reaktivieren" + Standard-Plan-Checkout |
frozen |
past_due |
Reduzierte Gebühr scheitert |
* |
cancelled |
User klickt „Tenant kündigen" — wirkt zum Period-Ende, oder sofort bei Trial |
cancelled |
active |
User zieht Kündigung innerhalb 30 Tagen zurück (Tenant noch nicht hard-deleted) |
Übergangs-Hooks (Side-Effects)
Bei jedem Übergang wird ausgelöst:
- Audit-Log-Eintrag mit alt/neu Status, Trigger-Quelle, Stripe-Event-ID
- Activity-Stream-Eintrag (
subscription_status_changed) - Tenant-Status-Synchronisation (M27 A.5)
- Notification an Tenant-Admin (Email + In-App via M22)
- Bei Übergang in
past_due/cancelled/frozen: Read-Only-Middleware-Cache invalidieren
Block C — Self-Service-Signup-Flow + Stripe-Checkout
Signup-Pfad (extern → intern)
Externer Pfad — siehe M27 A.1:
- Marketing-Site
hivegrid.de/signup(Next.js, extern, nicht Teil dieser Codebase) - Leitet zu
app.hivegrid.de/signupfür Account-Erstellung - Nach Account-Anlage: automatischer Tenant-Anlage-Schritt + Stripe-Checkout
In-App-Signup-Schritte (/signup)
- Auth-Account-Anlage (siehe M27 B.1) — Email, Pwd, AGB, DSGVO, AVV
- Tenant-Daten — Firmenname, Slug, Branche, Größe (siehe M27 A.1)
- Plan-Auswahl mit
is_self_service_eligible=true-Filter - Stripe-Checkout-Session-Erstellung mit
intent=initial_signup - Redirect auf Stripe-Checkout (extern bei Stripe gehostet)
- Nach Erfolg →
/{tenant}/onboarding/setup/(siehe M29)
Checkout-Konfiguration
- Stripe-Customer mit Tenant-
idalsmetadata.tenant_id - Subscription-Setup mit Trial (siehe Block E)
- Steuer-Behandlung: Stripe Tax aktiv, EU-Reverse-Charge-Logik
- Zahlungsmethoden: SEPA-Lastschrift (Default DE/EU), Kreditkarte
- DPA (Auftragsverarbeitungsvertrag): wird elektronisch im Signup unterzeichnet (M27 A.1 — AVV-Akzeptanz-Checkbox)
- EU-Entity-Präferenz: Stripe Ireland Ltd. (Stripe Payments Europe DAC — EU-Vertragspartner)
Stripe-Webhooks (Pflicht-Handling)
| Webhook-Event | Wirkung in HiveGrid |
|---|---|
checkout.session.completed |
CheckoutSessionStatus.status = completed, Trial-Aktivierung oder direkter Active-Wechsel |
invoice.created |
Rechnung lokal als Referenz speichern (Customer-Portal lädt Original aus Stripe) |
invoice.payment_succeeded |
Falls past_due → active-Übergang triggern |
invoice.payment_failed |
past_due-Übergang triggern, Dunning-Counter erhöhen |
customer.subscription.updated |
Plan-Wechsel propagieren (Snapshot aktualisieren) |
customer.subscription.deleted |
cancelled-Übergang triggern |
customer.subscription.trial_will_end |
3 Tage vor Trial-Ende: Notification + In-App-Banner |
Idempotenz
Webhook-Handler Pflicht-idempotent über stripe_event_id als Unique-Constraint in einer StripeWebhookLog-Tabelle (Schreib-Idempotenz, AV #8).
Block D — Seat-Modell
Spiegelt LP-2 (Büro vs. Baustelle als kontextuelle Modi).
Seat-Typen
| Typ | Inhalt |
|---|---|
| Büro-Seat | Vollzugang zur Plattform, Default-UI-Modus „Büro", alle Module zugreifbar gemäß Permissions |
| Monteur-Seat | Eingeschränkter Zugang, Default-UI-Modus „Baustelle", reduzierte Modul-Sicht (Projekt-Detail, Tasks, Acceptance, Foto-Forms, Chat in eigenen Channels, HR-Hub eigene Doks) — siehe M27 C.2 Monteur-Rolle |
Pflicht-Verknüpfung mit Rolle
- Jeder Tenant-User hat genau einen Seat-Typ zur aktiven Zeit
- Bei User-Einladung (M27 D.1) wählt Tenant-Admin Seat-Typ als Pflichtfeld
- Seat-Typ ist Lizenz-Konzept, Rolle ist Permission-Konzept — beide separat
- Ein Monteur-Seat kann nur Rollen mit Default-UI-Modus
Baustellezugewiesen bekommen (Validierungs-Regel) - Ein Büro-Seat kann jede Rolle bekommen
Seat-Counting + Stripe-Sync
- Bei Einladung/Aktivierung eines neuen Users: Seat-Counter hochzählen, Stripe-
Subscription.items[].quantityupdaten - Bei Deaktivierung/Entfernung: Seat-Counter runter, Stripe-Update zum Period-Ende (proration nach Tenant-Admin-Setting)
- Übersicht in
/{tenant}/admin/usage/: aktuelle Seats pro Typ + verfügbare Seats laut Plan + Trend-Chart
Plan-Quotas
SubscriptionProductFeature.quota_value enthält (Beispiel):
{
"max_buero_seats": 10,
"max_monteur_seats": 50,
"max_buero_seats_overage_blocked": false
}
Bei Erreichung Quota:
- Hard-Limit (Default): Einladung blockiert, Klartext: „Plan-Limit von 10 Büro-Seats erreicht. Upgrade nötig — siehe Marketing-Site."
- Soft-Limit (Konfiguration): zusätzliche Seats werden automatisch über Usage-Based-Price abgerechnet
Block E — Trial-Phase
Eigenschaften
- Dauer: 5-7 Tage (per
SubscriptionProduct.trial_default_days) - Vertriebs-freigegeben: Vertrieb (Plattform-Admin) muss Trial manuell aktivieren (
requires_manual_activation=truefür Phase-1-Trials), um Spam zu verhindern - Voller Funktionsumfang während Trial (kein Read-Only)
- Stripe: Subscription mit
trial_endgesetzt, keine Zahlungs-Auslösung vor Trial-Ende - Zahlungsmethode: nicht zwingend zu Trial-Beginn (Vertriebs-Modus). Wird in Phase 2 für Self-Service-Trials erforderlich (User-Diskussion auslagerbar)
Trial-Aktivierungs-Flow
- User schließt Signup ab →
TenantSubscription.status = trial, abertrial_start = null - Tenant erscheint in Plattform-Admin-Liste „Trial-Anfragen" (M37)
- Vertrieb prüft + aktiviert → setzt
trial_start = now(),trial_end = now() + trial_default_days - Tenant erhält Klartext-Email: „Dein HiveGrid-Trial ist freigeschaltet. Bis [Datum] kannst du alles testen."
Trial-Ende-Flow
- 3 Tage vor Trial-Ende: In-App-Banner + Email-Notification mit Klartext und Hinweis auf Marketing-Site (kein In-App-Upgrade — LP-8)
- 1 Tag vor Trial-Ende: erneute Notification
- Trial-Ende ohne Konversion:
TenantSubscription.status = cancelled, Tenant geht indeletion_pending(30 Tage Grace, M27 A.5)
Trial-Konversion
Tenant-Admin geht auf Marketing-Site (hivegrid.de/upgrade), wählt Plan, gelangt zurück nach app.hivegrid.de/{tenant}/admin/subscription/checkout-return mit Stripe-Session-Token → CheckoutSessionStatus + Webhook-Verarbeitung → active-Übergang.
Block F — Dunning + Past-Due
Stripe-Standard-Dunning
- 3 Wiederholungs-Versuche über 14 Tage (Stripe Smart Retries oder eigene Schedule)
- Versuche typischerweise: Tag 0 (initial), Tag 3, Tag 7, Tag 14
- HiveGrid empfängt
invoice.payment_failedundinvoice.payment_succeeded-Events
Past-Due-Verhalten
- Workspace read-only (siehe Block G)
- In-App-Banner: „Zahlung fehlgeschlagen, Konto in 14 Tagen gesperrt — [Zahlungsmethode aktualisieren]" mit Direkt-Link zum Stripe-Customer-Portal
- Email-Notification an Tenant-Admins mit Klartext + Direkt-Link
- Nach 3 Fehlversuchen + 14 Tagen ohne Erfolg:
cancelled
Notification-Strategie
| Event | Empfänger | Kanal |
|---|---|---|
| Erste Zahlungs-Fehler | Tenant-Admins | Email + In-App |
| Versuch 2/3 fehlgeschlagen | Tenant-Admins | Email + In-App |
| 1 Tag vor finalem Cancel | Tenant-Admins | Email + In-App + Push (M22) |
| Final-Cancel | Tenant-Admins + Plattform-Admin | Email + Plattform-Admin-Alert |
Block G — Downgrade-Read-Only-Middleware
Wirkung
Bei Status past_due ODER frozen ODER cancelled (mit cancel_at_period_end=true und Period bereits abgelaufen):
- App rendert noch alle Daten lesend
- Schreibende Aktionen blockiert auf Middleware-Ebene
- Schreibende API-Endpoints liefern 402 Payment Required mit Klartext-Body
- Schreibende UI-Elemente (Buttons, Forms) entweder ausgeblendet oder ausgegraut mit Tooltip
Genaue Read/Write-Grenzen (analog M27 A.5 Frozen-Modus)
| Aktion | past_due / frozen / cancelled-period-over |
|---|---|
| Login (alle Rollen) | ✓ |
| Daten lesen | ✓ |
| Stammdaten-Edit, neue Datensätze | ✗ |
| Email/WhatsApp/Push senden | ✗ |
| Automationen ausführen | ✗ (eingehende Trigger werden verworfen, nicht gequeued) |
| Eingehende Webhooks | ✗ (mit 402-Response, nicht gequeued) |
| Customer-Portal-Zugang (Endkunden) | ✓ (read-only) |
| API (Read) | ✓ |
| API (Write) | ✗ (402) |
| Stripe-Customer-Portal-Zugang | ✓ (Pflicht zur Reaktivierung) |
| Plan-Wechsel / Reaktivierung | ✓ (einziger erlaubter Schreib-Pfad) |
Implementierungs-Vorgabe
- Middleware vor Route-Loader: prüft
TenantSubscription.status+is_writable_status()-Helper - Middleware vor Worker-Job-Ausführung: blockiert Automation-Worker, Notification-Worker, Webhook-Outbound-Worker bei nicht-schreibendem Status
- Cache der Status-Auflösung: 30 Sekunden (TTL), bei Stripe-Webhook explizit invalidieren
Klartext-Hinweise
- In-App-Banner pro Status (siehe M27 A.5)
- Bei Klick auf blockierten Schreib-Button: Toast „Workspace ist read-only. Status: [Klartext]. [Reaktivieren]"
Block H — Frozen-Modus (User-Pause)
Use-Case
Tenant kann Workspace pausieren statt zu kündigen (z.B. Saison-Pause bei kleinen Betrieben). Reduzierte Gebühr, Workspace read-only, jederzeit reaktivierbar — Daten bleiben.
Pause-Flow
- Tenant-Admin in
/{tenant}/admin/subscription/→ „Workspace pausieren" - Klartext-Erklärung: was bleibt, was nicht geht, welche Gebühr
- Bestätigung mit Tipp-Eingabe „PAUSIEREN" (Multi-Step-Schutz)
- Stripe: Subscription-Wechsel auf
frozen_plan_price(eigenerSubscriptionPricemit reduzierter Gebühr) TenantSubscription.status = frozen,frozen_since = now()- M27 Tenant-Status synchronisiert auf
frozen
Reduzierte Gebühr
- Konfigurations-Setting (
SubscriptionProducthat optionalenfrozen_mode_price_id) - Phase 1: Default ca. 1/10 des Standard-Plan-Preises (konkrete Preise im Pricing-Setup, geparkt)
Reaktivierungs-Flow
- User klickt In-App-Banner „Reaktivieren"
- Stripe-Checkout-Session mit
intent=reactivate_from_frozenund gewähltem Standard-Plan - Nach Erfolg:
frozen → active-Übergang
Sonderfälle
- Frozen-Tenant kann sich nicht in den anderen End-Status (
cancelled) wechseln, ohne erst zu reaktivieren — außer expliziter Kündigung mit Klartext-Hinweis - Bei Zahlungsausfall der reduzierten Gebühr:
frozen → past_due
Block I — Service-Metering
Metriken (Phase 1)
Engine baut bereits Tracking auf, konkrete Pricing-Schwellen geparkt:
| Metrik-Code | Was wird gezählt |
|---|---|
automation_trigger |
Anzahl Trigger-Auslösungen in der Automation-Engine pro Tenant pro Period (siehe Modul 17 EC-1 Hard-Limit) |
ai_token |
LLM-Token-Verbrauch (siehe Block J) — Prompt-Token + Completion-Token pro Provider, normalisiert |
sms_outbound |
SMS-Versand falls Modul 22 aktiv (Phase 2) |
whatsapp_session |
WhatsApp-Session-Kosten (Phase 2) |
Tracking-Pflichten
- Jede Automation-Trigger-Ausführung:
TenantSubscriptionUsageRecordinkrementieren (idempotent über Trigger-Run-ID) - Jeder LLM-Call: Token-Verbrauch nach Response loggen, in
Credit-Verbrauch verrechnen - Aggregation pro Period (entspricht Stripe-Subscription-Period — typisch monatlich)
Reporting an Stripe
- Bei
metric_codemitis_metered=true-Subscription-Item: Usage-Reporting an Stripe viausage_records.createam Period-Ende oder real-time (Strategie pro Metrik konfigurierbar) reported_to_stripe_at-Feld für Tracking
In-App-Sicht (/{tenant}/admin/usage/)
Zu zeigende Daten:
- Aktuelle Period mit Verbrauch pro Metrik
- Plan-Inklusiv-Menge pro Metrik
- Verbrauch in % (Progress-Indikator)
- Trend gegenüber Vorperiode
- Historische Perioden (letzte 12)
Benötigte UI-Elemente:
- Listen-/Tabellen-Anzeige der Metriken
- Detail-Drill-Down pro Metrik mit Zeitachsen-Visualisierung
- Filter nach Period
- Export der Verbrauchs-Daten als CSV
Operationen:
- Reine Lese-Sicht — keine Verbrauchs-Änderungen
- Add-On-Käufe (Trigger-Volumen-Pakete) → Phase 2
Permissions: billing.read oder tenant.admin.
Block J — AI-Credits-System
Konzept
Pro Plan inkludierte AI-Token-Quota (z.B. Cloud: 1 Mio Token/Monat — konkrete Mengen geparkt).
Saldo-Berechnung
- Saldo aus
Credit-Tabelle (Sum aller Einträge einescredit_type) - Bei Plan-Period-Start: Plan-Inclusion als positiver
Creditmitvalid_until = period_end - Bei jedem LLM-Call: negativer
Credit-Eintrag mit Token-Anzahl
Verbrauchs-Verrechnung
- LLM-Call ausgelöst via Modul 18
- Pre-Check: Saldo > erwarteter Maximal-Verbrauch?
- Ja → Call ausführen
- Nein → Klartext-Fehler: „AI-Credits aufgebraucht. Plan-Wechsel oder Add-On nötig — siehe Marketing-Site." (Apple-Compliance-konform — kein In-App-Upgrade)
- Nach Call: Token aus Response-Metadata, negativer Credit-Eintrag
Carry-Over-Regeln (Phase 1)
- Plan-Inclusion verfällt am Period-Ende (
valid_untilenforced) - Manual-Grants (Vertrieb gibt Bonus) ohne
valid_untilbleiben - Promo-Codes mit fixem
valid_until
In-App-Sicht (/{tenant}/admin/usage/ai-credits/)
Zu zeigende Daten:
- Aktueller Saldo pro
credit_type - Verbrauchs-Historie mit Quelle (welche User, welcher Modul-Aufruf)
- Hochrechnung: bei aktuellem Verbrauchs-Trend wann wäre Saldo aufgebraucht
Benötigte UI-Elemente:
- Saldo-Anzeige
- Verbrauchs-Liste mit Filter
- Trend-Chart
Permissions: billing.read.
Block K — Setup-Fee-Handling
Anwendung
- Cloud-Plan: Setup-Fee einmalig
- Cloud+-Plan: Setup-Fee einmalig, höher (Pricing geparkt)
- Enterprise: individuell
Konfiguration
SubscriptionProduct.setup_fee_required = trueaktiviert Pflicht-Setup-Fee- Separater
SubscriptionPricemitinterval=one_time - Im Stripe-Checkout: zwei Items (recurring + one_time)
Tracking
TenantSubscription.setup_fee_paid_atwird gesetzt beiinvoice.payment_succeededder ersten Rechnung- Vor Zahlung: Workspace ist im Trial- oder Pre-Active-Modus (vollfunktional, weil Trial)
- Falls Setup-Fee nicht gezahlt aber Subscription aktiv (Edge-Case): wie
past_duebehandeln
Plan-Wechsel von Cloud → Cloud+
- Falls Setup-Fee schon für Cloud bezahlt: nur Differenz berechnen
- Logik:
effective_setup_fee = neuer_plan.setup_fee - bisherige_setup_fee_summe(clamped >= 0)
Block L — Demo-Buchung (extern)
Konzept
Enterprise-Anfragen → Marketing-Site bietet Demo-Buchung über externes Tool (Calendly o.ä.). Nicht in HiveGrid integriert.
Pflicht-Properties
- Marketing-Site
hivegrid.de/demomit Calendly-Embed - Vertrieb erhält Termin, führt Demo, legt manuell Tenant über
admin.hivegrid.dean (M37) - Tenant erhält Initial-Login-Email mit Magic-Link (M27 B.2)
TenantSubscription.created_via = platform_admin
Was nicht in dieses Modul gehört
- Demo-Booking-Engine (das ist M6 Booking — wird aber nicht für Demo-Termine genutzt; M6 ist tenant-internal für Endkunden-Termine)
Block M — Routes + In-App-Sicht
Sidebar-Eintrag
Sidebar-Hauptpunkt Admin (Workspace-Admin, siehe M27 A.3) → Sub-Eintrag „Subscription".
Routes
/{tenant}/admin/subscription/ → Subscription-Übersicht
/{tenant}/admin/subscription/plan-info/ → Plan-Details (Read-Only)
/{tenant}/admin/subscription/checkout-return/ → Stripe-Checkout-Return-Handler
/{tenant}/admin/usage/ → Verbrauchs-Übersicht (Trigger, AI-Token, Seats)
/{tenant}/admin/usage/ai-credits/ → AI-Credits-Detail
/{tenant}/admin/billing/ → Stripe-Customer-Portal-Embed (Rechnungen + Zahlungsmethode)
/admin/subscription/ — Übersicht
Zu zeigende Daten:
- Aktueller Plan-Name + Status (Klartext)
- Period-Start + Period-Ende
- Trial-Status falls aktiv
- Seat-Übersicht pro Typ
- Inklusiv-Quotas pro Metrik mit aktuellem Verbrauch
Benötigte UI-Elemente:
- Read-Only-Anzeige aller Plan-Eigenschaften (KEIN „Upgrade"-Button — LP-8)
- Hinweis-Box: „Plan-Wechsel über deinen Account-Manager oder hivegrid.de" mit Link nach extern (öffnet in neuem Tab)
- Pause-Button (Frozen-Modus, siehe Block H)
- Kündigen-Button
Operationen:
- Workspace pausieren (Block H)
- Tenant kündigen (M27 A.5)
- Status anzeigen
/admin/usage/
Siehe Block I.
/admin/billing/
Stripe-Customer-Portal-Embed (Stripe-gehostet, IFrame oder Redirect):
- Rechnungs-Historie (PDF-Download)
- Zahlungsmethode aktualisieren
- Steuer-Daten / USt-Id editieren
Permission: billing.read für Anzeige, tenant.admin für Zahlungsmethoden-Änderung.
Was NICHT in der App ist (LP-8)
- Pricing-Tabelle / Plan-Vergleich
- Upgrade-Buttons
- Add-On-Käufe (Phase 2 evtl. via Marketing-Site)
- Promotional-Banner für höhere Plans
- Plan-Wechsel-UI
Block N — Permissions-Anwendung
Permission-IDs
| Permission | Erlaubt |
|---|---|
billing.read |
Subscription-Übersicht, Usage, Rechnungen einsehen |
tenant.admin |
Plan-Wechsel, Pause/Reaktivierung, Kündigung, Zahlungsmethoden, Seat-Management |
Bundle-Mapping (Variante 2)
| Bundle | Atomare Permissions |
|---|---|
billing.none |
(keine) |
billing.read |
billing.read |
billing.admin |
billing.read + tenant.admin-Sub-Permissions für Subscription |
Anwendung in Routes
/{tenant}/admin/subscription/→billing.readodertenant.admin/{tenant}/admin/usage/→billing.readodertenant.admin/{tenant}/admin/billing/→billing.readodertenant.admin- Pause-/Kündigen-/Reaktivierung-Aktion →
tenant.admin
Anwendung in Action-Buttons
- Workspace pausieren:
tenant.admin - Workspace reaktivieren:
tenant.admin - Tenant kündigen:
tenant.admin(zusätzlich Last-Tenant-Admin-Schutz, M27 D.4) - Verbrauchs-Daten exportieren:
billing.read
Plattform-Admin-Sicht (M37)
- Sicht auf alle
TenantSubscription-Datensätze - Manuelle Trial-Aktivierung (
requires_manual_activationFlow) - Manuelle Subscription-Anlage für Enterprise
- Manuelle Credit-Grants (Bonus / Promo-Codes)
- Permission:
platform.billing.admin
Block O — Klartext-Hinweise/-Fehler
Pflicht-Texte (LP-10 + AV #1):
- Trial-Aktivierungs-Email: „Dein HiveGrid-Trial ist freigeschaltet. Bis [Datum] kannst du alles testen. Bei Fragen: [Support-Link]"
- Trial-Ende-3-Tage-Warning: „Dein Trial endet in 3 Tagen am [Datum]. Plan wählen unter hivegrid.de"
- Trial-abgelaufen: „Dein Trial ist abgelaufen. Workspace ist read-only. Plan wählen unter hivegrid.de"
- Past-Due-Initial: „Zahlung gescheitert für Rechnung vom [Datum]. Wir versuchen es in [N] Tagen erneut. [Zahlungsmethode aktualisieren]"
- Past-Due-Final: „Letzter Zahlungsversuch fehlgeschlagen. Konto wird in 24h gesperrt. [Zahlungsmethode aktualisieren]"
- Frozen-Banner: „Workspace pausiert seit [Datum]. Reaktivieren um wieder zu schreiben."
- Read-Only-Klick auf Schreib-Button: „Diese Aktion ist nicht möglich, weil dein Workspace pausiert ist. [Reaktivieren]"
- Seat-Quota-erreicht: „Plan-Limit von [N] [Typ]-Seats erreicht. Plan-Wechsel über hivegrid.de"
- AI-Credits-aufgebraucht: „AI-Credits für diese Period verbraucht. Verfügbar wieder am [Period-Start]. Plan-Wechsel über hivegrid.de"
- Trigger-Volumen-Limit: „Automation-Trigger-Limit erreicht für diese Period. Eingehende Trigger werden verworfen bis [Period-Start]. Plan-Wechsel über hivegrid.de"
- Stripe-Webhook-Verarbeitungsfehler: Plattform-Admin-Alert mit Klartext-Stack-Trace
- Plan-Wechsel-Confirmation-Email: „Plan-Wechsel auf [Plan-Name] wirksam ab [Datum]. Differenz: [Betrag] EUR."
- Setup-Fee-Hinweis im Checkout: „Bei Buchung wird zusätzlich eine einmalige Setup-Gebühr von [Betrag] berechnet."
Fehler-Cases
| Fehler | Klartext + Lösungs-Hinweis |
|---|---|
| Stripe-Checkout-Session abgelaufen | „Die Bezahl-Session ist abgelaufen. Bitte Plan-Wahl wiederholen." + Direkt-Link |
| Stripe-Customer-Portal nicht erreichbar | „Die Rechnungs-Verwaltung ist gerade nicht erreichbar. Bitte später erneut versuchen oder [Support-Link]" |
| Plan-Wechsel auf nicht existierenden Plan | „Der gewählte Plan ist nicht mehr verfügbar. Bitte aktuelle Plan-Liste auf hivegrid.de wählen." |
| Setup-Fee-Zahlung fehlgeschlagen, Subscription aktiv | Plattform-Admin-Alert + Tenant-Admin-Klartext: „Setup-Gebühr-Zahlung gescheitert. Bitte Zahlungsmethode prüfen — sonst wird Konto in 14 Tagen gesperrt." |
| Webhook-Signature ungültig | 401-Response, Plattform-Admin-Alert (möglicher Angriff) |
| Tenant kündigt während Trial | Klartext: „Dein Trial wurde beendet. Workspace wird in 30 Tagen gelöscht — [Zurückziehen]" |
Block P — Phase 2 / Backlog
Bewusst NICHT in Phase 1 (LP-8 + Apple-Compliance):
- In-App-Subscription-Self-Service (Plan-Wechsel direkt in der App ohne Marketing-Site-Umweg) — wartet auf App-Store-Compliance-Klärung
- Add-On-Käufe (zusätzliche Trigger-Volumen-Pakete, AI-Credits-Top-Ups) — Marketing-Site oder Phase-2-In-App
- Multi-Tenant-Billing für Konzerne (mehrere Tenants in einer Stripe-Subscription gebündelt)
- Custom-Pricing-Tier per Plattform-Admin (individuelle Preisverhandlung Enterprise)
- Promo-Code-System (Discount-Codes im Checkout)
- Annual-Billing-Toggle mit konfigurierbarem Jahres-Rabatt
- Self-Service-Trial ohne Vertriebs-Freigabe (
requires_manual_activation = false-Modus, mit Zahlungsmethoden-Pflicht) - Reseller-Modell (Agentur kauft Subscription für Endkunde-Tenant, eigene Margin)
- Usage-Forecasting (KI-basiert: „bei aktuellem Verbrauch wirst du den Plan in 7 Tagen sprengen")
- Self-Service-Cancellation-Survey mit Auto-Discount-Angebot
Architektur-Vorschriften (Querschnitt)
- AV #6 — Audit-Trail-Default: Jede Subscription-Status-Änderung, jeder Plan-Wechsel, jede Credit-Veränderung geloggt mit Diff
- AV #8 — Idempotenz pro Webhook-Verarbeitung: Stripe-Events nicht doppelt verarbeiten (über
stripe_event_id) - AV #5 — Permissions-aware-Default: Listen / Verbrauchs-Sicht filtert nach
billing.read/tenant.admin - LP-8 — Apple-Compliance: keinerlei Pricing-/Upgrade-UI in App; alle externen Links öffnen in neuem Browser-Fenster mit
noopener noreferrer - LP-10 + AV #1 — Klartext-Diagnostik: alle Subscription-/Billing-Fehler menschenlesbar mit Lösungs-Hinweis
- LP-13 — EU-Stack: Stripe-Vertragspartner ist Stripe Payments Europe DAC (Ireland), DPA elektronisch unterzeichnet
- AV #11 — Read-Only-Middleware strukturell für alle Schreib-Pfade (Routes, API, Worker)
Detail-Spec-Status: DRAFT
Erste vollständige Detail-Spec für Modul 28. Vor Bau: Verifizierung mit Noah + Pricing-Modell-Konkretisierung (separate Runde). Konkrete Stripe-Price-IDs, Webhook-Endpoint-URLs, EU-Entity-Vertragsdetails kommen mit Implementierung.
Offene Detail-Punkte (nicht-blockierend):
- Konkretes Pricing-Modell (Cloud/Cloud+/Enterprise-Preise, Trigger-Volumen-Schwellen, AI-Token-Mengen, Setup-Fee-Beträge, Frozen-Mode-Preis, Annual-Discount-%) — geparkt für eigene Runde
- Konkretes Branchen-Plan-Mapping (alle Branchen können alle Pläne, oder Branchen-spezifische Trial-Tage?)
- Genaue Stripe-Smart-Retries-Konfiguration vs. eigene Schedule
- Edge-Case bei Plan-Downgrade mid-period (Pro-rata vs. Period-End)
- Migration für Bestandskunden (Geerkens beim Go-Live) — Pricing-Modell-Festlegung erforderlich
- Konkrete Permission-IDs zusammen mit M27-Permission-Konsolidierung
- Subframe-UI-Designs (separat)