One failed charge, three documents disagreeing about whether you may retry it
A subscription charge fails on a Tuesday. Sometimes that is a balance that will clear on payday. Sometimes it is a card the cardholder reported stolen on Monday. Charge the first one again and you probably get paid. Charge the second one again and you are not being annoying, you are breaking a card network rule, and the party that pays for it is the merchant whose payment account gets flagged.
So the first thing a recovery agent has to do is not retry. It has to work out which failures it is permitted to touch, and the answer to that lives in three separate documents that do not share a vocabulary.
Three sources, three ways of saying no
Stripe publishes a list of nine decline codes it will not automatically retry, on its Smart Retries page: incorrect_number, lost_card, pickup_card, stolen_card, revocation_of_authorization, revocation_of_all_authorizations, authentication_required, highest_risk_level, transaction_not_allowed. Its own words for that category are that the issuing bank has rejected the transaction and you can't retry it.
Visa says it differently and with more force. Its rules update AI10325, dated 3 September 2020 and effective 17 April 2021, sorts response codes into categories. Category 1 means the issuer will never approve, and the merchant is not permitted to reattempt. Category 2 means the issuer cannot approve at this time, and there you may reattempt up to 15 times in 30 days. Response code 14, invalid account number, gets its own sentence forbidding any reattempt on the same account number.
And then there is the issuer speaking directly. Stripe surfaces the bank's own guidance as an advice code, and do_not_try_again means the card must not be used again for this charge.
Any one of the three can be missing on a given failure. None of them can be ignored.
The order is the interesting part
They get checked in order of how close each one is to the bank that actually refused:
export function hardDeclineStop(signals: DeclineSignals): StopVerdict {
const advice = norm(signals.networkAdviceCode);
if (advice === DO_NOT_RETRY_ADVICE_CODE)
return { stop: true, reason: "issuer_advice", authority: "Stripe · Card declines · advice codes", ... };
const code = norm(signals.declineCode); if (code && HARD_DECLINE_CODES.includes(code)) return { stop: true, reason: "hard_decline", authority: "Stripe · Hard decline codes", ... };
const network = norm(signals.networkDeclineCode).toUpperCase(); if (network && NETWORK_NEVER_APPROVE_CODES.includes(network)) return { stop: true, reason: "network_never_approve", authority: "Visa Rules · AI10325", ... };
return OPEN; } ```
The advice code wins because it is the bank itself. The decline code is Stripe reading the bank. The raw two-digit response code is what is left when neither of the first two came through. Every stop carries the sentence a human will read and the document it traces to, so a merchant looking at a stopped customer can see who said no.
| Signal on the failed charge | Who is speaking | Verdict |
|---|---|---|
advice code do_not_try_again | the issuing bank, passed through by Stripe | stop, permanently |
decline code stolen_card | Stripe's published list of nine | stop, permanently |
response code 14 | Visa, named in its own sentence | stop, permanently |
response code 05, do not honor | Visa Category 2, cannot approve now | retry on the schedule |
decline code insufficient_funds | Stripe, a soft decline | retry on the schedule |
| a code none of the three documents lists | nobody | retry on the schedule |
The last row is the one people argue about
A gate built to fail closed should refuse anything it does not recognise. This one does the opposite, and there is a test pinning it: an unknown code does not stop.
The reason is that the two sets are shaped differently. The set of permanently dead cards is published, finite and maintained by Stripe and Visa. The set of temporarily failed cards is open ended, and nobody publishes it. Treating an unfamiliar code as terminal would mean every code added after the last read of those documents silently gives up on a paying customer, and the merchant never hears about it. So the closed list is the stop list, and everything else climbs the schedule.
Where failing closed does apply is the ceiling. Visa allows 15 reattempts in 30 days. The shipped default is four, spread over a week at days 1, 3, 5 and 7, with the first one silent because a Tuesday balance often clears by Wednesday and the customer never needs the email. A merchant who types 99 into the cap field gets 14, because the clamp refuses any value at or above the network's own limit.
How we built this: CardChase
---
One shipped product, taken apart, once a month. What it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did, read off the repository and the live site, not written from memory. Join the list.