Found this while looking at a scheme fee line that kept growing on a subscription product. The dunning job had one rule: if a renewal fails, retry at 24h, 72h and 7 days. It never looked at why the renewal failed.
The decline codes were the problem. Code 51 (insufficient funds) is a decent retry candidate, because the balance can change by payday. Codes 41 (lost card) and 43 (stolen card), and 14 (invalid card number), are different. The issuer is telling you not to come back, and no amount of waiting changes that. We were sending those through the same schedule anyway.
Visa and Mastercard both penalize this. As I understand Visa's rules, 'never approve' decline categories get no reattempts at all, and the other categories are capped at roughly 15 attempts in 30 days. Past that you pay per attempt, and the decline ratio on the merchant account climbs.
The fix was small. We map the response code to one of three buckets: retry later, retry only after the customer updates the card, and never retry. The 'never' bucket cancels the schedule and sends the customer to a payment-update page.
The lesson for me was that the gateway's raw response code belongs in your retry state, not just in the log line. If all you persist is 'failed', you can't build this.
How do you handle it? Do you keep your own decline-code table, or trust the normalized category your gateway returns?
Payneteasy handles this kind of payment flow for merchants and PSPs — see how it works.
Top comments (0)