diff --git a/app/Console/Commands/SyncStripePlans.php b/app/Console/Commands/SyncStripePlans.php index 7016dd4f..bb04b55e 100644 --- a/app/Console/Commands/SyncStripePlans.php +++ b/app/Console/Commands/SyncStripePlans.php @@ -36,7 +36,20 @@ class SyncStripePlans extends Command foreach (Plan::query()->active()->get() as $plan) { if ($plan->stripe_product_id && $plan->stripe_price_id_monthly && $plan->stripe_price_id_yearly) { - $this->line("{$plan->slug}: vollständig verknüpft, übersprungen."); + // Preise sind unveränderlich, der Produktname nicht: Er wird + // bei jedem Lauf nachgezogen, damit Stripe-Belege den + // sprechenden Namen tragen (statt z. B. nur "Starter"). + if ($dryRun) { + $this->line("[dry-run] {$plan->slug}: verknüpft — Produktname auf \"{$this->stripeProductName($plan)}\" aktualisieren."); + + continue; + } + + $stripe->products->update($plan->stripe_product_id, [ + 'name' => $this->stripeProductName($plan), + ]); + + $this->line("{$plan->slug}: verknüpft — Produktname aktualisiert."); continue; } @@ -56,7 +69,7 @@ class SyncStripePlans extends Command if (! $productId) { $product = $stripe->products->create([ - 'name' => $plan->name, + 'name' => $this->stripeProductName($plan), 'metadata' => ['plan_slug' => $plan->slug], ]); $productId = $product->id; @@ -124,6 +137,19 @@ class SyncStripePlans extends Command $this->warn("Bitte in die .env eintragen: STRIPE_PRICE_SINGLE_PM={$price->id}"); } + /** + * Sprechender Produktname für Stripe-Belege: Auf Rechnung und + * Checkout-Seite erscheint sonst nur der nackte Tarifname. + */ + private function stripeProductName(Plan $plan): string + { + return sprintf( + 'Tarif %s — %d Pressemitteilungen/Monat (pressekonto)', + $plan->name, + $plan->press_release_quota, + ); + } + private function createPrice(object $stripe, string $productId, Plan $plan, int $unitAmount, string $interval): string { $price = $stripe->prices->create([ diff --git a/app/Models/User.php b/app/Models/User.php index ba35f94b..9609486c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -123,6 +123,18 @@ class User extends Authenticatable implements MustVerifyEmail return $address?->company ?: ($address?->name ?: $this->name); } + /** + * Sprache für Stripe-Belege (Cashier-Hook): Rechnungen, Quittungen und + * Mahn-Mails von Stripe folgen der Kontosprache statt Stripes + * Englisch-Default. + * + * @return array + */ + public function stripePreferredLocales(): ?array + { + return [$this->language === 'en' ? 'en' : 'de-DE']; + } + /** * Buchungs-Voraussetzung (Entscheidung 12.06.2026): Tarif- und * Einzel-PM-Checkouts erfordern eine vollständige Rechnungsadresse. diff --git a/app/Services/Billing/StripeCheckoutService.php b/app/Services/Billing/StripeCheckoutService.php index 3d6d3c05..4956aefa 100644 --- a/app/Services/Billing/StripeCheckoutService.php +++ b/app/Services/Billing/StripeCheckoutService.php @@ -348,6 +348,8 @@ class StripeCheckoutService 'cancel_url' => route('me.bookings.index', ['checkout' => 'abbruch']), 'billing_address_collection' => 'auto', 'automatic_tax' => ['enabled' => true], + // Checkout-Seite in der Kontosprache statt Browser-Erkennung. + 'locale' => $user->language === 'en' ? 'en' : 'de', ]; if ($user->stripeAddress() === null) { diff --git a/tests/Feature/Billing/StripeCustomerHooksTest.php b/tests/Feature/Billing/StripeCustomerHooksTest.php index 50bb4339..2e0c14fa 100644 --- a/tests/Feature/Billing/StripeCustomerHooksTest.php +++ b/tests/Feature/Billing/StripeCustomerHooksTest.php @@ -31,6 +31,15 @@ test('the stripe recipient name falls back to the billing name, then the display expect($withoutAddress->stripeName())->toBe('Nur Anzeigename'); }); +test('the stripe locale follows the account language', function () { + // Steuert die Sprache der Stripe-Belege (Rechnung, Quittung, Mahnung). + $german = User::factory()->create(['language' => 'de']); + $english = User::factory()->create(['language' => 'en']); + + expect($german->stripePreferredLocales())->toBe(['de-DE']) + ->and($english->stripePreferredLocales())->toBe(['en']); +}); + test('the stripe address mirrors the billing address', function () { $user = User::factory()->create(); BillingAddress::factory()->create([