Technisches Planungsdashboard · Gottfried Media × Pressmatrix

Subscription-Kit —
Architektur & Engineering-Plan

Wie digitale Magazin-Abos verkauft und Leser-Zugang provisioniert werden — Stripe als zentrale Abo-State-Machine, übersetzt in Pressmatrix-Zugang über die unveränderte External-Auth-API.

Ein Stripe-zentriertes Subscription-Kit, in dem Stripe Billing als einzige Abo-State-Machine fungiert und Supabase Edge Functions die Events idempotent (Re-Fetch + Advisory-Locks) in Reader-Zugang über die unveränderte Pressmatrix External-Auth-API übersetzen.
🧱 6 Schichten
🧩 13 Komponenten
🔀 6 Datenflüsse
🧠 9 Design-Entscheidungen
4 bereits live bewiesen
Live bewiesen (aus produktivem Referenz-Projekt) Anzupassen Neu zu bauen
📱 LeserMagazin-App
kauft / verwaltet ▼
💳 StripeCheckout · Portal · Billing
Events ▼
▲ liest Inhalte
📚 PressmatrixExternal-Auth-API
▲ authorize
⚙️ Subscription-Kit · Supabase Edge Functions stripe-webhook · checkout · portal · pmx-api · forgot-code
🗄️ Postgressubscribers · stripe_subscriptions · webhook_events
🔄 reconcile-cronSafety-Net
📧 ResendEmails
Stripe ist die Quelle der Wahrheit · das Kit übersetzt Abo-Status idempotent in Leser-Zugang · Reader-Interface bleibt unverändert
Aufbau

Architektur-Schichten

Von der externen Source of Truth (Stripe) bis zur Persistenz. Jede Schicht hat eine klar abgegrenzte Verantwortung.

💳
L1 · External Source of Truth · Stripe Billing + Stripe Dashboard
Stripe Billing als kanonische Abo-State-Machine
Stripe Billing als kanonische Abo-State-Machine; verantwortet Renewals, Dunning, Pro-Rata, gehostetes Checkout und Portal
Input
Stripe Subscription (status-Machine)Stripe Checkout (gehostet)Stripe Customer Portal (gehostet)Smart-Retries / Dunning-KonfigurationStripe API (Re-Fetch-Quelle)
Output
📥
L2 · Ingestion · Supabase Edge Function (Deno/TypeScript)
Empfängt und validiert Stripe-Events, sichert Idempotenz, holt kanonischen State und dispatcht zu Handlern
Empfängt und validiert Stripe-Events, sichert Idempotenz, holt kanonischen State und dispatcht zu Handlern
Input
stripe-webhook EndpointHMAC-SHA256 Signatur-VerifyEvent-Dedup (stripe_event_id)Advisory-Lock pro CustomerRe-Fetch-on-Event
Output
🛒
L3 · Commerce · Supabase Edge Functions + Stripe API
Initiiert Checkout mit Doppelkauf-Schutz und stellt Self-Service-Portal für Plan-Wechsel/Kündigung bereit
Initiiert Checkout mit Doppelkauf-Schutz und stellt Self-Service-Portal für Plan-Wechsel/Kündigung bereit
Input
stripe-checkout (Pre-Checkout-Prüfung)stripe-portal (Self-Service, Pro-Rata-Preview)Region-adaptive Auslieferung
Output
🔑
L4 · Access-Bridge · Supabase Edge Functions + RPCs
Uebersetzt Abo-Status in Reader-Zugang
Uebersetzt Abo-Status in Reader-Zugang; unverändertes External-Auth-Interface zur Pressmatrix-App
Input
pmx-api /authenticatepmx-api /authorizecheck_access RPCverify_access_code RPCforgot-code
Output
🗄️
L5 · Persistence · Supabase PostgreSQL (RLS, service_role)
Lokaler Cache/Audit der Stripe-State-Machine, stabile Reader-Identität, Idempotenz- und Sicherheits-Constraints
Lokaler Cache/Audit der Stripe-State-Machine, stabile Reader-Identität, Idempotenz- und Sicherheits-Constraints
Input
subscribers (stripe_customer_id, access_code_hash, user_token)stripe_subscriptions (Status-Cache)stripe_webhook_events (Dedup + raw_payload)issues / issue_accesssecurity_eventsUnique-Constraints + Advisory-Locks
Output
🔄
L6 · Consistency & Notifications · pg_cron + Resend
Konvergenz gegen Stripe (Safety-Net) und transaktionale Kommunikation
Konvergenz gegen Stripe (Safety-Net) und transaktionale Kommunikation
Input
reconcile-cron (Re-Fetch aktiver Abos)Log-Retention (90 Tage)Resend Email-VersandEmail-Templates
Output
Verhalten

Zentrale Datenflüsse

Was bei jedem relevanten Lebensereignis eines Abos passiert — End-to-End, idempotent, ohne Doppelbelastung oder Zugangslücke.

Neukauf (Checkout -> invoice.paid -> Provisioning)
1Reader öffnet Checkout; Pre-Checkout-Prüfung: hat Customer schon aktives Abo auf dieses Produkt? -> falls ja Portal-Redirect
2Stripe Checkout (gehostet/WebView) wickelt Zahlung ab, erzeugt Subscription
3Stripe sendet invoice.paid; stripe-webhook verifiziert Signatur
4Event-Dedup via UNIQUE(stripe_event_id); bei Duplikat sofort 200
5Advisory-Lock auf stripe_customer_id; Re-Fetch der Subscription per Stripe-API (Payload nicht vertrauen)
6get_or_create_subscriber (bcrypt-Code, user_token), INSERT/UPSERT stripe_subscriptions mit status=active
7pmx-api/Pressmatrix-Zugang provisioniert; Welcome-Email mit Zugangscode via Resend
Renewal (invoice.paid im Folgezyklus)
1Stripe bucht automatisch ab und sendet invoice.paid
2stripe-webhook: Dedup + Advisory-Lock
3Re-Fetch: status weiterhin active, current_period_end auf nächsten Zyklus
4UPDATE stripe_subscriptions (Periode verschoben, billing_failure_count=0)
5Kein Email-Versand (kein überraschender Zugang), Zugang läuft nahtlos weiter
Zahlungsausfall + Dunning (invoice.payment_failed)
1Renewal-Zahlung schlägt fehl; Stripe setzt status=past_due und startet Smart-Retries (konfigurierbar, ~2 Wochen)
2stripe-webhook: Dedup + Re-Fetch -> status=past_due; UPDATE status, billing_failure_count++
3Policy-Entscheidung: bei past_due bleibt Zugang offen (Default), Smart-Retries laufen
4Bei erfolgreichem Retry: invoice.paid -> status zurück auf active, Zähler reset
5Bei erschöpften Retries: Stripe setzt canceled/unpaid -> subscription.deleted/updated
6stripe-webhook setzt is_active=false; Pressmatrix-Zugang wird entzogen
Mid-Cycle Plan-Wechsel (Pro-Rata)
1Reader öffnet Customer-Portal, wählt anderen Plan; Stripe zeigt Invoice-Preview mit Proration
2Stripe ändert Subscription-Item am selben Objekt (proration_behavior=create_prorations)
3Stripe sendet customer.subscription.updated (+ invoice.created/paid)
4stripe-webhook: Re-Fetch, UPDATE price/product, current_period_end bleibt
5Zugang läuft durchgehend (check_access prüft nur is_active + Kategorie), keine Lücke, kein Doppel-Abo
Email-Wechsel (Identity-Sync, customer.updated)
1Reader ändert Email im Stripe-Portal; Stripe-Customer.id bleibt, email ändert sich
2Stripe sendet customer.updated
3stripe-webhook findet subscriber BY stripe_customer_id (nie per Email-Join)
4UPDATE subscribers.email; access_code_hash + user_token bleiben unverändert
5Nächster Login: neue Email + alter Code, keine Identitäts-Ruptur
GDPR-Löschung (koordiniert)
1Löschanfrage trifft ein (manuell oder customer.deleted)
2Mapping stripe_customer_id -> subscriber/access_code auflösen
3ZUERST Pressmatrix-Zugang deprovisionieren (externe API), Retry bei Fehler
4Lokalen Subscriber löschen/anonymisieren; Rechnungsdaten anonymisieren (HGB-Aufbewahrung)
5Stripe-Customer löschen/anonymisieren; alle Schritte unabhängig im Audit-Log protokolliert
Das Warum

Design-Entscheidungen

Die neun kritischen Engineering-Fragen eines Subscription-Systems — jeweils mit Problem, getroffener Entscheidung, Begründung und Trade-off, verortet in der Architektur. Das ist die Lektion aus dem produktiven Referenz-Projekt, sauber gelöst.

Stripe-State-Machine als Single Source of Truth

External SoT (Stripe) + Persistence (stripe_subscriptions als Read-Replica)
Problem

Im Referenz-Projekt war der Abo-Status über vier Systeme verteilt (Shop, Subscription-App, Webhook-DB, Reader-Plattform) -> Drift, Sync-Aufwand, vier Fehlerdomänen

Entscheidung

Stripe-Subscription-Objekt mit nativem status (incomplete/trialing/active/past_due/canceled/unpaid/paused) ist kanonisch. Die lokale stripe_subscriptions-Tabelle ist nur Cache + Audit. Status -> Zugang wird 1:1 übersetzt

Warum Eine Quelle der Wahrheit eliminiert Drift; jede Zustandsänderung ist ein dokumentiertes Stripe-Event; Fragen zum Status werden gegen Stripe beantwortet, nicht gegen den Cache
Trade-off Mehr Stripe-API-Calls (Re-Fetch pro Event) und Abhängigkeit von Stripe-Verfügbarkeit; aber Calls sind billig und Konsistenz überwiegt

Webhooks

Ingestion (stripe-webhook Edge Function)
Problem

Stripe liefert at-least-once, ohne garantierte Event-Reihenfolge; rohe Payloads können veraltet/unvollständig sein

Entscheidung

Single Webhook-Endpoint mit event.type-Router für invoice.paid, invoice.payment_failed, customer.subscription.updated/deleted, customer.updated. Webhook = Trigger, nicht Wahrheit: bei jedem Event wird der aktuelle Subscription-State frisch per Stripe-API geholt

Warum Re-Fetch-on-Event macht Out-of-Order-Lieferung harmlos; egal welche Reihenfolge, der Handler liest den kanonischen Stand und konvergiert
Trade-off Zusatzlatenz + ein API-Call pro relevantem Event; akzeptabel da Provisioning ohnehin asynchron

Idempotenz / Advisory-Locks

Ingestion (stripe-webhook) + Persistence (stripe_webhook_events, Constraints)
Problem

Doppelte Events und parallele Webhooks für dieselbe Subscription können Doppel-Provisioning oder Race-Conditions auslösen

Entscheidung

Dreistufig: (1) Event-Dedup via UNIQUE(stripe_event_id), (2) Postgres Advisory-Lock pro stripe_customer_id/subscription serialisiert parallele Events, (3) DB-Unique-Constraint als letztes Netz

Warum Stripe-eigene Event-IDs machen Dedup trivial; feingranulare Locks serialisieren nur pro Abo, tausende Renewals laufen weiter parallel
Trade-off Lock-Overhead pro Event; minimal, da kein globaler Bottleneck

Customer-Portal / Checkout

Commerce (stripe-checkout, stripe-portal Edge Functions)
Problem

Eigenes Checkout/Portal bedeutet PCI-Scope, Session-Management und UI-Wartung

Entscheidung

Stripe Checkout (gehostet) und Stripe Customer Portal (gehostet) übernehmen Zahlung, Zahlungsmethoden-Update, Kündigung, Plan-Wechsel und Rechnungshistorie. Region-adaptiv: EU In-App-WebView, sonst externer Link

Warum PCI-Verantwortung liegt bei Stripe; kein eigener Auth/UI-Code; Self-Service reduziert Support
Trade-off Weniger Branding-Kontrolle, Reader verlässt ggf. die App-Domain; ausreichend für den Use-Case

Dunning / Smart-Retries

External SoT (Stripe Dashboard) + Ingestion (Status-Mapping im Handler)
Problem

Im Referenz-Projekt musste Dunning selbst gebaut werden (billing_failure_count, Grace-Period, pg_cron-Deaktivierung) über eine Blackbox-App

Entscheidung

Dunning wird an Stripe Smart-Retries delegiert (ML-getimt, im Dashboard konfigurierbar: Retry-Fenster, max. Versuche, Endaktion cancel/unpaid). Der Code mappt nur status: past_due -> Zugang bleibt (Policy), canceled/unpaid -> Zugang weg

Warum Dunning-Policy ist Geschäftsentscheidung des Verlags und gehört ins Dashboard, nicht in Code; Stripe optimiert Retry-Timing besser
Trade-off Weniger Custom-Flexibilität, Abhängigkeit von Stripe-Konfiguration; deckt die Fälle ab und entlastet Betrieb

Pro-Rata Plan-Wechsel

Commerce (stripe-portal) + External SoT (Stripe)
Problem

Im Shop-Stack war ein Mid-Cycle-Upgrade nur als Cancel+Neukauf möglich -> zwei parallele Abos, kein Preisabgleich, Zugangslücke

Entscheidung

Plan-Wechsel ändert das Item am selben Stripe-Subscription-Objekt mit nativer Proration; Invoice-Preview zeigt dem Reader den exakten Betrag vor Bestätigung

Warum Stripe modelliert das Abo als Entität, nicht als Order; Proration und durchgehender Zugang sind First-Class
Trade-off Erfordert Stripe-API-Mastery (Items, proration_behavior); deutlich sauberer als der Workaround

Identity-Sync (mutable Email / stabiler Code)

Persistence (subscribers.stripe_customer_id) + Ingestion (customer.updated-Handler)
Problem

Email ist mutabel; Keyen auf Email führt bei Email-Wechsel zu Doppel-Identitäten

Entscheidung

Interner Anker ist stripe_customer_id (immutabel). Pressmatrix-Zugang hängt am stabilen access_code_hash + user_token. customer.updated synchronisiert nur die Email, der Code bleibt

Warum Stripe-Customer.id überlebt Email-Wechsel; Reader behält seinen Code, keine Re-Registrierung
Trade-off Zusätzliche Mapping-Spalte stripe_customer_id; essenziell für Datenkonsistenz

Doppelkauf-Garantie

Commerce (stripe-checkout Pre-Check) + Persistence (Unique-Constraint)
Problem

Im Referenz-Projekt brauchte der Doppelkauf-Schutz vier unabhängige Layer (Account-Pflicht, Theme-Snippet, App-Setting, DB-Constraint) -> vier Ausfallpunkte

Entscheidung

Eine Pre-Checkout-Prüfung (aktives Abo des Customers für dieses Produkt? -> Portal-Redirect) plus DB-Unique-Constraint auf (stripe_customer_id, product/price) WHERE aktiv

Warum Stripe kennt aktive Subscriptions eines Customers als First-Class-Objekt -> eine Prüfung, ein Constraint
Trade-off Ein zusätzlicher API-Call vor Checkout; vernachlässigbar

Webhook-Reihenfolge / Eventual Consistency

Ingestion (Handler-Logik) + Consistency (reconcile-cron) + Persistence (stripe_webhook_events.raw_payload)
Problem

Events können out-of-order und verspätet kommen, einzelne Events können ausfallen

Entscheidung

Re-Fetch-on-Event + Advisory-Lock + Timestamp/State-Vergleich (ältere Events werden verworfen). Zusätzlich periodischer Reconcile-Cron, der aktive Abos gegen Stripe abgleicht und Drift korrigiert; raw_payload erlaubt Replay

Warum Das System konvergiert auf den kanonischen Stripe-Stand statt auf Payload-Reihenfolge zu vertrauen; der Cron ist Safety-Net gegen verpasste Webhooks
Trade-off Reconcile verursacht periodische API-Last; gering und planbar
Inventar

Komponenten

Was bereits live bewiesen ist, was angepasst und was neu gebaut wird.

KomponenteSchichtVerantwortungStatus
pmx-api (Edge Function)Access-BridgeHTTP-Basic-Auth-API für die Reader-App: /authenticate (Email+Code -> user_token) und /authorize (Token+Kategorie -> Zugang ja/nein). Interface bleibt identisch zum Referenz-ProjektLive bewiesen
forgot-code (Edge Function)Access-BridgeRate-limitiertes Selbstbedienungs-Formular zum Code-Reset per Email, Anti-EnumerationLive bewiesen
stripe-checkout (Edge Function)CommerceErzeugt Stripe-Checkout-Session, führt Pre-Checkout-Doppelkauf-Prüfung durch (aktives Abo -> Portal-Redirect), region-adaptiv (EU WebView / extern)Neu zu bauen
stripe-portal (Edge Function)CommerceErzeugt Stripe-Billing-Portal-Session für Self-Service: Kündigung, Zahlungsmethode, Plan-Wechsel mit Pro-Rata-VorschauNeu zu bauen
reconcile-cron (pg_cron + Edge Function)ConsistencyPeriodischer Re-Fetch aktiver Subscriptions gegen Stripe-API, korrigiert Drift, Safety-Net bei verpassten WebhooksAnzupassen
Stripe BillingExternal SoTKanonische State-Machine für Abos, native Renewals, Smart-Retries/Dunning, Pro-Rata, gehostetes Checkout + Customer-PortalNeu zu bauen
stripe-webhook (Edge Function)IngestionEmpfängt Stripe-Events, verifiziert HMAC-Signatur (timestamp.payload), dedupliziert via event.id, holt frischen Subscription-State per API, dispatcht zu Event-HandlernNeu zu bauen
ResendNotificationsTransaktionale Emails (Welcome, Code-Reset, optional Renewal/Cancellation), Lazy-Init, Fire-and-ForgetLive bewiesen
subscribers (Tabelle)PersistenceReader-Identität: Email (mutabel), bcrypt access_code_hash (stabil), user_token, stripe_customer_id (stabiler interner Key)Anzupassen
stripe_subscriptions (Tabelle)PersistenceLokaler Cache/Audit der Stripe-Subscription-State-Machine: status, current_period_start/end, price/product, billing_failure_countNeu zu bauen
stripe_webhook_events (Tabelle)PersistenceIdempotenz- und Audit-Log: UNIQUE(stripe_event_id), event_type, raw_payload für ReplayNeu zu bauen
check_access RPCPersistenceSingle-Roundtrip-Prüfung: aktives Abo der Kategorie im gültigen Zeitfenster ODER Einzelausgaben-Kauf. Quelle wechselt von Seal-Status zu Stripe-StatusAnzupassen
verify_access_code RPCPersistenceConstant-Time bcrypt-Prüfung mit Dummy-Hash gegen Timing-EnumerationLive bewiesen
Der Wechsel

Migration Shopify+Seal → Stripe

Jedes Konzept des bisherigen Stacks und wie es im Kit auf Stripe abgebildet wird — meist einfacher.

Bisher (Shop-Stack)Subscription-Kit (Stripe)Konsequenz
Shop + Subscription-App (verteilter Abo-Status)Stripe Billing als einzige State-MachineVier Systeme -> eine Source of Truth; lokale DB wird Read-Replica/Audit
shopify-webhook (Topic-Router: orders/paid, billing_attempts/*, contracts/update)stripe-webhook (Event-Router: invoice.paid, invoice.payment_failed, subscription.updated/deleted, customer.updated)Topic-basiert -> Event-basiert; Re-Fetch statt Payload-Vertrauen
Idempotenz via webhook_log UNIQUE(order_id, event_type) + ReservationDedup via stripe_webhook_events UNIQUE(stripe_event_id) + Advisory-LockStripe-Event-IDs sind eindeutig; Lock ergänzt gegen Out-of-Order
renew_recurring_subscription RPC mit current_period_end<=heute+1 Race-GuardStripe renewt nativ; Webhook synct nur, Status ist WahrheitEigene Renewal-Mathematik entfällt
Grace-Period via ends_at = current_period_end + GRACE_DAYS + pg_cron-Deaktivierungstatus=past_due (Zugang per Policy offen) + Stripe Smart-RetriesDunning von Custom-Logik zu Stripe-nativer ML-Retry delegiert
Subscription-App-Polling/Sync (Eventual-Consistency mit Lag)Webhooks primär + täglicher Reconcile-Cron gegen Stripe-APIKein Polling externer App nötig; Cron nur als Safety-Net
Doppelkauf-Schutz über 4 Layer (Account, Theme-Snippet, App-Setting, DB-Constraint)1 Pre-Checkout-Prüfung + 1 DB-Unique-ConstraintStripe-Customer kennt aktive Abos als First-Class-Objekt
Plan-Wechsel = Cancel + Neukauf (Doppel-Abo-Risiko, keine Proration)Item-Update am selben Abo mit nativer Proration + Invoice-PreviewDurchgehender Zugang, exakte anteilige Verrechnung
Identität via shopify_customer_id + Email (Email-Wechsel problematisch)stripe_customer_id als immutabler Anker; access_code stabilEmail-Wechsel = reines Mapping-Update, kein Identitätsbruch
Customer Identity mutabel, kein Self-Service-PortalStripe Customer Portal (gehostet) für Self-ServiceKündigung/Zahlungsmethode/Plan-Wechsel ohne eigenen Code
GDPR via atomare RPC delete_subscriber_data (single system)Koordinierte Löschung Reader-Zugang -> Stripe -> lokalMulti-System-Orchestrierung mit Retry statt einer DB-Transaktion
pmx-api /authenticate + /authorize (check_access gegen App-Status)pmx-api unverändert; check_access liest Stripe-abgeleiteten StatusReader-Interface bleibt bitidentisch; nur die Statusquelle wechselt
Härtung

Sicherheitsmodell

  • Stripe-Webhook-Signatur: HMAC-SHA256 über timestamp.payload gegen Endpoint-Secret, timing-safe; Timestamp-Fenster gegen Replay
  • Event-Dedup über UNIQUE(stripe_event_id) verhindert doppelte Verarbeitung
  • Advisory-Locks pro Customer/Subscription serialisieren parallele Events, DB-Unique-Constraints als letztes Netz
  • Zugangscodes als bcrypt-Hash (cost 10), nie im Klartext gespeichert; Constant-Time-Verify mit Dummy-Hash gegen Username-Enumeration
  • Rate-Limiting auf Auth-Endpoints: pro IP und pro Email (DB-basiert über security_events); Account-Lockout nach 5 Fehlversuchen (30 min)
  • Anti-Enumeration: generische Antworten bei forgot-code und Login
  • Re-Fetch-on-Event: Payload wird nie blind vertraut, kanonischer State kommt von Stripe-API
  • RLS auf allen Tabellen, Zugriff nur via service_role; Secrets ausschließlich in ENV
  • GDPR: koordinierte Löschung (zuerst Reader-Zugang entziehen, dann Stripe/lokal), Anonymisierung von Rechnungsdaten unter Aufbewahrungspflicht, Log-Retention 90 Tage via pg_cron
  • PCI-Scope vollständig bei Stripe durch gehostetes Checkout + Portal
Werkzeuge

Tech-Stack & Begründung

Compute / Webhook-Handler
Supabase Edge Functions (Deno/TypeScript)
Bewährt im Referenz-Projekt; stateless, verify_jwt=false für Custom-Auth (Signatur-Verify)
Datenbank
Supabase PostgreSQL mit RLS (nur service_role)
State-Cache, Audit, atomare RPCs, Advisory-Locks, Constraints nativ
Scheduler
pg_cron (Supabase Pro)
Periodische Reconciliation gegen Stripe-API, Log-Retention (90 Tage, GDPR)
Billing / Subscription-Engine
Stripe Billing
Native State-Machine, Smart-Retries, Pro-Rata, gehostetes Checkout + Portal, PCI-Scope bei Stripe
Email
Resend (Lazy-Init)
Transaktionale Mails; Build fällt nie wegen fehlender ENV; Fire-and-Forget
Code-Hashing
bcrypt via pgcrypto (cost 10), Constant-Time-Verify
Zugangscodes nicht umkehrbar speichern, Timing-Enumeration verhindert
Reader-Anbindung
Pressmatrix External-Auth-API (HTTP Basic Auth)
Interface /authenticate + /authorize bleibt unverändert; Kit ist transparenter Translator
Umsetzung

Implementierungs-Roadmap

Phase 0 ist im produktiven Referenz-Projekt bereits bewiesen und übernehmbar. Die Stripe-spezifischen Phasen sind der eigentliche Bau-Aufwand.

Live bewiesen

Phase 0 — Bewiesene Fundamente (aus Referenz-Projekt übernehmbar)

  • bcrypt Access-Code-Generierung + Constant-Time verify_access_code
  • pmx-api /authenticate + /authorize (External-Auth-Interface)
  • forgot-code mit Rate-Limiting + Anti-Enumeration
  • Resend Email-Versand + Templates (Lazy-Init, Fire-and-Forget)
  • security_events Audit + Rate-Limiting + pg_cron Log-Retention
  • check_access RPC (Abo ODER Einzelausgabe, Zeitfenster)
  • RLS-Schema (subscribers, issues), service_role-only
Geplant

Phase 1 — Stripe-Webhook-Ingestion

  • stripe-webhook Edge Function mit Signatur-Verify (timestamp.payload, Replay-Fenster)
  • stripe_webhook_events Tabelle (Dedup + raw_payload)
  • Event-Router invoice.paid / invoice.payment_failed / subscription.updated/deleted / customer.updated
  • Re-Fetch-on-Event gegen Stripe-API
  • Advisory-Lock pro stripe_customer_id
Geplant

Phase 2 — State-Machine + Provisioning

  • stripe_subscriptions Tabelle + Status-Mapping zu Zugang
  • subscribers.stripe_customer_id Mapping + get_or_create_subscriber-Anpassung
  • check_access auf Stripe-Status umstellen
  • Provisioning/Deprovisioning gegen Pressmatrix
  • Unique-Constraint Doppelkauf-Schutz
Geplant

Phase 3 — Commerce (Checkout + Portal)

  • stripe-checkout mit Pre-Checkout-Doppelkauf-Prüfung
  • Region-adaptive Checkout-Auslieferung (EU WebView / extern)
  • stripe-portal für Self-Service + Plan-Wechsel mit Invoice-Preview
  • Welcome/Renewal/Cancellation-Emails
Geplant

Phase 4 — Dunning, Consistency, GDPR

  • Dunning-Policy-Mapping (past_due Zugang offen, canceled/unpaid weg)
  • Stripe Smart-Retries im Dashboard konfigurieren
  • reconcile-cron (täglicher Re-Fetch gegen Stripe-API)
  • Koordinierte GDPR-Löschung + Rechnungs-Anonymisierung
Technisches Planungsdashboard · Subscription-Kit · Gottfried Media · Stand 2026-06-09 · vertraulich