CluPilotCloud/app/Actions/SettleDunningCase.php

70 lines
2.3 KiB
PHP

<?php
namespace App\Actions;
use App\Models\DunningCase;
use App\Models\Instance;
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');
$unsere = array_merge([$case->stripe_invoice_id], array_values($case->fee_invoice_ids ?? []));
// 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 des betroffenen Vertrags —
// andere Verträge desselben Kunden waren nie betroffen.
foreach (Instance::query()
->where('customer_id', $case->subscription?->customer_id)
->whereNotNull('suspended_at')
->get() as $instance) {
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;
}
}