Swift 4: Validating Credit Card Expiration Date

Swift 4: validating credit card expiration date

enteredDate will be midnight local time on the first of the month of the expiry date. Since you want that whole month to be valid, add 1 month to that value and then compare Date() to that updated value.

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yyyy"
let enteredDate = dateFormatter.date(from: expiryDate.text!)!
let endOfMonth = Calendar.current.date(byAdding: .month, value: 1, to: enteredDate)!
let now = Date()
if (endOfMonth < now) {
print("Expired - \(enteredDate) - \(endOfMonth)")
} else {
// valid
print("valid - now: \(now) entered: \(enteredDate)")
}

Please note that I left proper handling of optionals as an exercise for the reader.

How do I configure the text field to account for the expiry date?

Try like this, Its working at my end.

 func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
//Range.Lenth will greater than 0 if user is deleting text - Allow it to replce
if range.length > 0
{
if range.location == 3 {
var originalText = textField.text
originalText = originalText?.replacingOccurrences(of: "/", with: "")
textField.text = originalText
}
return true
}

//Dont allow empty strings
if string == " "
{
return false
}

//Check for max length including the spacers we added
if range.location >= 5
{
return false
}

var originalText = textField.text
let replacementText = string.stringByReplacingOccurrencesOfString(" ", withString: "")

//Verify entered text is a numeric value
let digits = NSCharacterSet.decimalDigitCharacterSet()
for char in replacementText.unicodeScalars
{
if !digits.longCharacterIsMember(char.value)
{
return false
}
}

//Put / space after 2 digit
if range.location == 2
{
originalText?.appendContentsOf("/")
textField.text = originalText
}

return true
}

Hope this help you.

How can I limit the size of the year entered on the text field

Usually, the formatting of the entered text and its validation are two separate issues. It's a really good place to use NSRegularExpression. Here you have an example of the date validation with a year in the range of 2000-2099 and a month between 01-12.

func validate(string: String) -> Bool {
let regex = try! NSRegularExpression(pattern: "^(20)\\d\\d[/](0[1-9]|1[012])$")

return regex.firstMatch(in: string, options: [], range: NSMakeRange(0, string.characters.count)) != nil
}

validate(string: "2012/02") // true
validate(string: "2012/2") // false
validate(string: "1912/12") // false
validate(string: "2012/112") // false

Update:

In your case it would look like this:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let newLength = text.count + string.count - range.length
let characterSet = CharacterSet(charactersIn: string)

if text.characters.count == 4, !string.characters.isEmpty {
textField.text = text + "/"
}

return CharacterSet.decimalDigits.isSuperset(of: characterSet) && newLength <= 7
}

func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else { return }
let isUserInputValid = validate(string: text)

if !isUserInputValid {
//TODO: Notify user that expiration date is invalid
}
}

Regular Expression to describe Credit Card expiry (valid thru) date

This regexp does the job:

(0[1-9]|1[0-2])\/[0-9]{2}

Note that it has a capturing group, depending on your regexp engine you can make it non-capturing like this:

(?:0[1-9]|1[0-2])\/[0-9]{2}

Validate credit card number

Don't change the user's entered text, it will just cause confusion. Don't cause the user to think: WTF. The user entered the number in the way he understood, honor that as much as possible.

Just sanitize what the user has entered. Generally just remove all leading, training and interspersed space characters, possibly any non-numeric characters. Then ensure the entered text is all numeric and of the correct length.

Keep in mind that the number can have a length of 13 to 19 digits, American Express is 15 digits. See: Bank card number

Consider the code:

if ([temp.text length]>19) {
txtCard.text= [temp.text substringToIndex:[temp.text length] - 1];
}

If the user entered an extra space character between groups the last digit will be deleted. It is all to easy to come up with such a scheme will avoid all possible pitfalls.

Example: "1234 4567 9012 3456" would be truncated to "1234 4567 9012 345".

Extra, Method to verify the check digit:

+ (BOOL)isValidCheckDigitForCardNumberString:(NSString *)cardNumberString {
int checkSum = 0;
uint8_t *cardDigitArray = (uint8_t *)[cardNumberString dataUsingEncoding:NSUTF8StringEncoding].bytes;
int digitsCount = (int)cardNumberString.length;
BOOL odd = cardNumberString.length % 2;

for (int digitIndex=0; digitIndex<digitsCount; digitIndex++) {
uint8_t cardDigit = cardDigitArray[digitIndex] - '0';
if (digitIndex % 2 == odd) {
cardDigit = cardDigit * 2;
cardDigit = cardDigit / 10 + cardDigit % 10;
}
checkSum += cardDigit;
}

return (checkSum % 10 == 0);
}

BOOL checkDigitValid = [TestClass isValidCheckDigitForCardNumberString:@"371238839571772"];
NSLog(@"check digit valid: %@", checkDigitMatch ? @"yes" : @"no");

Output:

check digit valid: yes

Regex for credit card expiration

I suggest using following regular expression:

/^(?:0?[1-9]|1[0-2]) *\/ *[1-9][0-9]$/

The month must be with the OR expression in a non marking group

  • either a single digit number in range 1 to 9 and therefore 0 is not valid,
  • or a two digit number with first digit being 0 and second digit being 1 to 9 and therefore 00 is not valid,
  • or a two digit number being 10 or 11 or 12.

The year must be a two digit number with first digit being not 0 as this is impossible for an expiration date up to year 2100.

0 or more spaces are allowed around /, but no other whitespace characters like carriage return, line-feed, horizontal tab, vertical tab, etc. all matched by \s.

Regular expression to match credit card expiration date

You're missing start of line anchor ^ and parenthesis are unmatched.

This should work:

re = /^(0[1-9]|1[0-2])\/?([0-9]{4}|[0-9]{2})$/;

OR using word boundaries:

re = /\b(0[1-9]|1[0-2])\/?([0-9]{4}|[0-9]{2})\b/;

Working Demo: http://regex101.com/r/gN5wH2

Credit card expiration dates - Inclusive or exclusive?

It took me a couple of minutes to find a site that I could source for this.

The card is valid until the last day of the month indicated, after the last [sic]1
day of the next month; the card cannot be used to make a purchase if the
merchant attempts to obtain an authorization.
- Source

Also, while looking this up, I found an interesting article on Microsoft's website using an example like this, exec summary: Access 2000 for a month/year defaults to the first day of the month, here's how to override that to calculate the end of the month like you'd want for a credit card.

Additionally, this page has everything you ever wanted to know about credit cards.


  1. This is assumed to be a typo and that it should read "..., after the first day of the next month; ..."


Related Topics



Leave a reply



Submit