A card expiry test v2 confirms that an expiration date is well formed, falls in a real month, and has not already passed. That is the whole job. It does not confirm the card exists, that it is active, or that it belongs to the person typing it. Only the issuer can answer those questions, and only through an authorization request.

Card Expiry Test V3: How to Perform It for Security

I see people conflate the two all the time. Format validation is cheap and runs before you ever touch a network. Authorization is expensive, rate limited, and logged. Keep them separate in your head and in your code.

card expiry test v4

What the v2 checks actually cover

The second revision of most expiry specs tightens four things that older implementations got wrong.

read more

  • Month range. Month must be 01 through 12. A value of 00 or 13 is a hard fail, not a warning. Watch for inputs like "13/28" that pass a naive digit count.
  • Year normalization. Two-digit years get expanded against a rolling window. A "26" means 2026 now, but the same string meant 1926 in a legacy system. Pin the window explicitly.
  • End-of-month inclusivity. A card marked 12/26 is valid through December 31, 2026, usually to 23:59 in the issuer's timezone. Comparing against the first of the month rejects live cards at the boundary.
  • Server-side re-check. The client check is a courtesy. The server re-validates on submit, and again on any stored card before reuse.

Why the Luhn digit is not a liveness test

The Luhn mod-10 checksum catches transposed digits and typos in the primary account number. It is a typo filter, nothing more. A randomly generated number passes Luhn roughly one time in ten. Do not let a passing checksum convince you a card is real, and do not let it stand in for expiry validation either. They are independent checks that happen to run in the same form.

card expiry test v2

Where sandbox test cards belong

Legitimate expiry testing happens against your processor's sandbox. Every major gateway publishes test card numbers that accept any future expiration date, plus dedicated decline scenarios. You want a card that returns an expired-card decline so you can verify your error handling end to end. You want a card that returns insufficient funds. You want an invalid expiry format case that never leaves your server.

Build a test matrix: valid future date, expired date, month 00, month 13, four-digit year, missing leading zero, and a leap-year boundary. Run all seven through the same code path. If the expired case returns a generic failure instead of a specific one, your users will not know what to fix.

The line you should not cross

There is no legitimate way to test a card you do not own or have written authorization to test. Anyone who tells you otherwise, or offers to run a card through a checker for you, is describing fraud. Real validation requires a merchant account, an authorization request, and the issuer's response. Everything before that is string parsing.

Quick implementation checklist

  1. Normalize input to MM/YY on the client, but never trust it.
  2. Reject month values outside 01 to 12 before anything else.
  3. Expand the year with an explicit, documented window.
  4. Compare against end-of-month, not the first.
  5. Re-run the same function server-side on submit and on stored-card reuse.
  6. Map processor decline codes to specific user-facing messages.

That is a complete expiry test. It is boring, it is deterministic, and it will never tell you whether a card is live. That is a feature.