- Buchungs-Seite zeigt das echte 4-Tier-Raster aus plans (Monat/Jahr-
Toggle, Jahrespreis als "2 Monate gratis") mit Checkout-Buttons,
Einzel-PM als separaten No-Abo-Block und Enterprise-Hinweis;
Credit-Konzept-Mock entfernt (Credits folgen mit 9I bzw. Phase 2)
- Aktueller-Tarif-Panel real: Abo (Preis, Kontingent, Kündigungsstatus),
Bestandstarif (unbegrenzt, nächste MAN-Rechnung), offene Einzelkäufe;
Kontingent-Kachel zeigt "Unbegrenzt" bei Bestandsschutz
- "Abo verwalten" über das Stripe Billing Portal
(me.checkout.billing-portal; Zahlungsmethode, Rechnungen, Kündigung)
- Aktive Buchungen + Verlauf aus echten Daten (Abo, Legacy-Vereinbarung,
offene/eingelöste Einzelkäufe mit PM-Verknüpfung)
- Tests: BookingsPageTest (9 Tests), PanelConsolidationTest angepasst;
Suite 519 passed / 4 skipped
- Doku: PHASE-9-Plan 9F ✅, Billing-Doku (Routen, Stripe Tax aktiviert),
STATUS-ABGLEICH, Checkliste, PROGRESS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
15 KiB
Phase 9 · Veröffentlichungs-Flow (Launch) & Tarif-Modul
Stand: 2026-06-12 — Block 1 (9A–9C) abgeschlossen; Review-Stopp vor
Block 2 (9D–9J, Tarif-Modul). Suite nach Block 1: 451 passed, 4 skipped.
Vorgänger: Phase 8 (User-Panel-Konsolidierung) + KI-Prüf-Pipeline (beide abgeschlossen).
Verbindliche Entscheidungen: docs/Decision-Update Preisstruktur & Veröffentlichungs-Flow.md
Abgleich-Doku: docs/STATUS-ABGLEICH-USER-PANEL.md
0. Worum es geht
Phase 9 setzt das Decision-Update vom 11./12.06.2026 um — in zwei Blöcken:
- Block 1 — Veröffentlichungs-Flow (9A–9C): Die Flow-Regeln, die unabhängig vom Tarif-Modul gelten und auf denen das Tarif-Modul aufsetzt. Funktioniert vollständig mit dem vorhandenen Quota-Stub.
- Block 2 — Tarif-Modul (9D–9I): Zahlung, Tarife, Einzel-PM, Tageslimit und die drei Launch-Credit-Posten. Löst den Quota-Stub ab.
Leitplanken aus dem Decision-Update:
- Gelb geht zum Launch direkt live wie Grün — keine manuelle Prüf-Queue. Nur Rot wird abgelehnt (Meldung mit Begründung an den Autor).
- Der PM-Slot zählt bei Veröffentlichung runter, nicht bei der Prüfung. Rot verbraucht keinen Slot.
- „Speichern" bleibt immer frei; „Speichern & zur Prüfung einreichen" ist hinter eine aktive Buchung gegated (der Button konvertiert, er verschwindet nicht).
- Kein Re-Check zum Launch: eine Einreichung = eine Prüfung = (bei Gelb/Grün) eine Veröffentlichung. Vorab-Prüfung/Redigieren sind Phase 2.
1. Sub-Päckchen-Übersicht
| ID | Thema | Größe | Risiko |
|---|---|---|---|
| 9A ✅ | Gelb-Routing auf Direkt-Live umstellen (Routing, Scheduler, Tests) | S | gering |
| 9B ✅ | Slot-Verbrauch von Einreichung auf Veröffentlichung umstellen (Rot = kein Slot) | M | mittel (Idempotenz) |
| 9C ✅ | Submit-Gate-Schnittstelle (hasActiveBooking()-Stub, Modal-Hinweis, Server-Guard) + Fix: Create-Form lief am Funnel vorbei |
M | gering |
| — | Review-Stopp mit User | ||
| 9D ✅ | Tarif-Datenmodell: Pläne, Einzelkäufe, Cashier, Rechnungskreise STR-/MAN-, MAN-Fälligkeitslauf (Stub-Ablösung folgt mit 9E) | L | hoch (Datenmodell) |
| 9E ✅ | Stripe-Anbindung: Produkt-Sync (Tarife + Einzel-PM), Webhook-Verarbeitung (STR-Spiegelung, Einmalkauf-Erfüllung, Endpoint registriert), Checkout-Flows (Backend), Slot-Logik auf Plan-Kontingent (Grandfathered = unbegrenzt), Stripe Tax | L | mittel |
| 9F ✅ | Tarif-Seite + Checkout-UI: Buchungs-Seite mit echtem 4-Tier-Raster (Monat/Jahr-Toggle, „2 Monate gratis"), Einzel-PM-Block, Bestandstarif-Anzeige, „Abo verwalten" (Stripe Billing Portal), Enterprise-Hinweis | M | gering |
| 9G | Tageslimit je Tier (Business 2 / Pro 3 / Agency 5; gilt auch für Extra-PMs) | S | gering |
| 9H | Einzel-PM-Kauf (19 €) + Einzel→Abo-Brücke (Anrechnung 30 Tage) | M | mittel |
| 9I | Launch-Credits: Extra-PM, Boost (nur Grün), Veröffentlichungsnachweis-PDF | L | mittel |
| 9J | Abschluss: Tests, Pint, Build, Doku-Sync, PROGRESS-Eintrag | S | keine |
Nach jedem Päckchen Review-Stopp mit dem User; vor 9D ein größerer (Datenmodell-Entscheidungen + Cashier-Freigabe).
2. Block 1 — Veröffentlichungs-Flow
9A · Gelb-Routing auf Direkt-Live
Entscheidung (12.06.2026): Rot = nicht veröffentlichbar (rechtlich/ inhaltlich) → Ablehnung mit Meldung. Gelb/Grün = veröffentlichbar → geht in der ersten Phase direkt online. Gelb bleibt als interne Markierung erhalten (nicht boostbar, Admin-Signal), löst aber keine manuelle Prüfung aus.
Anpassungen:
PressReleaseService::routeByClassification(): Gelb durchläuft denselben Auto-Publish-Pfad wie Grün (autoPublishGreen()→ generalisiert zuautoPublishApproved()); Verzögerungsfenster (scoring.classification.green_delay_minutes) gilt für beide.PublishScheduledPressReleases: Kandidaten-Query vonclassification = greenaufclassification IN (green, yellow).- Admin-Review-Queue bleibt als Fallback bestehen: unklassifizierte PMs
(Job noch nicht gelaufen / KI-Ausfall ohne Fallback-Ergebnis) bleiben in
reviewund sind manuell behandelbar. KI-Badge und Klassifikations-Filter im Admin bleiben unverändert.
Tests: PressReleaseClassificationJobTest (Gelb-sofort → published,
Gelb-geplant → bleibt review bis Termin), PressReleaseSchedulingTest
(gelbe fällige PM wird publiziert).
9B · Slot-Verbrauch bei Veröffentlichung
Regel: Der Slot zählt genau einmal pro PM, beim ersten Übergang zu
published. Rot abgelehnte PMs verbrauchen nichts.
Anpassungen:
submitForReview(): Increment vonpress_release_quota_used_this_monthentfernen. Stattdessen Guard: Einreichen erfordertpressReleaseQuotaRemaining() > 0(sonst würde eine grüne PM ohne verfügbaren Slot veröffentlicht).publish(): Increment beim Statuswechsel aufpublished, idempotent — nur wenn die PM zuvor noch nie veröffentlicht war (Prüfung überpress_release_status_logs, kein neues Schema-Feld). Zählt auf den PM-Eigentümer (user_id).- Veröffentlichungs-Modal: Text von „wird bei Einreichung verbraucht" auf „wird bei Veröffentlichung verbraucht; abgelehnte PMs kosten keinen Slot".
Tests: Submit verbraucht keinen Slot; Publish (Admin, Auto-Publish, Scheduler) verbraucht genau einen; Rot → kein Verbrauch; Archivieren + erneutes Publizieren zählt nicht doppelt; Submit bei 0 Rest-Slots blockiert.
9C · Submit-Gate-Schnittstelle
Ziel: Das Gate aus dem Decision-Update §5.1, gebaut gegen eine schmale Schnittstelle, die zunächst ein Stub bedient und in 9D/9E vom Tarif-Modul implementiert wird — Modal und Service müssen dann nicht mehr angefasst werden.
Anpassungen:
User::hasActiveBooking(): bool— Launch-Schnittstelle. Stub-Verhalten überconfig/billing.php(billing.enforce_booking, Defaultfalse): solange das Tarif-Modul fehlt, gibt die Methodetruezurück; mit aktiviertem Flag (und später echter Subscription-Prüfung) greift das Gate.- Einreichungs-Modal (
press-release-submit-modal): ohne aktive Buchung zeigt das Modal statt des Prüf-Flows einen Buchungs-Hinweis mit CTA („Buchung erforderlich" → Tarif-Seite). Der Button bleibt sichtbar. - Server-Guard:
submitForReview()wirft ohne aktive Buchung eine Exception (UI allein reicht nicht); API-Submit-Route antwortet mit 402.
Tests: Gate aus (Default) → Verhalten unverändert; Gate an →
Modal-Hinweis statt Checkboxen, submitForReview wirft, API gibt 402.
3. Block 2 — Tarif-Modul (nach Review-Stopp)
Entscheidung 12.06.2026 — Hybride Rechnungsarchitektur
Alle neuen Abschlüsse und Zahlungen laufen über Stripe. Die Umsetzung ist hybrid mit zwei getrennten Rechnungskreisen (plus Altbestand):
| Kreis | Präfix | Inhalt |
|---|---|---|
| Stripe-Shop | STR- |
Alles Neue (Abos, Einzel-PM, Credits) — komplette Abwicklung über Stripe, fortlaufende Nummer im STR-Kreis |
| Manuell/Legacy | MAN- |
Laufende, noch aktive Alt-Zahlungen ab Relaunch: Fälligkeit wird im Hintergrund geprüft, Rechnung wie im Legacy-System ausgestellt |
| Alt-Archiv | — | Die importierten Alt-Rechnungen (legacy_invoices, 864 Stück) bleiben unverändert bestehen |
9D · Tarif-Datenmodell — ✅ umgesetzt (12.06.2026)
- Cashier installiert (
laravel/cashier^16.5, freigegeben);UseristBillable, Cashier-Tabellen (subscriptions,subscription_items, Customer-Spalten) migriert. Die lokaleinvoices()-Relation überschreibt bewusst die Cashier-Methode. plans: Starter/Business/Pro/Agency mit Monats-/Jahrespreis (Jahres = 10 × Monat), PM-Kontingent, Tageslimit, Stripe-IDs (nullable, werden in 9E gepflegt).PlanSeederidempotent.single_purchases: Einzel-PM, Extra-PM, Boost, PDF-Nachweis mit Status Pending/Paid/Consumed/Refunded und Stripe-Checkout-Referenzen.invoice_number_sequences+InvoiceNumberGenerator: atomare, fortlaufende Nummern pro Kreis (STR-00001,MAN-00001; Padding konfigurierbar inconfig/billing.php).- MAN-Kreis:
ManualInvoiceService+ Commandbilling:generate-manual-invoices(Scheduler täglich 04:30) — findet aktive/grandfathereduser_payment_optionsohnestripe_subscription_idmit erreichtemcurrent_period_end, friert die Rechnungsadresse als Snapshot ein, stellt eine MAN-Rechnung aus (Zahlungszielbilling.manual_due_days) und schaltet die Periode weiter. Konditions-Overrides pro Vereinbarung überlegacy_conditions(amount_cents/tax_cents/total_cents/interval); ohne Override Netto-Preis derpayment_option+billing.vat_rate. Nicht abrechenbare Fälle (fehlende Rechnungsadresse) werden geloggt und erneut versucht. User::hasActiveBooking()prüft jetzt echt (hinterbilling.enforce_booking): Cashier-Abo ∨ bezahlter Einzel-/Extra-PM-Kauf ∨ aktive/grandfathered Legacy-Vereinbarung (MAN-Kreis).- USt-Behandlung (Einwand 12.06.): Alle neuen Preise sind netto.
VatResolverbestimmt die Steuer pro Rechnung aus der Rechnungsadresse: DE immer mit Steuer, EU nur mit (formal plausibler) USt-ID befreit (Reverse Charge inkl. Pflichthinweis ininvoices.tax_note), Drittland befreit.vat_idanbilling_addresses+ Snapshot, gepflegt über das bestehende USt-ID-Feld im Profil. Grandfathered-Vereinbarungen rechnen auf der Netto-Basis der letzten Legacy-Rechnung (net_cents, brutto ÷ 1,19 bzw. Netto-Ausweis direkt). Offen: echte VIES-Validierung der USt-ID (aktuell Formatprüfung) — Folgeschritt, vor Gate-Aktivierung empfohlen. - Legacy-Migration (12.06.):
legacy:grandfather-subscriptionsleitet die aktiven, jährlich wiederkehrenden Vereinbarungen aus dem Rechnungsarchiv ab und schreibt sie alsgrandfatheredinuser_payment_options(Replay-fähig — die Kern-Migration läuft kurz vor dem Relaunch erneut). Details:dev/migration 2026/05-DATABASE-MERGE.md§5.6 +MIGRATION-STEPS.md. Noch offen in 9D→ mit 9E erledigt: Slot-Logik auf Plan-Kontingent umgestellt, Stub-Spalte entfernt.
9E · Stripe (Laravel Cashier) ✅ (12.06.2026)
- ✅ Produkt-Sync:
billing:sync-stripe-planslegt Tarife (Monats-/Jahres- preise) und das Einzel-PM-Produkt als Netto-Preise in Stripe an (Test-Mode gelaufen; IDs inplansbzw.STRIPE_PRICE_SINGLE_PM). - ✅ Webhooks:
ProcessStripeWebhookspiegelt bezahlte Stripe-Rechnungen mit STR-Nummer ininvoicesund erfüllt Einmalkäufe; Endpointhttps://pressekonto.com/stripe/webhookregistriert, Secret gesetzt. - ✅ Checkout-Flows (Backend):
me.checkout.subscription+me.checkout.single-pm(CheckoutController → StripeCheckoutService); Stripe Tax viaCashier::calculateTaxes()(Netto-Preise). UI-Anbindung der Buttons folgt in 9F. - ✅ Slot-Logik: Plan-Kontingent + Einmalkauf-Verbrauch statt Stub;
Grandfathered = unbegrenzt (Entscheidung 12.06.2026, Bestandsschutz).
Details:
docs/user-admin/Billing-und-Rechnungskreise.md§2. - Offen → §7 der Billing-Doku: Stripe Tax im Dashboard aktivieren, Live-Mode-Sync vor Relaunch.
9F · Tarif-Seite + Checkout-UI ✅ (12.06.2026)
- ✅ „Buchungen & Add-ons" zeigt das echte 4-Tier-Raster aus
plans(Monat/Jahr-Toggle, Jahrespreis als „2 Monate gratis") mit Checkout-Buttons aufme.checkout.subscription; Einzel-PM als separater No-Abo-Block (me.checkout.single-pm); Enterprise als dezenter Hinweis unter dem Raster. Der Credit-Konzept-Mock ist abgelöst (Credits → 9I bzw. Phase 2). - ✅ Aktueller Tarif real: Abo (Preis, Kontingent, Kündigungsstatus),
Bestandstarif (unbegrenzt, nächste MAN-Rechnung) oder offene
Einzelkäufe; Kontingent-Kachel (
Unbegrenztbei Bestandsschutz). - ✅ „Abo verwalten" → Stripe Billing Portal (
me.checkout.billing-portal: Zahlungsmethode, Rechnungen, Kündigung). - ✅ Aktive Buchungen + Verlauf aus echten Daten (Abo, Legacy-Vereinbarung, offene/eingelöste Einzelkäufe mit PM-Verknüpfung).
- Einstieg aus dem Submit-Gate-Hinweis (9C) führt bereits hierher.
9G · Tageslimit
plans.daily_limit(Starter ohne Limit); Prüfung beim Veröffentlichen (nicht beim Einreichen), zählt veröffentlichte PMs des Users pro Kalendertag (Europe/Berlin); gilt auch für Extra-PMs. Überschreitung → PM bleibt inreviewmit Hinweis, Veröffentlichung am Folgetag durch den Scheduler.
9H · Einzel-PM + Abo-Brücke
- Einzel-PM-Kauf 19 € → genau eine Einreichung/Veröffentlichung.
- Brücke: Abo-Abschluss innerhalb 30 Tagen rechnet 19 € auf den ersten Monat an (Stripe-Coupon oder Rabatt-Position).
9I · Launch-Credits
- Credit-Wallet (1 Credit = 1 € Listenpreis, Pakete mit Volumenrabatt).
- Posten: Extra-PM (Kontingent voll → einzelne PM nachkaufen), Boost (nur für grün klassifizierte PMs, nachträglich), Veröffentlichungsnachweis-PDF.
- Kein Dofollow-Backlink-Verkauf (bewusst ausgeschlossen).
9J · Abschluss
- Volle Suite grün, Pint clean,
npm run buildclean. - Doku-Sync:
STATUS-ABGLEICH,checkliste-user-backend.md, dieses Dokument,PROGRESS.md-Eintrag.
4. Was außerhalb von Phase 9 bleibt
- Vorab-KI-Prüfung, Redigieren/Re-Check-Loop, Prüfzähler, Credit-Overflow (Decision-Update §7 — Phase 2)
- Score-Feinstufung für Boost („nur Geprüft/Hochwertig boostbar")
- Magic-Link-Flow, Statistik-/Abrechnungs-Tabs, Anhänge-Reaktivierung, Trust-Score, Notice-and-Action
5. Risiken & Annahmen
- Idempotenz Slot-Verbrauch (9B): Prüfung über Status-Logs statt neuem Feld — bei Alt-Daten mit unvollständigen Logs schlimmstenfalls ein doppelter Zähler; akzeptabel für den Stub, wird mit 9D-Periodenzähler sauber.
- Gate-Stub (9C):
enforce_booking=falseals Default hält das System bis zum Tarif-Modul voll funktionsfähig; das Flag erlaubt Tests und frühe Aktivierung. - 9D/9E Datenmodell + Cashier: größter Block, eigener Review-Stopp davor;
Stub-Ablösung (
press_release_quota-Spalten entfernen) erst nach verifizierter Migration. - Rechtstexte (Einreichungs-Modal) sind weiterhin Platzhalter — anwaltliche Prüfung läuft parallel, unabhängig von Phase 9.
- Betrieb: Queue-Worker für
classificationin Produktion bleibt Go-Live-Voraussetzung (unabhängig von Phase 9).
6. Akzeptanzkriterien Phase 9 gesamt
- Gelb klassifizierte PMs gehen ohne manuelle Prüfung live (sofort/Termin)
- Rot verbraucht keinen Slot; Slot zählt genau einmal, bei Veröffentlichung
- Einreichen ohne aktive Buchung zeigt Buchungs-Hinweis (UI) und wird serverseitig abgelehnt (Gate aktiviert)
- Tarife buchbar (4 Tiers, monatlich/jährlich), Einzel-PM kaufbar
- Tageslimit greift je Tier, auch für Extra-PMs
- Extra-PM, Boost (nur Grün) und PDF-Nachweis als Credits kaufbar
- Quota-Stub vollständig abgelöst,
pressReleaseQuotaRemaining()stabil - Tests grün, Pint clean, Build clean, Doku synchron