Open a Stripe invoice that failed three weeks ago and read it the way an investigator reads a case file. The events are all there: the first attempt, the decline code, the automatic retries spaced across the following days, the final state. What the file does not contain is any sign that anyone read it while it was still open. The retries fired on schedule. The schedule ended. The case closed itself, which is not the same as anyone deciding it should.
This is the structural fact about billing on Stripe that feature comparisons miss. Stripe's native recovery is a schedule, and a schedule is a fine thing — tireless, consistent, informed by network-level timing data no single merchant could gather alone. But a schedule cannot read. It cannot notice that this invoice belongs to a subscriber whose card was replaced by their bank nine days ago, whose new credential is sitting in a wallet the vault has never met. It cannot tell the difference between a subscriber who has ignored four notices and one who never received a notice a human would recognise as theirs. Reading the case is a different job from running the clock, and the cases that close themselves are usually the ones nobody read.
A case file, worked properly, has a short anatomy. First, the cause, in the issuer's own compressed language: a timing problem, a credential problem, an identity check that never completed, or a stop instruction wearing a failure's clothes. Second, the subscriber's own history: have they failed before, what fixed it last time, when are they paid, have they ever answered a billing message. Third, the state of the credential: current, replaced, refreshed by the network's updater, or dead beyond argument. Fourth, the next action that follows from the first three facts — which is sometimes a retry, sometimes a message, sometimes an immediate and polite closing of the case. Four facts, most of them already sitting in the billing record, unused.
Teams whose native retries keep ending early — whose dashboards show effort and whose churn reports show losses anyway — do not usually have a retry problem. They have an assignment problem: the reading work belongs to nobody. It does not appear in anyone's job description, so it happens in the gaps, when a support agent happens to notice a familiar name in the failure list. The fix is not a louder schedule. It is a layer whose entire job is the case file: sorting by cause before sequencing, checking the credential before spending an attempt, writing the message a specific subscriber needs, and closing the unwinnable cases early so they stop taxing the winnable ones.
That layer is what stripe dunning for teams whose native retries keep ending early describes in working order: where Stripe's schedule legitimately stops, what a reading layer adds on top of it, and how to judge the addition by your own closed cases rather than by a vendor's blended recovery rate. Bring a month of finished invoices to it. The ones that recovered will mostly credit the schedule. The ones that did not are the syllabus.
There is a discipline point worth stating plainly, because it decides whether the second layer earns anything at all. A card update that lands on day twelve must wake the case on day twelve, not at the next weekly review. A cancellation must close the case the moment it happens, everywhere, so no sequence keeps writing to a person who already left. A hard decline must end the attempts and move the subscriber to a human conversation or a clean goodbye. These are not features to demo. They are the difference between a case file that is worked and one that is merely stored.
Stripe will keep doing its part well; the schedule is genuinely good at the job it was designed for. The open question for every team billing on it is whether anyone is doing the other job — the reading, the sorting, the timely human sentence — in the days after the schedule ends. That is the work Swipe Again was built to do: not a louder clock, but a reader for every case the clock leaves behind.
Top comments (1)
The cancellation point deserves an integration test of its own. A queued retry can race with a cancellation or a successful payment webhook, so I would re-check the current invoice and subscription state immediately before each charge or message. Deduplicating webhook events and recording the reason for each next action would also make that case file much easier to audit.