Refunds & Notifications · Article 6.10
Failed refunds & recovery — common Stripe failure reasons
A `failed` credit note has a `refund_failure_reason` string from Stripe. Most reasons fall into a small set; here's what each means and the recommended recovery.
Failed refunds are rare but high-stress when they happen — the client expects their money, you've signed an amendment promising it, and Stripe's API just said no. Most failures fit a small catalogue with known recoveries. This article walks through each, in order of frequency, with the practical step you take to resolve.
Step by step
Read the failure reason in the Refund banner.
It's stored verbatim from Stripe; the catalogue above covers ~90% of what you'll see.
Match to the recovery.
Transient → Retry. Disputed → wait. Anything else → switch to manual SEPA.
Issue the refund out-of-band if needed.
Email the client to confirm IBAN, transfer the funds, click Mark refunded with the bank reference.
Confirm the credit note is in
manual(orsucceededif Retry worked).Both are terminal; both close the credit note for accounting purposes.
Confirm the proposal closed if applicable.
When the credit note settles AND no other invoices have outstanding
amount_due,close_proposal_if_settledflips the proposal toPaid. If the proposal is the only one with the credit note, this fires automatically.
A red Refund failed banner with the failure reason quoted, plus Retry and Mark refunded buttons. After recovery, the banner flips green ("Refund completed via Stripe" or "Refund completed via manual transfer").
Why this works this way
Stripe surfaces failure reasons via the Refund object's failure_reason field. We capture it on CreditNote.refund_failure_reason (truncated to 500 chars) when _trigger_stripe_refund raises an exception or when the webhook reports status: "failed". The reason string is shown verbatim in the Refund banner so you can act on it without digging into Stripe logs.
### Failure catalogue
#### charge_disputed
What it means: the original deposit charge has an open dispute (the cardholder's bank requested a chargeback). Stripe blocks refunds on disputed charges to prevent double-paying.
Recovery: wait for the dispute to resolve (typically 30–90 days). If you win the dispute, the charge stays settled and you can retry the refund. If you lose, the cardholder already received the money via the chargeback — the Stripe refund is no longer needed; mark the credit note refunded manually with reason "Chargeback won by cardholder — funds returned via dispute" so your audit trail is clean.
#### balance_insufficient
What it means: your Stripe account balance (or the connected account's balance, on Direct Charges) is below the refund amount. Common when you've withdrawn most of the platform funds to your bank.
Recovery: top up the connected account by reducing pending payouts in Stripe dashboard, OR issue the refund out-of-band via SEPA (faster) and Mark refunded manually. Stripe will not retry automatically when your balance recovers; the refund is permanently failed until you Retry from Clozo.
#### card_account_closed / expired_or_invalid_card
What it means: the cardholder's card is no longer valid (closed account, expired card, replaced card with same number but different CVV). Stripe will queue the refund and may eventually succeed if the cardholder's bank has card-update services, but timing is unpredictable (days to never).
Recovery: don't wait. Email the client, ask for a SEPA-receivable bank account, issue the refund via SEPA, Mark refunded manually with reason "Original card closed — refunded via SEPA to IBAN XXXX." This is by far the most common non-transient failure and the manual path is the cleanest recovery.
#### lost_or_stolen_card
What it means: cardholder reported the card lost or stolen after paying you. Stripe blocks refunds to avoid sending money to a compromised account.
Recovery: same as closed card — switch to SEPA, ask the client for a destination IBAN. Document explicitly in the credit note reason ("Original card reported lost/stolen — refunded via SEPA to verified IBAN").
#### Generic failed with no specific reason / Stripe API outage
What it means: transient or undocumented. Could be a temporary Stripe issue, a network glitch on our end, or a subtle account configuration problem.
Recovery: Retry once after 30 seconds (article 8.8). If retry fails the same way, switch to manual SEPA + Mark refunded. Surface via support if it's blocking you — we'll dig into Stripe's logs to identify the underlying cause.
Troubleshooting
Keep reading
Refunds & Notifications
Retry Stripe refund — when transient failures clear
A failed Stripe refund can usually be retried — most failures are transient (rate limits, brief Stripe outages, or deposit-paid webhook arriving after the post-sign pipeline ran). The Retry button calls `_trigger_stripe_refund` afresh on the credit note.
Refunds & Notifications
Mark refunded manually — for SEPA and out-of-band proof
When a refund is issued outside Stripe — SEPA bank transfer, cash, PayPal, mailed cheque — `Mark refunded` is how you record it in Clozo. Reason field is mandatory (typically a bank reference). The credit note moves to `manual` status and the client receives a confirmation email.
Refunds & Notifications
SEPA / out-of-band refund: when automation can't help
When the original deposit was paid via SEPA bank transfer (or any non-Stripe channel), Clozo can't refund automatically — Stripe API has nothing to refund. You issue the SEPA transfer manually from your bank, then click `Mark refunded` to update the credit note and notify the client.
Refunds & Notifications
Refund stages: issued → requested → succeeded (or manual)
The credit note moves through up to four states from creation to settled. Each state corresponds to a specific point in the refund lifecycle, with predictable UI badges and email triggers.
Refunds & Notifications
Stripe automatic refund (Direct Charges via Connect)
When the original deposit was paid via Stripe, the refund is automatic. Clozo issues a Refund on the freelancer's connected account, watches for the `refund.updated` webhook, and flips the credit note to `succeeded` once Stripe confirms — typically within 3–5 business days for the cash to reach the client.