Retry a failed charge
Re-attempt a declined transaction against the same, or a different, payment method.
What you’ll build
A recovery step: a charge failed last night with insufficient_funds. Retry it now: on the same card, or on a new card the customer just added.
Which tool?
There are two ways to retry, and the right one depends on whether you’re keeping the same card:
| Use… | When | What it does |
|---|---|---|
retry_order | Same card on file, including a failed subscription renewal. | Re-attempts the charge on the same order. For a subscription cycle it reconciles the cycle, so the daily billing cron won’t charge again. |
retry_failed_charge | You need to charge a different card. | Reconstructs the order from the failed transaction and creates a new order on the chosen card. Does not reconcile the original subscription cycle. |
For a failed subscription renewal you want to move to a new card, update the subscription’s payment method first (
update_subscription), then callretry_order; that keeps future cycles on the new card and reconciles the cycle.
Same card: retry_order
{
"name": "retry_order",
"arguments": {
"order_id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
"idempotency_key": "retry-2026-04-21-6ba7b811"
}
}
Only orders in failed or refund_failed status are eligible. The response is the updated order: check status (a re-decline leaves it failed with an incremented attempt_count). For a subscription cycle, success reconciles the cycle automatically.
Different card: retry_failed_charge
Use retry_failed_charge (a composite tool) to charge a different card.
{
"name": "retry_failed_charge",
"arguments": {
"transaction_id": "6ba7b813-9dad-11d1-80b4-00c04fd430c8",
"payment_method_id": "6ba7b815-9dad-11d1-80b4-00c04fd430c8",
"idempotency_key": "retry-2026-04-21-6ba7b813"
}
}
| Argument | Meaning |
|---|---|
transaction_id | The failed transaction to retry. |
payment_method_id | Optional. If omitted, retries against the same card; if present, charges the new card. |
idempotency_key | Pass a stable key per retry attempt so accidental re-runs do not double-charge. |
When to retry against the same card
- Decline reason was
insufficient_fundsand a few days have passed (paydays). - Decline reason was
gateway_error(transient).
When to retry against a different card
- Decline reason was
expired_cardand the customer added a new one. - Decline reason was
processor_declinedrepeatedly; the issuer is consistently saying no.
What you get back
Both tools return an order object, not a bare transaction and not an error envelope. Branch on the order’s status. On success it is succeeded:
{
"id": "9c1d2e3f-4a5b-4c6d-8e7f-0123456789ab",
"status": "succeeded",
"amount": 2999,
"currency": "usd",
"failure_code": null,
"failure_reason": null
}
A re-decline comes back as a failed order carrying the normalized failure_code:
{
"id": "9c1d2e3f-4a5b-4c6d-8e7f-0123456789ab",
"status": "failed",
"amount": 2999,
"currency": "usd",
"failure_code": "processor_declined",
"failure_reason": "processor decline"
}
Stop retrying
Most issuers will eventually mark a card as “do not retry” if you keep hammering it. If you drive your own manual retry cadence on top of EPD’s daily cron, keep it finite (e.g. one manual retry after a likely-cleared decline, then stop and ask for a new card), and notify the customer at each step. (EPD’s cron itself just re-attempts the failed cycle once per daily run; it has no day-1/3/7 curve, so layering your own loop on top can over-charge attempts if you’re not careful.)
When a subscription renewal fails, EPD’s billing cron re-attempts the failed cycle on its next daily run (10:00 UTC) until it succeeds or the subscription ends. There is no accelerated backoff curve. Use retry_order to re-attempt a failed cycle on demand before then; it reconciles the cycle so the cron won’t also charge it. Reach for retry_failed_charge when you need to move the charge to a different card.