94 lines
4.2 KiB
PHP
94 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\PlanFamily;
|
|
use App\Models\PlanPrice;
|
|
use App\Models\StripeAddonPrice;
|
|
use App\Models\StripePlanPrice;
|
|
|
|
/**
|
|
* In welchem Stripe-KONTO die gespeicherten Objekt-IDs entstanden sind.
|
|
*
|
|
* Der Betriebsmodus schaltet die Zugangsdaten um. Er schaltet nicht um, was aus
|
|
* ihnen entstanden ist: eine Stripe-Price-ID und eine Product-ID gehören dem
|
|
* Konto, das sie ausgestellt hat, und ein Testkonto und ein Livekonto sind zwei
|
|
* Konten. Jedes Zugangsdatum hat auf dieser Installation zwei Plätze; die
|
|
* daraus abgeleiteten IDs stehen in einwertigen Spalten
|
|
* (`plan_families.stripe_product_id`, `plan_prices.stripe_price_id`) und in den
|
|
* zwei Registern (`stripe_plan_prices`, `stripe_addon_prices`).
|
|
*
|
|
* Ohne diese Zeile lief der geplante Ablauf dieser Installation ins Leere:
|
|
* abgleichen im Testbetrieb, Live-Schlüssel hinterlegen, umschalten — und
|
|
* `billing.catalogue_synced` meldete weiter „erfüllt", weil es nur prüfte, ob
|
|
* die Spalte gefüllt ist. Die Seite sagte „Bereit für Livebetrieb", und die
|
|
* erste echte Bestellung schickte eine Test-Preis-ID an die Live-API: *No such
|
|
* price*, kein Auftrag. Genau die falsche Grünmeldung, gegen die diese Seite
|
|
* gebaut ist, ausgelöst durch genau den Schalter, den dieses Vorhaben einführt.
|
|
*
|
|
* **Warum eine Einstellungszeile und nicht die Objekte selbst.** Stripe schreibt
|
|
* die Kontoart nicht in die ID: `prod_…` und `price_…` sehen im Test- und im
|
|
* Livekonto gleich aus (nur SCHLÜSSEL tragen `_test_`/`_live_` im Präfix — das
|
|
* ist OperatingMode::ofStripeKey()). Die Herkunft ist aus einer gespeicherten ID
|
|
* also nicht ableitbar, und nachfragen dürfte die Bereitschaftsseite nicht: sie
|
|
* erreicht beim Seitenaufruf nichts über das Netz. Bleibt: beim Abgleich
|
|
* festhalten, in welchem Modus die IDs entstanden sind.
|
|
*
|
|
* **Warum EINE Zeile für den ganzen Katalog.** Ein Lauf von
|
|
* stripe:sync-catalogue benutzt genau einen Schlüssel, also genau ein Konto. Und
|
|
* der Befehl weigert sich, in einen Katalog hineinzuarbeiten, der zu einem
|
|
* anderen Konto gehört (siehe dort) — ohne diese Weigerung könnte ein Lauf
|
|
* einen halb gemischten Zustand hinterlassen, über den eine einzelne Zeile dann
|
|
* lügen würde.
|
|
*/
|
|
final class StripeCatalogueMode
|
|
{
|
|
public const SETTING = 'billing.catalogue_mode';
|
|
|
|
/** Der Modus, in dem der Katalog zuletzt angelegt wurde — null: noch nie. */
|
|
public static function recorded(): ?OperatingMode
|
|
{
|
|
$stored = Settings::get(self::SETTING);
|
|
|
|
return $stored === null ? null : OperatingMode::tryFrom((string) $stored);
|
|
}
|
|
|
|
/** Festhalten, wessen Konto die gerade angelegten IDs gehören. */
|
|
public static function record(?OperatingMode $mode = null): void
|
|
{
|
|
Settings::set(self::SETTING, ($mode ?? OperatingMode::current())->value);
|
|
}
|
|
|
|
/**
|
|
* Gehört der gespeicherte Katalog zum Konto des aktiven Modus?
|
|
*
|
|
* „Noch nie abgeglichen" (null) ist hier bewusst KEINE Übereinstimmung: eine
|
|
* Installation, in der IDs liegen, über deren Herkunft nichts festgehalten
|
|
* ist, kann nicht behaupten, sie gehörten zum aktiven Konto. Die Migration,
|
|
* die die Plätze einführt, trägt für den einen Fall nach, den sie belegen
|
|
* kann (der vorhandene Schlüssel ist der einzige, mit dem je etwas angelegt
|
|
* worden sein kann).
|
|
*/
|
|
public static function matchesActiveMode(): bool
|
|
{
|
|
return self::recorded() === OperatingMode::current();
|
|
}
|
|
|
|
/**
|
|
* Liegt überhaupt eine Stripe-Objekt-ID in der Datenbank?
|
|
*
|
|
* Alle vier Orte, an denen der Abgleich etwas hinterlässt — auch die zwei
|
|
* Register, weil PlanPrices::inStep() für den Netto-Preis eines
|
|
* Reverse-Charge-Kunden ALLEIN das Register fragt: eine geleerte Spalte in
|
|
* `plan_prices` ohne gelöschte Registerzeile ließe genau diese Hälfte des
|
|
* Katalogs still auf das alte Konto zeigen.
|
|
*/
|
|
public static function hasStoredObjects(): bool
|
|
{
|
|
return PlanFamily::query()->whereNotNull('stripe_product_id')->exists()
|
|
|| PlanPrice::query()->whereNotNull('stripe_price_id')->exists()
|
|
|| StripePlanPrice::query()->exists()
|
|
|| StripeAddonPrice::query()->exists();
|
|
}
|
|
}
|