Name the recurring filter, and stop the spec contradicting itself
Three sentences left over from the wave before, all of the kind this branch exists to end. - "Every active Price" was one word short. activePricesFor() sends `type: recurring` as well as `active: true`, so a one-time Price is never listed — which made the hand-built-payment-link sentence false for a one-time link: neither listed nor archived. Both places say `recurring` now, and the docblock says why the filter is not a gap: createPrice() always sends `recurring[interval]` and is the only call that mints a Price at one of our Products, so the filter can only ever hide somebody else's one-time Price. A filter left out of a sentence is a gap the next reader goes looking for. - The sweep test file still opened "The orphans nobody can adopt" — the exact narrowing the command's own docblock had to be corrected away from. It happens to describe the fixtures, which are all metadata-less, so it now says that instead of claiming it of the command. - The design doc called --archive "harmlos" seven lines above the correction establishing that it is not, for an adoptable orphan. A document that contradicts itself seven lines apart teaches nobody anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>feature/host-bootstrap
parent
a43be4186a
commit
79654333a8
|
|
@ -18,10 +18,18 @@ use Illuminate\Console\Command;
|
||||||
* was the right call and it left an operator with a log line and no way to act
|
* was the right call and it left an operator with a log line and no way to act
|
||||||
* on it.
|
* on it.
|
||||||
*
|
*
|
||||||
* **What this lists is wider than that log line.** Every active Price at one of
|
* **What this lists is wider than that log line.** Every active RECURRING Price
|
||||||
* our Products that no row of ours knows — adoptable or not. The only question
|
* at one of our Products that no row of ours knows — adoptable or not. The only
|
||||||
* asked below is whether a register row holds the id; nothing here re-asks
|
* question asked below is whether a register row holds the id; nothing here
|
||||||
* AdoptStripePrice whether it would have taken the Price over.
|
* re-asks AdoptStripePrice whether it would have taken the Price over.
|
||||||
|
*
|
||||||
|
* Recurring, because activePricesFor() sends `type: recurring` as well as
|
||||||
|
* `active: true`. That filter can hide somebody else's one-time Price from the
|
||||||
|
* list; it can never hide one of ours. createPrice() always sends
|
||||||
|
* `recurring[interval]`, and it is the only call that mints a Price at one of
|
||||||
|
* our Products at all — the setup fee is priced inline at the checkout, under an
|
||||||
|
* ad-hoc Product of its own. So the filter errs toward listing too little, never
|
||||||
|
* toward missing an orphan of ours.
|
||||||
*
|
*
|
||||||
* **So stripe:sync-catalogue runs first.** Until it has, an orphan listed here
|
* **So stripe:sync-catalogue runs first.** Until it has, an orphan listed here
|
||||||
* may be one the next sync would simply have adopted, and --archive on that one
|
* may be one the next sync would simply have adopted, and --archive on that one
|
||||||
|
|
@ -41,10 +49,12 @@ use Illuminate\Console\Command;
|
||||||
* the reason it always is here: Stripe goes on billing every subscription
|
* the reason it always is here: Stripe goes on billing every subscription
|
||||||
* already on an archived Price, it only stops being SOLD — and nothing this
|
* already on an archived Price, it only stops being SOLD — and nothing this
|
||||||
* codebase sells is sold on a Price no row knows. That reasoning reaches no
|
* codebase sells is sold on a Price no row knows. That reasoning reaches no
|
||||||
* further than this codebase. A Price an operator wired up by hand — a payment
|
* further than this codebase. A Price an operator wired up by hand — a
|
||||||
* link built in Stripe's own dashboard against one of our Products — is exactly
|
* SUBSCRIPTION payment link built in Stripe's own dashboard against one of our
|
||||||
* the Price carrying none of our metadata described above, and --archive
|
* Products — is exactly the Price carrying none of our metadata described above,
|
||||||
* withdraws it too.
|
* and --archive withdraws it too. A one-time link is the other side of the
|
||||||
|
* recurring filter: neither listed nor archived, and so neither protected nor
|
||||||
|
* disturbed by this command.
|
||||||
*/
|
*/
|
||||||
class SweepOrphanStripePrices extends Command
|
class SweepOrphanStripePrices extends Command
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -223,9 +223,12 @@ Kette an einer anderen Stelle, und mehrere brechen sie **nach** der Zahlung.
|
||||||
`stripe:reprice-subscriptions` (je zuerst `--dry-run`). Der Abgleich legt jetzt
|
`stripe:reprice-subscriptions` (je zuerst `--dry-run`). Der Abgleich legt jetzt
|
||||||
**zwei** Preise je Paket an; bis er lief, kann ein geprüfter EU-Firmenkunde nicht
|
**zwei** Preise je Paket an; bis er lief, kann ein geprüfter EU-Firmenkunde nicht
|
||||||
bestellen — das ist Absicht.
|
bestellen — das ist Absicht.
|
||||||
- `stripe:sweep-orphan-prices` listet jeden **aktiven** Stripe-Preis an unseren
|
- `stripe:sweep-orphan-prices` listet jeden **aktiven wiederkehrenden**
|
||||||
Produkten, den **keine** Zeile in `stripe_addon_prices` oder
|
Stripe-Preis an unseren Produkten, den **keine** Zeile in
|
||||||
`stripe_plan_prices` kennt — mit ID, Betrag, Währung, Intervall und Metadaten.
|
`stripe_addon_prices` oder `stripe_plan_prices` kennt — mit ID, Betrag,
|
||||||
|
Währung, Intervall und Metadaten. Einmalige Preise sieht es nicht; von uns
|
||||||
|
angelegt ist nie einer davon, `createPrice()` schickt immer
|
||||||
|
`recurring[interval]`.
|
||||||
Ohne Argument wird nichts angefasst; mit `--archive` werden die aufgelisteten
|
Ohne Argument wird nichts angefasst; mit `--archive` werden die aufgelisteten
|
||||||
Preise bei Stripe stillgelegt, also **nicht mehr verkauft** (laufende Abos
|
Preise bei Stripe stillgelegt, also **nicht mehr verkauft** (laufende Abos
|
||||||
darauf verrechnet Stripe weiter). **Zuerst `stripe:sync-catalogue` laufen
|
darauf verrechnet Stripe weiter). **Zuerst `stripe:sync-catalogue` laufen
|
||||||
|
|
|
||||||
|
|
@ -128,13 +128,14 @@ begründet. Die Log-Zeile allein reicht nicht: sie nennt die Waise, aber niemand
|
||||||
kann etwas mit ihr tun, ohne von Hand in Stripes Oberfläche zu gehen.
|
kann etwas mit ihr tun, ohne von Hand in Stripes Oberfläche zu gehen.
|
||||||
|
|
||||||
- **Ohne Argument:** ein Bericht. Für jedes unserer Produkte werden die aktiven
|
- **Ohne Argument:** ein Bericht. Für jedes unserer Produkte werden die aktiven
|
||||||
Preise geholt und die aufgelistet, die **keine Zeile** in
|
**wiederkehrenden** Preise geholt und die aufgelistet, die **keine Zeile** in
|
||||||
`stripe_addon_prices` oder `stripe_plan_prices` kennt — mit ID, Betrag,
|
`stripe_addon_prices` oder `stripe_plan_prices` kennt — mit ID, Betrag,
|
||||||
Währung, Intervall und Metadaten. Nichts wird angefasst.
|
Währung, Intervall und Metadaten. Nichts wird angefasst.
|
||||||
- **Mit `--archive`:** dieselbe Liste wird stillgelegt. Das ist harmlos, und
|
- **Mit `--archive`:** dieselbe Liste wird stillgelegt. Für alles, was **dieses
|
||||||
zwar aus demselben Grund wie überall sonst in diesem Projekt: ein
|
Projekt** verkauft, ist das harmlos, und zwar aus demselben Grund wie überall
|
||||||
archivierter Preis verrechnet laufende Abos weiter, er wird nur nicht mehr
|
sonst hier: ein archivierter Preis verrechnet laufende Abos weiter, er wird
|
||||||
verkauft.
|
nur nicht mehr verkauft. Für eine **übernehmbare** Waise gilt das ausdrücklich
|
||||||
|
**nicht** — siehe die Berichtigung unten.
|
||||||
- **`--dry-run`** wie bei `stripe:sync-catalogue`, damit die beiden Kommandos
|
- **`--dry-run`** wie bei `stripe:sync-catalogue`, damit die beiden Kommandos
|
||||||
sich gleich anfühlen.
|
sich gleich anfühlen.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,19 @@ use App\Services\Stripe\StripeClient;
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
use Illuminate\Contracts\Console\Kernel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The orphans nobody can adopt.
|
* The orphans an operator can finally act on.
|
||||||
*
|
*
|
||||||
* A Price at one of our Products that no row knows, and that AdoptStripePrice
|
* The command lists EVERY active recurring Price at one of our Products that no
|
||||||
* refuses — because nothing in its metadata proves it is ours, or because it
|
* row knows — adoptable or not; the only question it asks is whether a register
|
||||||
* charges in a way we never mint. The recognition step names it in the log and
|
* row holds the id. That is wider than the log line AdoptStripePrice writes, and
|
||||||
* leaves it, deliberately: adopting a stranger's Price is worse than minting a
|
* it is why stripe:sync-catalogue has to run first: see the command's own
|
||||||
* second one. This is the other half of that decision, the one an operator can
|
* docblock, which had to be corrected away from the narrower claim.
|
||||||
* act with.
|
*
|
||||||
|
* The fixtures below are all at the narrow end anyway — planted with no metadata
|
||||||
|
* at all, so nothing could have adopted them, which is what a person clicking
|
||||||
|
* through Stripe's dashboard leaves behind. That keeps each test about the
|
||||||
|
* sweep's own question (does a row know this id?) rather than about what the
|
||||||
|
* recognition step would have done with it.
|
||||||
*/
|
*/
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
$this->stripe = new FakeStripeClient;
|
$this->stripe = new FakeStripeClient;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue