Skip to content

Stripe payment errorStripe: “You cannot confirm this PaymentIntent because it has a status of succeeded”

A customer pays, then sees an error, or your logs fill with payment_intent_unexpected_state. The payment usually went through: something asked Stripe to confirm the same PaymentIntent a second time. That second request is the bug, and a double click or a reloaded page is often what sends it. Here’s what Stripe’s docs say, in their words, or hand it over at a fixed price.

Short answer

The PaymentIntent was already paid, so Stripe refuses to confirm it again. Check the Dashboard first: if the payment shows as succeeded, the customer was charged once and nothing needs refunding. Then find what confirmed it twice: a pay button that stays clickable, a page that confirms again on reload, or a retry after a timeout. Fulfil the order from the payment_intent.succeeded webhook, not from the browser.

Why it was confirmed twice, and the fix

  1. 01

    The payment already went through

    “The PaymentIntent’s state was incompatible with the operation you were trying to perform.” Stripe, Error codes

    Stripe returns payment_intent_unexpected_state when the PaymentIntent isn’t in a state that allows the request. A status of succeeded is final. Look the PaymentIntent up in the Dashboard by its pi_ ID before you change any code, and tell the customer their payment is safe if it shows as succeeded.

  2. 02

    The pay button sends a second confirm

    “A PaymentIntent with a succeeded status means that the corresponding payment flow is complete.” Stripe, How Payment Intents and Setup Intents work

    A double click, a slow network or a button that isn’t disabled while the first request runs can call confirmPayment twice for the same PaymentIntent. Disable the button as soon as it’s pressed, and only re-enable it if the first attempt fails. A page that confirms again when it reloads, or when a customer comes back from a 3D Secure check, does the same. Also check that you don’t confirm in both places: creating the PaymentIntent with confirm: true on the server and then calling confirmPayment in the browser.

  3. 03

    The app reuses an old PaymentIntent for a new order

    “Each PaymentIntent typically correlates with a single shopping cart or customer session in your application.” Stripe, The Payment Intents API

    If the app stores one PaymentIntent ID and confirms it again for the next purchase, the second purchase fails with this error. Stripe pairs a PaymentIntent with one cart or session: reuse it while that checkout is still open, and create a new one once it has succeeded.

  4. 04

    Retries create or confirm twice

    “Remember to provide an idempotency key to prevent the creation of duplicate PaymentIntents for the same purchase.” Stripe, The Payment Intents API

    When a server request times out and the app sends it again, use an idempotency key based on the order or cart ID, so the retry returns the first result instead of doing the work twice.

  5. 05

    Fulfil from the webhook, once per event

    “After the client confirms the payment, it’s a best practice for your server to monitor webhooks to detect when the payment successfully completes or fails.” Stripe, The Payment Intents API

    If the browser is what unlocks the order, a failed second confirm can leave a paid customer with nothing. Fulfil from the payment_intent.succeeded webhook on your server, and record each event ID so a repeated event doesn’t fulfil the order twice.

Checked against the vendor’s docs on : Stripe docs: Error codes; Stripe docs: How Payment Intents and Setup Intents work; Stripe docs: The Payment Intents API; Stripe API reference: Idempotent requests; Stripe docs: Receive Stripe events in your webhook endpoint.

Rather hand it over?

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

Related: Stripe paid but account not upgraded · Stripe webhook 200 but database not updated · Stripe subscription incomplete, requires action · Stripe Invalid API Key provided

Questions

More on this error

Was my customer charged twice?

Not by this error. Stripe refused the second confirm, so it didn’t create a second charge. Check the customer’s payments in the Dashboard: if you see two succeeded payments, they came from two different PaymentIntents, which points at the app creating a new one per click.

Should I catch the error and show a success message?

Only after checking. Retrieve the PaymentIntent on your server and show success if its status is succeeded. Then fix the double confirm, because the same bug can also create a second PaymentIntent and charge twice.

Does Stripe retry webhook events?

Stripe says webhook endpoints might occasionally receive the same event more than once. Its advice is to log the event IDs you’ve processed and skip any you’ve already handled.

Can you fix it for me?

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.

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; we reply by email, usually within one business day.