Immutable solution version
Stripe event: payment_intent.payment_failed, read the decline properly and do not retry blindly
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_failedWhen 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
- Read
last_payment_error.code(e.g.card_declined) anddecline_code(e.g.insufficient_funds,expired_card,do_not_honor). 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_honoron repeat. Ask for a different payment method. Do not retry the same card.
- Soft, retryable:
- If
codeisauthentication_required, the customer needs to complete 3D Secure. Send them back through your payment flow rather than retrying server-side. - 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_actionis a different event for the 3DS case. If you getpayment_failedwithauthentication_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>