81 lines
2.9 KiB
PHP
81 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace App\Actions;
|
|
|
|
use App\Console\Commands\AdvanceDunning;
|
|
use App\Models\DunningCase;
|
|
use App\Services\Billing\DunningMailer;
|
|
use App\Services\Billing\DunningSchedule;
|
|
use App\Services\Stripe\StripeClient;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
/**
|
|
* Einen Mahnfall schliessen — aber erst, wenn WIRKLICH alles bezahlt ist.
|
|
*
|
|
* Die Bedingung des Betreibers lautete „bis der Betrag + Mahngebühren beglichen
|
|
* ist". Wer den Rückstand zahlt und die Mahnspesen offen lässt, ist damit nicht
|
|
* fertig, und seine Cloud bleibt aus.
|
|
*
|
|
* Gefragt wird STRIPE, nicht der eigene Zustand: die offenen Rechnungen des
|
|
* Kunden sind die Wahrheit über das, was noch aussteht. Ein eigenes Feld
|
|
* „bezahlt" je Gebührenrechnung wäre eine zweite Buchhaltung, die mit der
|
|
* ersten auseinanderläuft — und zwar genau dann, wenn eine Zahlung ausserhalb
|
|
* unseres Ablaufs eingeht.
|
|
*/
|
|
class SettleDunningCase
|
|
{
|
|
public function __invoke(DunningCase $case): bool
|
|
{
|
|
if (! $case->isOpen()) {
|
|
return false;
|
|
}
|
|
|
|
$customerId = $case->subscription?->customer?->stripe_customer_id;
|
|
|
|
if (blank($customerId)) {
|
|
return false;
|
|
}
|
|
|
|
$offen = array_column(app(StripeClient::class)->openInvoices((string) $customerId), 'id');
|
|
|
|
// Platzhalter zählen nicht mit: sie sind keine Stripe-ID, und wogegen
|
|
// man sie prüfen sollte, weiss niemand. Sie stehen im Fall und auf der
|
|
// Seite — dort kann ein Mensch nachsehen. Sie hier als „offen" zu
|
|
// werten hiesse, den Fall für immer festzuhalten.
|
|
$unsere = array_values(array_filter(
|
|
array_merge([$case->stripe_invoice_id], array_values($case->fee_invoice_ids ?? [])),
|
|
fn (string $id) => $id !== AdvanceDunning::PENDING,
|
|
));
|
|
|
|
// Steht noch eine davon offen, ist der Fall nicht zu.
|
|
if (array_intersect($unsere, $offen) !== []) {
|
|
return false;
|
|
}
|
|
|
|
$case->update(['settled_at' => Carbon::now()]);
|
|
|
|
$stand = $case->level;
|
|
|
|
// Und die Cloud kommt zurück — NUR die dieses Vertrags.
|
|
//
|
|
// Vorher holte eine Zahlung auf Vertrag A jede gesperrte Cloud des
|
|
// Kunden zurück, auch die, für die noch geschuldet wird (Codex P1).
|
|
// Ein Kunde mit zwei Verträgen hätte durch das Begleichen des einen
|
|
// den anderen freibekommen.
|
|
$instance = $case->instance();
|
|
|
|
if ($instance !== null && $instance->suspended_at !== null) {
|
|
app(ResumeInstance::class)($instance);
|
|
}
|
|
|
|
// Nur wer wirklich stand, bekommt die Entwarnung. Wer auf Stufe 1
|
|
// bezahlt hat, hat nie eine Sperre gesehen — ihm zu schreiben, seine
|
|
// Cloud laufe wieder, wäre eine Nachricht über etwas, das nie war.
|
|
if ($stand >= DunningSchedule::SUSPENDED) {
|
|
app(DunningMailer::class)->resumed($case);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|