CluPilotCloud/app/Services/Billing/SetupFee.php

80 lines
3.1 KiB
PHP

<?php
namespace App\Services\Billing;
use App\Support\CompanyProfile;
/**
* The one-off charge for setting a customer up — and the one place it is turned
* into money.
*
* The fee had been advertised for months and charged to nobody. It was on the
* public price sheet ("Einrichtung kostet einmalig 118,80 €"), on the booking page
* ("zzgl. Einrichtung einmalig"), and the operator set the figure in the console —
* and CompanyProfile::setupFeeCents() had no reader outside those two sentences
* and the form that wrote it. Nothing put it on a checkout, on an invoice item or
* on any bill at all.
*
* It is charged now. The two sentences and the money read the same figure through
* this class, so a change of rate or of fee cannot move one without moving the
* others.
*
* **The figure is GROSS, like every other price this platform charges.** The
* console asks the operator for the net amount, because that is the commercial
* number and the field says so; what a customer pays is that with the domestic
* rate on it, formed by TaxTreatment::chargedCents(), which is the single place a
* gross is formed. That is also why the fee is the same for a consumer in Vienna
* and for a reverse-charge business in Rotterdam: the price on the page is the
* price at the till, for everybody, and the invoice is where the split differs.
*
* Zero means there is no such fee. Every reader has to treat that as "no line at
* all" rather than "a line of nought" — a checkout that itemises nothing owed is
* worse than one that says nothing.
*/
final class SetupFee
{
/** The net figure an operator typed, in cents. Zero when there is no fee. */
public static function netCents(): int
{
return CompanyProfile::setupFeeCents();
}
/** What a customer is actually charged for it, tax included. */
public static function chargedCents(): int
{
$net = self::netCents();
return $net === 0 ? 0 : TaxTreatment::chargedCents($net);
}
/**
* The fee as the extra, non-recurring line of a checkout session — or null
* when the operator has set none.
*
* `$currency` is the recurring Price's own, not the catalogue default: Stripe
* requires every line of one session to share a currency, and reading two
* sources for it is how a checkout ends up refused for a mismatch nobody can
* see from either page.
*
* @return array{amount_cents: int, label: string, currency: string}|null
*/
public static function checkoutLine(string $currency): ?array
{
$charged = self::chargedCents();
if ($charged === 0) {
return null;
}
return [
'amount_cents' => $charged,
// What the customer reads on Stripe's page and on Stripe's receipt.
// The same words the price sheet uses, because meeting a different
// name for the same charge at the till is exactly the moment somebody
// abandons a checkout.
'label' => __('checkout.setup_fee_line'),
'currency' => strtoupper($currency),
];
}
}