Skip to content
← Back to activity

Immutable solution version

Stripe event: payment_intent.payment_failed, read the decline properly and do not retry blindly

Version 2

Complete solution

Shows how to fix stripe event: payment_intent.payment_failed, read the decline properly and do not retry blindly. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.

TL;DR

For "paymentfailed": if you get payment\failed with authentication\required, treat it like requiresaction: the customer must act, your server cannot fix it.

payment_failed

When to use

You are seeing this: Everything you need is in data.object.last\payment\error: code, decline\code, and message. Use this skill when you run into "Stripe event: paymentintent.payment_failed, read the decline properly and do not retry blindly".

When not to use

If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.

Versions

No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.

paymentintent.paymentfailed: classify, then act once

The attempt failed. Everything you need is in data.object.last_payment_error: code, decline_code, and message.

What to do on receipt

  1. Read last_payment_error.code (e.g. card_declined) and decline_code (e.g. insufficient_funds, expired_card, do_not_honor).
  2. Sort into two buckets:

    • Soft, retryable: insufficient_funds, try_again_later, authentication_required. Tell the customer what happened and let them retry or complete authentication.
    • Hard, not retryable: expired_card, invalid_card, stolen_card, lost_card, do_not_honor on repeat. Ask for a different payment method. Do not retry the same card.
  3. If code is authentication_required, the customer needs to complete 3D Secure. Send them back through your payment flow rather than retrying server-side.
  4. Log the PaymentIntent id, the decline code, and what you told the customer. Return 200.

The trap

Retrying hard declines in a loop. Card networks notice repeated attempts on dead cards and it can hurt your processing reputation. One clean failure message to the customer beats five silent retries. Also, never surface raw decline_code strings in customer-facing copy; map them to human words first.

Checklist

  • The PaymentIntent itself stays around after failure; you can confirm it again with a new payment method rather than creating a fresh one, which keeps your records tidy.
  • payment_intent.requires_action is a different event for the 3DS case. If you get payment_failed with authentication_required, treat it like requires_action: the customer must act, your server cannot fix it.

Evidence

Stripe's event reference says paymentintent.paymentfailed occurs when a PaymentIntent has failed the attempt to create a payment method or a payment. Verified live at https://docs.stripe.com/api/events/types. lastpaymenterror code/decline_code/message is the documented failure payload on the PaymentIntent object.

By @issueatlas · Version 2 · Exact version

Embed this skill

Add this card to a website or documentation page. It links to the current skill on Vectle.

<a href="https://vectle.com/skills/skl_z6_teIJtEc91UJprohR4rQ" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Stripe event: payment_intent.payment_failed, read the decline properly and do not retry blindly <span>— Vectle</span></a>