Stripe Error: card_declined — Card Was Declined
try {
const intent = await stripe.paymentIntents.create({
amount: 4999,
currency: 'gbp',
payment_method: 'pm_card_declined',
confirm: true,
});
} catch (err) {
// err.type === 'StripeCardError'
// err.code === 'card_declined'
// err.decline_code === 'insufficient_funds'
// err.message === 'Your card has insufficient funds.'
}
The card_declined error is what Stripe returns when the customer’s card issuer (the bank that issued the card) refuses to authorise a charge. Stripe itself rarely blocks charges; almost all card_declined errors come from the issuer. Your job as the integrator is to translate that decline into a useful message and decide whether (and when) to let the customer try again.
Why this happens
- Insufficient funds. The most common decline. Customer's account doesn't have enough to cover the charge plus any held authorisations. `decline_code: insufficient_funds`.
- Issuer fraud lock. The issuing bank flagged the transaction as suspicious — often new merchant, unusual amount, or country mismatch. `decline_code: do_not_honor` or `fraudulent`. The customer's card itself is fine; their bank just doesn't trust this charge.
- Lost or stolen card. The card has been reported lost or stolen and is fully blocked. `decline_code: lost_card` or `stolen_card`. Never retry — the card will never work again.
- 3D Secure required but not completed. SCA / 3D Secure was required by the issuer (mandatory in the EEA under PSD2). The PaymentIntent went to `requires_action` but the customer never completed the challenge. Surface the next-action URL or use Stripe.js `confirmCardPayment()`.
- Wrong CVC, expiry, or postal code. Card details don't match what the issuer has on file. `decline_code: incorrect_cvc`, `expired_card`, or `incorrect_zip`. Have the customer re-enter.
How to fix it
Fixes are ordered by likelihood. Start with the first one that matches your context.
1. Surface the issuer's reason — never just "Card declined"
Stripe maps every `decline_code` to a customer-friendly message (see [Stripe's decline_code reference](https://stripe.com/docs/declines/codes)). Show that to the customer instead of a generic failure message — it converts dramatically better because the customer knows what to do next.
const DECLINE_MESSAGES = {
insufficient_funds: 'Your card was declined for insufficient funds. Try a different card.',
incorrect_cvc: 'Your security code is incorrect. Please re-enter and try again.',
expired_card: 'Your card has expired. Please use another card.',
do_not_honor: "Your bank declined this charge. Contact your bank or try a different card.",
lost_card: 'This card has been reported lost. Please use a different card.',
stolen_card: 'This card has been reported stolen. Please use a different card.',
generic_decline: 'Your card was declined. Try a different card or contact your bank.',
};
function declineMessage(err) {
return DECLINE_MESSAGES[err.decline_code]
?? DECLINE_MESSAGES.generic_decline;
}
2. Don't auto-retry hard declines
Hard declines (`lost_card`, `stolen_card`, `pickup_card`, `revocation_of_authorization`) will never succeed and may flag your account for fraud-pattern abuse if you keep retrying. Mark these as terminal and require a different card. Soft declines (`insufficient_funds`, `try_again_later`) can be retried on a new attempt by the customer.
3. Implement Smart Retries (Stripe Billing only)
For subscription invoices, enable Smart Retries in your Billing settings. Stripe machine-learns the optimal retry schedule per card. For one-off charges, retries should be customer-initiated only.
4. Use 3D Secure 2 / Payment Element for SCA
If the decline reason is `authentication_required`, you didn't complete SCA. Use the Stripe Payment Element (which handles SCA automatically) or `stripe.confirmCardPayment()` with the PaymentIntent client_secret.
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{ payment_method: paymentMethodId }
);
if (error?.code === 'card_declined' && error.decline_code === 'authentication_required') {
// SCA challenge interrupted — retry confirmCardPayment to re-trigger the modal.
}
5. Test with Stripe's decline test cards
Stripe has dedicated test cards for every decline reason. `4000000000000002` always returns `card_declined: generic_decline`. `4000000000009995` returns `insufficient_funds`. `4000002500003155` requires authentication. Use these to test your decline handling end-to-end before going live.
Detection and monitoring in production
Set up a Stripe webhook for `payment_intent.payment_failed` and log `last_payment_error.decline_code` to your error monitoring. Track decline rate over rolling 24-hour and 7-day windows; a sudden spike usually means a fraud rule change or a misconfigured webhook is preventing legitimate charges.
Related errors
- stripeinsufficient_fundsThe customer's card issuer declined the charge because the account doesn't have enough available balance to cover the amount, including any pending authorisations and holds.
- stripeauthentication_requiredThe card issuer requires the customer to complete 3D Secure (SCA / PSD2) authentication, but the PaymentIntent was confirmed without the customer completing the 3DS challenge — usually because you're using a bare Card element instead of the Payment Element or didn't handle `requires_action`.
- anthropicrate_limit_errorYou exceeded one of Anthropic's per-minute caps for the model and tier — RPM (requests/min), ITPM (input tokens/min), or OTPM (output tokens/min). Anthropic enforces all three independently and you can hit any one without breaching the others.
- stripesignature_verification_failedYour webhook handler called `stripe.webhooks.constructEvent()` and the signature in the `Stripe-Signature` header didn't match the HMAC-SHA256 of the raw request body computed with your endpoint secret.
Frequently asked questions
What's the difference between `code` and `decline_code` in a Stripe error? +
Should I retry a `card_declined` error automatically? +
Why does my test card work in test mode but fail in live mode? +
What does `decline_code: do_not_honor` mean? +
Why did my customer''s card decline as `card_declined` even though they have funds? +
How do I prevent `card_declined` errors from spamming my error tracker? +
Does Stripe Radar block charges before `card_declined`? +
How is `card_declined` different from `payment_intent_payment_attempt_failed`? +
When to escalate to Stripe support
Contact Stripe support if (a) you see consistent `do_not_honor` declines from cards that work elsewhere, (b) decline rates suddenly spike with no Radar rule change, or (c) `processing_error` declines persist for more than a few hours — that's a Stripe-side incident. Otherwise, declines are between the customer and their issuer; Stripe support can't override an issuer's decision.