Short answer
Check error.code for expired_card, show a message that the card has expired, and ask for another one, keeping the same PaymentIntent. For renewals, send the customer to a page where they can replace the card, such as the Stripe customer portal, then pay the open invoice with stripe.invoices.pay. A wrong expiry typed at checkout returns invalid_expiry_month or invalid_expiry_year instead, which a retry with the right date fixes.
Rather not fix it yourself? Stripe Billing Fix and Setup handles it at a fixed price, agreed before work starts.
What expired_card means, and the fix
01
The card is past its date
“The customer needs to use another card.” Stripe, Decline codes (expired_card)
The bank refused the card because it has expired. The customer has to pay with a new card or another payment method.
02
Ask for another card at checkout
“The card has expired. Check the expiration date or use a different card.” Stripe, Error codes (expired_card)
Stripe’s card forms usually catch a past date before submitting, so at checkout this mostly comes from a saved card. Tell the customer the card has expired and let them enter a new one. Check error.code, because decline_code can be empty for this error.
if (error?.code === 'expired_card' || error?.decline_code === 'expired_card') { showMessage('This card has expired. Use another card to finish paying.'); }03
A saved card expired on a subscription
Renewals fail when the saved card expires. Email the customer a link to update the card, for example the customer portal with payment method updates turned on in its settings. Updating the card doesn’t pay the open invoice by itself: pay it, or wait for the next retry.
const session = await stripe.billingPortal.sessions.create({ customer: customerId, return_url: 'https://example.com/account', }); // email session.url to the customer // after the card is updated await stripe.invoices.pay(invoiceId);04
A typed date is a different error
“The expiration month is invalid.” Stripe, Decline codes (invalid_expiry_month)
If the customer mistyped the month or year, Stripe returns invalid_expiry_month or invalid_expiry_year, not expired_card. Point them at the date field for those.
05
Test the message
In a sandbox, pay with test card 4000000000000069 to get the expired_card decline and check what your checkout shows.
Checked against the vendor’s docs on : Stripe docs: Decline codes; Stripe docs: Error codes; Stripe docs: Testing.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- $950Stripe Billing Fix and SetupSubscriptions, webhooks, customer portal and failed payments set up or fixed, tested in Stripe test mode.See scope and start
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA 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
“Working with Simon makes everything run so smoothly and better than I could ask for it. Amazingly communicative, extremely knowledgeable of coding and the backend processes. I strongly recommend.”
Related: Stripe processing_error · Stripe incorrect_cvc · Stripe do_not_honor decline · Stripe customer portal not working