CluPilotCloud/app/Services/Billing/DunningSchedule.php

93 lines
2.8 KiB
PHP

<?php
namespace App\Services\Billing;
use App\Support\Settings;
/**
* Wann gemahnt wird und was es kostet — an einer Stelle, einstellbar.
*
* Fünf Stufen, gezählt in Tagen seit der ersten gescheiterten Abbuchung:
*
* 0 Hinweis „Ihre Abbuchung ist gescheitert, hier ist der Knopf"
* 1 erste Mahnung
* 2 zweite Mahnung ab hier Gebühr (Vorgabe)
* 3 dritte Mahnung
* 4 Cloud aus
*
* **Stufe 0 ist keine Mahnung.** Keine Gebühr, keine Frist im Rechtssinn — nur
* die Nachricht, dass etwas schiefging. Eine abgelaufene Karte ist keine
* Zahlungsverweigerung, und wer sie am selben Tag mahnt, verliert Kunden, die
* zahlen wollten.
*/
final class DunningSchedule
{
public const DAYS = 'dunning.days';
public const FEE_FROM_LEVEL = 'dunning.fee_from_level';
public const FEES = 'dunning.fee_cents';
/** Die letzte Stufe: ab hier steht die Cloud. */
public const SUSPENDED = 4;
/** @var array<int, int> Stufe => Tage seit dem ersten Fehlschlag */
public const DEFAULT_DAYS = [0, 3, 10, 17, 24];
/** @var array<int, int> Stufe => Gebühr in Cent */
public const DEFAULT_FEES = [2 => 500, 3 => 1000];
public const DEFAULT_FEE_FROM_LEVEL = 2;
/**
* Die fünf Fristen, aufsteigend.
*
* Sortiert statt geglaubt: eine Reihenfolge, die rückwärts läuft, ist kein
* Zeitplan — der Tageslauf spränge über Stufen, und die dritte Mahnung
* stünde vor der ersten. Ein Formular kann das erzeugen, also wird es hier
* abgefangen und nicht dort erhofft.
*
* @return array<int, int>
*/
public static function days(): array
{
$stored = Settings::get(self::DAYS);
$days = is_array($stored) && count($stored) === count(self::DEFAULT_DAYS)
? array_map('intval', array_values($stored))
: self::DEFAULT_DAYS;
sort($days);
return $days;
}
public static function dayOfLevel(int $level): int
{
return self::days()[$level] ?? self::DEFAULT_DAYS[$level] ?? 0;
}
/** Ab welcher Stufe eine Gebühr anfällt. */
public static function feeFromLevel(): int
{
$stored = Settings::get(self::FEE_FROM_LEVEL);
// Nie vor Stufe 1: Stufe 0 ist der Hinweis, und eine Gebühr darauf
// wäre eine Mahngebühr ohne Mahnung.
return is_numeric($stored) ? max(1, (int) $stored) : self::DEFAULT_FEE_FROM_LEVEL;
}
/** Was diese Stufe kostet, in Cent. Null unterhalb der eingestellten Stufe. */
public static function feeCents(int $level): int
{
if ($level < self::feeFromLevel()) {
return 0;
}
$stored = Settings::get(self::FEES);
$fees = is_array($stored) ? $stored : self::DEFAULT_FEES;
return (int) ($fees[$level] ?? self::DEFAULT_FEES[$level] ?? 0);
}
}