93 lines
2.8 KiB
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);
|
|
}
|
|
}
|