83 lines
2.8 KiB
PHP
83 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Models\DunningCase;
|
|
use App\Models\FailedCheckout;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* Wo Geld ausbleibt — beide Arten an einer Stelle.
|
|
*
|
|
* 1. **Mahnfälle.** Ein laufender Vertrag, dessen Abbuchung gescheitert ist.
|
|
* Der Fall wandert durch die Stufen des DunningSchedule bis zur Begleichung
|
|
* oder zur Sperre.
|
|
* 2. **Geplatzte Käufe.** Bei SEPA oder Sofortüberweisung klärt die Zahlung
|
|
* erst nach dem Abschluss der Kasse. Klärt sie nicht, entsteht kein Auftrag
|
|
* — richtig so — und bis zum 31.7.2026 auch sonst nichts. Ein verlorener
|
|
* Verkauf, von dem niemand hörte.
|
|
*
|
|
* Eine Seite und nicht zwei: beide Male ist die Frage dieselbe — wer schuldet
|
|
* was, und was ist der nächste Schritt. Zwei Seiten hiessen zwei Orte, an denen
|
|
* man nachsehen muss, und einer davon wird vergessen.
|
|
*
|
|
* Hinter `billing.manage`, nicht hinter `hosts.manage`: hier stehen Kundennamen,
|
|
* Beträge und offene Schulden. Die Rolle `Billing` hat genau dieses Recht und
|
|
* sonst nur `console.view` — die Buchhaltung sieht das, ohne Admin zu sein.
|
|
*/
|
|
#[Layout('layouts.admin')]
|
|
class PaymentProblems extends Component
|
|
{
|
|
/** @var array<int, string> FailedCheckout-ID => Begründung */
|
|
public array $note = [];
|
|
|
|
public function mount(): void
|
|
{
|
|
abort_unless(Gate::allows('billing.manage'), 403);
|
|
}
|
|
|
|
/**
|
|
* Einen geplatzten Kauf als erledigt abhaken.
|
|
*
|
|
* Mit Begründung, nicht nur mit einem Haken: „erledigt" allein ist beim
|
|
* nächsten Nachfragen wertlos — überwiesen? neu bestellt? nicht erreichbar?
|
|
* Wer es war, steht mit dabei.
|
|
*/
|
|
public function resolve(int $id): void
|
|
{
|
|
$this->authorize('billing.manage');
|
|
|
|
$problem = FailedCheckout::query()->findOr($id, fn () => abort(404));
|
|
|
|
$begruendung = trim($this->note[$id] ?? '');
|
|
$wer = Auth::guard('operator')->user()?->email ?? 'console';
|
|
|
|
$problem->update([
|
|
'resolved_at' => Carbon::now(),
|
|
'note' => $begruendung !== '' ? $begruendung.' — '.$wer : $wer,
|
|
]);
|
|
|
|
unset($this->note[$id]);
|
|
$this->dispatch('notify', message: __('payment_problems.resolved'));
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.admin.payment-problems', [
|
|
'cases' => DunningCase::query()
|
|
->whereNull('settled_at')
|
|
->with('subscription.customer')
|
|
->orderBy('next_step_at')
|
|
->get(),
|
|
'failed' => FailedCheckout::query()
|
|
->whereNull('resolved_at')
|
|
->orderByDesc('failed_at')
|
|
->get(),
|
|
]);
|
|
}
|
|
}
|