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…WhenWhat it does
retry_orderSame 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_chargeYou 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 call retry_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"
  }
}
ArgumentMeaning
transaction_idThe failed transaction to retry.
payment_method_idOptional. If omitted, retries against the same card; if present, charges the new card.
idempotency_keyPass 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_funds and a few days have passed (paydays).
  • Decline reason was gateway_error (transient).

When to retry against a different card

  • Decline reason was expired_card and the customer added a new one.
  • Decline reason was processor_declined repeatedly; 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.