Refunds & Notifications ยท Article 6.8
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.
Stripe's refund API is reliable, but the path from "amendment signs" to "Stripe accepts the refund request" has multiple moving parts. Three common ways the original _trigger_stripe_refund can fail:
1. Webhook race โ original deposit's payment_intent.succeeded webhook hadn't landed yet when the amendment signed; per [D-113], the trigger refuses to refund until the webhook confirms the charge. By the time the freelancer notices, the webhook has arrived; a retry passes the charge-confirmed check and proceeds.
2. Stripe transient โ rate limit hit, brief outage, network blip. Retry usually clears it.
3. Pre-D-113 deployments โ an older code path that didn't recognise certain Stripe charge configurations. Retry on the new code finds the PaymentIntent correctly.
Step by step
Notice a
failedcredit note.Refund banner reads "Refund failed: [reason]." with Retry and Mark refunded options.
Read the failure reason first.
Some reasons aren't fixable by retry: "charge_disputed" (the original charge has an open dispute โ refunds are blocked), "balance_insufficient" (your Stripe balance is too low), "card_account_closed" (the cardholder's card account is closed). Retry won't help these; see article 8.10 for the recovery paths per failure type.
Click
Retry Stripe refund.Server runs
_trigger_stripe_refundagain.Wait ~2 seconds.
Banner updates: success โ "Refund in progress, expected in 3โ5 business days"; failure โ updated failure reason (often a new error code if the underlying issue is different).
If still failing, switch to manual.
Click
Mark refunded(article 8.9) after issuing the refund out-of-band.
Why this works this way
Implementation (CreditNoteRetryStripeRefundView in amendment_views.py:775):
1. Refuse retry if state is succeeded / manual / requested. Returns 409 with details.status so the frontend can show the right error. Retry is only meaningful on pending or failed.
2. Clear stale failure metadata. refund_failure_reason = "", save. The retry starts clean.
3. Re-call `_trigger_stripe_refund(cn, cn.amount_gross)`. The function never raises (it captures all exceptions and records them on the credit note row), so the retry endpoint always returns 200 with the resulting state.
4. Return the credit note's resolved state. The frontend updates the banner / row in place.
What the retry doesn't do: change which Stripe account, account holder, or amount. Those are derived from the credit note + proposal + amendment + Connect setup โ the retry just attempts the same action again with cleared error state.
Troubleshooting
Keep reading
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.
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
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.
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.