presseportale/routes/customer.php
Kevin Adametz a5bf33109e
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
billing: Rechnungsversand — PDF im Anhang der Zahlungsmail, optionale Rechnungs-E-Mail, Stripe-Rechnungs-PDF direkt aus dem Kundenbereich
Kundenwunsch 06.10.2026: Ein Kunde kam über die Stripe-Rechnungsseite nicht
an seine Rechnung und fragte nach Belegen und einer Rechnungs-E-Mail.

- Profil ▸ Rechnungsadresse ▸ Rechnungsversand: Schalter „Rechnungs-PDF an
  die Konto-E-Mail" (Standard an), optionale „E-Mail für Rechnungen" mit
  Schalter „Rechnungen als PDF an diese Adresse senden" (Standard an).
  Neue Spalten an billing_addresses (klein, ohne FULLTEXT).
- InvoiceDelivery entscheidet Empfänger und Anhang; gleiche Adresse wie das
  Konto ergibt eine Mail mit PDF.
- Zahlungsbestätigung (STR) und Bestandskunden-Rechnung/-Erinnerung (MAN)
  folgen der Einstellung; die Fassung an die Rechnungs-E-Mail nennt das
  Konto, ohne Blindkopie und ohne Login-Button.
- StripeInvoicePdf holt das Stripe-PDF einmal und legt es lokal ab
  (storage/app/private/invoices/stripe); Mailversand wiederholt bei
  Stripe-Ausfall (5 Versuche).
- Rechnungsseite: „PDF öffnen" aus pressekonto, „Beleg bei Stripe" für den
  Zahlungsbeleg auf der Stripe-Seite.

Suite 2.340 grün, einer übersprungen; Larastan und Pint ohne Meldung.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:47:49 +00:00

130 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Http\Controllers\CheckoutController;
use App\Http\Controllers\EmailUnsubscribeController;
use App\Http\Controllers\InvoicePdfController;
use App\Http\Controllers\LegacyInvoicePdfController;
use App\Http\Controllers\ProofPdfController;
use App\Http\Controllers\StripeInvoicePdfController;
use App\Http\Controllers\StripeInvoiceRedirectController;
use App\Http\Controllers\Web\PressReleaseReportController;
use App\Http\Middleware\EnsureUserIsCustomer;
use App\Http\Middleware\LogSlowAdminRequests;
use Illuminate\Support\Facades\Route;
// ============================================================
// "Mein Bereich" – Eigentümer-Sicht im gemeinsamen Admin-Panel
// URL-Prefix: /admin/me/*
// Routen-Name: me.*
// Zugriff: Rollen admin, editor, customer (alle eingeloggten Panel-User)
// ============================================================
Route::middleware(['auth', 'verified', EnsureUserIsCustomer::class, LogSlowAdminRequests::class])
->prefix('admin/me')
->name('me.')
->group(function () {
Route::livewire('/', 'customer.dashboard')->name('dashboard');
Route::livewire('press-releases', 'customer.press-releases.index')->name('press-releases.index');
Route::livewire('press-releases/create', 'customer.press-releases.create')->name('press-releases.create');
Route::livewire('press-releases/{id}', 'customer.press-releases.show')->name('press-releases.show');
Route::livewire('press-releases/{id}/edit', 'customer.press-releases.edit')->name('press-releases.edit');
Route::get('press-releases/{id}/nachweis', ProofPdfController::class)
->where('id', '[0-9]+')
->name('press-releases.proof');
// Meldeverfahren (Phase 3): Eigentümer-Einstieg ohne Magic-Link —
// die Zuordnung der PM ersetzt die Verifikation (logged_in).
Route::post('press-releases/{pressReleaseId}/melden', [PressReleaseReportController::class, 'startFromCustomer'])
->where('pressReleaseId', '[0-9]+')
->name('press-releases.report');
Route::livewire('firmen', 'customer.press-kits.index')->name('press-kits.index');
Route::livewire('firmen/anlegen', 'customer.press-kits.create')->name('press-kits.create');
Route::livewire('firmen/{id}', 'customer.press-kits.show')->name('press-kits.show');
Route::redirect('pressemappen', '/admin/me/firmen', 301);
Route::get('pressemappen/{id}', fn (string $id) => redirect("/admin/me/firmen/{$id}", 301))
->where('id', '[0-9]+');
Route::livewire('buchungen-add-ons', 'customer.bookings')->name('bookings.index');
Route::get('checkout/abo/{planSlug}/{interval}', [CheckoutController::class, 'subscription'])
->whereIn('interval', ['monthly', 'yearly'])
->name('checkout.subscription');
Route::post('checkout/tarifwechsel/{planSlug}/{interval}', [CheckoutController::class, 'swap'])
->whereIn('interval', ['monthly', 'yearly'])
->name('checkout.swap');
Route::get('checkout/einzel-pm', [CheckoutController::class, 'singlePm'])
->name('checkout.single-pm');
Route::get('checkout/credits/{pack}', [CheckoutController::class, 'creditTopup'])
->name('checkout.credit-topup');
Route::get('checkout/depublikation/{pressReleaseId}', [CheckoutController::class, 'depublication'])
->where('pressReleaseId', '[0-9]+')
->name('checkout.depublication');
Route::get('checkout/aenderung/{pressReleaseId}', [CheckoutController::class, 'change'])
->where('pressReleaseId', '[0-9]+')
->name('checkout.change');
Route::get('checkout/abo-verwalten', [CheckoutController::class, 'billingPortal'])
->name('checkout.billing-portal');
Route::livewire('invoices', 'customer.invoices')->name('invoices.index');
Route::get('legacy-invoices/{legacyInvoice}/pdf', LegacyInvoicePdfController::class)->name('invoices.pdf');
Route::get('invoices/{invoice}/stripe', StripeInvoiceRedirectController::class)->name('invoices.stripe');
Route::get('invoices/{invoice}/stripe-pdf', StripeInvoicePdfController::class)->name('invoices.stripe-pdf');
Route::get('invoices/{invoice}/pdf', InvoicePdfController::class)->name('invoices.manual-pdf');
Route::livewire('tokens', 'customer.tokens')->name('tokens.index');
Route::livewire('profile', 'customer.profile')->name('profile');
Route::livewire('security', 'customer.security')->name('security');
});
// ============================================================
// E-Mail-Benachrichtigungen: One-Click-Abmeldung ohne Login.
// Signierte Links aus den Benachrichtigungs-Mails (kein Ablauf, damit
// auch alte Mails funktionieren). Der POST auf die Abmelde-URL bedient
// den List-Unsubscribe-Header (RFC 8058, ohne CSRF — Ausnahme in
// bootstrap/app.php); der Wieder-Anmelden-POST kommt von der
// Bestätigungsseite selbst.
// ============================================================
Route::prefix('e-mail-benachrichtigungen')
->name('email-preferences.')
->middleware('throttle:30,1')
->group(function () {
Route::get('abmelden/{user}/{type}', [EmailUnsubscribeController::class, 'show'])
->name('unsubscribe');
Route::post('abmelden/{user}/{type}', [EmailUnsubscribeController::class, 'oneClickUnsubscribe'])
->name('unsubscribe.one-click');
Route::post('anmelden/{user}/{type}', [EmailUnsubscribeController::class, 'resubscribe'])
->name('resubscribe');
// Roadmap 1d.3: Abmeldung für Empfänger ohne Konto (Pressekontakt,
// Firmen-E-Mail einer API-Meldung) — adressbezogen, in der
// Sperrliste geführt. Signiert, damit nur ein Link aus unserer Mail
// eine Adresse abmelden kann.
Route::get('abmelden-adresse', [EmailUnsubscribeController::class, 'showAddress'])
->name('unsubscribe-address');
Route::post('abmelden-adresse', [EmailUnsubscribeController::class, 'oneClickUnsubscribeAddress'])
->name('unsubscribe-address.one-click');
Route::post('anmelden-adresse', [EmailUnsubscribeController::class, 'resubscribeAddress'])
->name('resubscribe-address');
});
// ============================================================
// Legacy /customer/*-Pfade als 301-Redirect erhalten,
// damit Bookmarks und alte Mails weiter funktionieren.
// ============================================================
Route::prefix('customer')->group(function () {
Route::redirect('/', '/admin/me', 301);
Route::redirect('press-releases', '/admin/me/press-releases', 301);
Route::redirect('press-releases/create', '/admin/me/press-releases/create', 301);
Route::get('press-releases/{id}', fn (string $id) => redirect("/admin/me/press-releases/{$id}", 301))
->where('id', '[0-9]+');
Route::get('press-releases/{id}/edit', fn (string $id) => redirect("/admin/me/press-releases/{$id}/edit", 301))
->where('id', '[0-9]+');
Route::redirect('pressemappen', '/admin/me/firmen', 301);
Route::get('pressemappen/{id}', fn (string $id) => redirect("/admin/me/firmen/{$id}", 301))
->where('id', '[0-9]+');
Route::redirect('buchungen-add-ons', '/admin/me/buchungen-add-ons', 301);
Route::redirect('invoices', '/admin/me/invoices', 301);
Route::redirect('tokens', '/admin/me/tokens', 301);
Route::redirect('profile', '/admin/me/profile', 301);
Route::redirect('security', '/admin/me/security', 301);
});