# Stripe: charge_already_refunded

> Stripe returned charge_already_refunded: the charge is fully refunded. Check its refunds before refunding, use idempotency keys, and stop double refunds.

Canonical: https://app369.com/fix/stripe-charge-already-refunded/
Last updated: 2026-11-09

charge_already_refunded is the Stripe error for a refund on a charge that is already fully refunded. No money moves on the second try. Common sources are a refund sent twice: from the Dashboard and your code, a retried webhook, or a double click.

**Short answer**

Open the charge and check its refunds. If the customer is already refunded, mark the order refunded in your database and stop. Then make refunds safe to repeat: check the charge first, send an idempotency key, and update your records from the refund.created or charge.refunded webhook, comparing amount_refunded with the charge amount before marking the order fully refunded.

Rather not fix it yourself? [**Stripe Billing Fix and Setup**](https://app369.com/fixed-price/stripe-billing-setup/) handles it at a fixed price, agreed before work starts.

## **Why the refund ran twice, and the fix**

1. 01 ### **Someone refunded it in the Dashboard first**
  > “Sent when a charge is refunded, including partial refunds.” [**Stripe, Refunds (charge.refunded event)**](https://docs.stripe.com/refunds) A refund made by hand in the Dashboard doesn’t update your database unless you listen for it. Your code then tries again. Handle refund.created or charge.refunded so both paths agree, and remember charge.refunded also fires for partial refunds.
2. 02 ### **The refund request was sent twice**
  > “The charge you’re attempting to refund has already been refunded.” [**Stripe, Error codes (charge_already_refunded)**](https://docs.stripe.com/error-codes) A double click, a retry after a timeout, or a webhook delivered twice sends the same refund again. Pass an idempotency key for each refund attempt, such as the order ID plus a refund request ID, so a repeat returns the first result. Keys expire after 24 hours.```
await stripe.refunds.create(
  { payment_intent: order.paymentIntentId },
  { idempotencyKey: \`refund-${order.id}-${refundRequest.id}\` },
);
```
3. 03 ### **The code refunds the wrong charge**
  > “Use a different charge ID only if you selected the wrong charge and intended to refund another charge.” [**Stripe, Error codes (charge_already_refunded)**](https://docs.stripe.com/error-codes) A stored charge ID that points at an older payment refunds the wrong one, or one that was already refunded. Look up the charge from the order’s PaymentIntent before refunding.
4. 04 ### **Partial refunds add up** Several partial refunds can reach the full amount. Check amount_refunded on the charge before you refund more, and show staff what is left.

Checked against the vendor’s docs on November 9, 2026: [Stripe docs: Error codes](https://docs.stripe.com/error-codes); [Stripe docs: Refunds](https://docs.stripe.com/refunds); [Stripe API reference: Idempotent requests](https://docs.stripe.com/api/idempotent_requests).

## **Rather hand it over?**

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

- [$950**Stripe Billing Fix and Setup**Subscriptions, webhooks, customer portal and failed payments set up or fixed, tested in Stripe test mode.**See scope and start**](https://app369.com/fixed-price/stripe-billing-setup/)
- [from $450**App Fix Ticket**One reproducible bug in an existing mobile or web app, diagnosed and fixed.**See scope and start**](https://app369.com/fixed-price/flutter-firebase-bug-fix/)
- [from $450**AI-Built App Rescue**A Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.****No issues worth fixing, no charge.**** If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.**See scope and start**](https://app369.com/fixed-price/ai-app-rescue/)

[**All 127 Fiverr reviews**](https://app369.com/reviews/)

Related: [**Stripe idempotency key in use**](https://app369.com/fix/stripe-idempotency-key-in-use/) · [**Stripe webhook 200 but database not updated**](https://app369.com/fix/stripe-webhook-200-but-database-not-updated/) · [**Stripe PaymentIntent already succeeded**](https://app369.com/fix/stripe-paymentintent-already-succeeded/) · [**Stripe card_not_supported**](https://app369.com/fix/stripe-card-not-supported/)

Questions

## **More on this error**

<details>

<summary>**Was the customer refunded twice? **+</summary>

Not by this call. Stripe refused it, so no second refund was created. Check the charge’s refunds to confirm what the customer received.

</details>

<details>

<summary>**Can I refund a disputed payment? **+</summary>

No. Stripe returns a different error for a charge that was charged back. Respond to the dispute instead.

</details>

<details>

<summary>**Can you fix it for me? **+</summary>

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

</details>

A different error?

## **Describe it and get a written quote**

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.

[**Describe your problem **→](https://app369.com/contact/) [**All fixes**](https://app369.com/fix/)