[Date Prev][Date Next][Thread Prev][Thread Next]
- Subject: Re: Credit card expiry date validation
- From: Jerome Vuarand <jerome.vuarand@...>
- Date: Mon, 15 Jun 2020 22:10:44 +0100
On Mon, 15 Jun 2020 at 21:15, Wilmar Pérez <firstname.lastname@example.org> wrote:
> I have an application collecting credit card data. Before sending the information out to the payment entity I am trying to make sure the information entered is, at least, valid. I already worked out the card number and cvv numbers but I am not so sure about the expiry date. The format I get the info is MMYY. So what I am doing is:
You should avoid posting HTML to this email list. I can view it OK but
others might not. And when I click reply it discards all the
formatting and put all the code on one line.
> I do not know if this is the most elegant solution but it works.
Your code is inconsistent, it reads "local card_expiry_date = 'YYMM'",
while "card_exp_year = string.sub(card_expiry_date, 3, 4)" suggests
the format is MMYY. My code belows assume YYMM.
> However it has a huge bug: it will fail at the end of the century. Since I only know the last two digits of the expiry date year, if a card expires in 2102 for instance and we were in 2099 my logic would wrongly reject the date (02 is less than 99).
> I am very aware that me an my simple app will likely not be around by then but it bugs me to leave it like this.
> Can anyone please suggest a proper way to do this validation?
Here's my take at the problem:
local max_lifetime = 20 -- years (cannot be more than 50)
local function check(date, now)
if not now then
now = tonumber(os.date('!%Y%m')) -- year and month in UTC
date = date:match('^%d%d%d%d$') -- 4 digits, nothing else
if not date then return false end
date = assert(tonumber(date))
if date % 100 > 12 then return false end -- reject bad month
date = (now // 10000 + 1) * 10000 + date -- assume next (+1) century
-- bring back all dates beyond max lifetime into the past
local max_expiry = now + max_lifetime * 100
while date > max_expiry do
date = date - 10000
-- if it's still in the future, it's valid
return date >= now
card_expiry_date = 'YYMM' -- replace this with four digits
And here are the test cases I used (try it before the end of the month):
assert(check('0106', 209905)) -- check century wrap
assert(not check('4105')) -- beyond 20 years
assert(not check('2013')) -- invalid month
assert(not check('201231')) -- invalid format
assert(not check('20dec')) -- invalid format
assert(not check('2106', 209905)) -- check century wrap
max_lifetime = 50
assert(check('7005')) -- may 2070
assert(check('7006')) -- june 2070
assert(not check('7007')) -- july 1970
max_lifetime = 20