# 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`](../Decision-Update%20Preisstruktur%20&%20Ver%C3%B6ffentlichungs-Flow.md) — verbindliche Launch-Entscheidungen (Tarife, Kontingente, Flow, Netto-Preise). - [`docs/PHASE-9-FLOW-UND-TARIFE-PLAN.md`](../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` 1. Findet `user_payment_options` mit Status `active`/`grandfathered`, **ohne** `stripe_subscription_id`, deren `current_period_end` erreicht ist. 2. Friert die Rechnungsadresse als Snapshot ein (`invoice_billing_addresses`, inkl. `vat_id`). 3. Stellt die Rechnung aus: Netto-Basis × USt-Regel (Abschnitt 4), MAN-Nummer, Zahlungsziel `billing.manual_due_days` (Default 14 Tage). 4. Schaltet die Periode weiter (`monthly`/`yearly` aus `legacy_conditions` bzw. `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) 1. **Phase 9E**: Stripe-Produkte/Preise anlegen (netto) + IDs in `plans` pflegen, Checkout (Abo + Einmalkauf), Webhooks inkl. Spiegelung der Stripe-Rechnungen nach `invoices` mit STR-Nummer, Slot-Logik von `users.press_release_quota`-Stub auf Plan-Kontingent umstellen. 2. **VIES-Validierung** der USt-ID (aktuell Formatprüfung). 3. **PDF-Erzeugung** für MAN-/STR-Rechnungen (Layout inkl. `tax_note`); Archiv-PDFs existieren bereits on-demand. 4. **`ExpireGrandfatheredSubscriptions`**: Benachrichtigung zur Umstellung auf neue Tarife am `grandfathered_until` (D-13-Rest). 5. **Steuerberater-Abnahme** der USt-Regeln und Rechnungstexte vor dem ersten produktiven MAN-/STR-Lauf.