- billing:sync-stripe-plans: legt die Tarife als Netto-Produkte/Preise
(tax_behavior exclusive, EUR) in Stripe an und pflegt die IDs zurueck
nach plans; idempotent, --dry-run. Gegen Stripe Test-Mode ausgefuehrt —
alle 4 Tiers verknuepft.
- ProcessStripeWebhook (Listener auf Cashier WebhookReceived):
- invoice.payment_succeeded -> Spiegelung in den lokalen STR-Kreis
(fortlaufende Nummer via InvoiceNumberGenerator, Adress-Snapshot
bevorzugt aus dem Stripe-Payload inkl. lokaler USt-ID, Status paid,
idempotent gegen doppelte Zustellung)
- checkout.session.completed -> markiert den referenzierten
single_purchases-Datensatz als bezahlt (Metadata single_purchase_id)
- CASHIER_CURRENCY=eur (+ Locale de_DE); Cashier-Webhook-Route aktiv
- Doku: Billing-Referenz §7 + Phase-9-Plan (9E-Backbone) aktualisiert
Offen fuer 9E-Rest: Checkout-Flows (Abo + Einmalkauf), Webhook-Endpoint
im Stripe-Dashboard + STRIPE_WEBHOOK_SECRET, Slot-Logik auf
Plan-Kontingent (fachliche Frage: Grandfathered = unbegrenzt?).
Tests: StripeWebhookProcessingTest (7, inkl. Event-Wiring).
Suite: 497 passed, 4 skipped. Pint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.8 KiB
Billing & Rechnungskreise (hybrides Modell)
Stand: 12.06.2026 — Datenmodell, MAN-Kreis und USt-Behandlung umgesetzt (Phase 9D); Stripe-Checkout/Webhooks in Arbeit (Phase 9E).
Dieses Dokument ist die zentrale Referenz für das Abrechnungssystem: Rechnungskreise, Tarif-Datenmodell, Steuerlogik, Befehle und Konfiguration.
Verwandte Dokumente:
docs/Decision-Update Preisstruktur & Veröffentlichungs-Flow.md— verbindliche Launch-Entscheidungen (Tarife, Kontingente, Flow, Netto-Preise).docs/PHASE-9-FLOW-UND-TARIFE-PLAN.md— Umsetzungsplan mit Päckchen-Status.dev/migration 2026/05-DATABASE-MERGE.md§5.5/§5.6 — Rechnungsarchiv (D-12) und Grandfathering (D-13).
1. Die drei Rechnungswelten
| Welt | Präfix | Tabelle | Inhalt |
|---|---|---|---|
| Stripe-Shop | STR- |
invoices |
Alle neuen Abschlüsse (Abos, Einzel-PM, Credits). Abwicklung komplett über Stripe; Rechnungen werden per Webhook in invoices gespiegelt und erhalten eine fortlaufende STR-Nummer. (Spiegelung: Phase 9E) |
| Manuell/Legacy | MAN- |
invoices |
Laufende, noch aktive Alt-Zahlungsvereinbarungen ab Relaunch. Fälligkeit wird täglich geprüft, Rechnung wie im Altsystem ausgestellt. |
| Alt-Archiv | — | legacy_invoices |
Read-only Archiv aller importierten Legacy-Rechnungen (D-12). Wird nie verändert; PDFs werden on-demand aus den Archivdaten erzeugt. |
Rechnungsnummern: InvoiceNumberGenerator vergibt atomar (Row-Lock auf
invoice_number_sequences) fortlaufende, lückenlose Nummern pro Kreis:
STR-00001, MAN-00001, … (Padding: billing.invoice_number_padding).
2. Tarif-Datenmodell
| Tabelle | Zweck |
|---|---|
plans |
Tarif-Katalog (Starter/Business/Pro/Agency): Monats-/Jahrespreis netto, PM-Kontingent/Monat, Tageslimit, Stripe-Produkt-/Preis-IDs. Seeder: PlanSeeder (idempotent). |
subscriptions, subscription_items |
Laravel-Cashier-Tabellen — Zustand der Stripe-Abos. User ist Billable. |
single_purchases |
Einmalkäufe: Einzel-PM (19 €), Extra-PM, Boost, Veröffentlichungsnachweis-PDF. Status: pending → paid → consumed (oder refunded). |
payment_options / user_payment_options |
Legacy-Zahlungsvereinbarungen. Grandfathered-Einträge tragen die Netto-Vertragsbasis in legacy_conditions; versteckte Katalog-Platzhalter LEGACY-{PE|BP}-{Artikel}. |
invoice_number_sequences |
Fortlaufende Nummern pro Rechnungskreis. |
Submit-Gate (User::hasActiveBooking(), hinter billing.enforce_booking):
Eine aktive Buchung ist ein Cashier-Abo oder ein bezahlter, noch nicht
eingelöster Einzel-/Extra-PM-Kauf oder eine aktive/grandfathered
Legacy-Vereinbarung. Bestandskunden behalten damit nach Gate-Aktivierung
volle Einreichungsrechte.
3. MAN-Kreis: Fälligkeitslauf für Legacy-Zahlungen
Täglicher Scheduler-Lauf (04:30): billing:generate-manual-invoices
- Findet
user_payment_optionsmit Statusactive/grandfathered, ohnestripe_subscription_id, derencurrent_period_enderreicht ist. - Friert die Rechnungsadresse als Snapshot ein (
invoice_billing_addresses, inkl.vat_id). - Stellt die Rechnung aus: Netto-Basis × USt-Regel (Abschnitt 4),
MAN-Nummer, Zahlungsziel
billing.manual_due_days(Default 14 Tage). - Schaltet die Periode weiter (
monthly/yearlyauslegacy_conditionsbzw.payment_options.interval).
Nicht abrechenbare Fälle (fehlende Rechnungsadresse, kein Intervall) werden
geloggt, die Periode bleibt stehen — der nächste Lauf versucht es erneut.
Optionen: --dry-run, --limit=50.
Befüllung: legacy:grandfather-subscriptions (Migrations-Runbook, nach
legacy:archive-invoices) leitet die aktiven jährlichen Vereinbarungen aus
dem Rechnungsarchiv ab — Replay-fähig für den Lauf kurz vor Relaunch.
Details: dev/migration 2026/05-DATABASE-MERGE.md §5.6.
4. USt-Behandlung (Entscheidung 12.06.2026)
Alle neuen Preise sind Netto-Preise. Die Steuer wird zur
Rechnungsstellung über App\Services\Billing\VatResolver aus der
Rechnungsadresse bestimmt:
| Fall | Behandlung | Rechnung |
|---|---|---|
| Deutschland | immer mit Steuer (billing.vat_rate, Default 19 %) |
Netto + USt ausgewiesen |
| EU mit gültiger USt-ID | befreit (Reverse Charge) | is_netto, Pflichthinweis in tax_note |
| EU ohne USt-ID | mit Steuer | Netto + USt ausgewiesen |
| Drittland | grundsätzlich befreit | is_netto, Hinweis „nicht im Inland steuerbar" |
- Die USt-ID wird im Profil gepflegt (bestehendes Feld) und zusätzlich an
der Rechnungsadresse (
billing_addresses.vat_id) gespeichert; jede Rechnung friert sie im Adress-Snapshot ein. - „Gültig" = vorhanden + formal plausibel (Länder-Präfix, EL für Griechenland). Offen: echte VIES-Validierung — vor Aktivierung von Gate/Checkout umsetzen.
- Legacy-Umrechnung: Das Altsystem fakturierte brutto (199 € inkl.
Steuer; Befreite mit Netto-Ausweis 167,23 €). Die Grandfather-Migration
leitet daraus die Netto-Basis ab (
legacy_conditions.net_cents) — für deutsche Bestandskunden bleibt der Bruttobetrag unverändert, die Steuer wird künftig nur sauber ausgewiesen.
5. Befehle & Scheduler
| Befehl | Zweck | Scheduler |
|---|---|---|
billing:generate-manual-invoices |
MAN-Fälligkeitslauf (Abschnitt 3) | täglich 04:30 |
legacy:grandfather-subscriptions |
Aktive Legacy-Abos aus dem Archiv migrieren | manuell (Migrations-Runbook) |
press-releases:reset-monthly-quota |
Quota-Stub-Reset (entfällt mit Plan-Kontingent, 9E) | monatlich, 1. um 00:05 |
6. Konfiguration
config/billing.php:
| Schlüssel | ENV | Default | Bedeutung |
|---|---|---|---|
enforce_booking |
BILLING_ENFORCE_BOOKING |
false |
Submit-Gate scharf schalten (Launch-Schalter) |
invoice_number_padding |
— | 5 |
Stellen der laufenden Nummer |
manual_due_days |
BILLING_MANUAL_DUE_DAYS |
14 |
Zahlungsziel MAN-Rechnungen |
vat_rate |
BILLING_VAT_RATE |
0.19 |
USt-Satz für steuerpflichtige Fälle |
eu_country_codes |
— | EU-27 ohne DE | Basis der Drittland-/EU-Unterscheidung |
Stripe/Cashier (config/cashier.php):
| ENV | Bedeutung |
|---|---|
STRIPE_KEY / STRIPE_SECRET |
Publishable/Secret Key (Test-Keys gesetzt) |
STRIPE_WEBHOOK_SECRET |
Signatur-Prüfung des Webhook-Endpoints — wird beim Einrichten des Endpoints gesetzt (9E) |
CASHIER_CURRENCY |
Default usd → für uns eur setzen (9E) |
7. Offene Punkte (Stand 12.06.2026, nach 9E-Backbone)
- 9E-Backbone erledigt: Tarife liegen als Netto-Produkte/Preise in
Stripe (Test-Mode,
billing:sync-stripe-plans, IDs inplans); Webhook-ListenerProcessStripeWebhookspiegelt bezahlte Stripe-Rechnungen in den STR-Kreis (fortlaufende Nummer, Adress-Snapshot aus dem Stripe-Payload, idempotent) und erfüllt Einmalkäufe (checkout.session.completed→single_purchases.status = paid). Cashier-RoutePOST /stripe/webhookist aktiv. - Phase 9E (Rest): Checkout-Flows (Abo + Einmalkauf) inkl.
Buchungs-Seite, Webhook-Endpoint im Stripe-Dashboard registrieren +
STRIPE_WEBHOOK_SECRETsetzen, Slot-Logik vonusers.press_release_quota-Stub auf Plan-Kontingent umstellen (fachlich zu klären: Kontingent-Semantik für Grandfathered — Legacy-Produkt war „unbegrenzte PMs pro Pressemappe"). - VIES-Validierung der USt-ID (aktuell Formatprüfung).
- PDF-Erzeugung für MAN-/STR-Rechnungen (Layout inkl.
tax_note); Archiv-PDFs existieren bereits on-demand. ExpireGrandfatheredSubscriptions: Benachrichtigung zur Umstellung auf neue Tarife amgrandfathered_until(D-13-Rest).- Steuerberater-Abnahme der USt-Regeln und Rechnungstexte vor dem ersten produktiven MAN-/STR-Lauf.