diff --git a/app/Services/Billing/StripeCheckoutService.php b/app/Services/Billing/StripeCheckoutService.php index 6d6b5c4d..ce2de996 100644 --- a/app/Services/Billing/StripeCheckoutService.php +++ b/app/Services/Billing/StripeCheckoutService.php @@ -82,6 +82,12 @@ class StripeCheckoutService * Name, Pflicht bei USt-ID-Abfrage) wird darum am Stripe-Customer * gespeichert (`customer_update: auto`). * + * `automatic_tax` muss hier explizit gesetzt werden: + * Cashier::calculateTaxes() greift nur bei direkter Abo-Erstellung, + * NICHT bei Checkout-Sessions — ohne das Flag zeigt Stripe nur den + * Nettopreis an (QA-Befund 07.07.). Voraussetzung im Stripe-Account: + * Stripe Tax mit Origin-Adresse und DE-Registrierung (WS-7). + * * @return array */ private function sessionOptions(): array @@ -90,6 +96,8 @@ class StripeCheckoutService 'success_url' => route('me.bookings.index', ['checkout' => 'erfolg']), 'cancel_url' => route('me.bookings.index', ['checkout' => 'abbruch']), 'billing_address_collection' => 'required', + 'automatic_tax' => ['enabled' => true], + 'tax_id_collection' => ['enabled' => true], 'customer_update' => [ 'address' => 'auto', 'name' => 'auto', @@ -97,6 +105,26 @@ class StripeCheckoutService ]; } + /** + * Line-Item für Ad-hoc-Beträge (ohne Stripe-Price). `tax_behavior` + * ist Pflicht, sobald `automatic_tax` aktiv ist — Cashiers + * checkoutCharge() setzt es nicht und ist darum hier ersetzt. + * + * @return array + */ + private function adHocLineItem(int $priceCents, string $name): array + { + return [ + 'price_data' => [ + 'currency' => config('cashier.currency', 'eur'), + 'product_data' => ['name' => $name], + 'unit_amount' => $priceCents, + 'tax_behavior' => 'exclusive', + ], + 'quantity' => 1, + ]; + } + /** * URL zum Stripe Billing Portal (Zahlungsmethode, Rechnungen, Kündigung). * Rücksprung auf die Buchungs-Seite. @@ -130,10 +158,8 @@ class StripeCheckoutService { $this->syncTaxIdFromBillingAddress($user); - return $user->checkoutCharge( - $purchase->price_cents, - 'Depublikation einer Pressemitteilung', - 1, + return $user->checkout( + [$this->adHocLineItem($purchase->price_cents, 'Depublikation einer Pressemitteilung')], [ ...$this->sessionOptions(), 'metadata' => ['single_purchase_id' => (string) $purchase->id], @@ -150,10 +176,8 @@ class StripeCheckoutService { $this->syncTaxIdFromBillingAddress($user); - return $user->checkoutCharge( - $topup->price_cents, - "Credit-Guthaben ({$topup->credits} Credits)", - 1, + return $user->checkout( + [$this->adHocLineItem($topup->price_cents, "Credit-Guthaben ({$topup->credits} Credits)")], [ ...$this->sessionOptions(), 'metadata' => ['credit_topup_id' => (string) $topup->id], diff --git a/docs/weiteres/Live-Deployment-Checkliste (WS-7).md b/docs/weiteres/Live-Deployment-Checkliste (WS-7).md index bc0d9a1f..9a44adf1 100644 --- a/docs/weiteres/Live-Deployment-Checkliste (WS-7).md +++ b/docs/weiteres/Live-Deployment-Checkliste (WS-7).md @@ -111,12 +111,15 @@ Diese Phase-Migrationen MÜSSEN durchlaufen (Details im Sicherheits-Doc §7): Einmalige Setups (idempotent, in dieser Reihenfolge): +- [ ] **Stripe Tax im LIVE-Account aktivieren** (QA-Befund 07.07.): Dashboard → Einstellungen → Steuer — Origin-Adresse (In der Lake 4, 33739 Bielefeld, DE) hinterlegen, Standard-Steuerverhalten „exklusiv", **Registrierung Deutschland (Standard)** anlegen. Alle Checkout-Sessions laufen mit `automatic_tax` — ohne aktives Stripe Tax lehnt Stripe jede Session ab (kein Checkout möglich). Im Testmode am 07.07. per API eingerichtet; im Live-Account manuell wiederholen. + ```bash # Stripe: Tarife als Netto-Produkte/Preise anlegen → liefert STRIPE_PRICE_SINGLE_PM php artisan billing:sync-stripe-plans # Hinweis: Credit-Pakete (Wallet-Topup) brauchen KEINE Stripe-Preis-IDs — sie -# laufen als Ad-hoc-Charge (checkoutCharge). Preise/Staffeln stehen in -# config/credits.php (Pakete, Extra-PM-Tiers, Boost, Nachweis, Prüf-Overflow). +# laufen als Ad-hoc-Charge mit price_data (tax_behavior=exclusive). Preise/ +# Staffeln stehen in config/credits.php (Pakete, Extra-PM-Tiers, Boost, +# Nachweis, Prüf-Overflow). # Bestandsschutz aus dem Rechnungsarchiv neu nach user_payment_options migrieren. # WICHTIG: auf Prod erneut laufen lassen, damit net_cents befüllt ist (vorher nur Dry-Run prüfen).