CluPilotCloud/app/Actions/OpenDunningCase.php

67 lines
2.5 KiB
PHP

<?php
namespace App\Actions;
use App\Models\DunningCase;
use App\Models\Subscription;
use App\Services\Billing\DunningMailer;
use App\Services\Billing\DunningSchedule;
use Illuminate\Support\Carbon;
/**
* Der Fall, der entsteht, wenn eine Abbuchung zum ersten Mal scheitert.
*
* Hängt an ApplyStripeBillingEvent::invoicePaymentFailed(), das bis hierher
* `past_due` setzte und schwieg. Der Kunde erfuhr davon erst, wenn seine Cloud
* stand.
*
* JE RECHNUNG GENAU EINMAL. Stripe versucht dieselbe Rechnung mehrfach, und
* jeder Versuch ist ein eigenes Ereignis — ohne diese Bedingung entstünden drei
* Fälle für eine Schuld, mit drei Mahnläufen und dreifachen Gebühren. Die
* Eindeutigkeit steht zusätzlich als Index in der Tabelle, damit zwei
* gleichzeitig eintreffende Ereignisse sie nicht umgehen können.
*
* Eröffnet wird auf Stufe 0 — dem HINWEIS. Keine Mahnung, keine Gebühr: eine
* abgelaufene Karte ist keine Zahlungsverweigerung.
*/
class OpenDunningCase
{
public function __invoke(Subscription $subscription, string $invoiceId): ?DunningCase
{
if ($invoiceId === '') {
return null;
}
$now = Carbon::now();
$case = DunningCase::query()->firstOrCreate(
['stripe_invoice_id' => $invoiceId],
[
'subscription_id' => $subscription->id,
'level' => 0,
'opened_at' => $now,
// Die erste Mahnung, nicht der Hinweis: der geht sofort hinaus.
'next_step_at' => $now->copy()->addDays(DunningSchedule::dayOfLevel(1)),
'fee_invoice_ids' => [],
'notified_levels' => [],
],
);
// Der Hinweis geht SOFORT hinaus, nicht erst mit dem Tageslauf: der
// Kunde soll von der gescheiterten Abbuchung erfahren, solange er noch
// weiss, wovon die Rede ist. Nur beim ERSTEN Mal — wasRecentlyCreated
// unterscheidet das Eröffnen vom Wiedersehen derselben Rechnung.
if ($case->wasRecentlyCreated) {
app(DunningMailer::class)->level($case->load('subscription.customer'), 0);
// VERMERKEN, sonst hält der Nachhol-Lauf des nächsten Tages diese
// Nachricht für verloren und schickt dem Kunden dieselbe Mail ein
// zweites Mal. Erst nach dem Einreihen: andersherum stünde hier
// eine Nachricht, die nie einging.
$case->update(['notified_levels' => [0]]);
}
return $case;
}
}