resetPage('subscriptionsPage'); $this->resetPage('purchasesPage'); $this->resetPage('invoicesPage'); } public function with(): array { $plans = Plan::query()->get(); /** @var array $plansByPriceId */ $plansByPriceId = []; foreach ($plans as $plan) { if ($plan->stripe_price_id_monthly) { $plansByPriceId[$plan->stripe_price_id_monthly] = ['plan' => $plan, 'interval' => __('monatlich')]; } if ($plan->stripe_price_id_yearly) { $plansByPriceId[$plan->stripe_price_id_yearly] = ['plan' => $plan, 'interval' => __('jährlich')]; } } $activeSubscriptions = Subscription::query()->active()->get(); $monthlyRecurringCents = $activeSubscriptions->sum(function (Subscription $subscription) use ($plansByPriceId): int { $entry = $plansByPriceId[$subscription->stripe_price] ?? null; if (! $entry) { return 0; } return $entry['interval'] === __('jährlich') ? (int) round($entry['plan']->yearly_price_cents / 12) : $entry['plan']->monthly_price_cents; }); return [ 'plansByPriceId' => $plansByPriceId, 'stats' => [ 'active_subscriptions' => $activeSubscriptions->count(), 'mrr_cents' => $monthlyRecurringCents, 'revenue_30d_cents' => (int) Invoice::query() ->where('status', InvoiceStatus::Paid->value) ->where('paid_at', '>=', now()->subDays(30)) ->sum('total_cents'), 'open_purchases' => SinglePurchase::query()->grantingSubmission()->count(), ], 'subscriptions' => $this->searchByUser(Subscription::query()->with('owner')) ->latest('created_at') ->paginate(25, pageName: 'subscriptionsPage'), 'purchases' => $this->searchByUser(SinglePurchase::query()->with(['user', 'pressRelease'])) ->latest('created_at') ->paginate(25, pageName: 'purchasesPage'), 'invoices' => $this->searchByUser(Invoice::query()->with('user')) ->latest('invoice_date') ->latest('id') ->paginate(25, pageName: 'invoicesPage'), ]; } /** * Wendet die User-Suche (Name oder E-Mail) auf eine der drei * Zahlungs-Tabellen an. Abos hängen über `owner` am User, Käufe und * Rechnungen über `user`. */ private function searchByUser(Builder $query): Builder { if (! filled($this->search)) { return $query; } $search = trim($this->search); $relation = $query->getModel() instanceof Subscription ? 'owner' : 'user'; return $query->whereHas($relation, function (Builder $query) use ($search): void { $query ->where('name', 'like', '%'.$search.'%') ->orWhere('email', 'like', '%'.$search.'%'); }); } }; ?>
{{-- ============== PAGE HEADER ============== --}}
{{ __('Admin Backend') }} {{ __('Administration · Finanzen') }}

{{ __('Zahlungen') }}

{{ __('Stripe-Abos, Einmalkäufe und der lokale Rechnungsausgang (STR-/MAN-Kreis) auf einen Blick. Stripe bleibt Zahlungs- und Belegquelle — diese Übersicht spiegelt die per Webhook synchronisierten Daten.') }}

{{ __('Legacy-Rechnungen') }} {{ __('Tarife & Pakete') }}
{{-- ============== KPI-Reihe ============== --}}
{{ __('Stripe-Subscriptions') }} {{ __('monatlich wiederkehrend') }} {{ __('bezahlte Rechnungen, brutto') }} {{ __('bezahlt, noch nicht eingelöst') }}
{{-- ============== SUCHE ============== --}}
{{ __('Suche') }}
{{-- ============== ABOS ============== --}}
{{ __('Abos') }} {{ __(':count Einträge', ['count' => number_format($subscriptions->total(), 0, ',', '.')]) }}
{{ __('User') }} {{ __('Tarif') }} {{ __('Status') }} {{ __('Seit') }} {{ __('Endet') }} @forelse ($subscriptions as $subscription) @if ($subscription->owner)
{{ $subscription->owner->name }}
{{ $subscription->owner->email }}
@else @endif
@php($planEntry = $plansByPriceId[$subscription->stripe_price] ?? null) @if ($planEntry)
{{ $planEntry['plan']->name }}
{{ $planEntry['interval'] }}
@else
{{ $subscription->stripe_price ?? '–' }}
@endif
@if (in_array($subscription->stripe_status, ['active', 'trialing'], true)) {{ $subscription->stripe_status === 'trialing' ? __('Testphase') : __('Aktiv') }} @elseif (in_array($subscription->stripe_status, ['past_due', 'unpaid', 'incomplete'], true)) {{ $subscription->stripe_status }} @else {{ $subscription->stripe_status }} @endif {{ $subscription->created_at?->format('d.m.Y') ?? '–' }} {{ $subscription->ends_at?->format('d.m.Y') ?? '–' }}
@empty
{{ __('Noch keine Stripe-Abos vorhanden.') }}
@endforelse
{{ $subscriptions->links('components.portal.pagination') }}
{{-- ============== EINMALKÄUFE ============== --}}
{{ __('Einmalkäufe') }} {{ __(':count Einträge', ['count' => number_format($purchases->total(), 0, ',', '.')]) }}
{{ __('User') }} {{ __('Typ') }} {{ __('Betrag (netto)') }} {{ __('Status') }} {{ __('Bezahlt am') }} {{ __('Eingelöst für') }} @forelse ($purchases as $purchase) @if ($purchase->user)
{{ $purchase->user->name }}
{{ $purchase->user->email }}
@else @endif
{{ $purchase->type->label() }} {{ number_format($purchase->price_cents / 100, 2, ',', '.') }} € @if ($purchase->status === \App\Enums\SinglePurchaseStatus::Paid) {{ $purchase->status->label() }} @elseif ($purchase->status === \App\Enums\SinglePurchaseStatus::Consumed) {{ $purchase->status->label() }} @elseif ($purchase->status === \App\Enums\SinglePurchaseStatus::Pending) {{ $purchase->status->label() }} @else {{ $purchase->status->label() }} @endif {{ $purchase->paid_at?->format('d.m.Y H:i') ?? '–' }} @if ($purchase->pressRelease) {{ \Illuminate\Support\Str::limit($purchase->pressRelease->title, 40) }} @else @endif
@empty
{{ __('Noch keine Einmalkäufe vorhanden.') }}
@endforelse
{{ $purchases->links('components.portal.pagination') }}
{{-- ============== RECHNUNGEN (STR/MAN) ============== --}}
{{ __('Rechnungsausgang (STR/MAN)') }} {{ __(':count Einträge', ['count' => number_format($invoices->total(), 0, ',', '.')]) }}
{{ __('Nummer') }} {{ __('Kreis') }} {{ __('User') }} {{ __('Betrag (brutto)') }} {{ __('Status') }} {{ __('Rechnungsdatum') }} @forelse ($invoices as $invoice) {{ $invoice->number }} @if ($invoice->stripe_invoice_id) {{ __('Stripe (STR)') }} @else {{ __('Manuell (MAN)') }} @endif @if ($invoice->user)
{{ $invoice->user->name }}
{{ $invoice->user->email }}
@else @endif
{{ number_format($invoice->total_cents / 100, 2, ',', '.') }} € @if ($invoice->status === \App\Enums\InvoiceStatus::Paid) {{ $invoice->status->label() }} @elseif ($invoice->status === \App\Enums\InvoiceStatus::Open) {{ $invoice->status->label() }} @else {{ $invoice->status->label() }} @endif
{{ $invoice->invoice_date?->format('d.m.Y') ?? '–' }}
@if ($invoice->paid_at)
{{ __('bezahlt: :date', ['date' => $invoice->paid_at->format('d.m.Y')]) }}
@endif
@empty
{{ __('Noch keine Rechnungen im neuen Rechnungsausgang.') }}
@endforelse
{{ $invoices->links('components.portal.pagination') }}