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
|
||||
* on it.
|
||||
*
|
||||
* **What this lists is wider than that log line.** Every active Price at one of
|
||||
* our Products that no row of ours knows — adoptable or not. The only question
|
||||
* asked below is whether a register row holds the id; nothing here re-asks
|
||||
* AdoptStripePrice whether it would have taken the Price over.
|
||||
* **What this lists is wider than that log line.** Every active RECURRING Price
|
||||
* at one of our Products that no row of ours knows — adoptable or not. The only
|
||||
* question asked below is whether a register row holds the id; nothing here
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* further than this codebase. A Price an operator wired up by hand — a payment
|
||||
* link built in Stripe's own dashboard against one of our Products — is exactly
|
||||
* the Price carrying none of our metadata described above, and --archive
|
||||
* withdraws it too.
|
||||
* further than this codebase. A Price an operator wired up by hand — a
|
||||
* SUBSCRIPTION payment link built in Stripe's own dashboard against one of our
|
||||
* Products — is exactly the Price carrying none of our metadata described above,
|
||||
* 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
**zwei** Preise je Paket an; bis er lief, kann ein geprüfter EU-Firmenkunde nicht
|
||||
bestellen — das ist Absicht.
|
||||
- `stripe:sweep-orphan-prices` listet jeden **aktiven** Stripe-Preis an unseren
|
||||
Produkten, den **keine** Zeile in `stripe_addon_prices` oder
|
||||
`stripe_plan_prices` kennt — mit ID, Betrag, Währung, Intervall und Metadaten.
|
||||
- `stripe:sweep-orphan-prices` listet jeden **aktiven wiederkehrenden**
|
||||
Stripe-Preis an unseren Produkten, den **keine** Zeile in
|
||||
`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
|
||||
Preise bei Stripe stillgelegt, also **nicht mehr verkauft** (laufende Abos
|
||||
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.
|
||||
|
||||
- **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,
|
||||
Währung, Intervall und Metadaten. Nichts wird angefasst.
|
||||
- **Mit `--archive`:** dieselbe Liste wird stillgelegt. Das ist harmlos, und
|
||||
zwar aus demselben Grund wie überall sonst in diesem Projekt: ein
|
||||
archivierter Preis verrechnet laufende Abos weiter, er wird nur nicht mehr
|
||||
verkauft.
|
||||
- **Mit `--archive`:** dieselbe Liste wird stillgelegt. Für alles, was **dieses
|
||||
Projekt** verkauft, ist das harmlos, und zwar aus demselben Grund wie überall
|
||||
sonst hier: ein archivierter Preis verrechnet laufende Abos weiter, er wird
|
||||
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
|
||||
sich gleich anfühlen.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,19 @@ use App\Services\Stripe\StripeClient;
|
|||
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
|
||||
* refuses — because nothing in its metadata proves it is ours, or because it
|
||||
* charges in a way we never mint. The recognition step names it in the log and
|
||||
* leaves it, deliberately: adopting a stranger's Price is worse than minting a
|
||||
* second one. This is the other half of that decision, the one an operator can
|
||||
* act with.
|
||||
* The command lists EVERY active recurring Price at one of our Products that no
|
||||
* row knows — adoptable or not; the only question it asks is whether a register
|
||||
* row holds the id. That is wider than the log line AdoptStripePrice writes, and
|
||||
* it is why stripe:sync-catalogue has to run first: see the command's own
|
||||
* docblock, which had to be corrected away from the narrower claim.
|
||||
*
|
||||
* 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 () {
|
||||
$this->stripe = new FakeStripeClient;
|
||||
|
|
|
|||
Loading…
Reference in New Issue