Last active
June 27, 2019 10:04
-
-
Save cwagdev/e66d4806c1f63fe9387a to your computer and use it in GitHub Desktop.
Credit Cards represented in Swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Describes a type of credit card for a subset of known types. | |
enum CreditCardType: Printable { | |
case Amex, DinersClub, Discover, JCB, MasterCard, Visa, Unknown | |
var description: String { | |
switch self { | |
case .Amex: | |
return "Amex" | |
case .DinersClub: | |
return "Diners Club" | |
case .Discover: | |
return "Discover" | |
case .JCB: | |
return "JCB" | |
case .MasterCard: | |
return "MasterCard" | |
case .Visa: | |
return "Visa" | |
case .Unknown: | |
return "Unknown" | |
} | |
} | |
/// A set of lengths that are considered valid for the type | |
var validLengths: Set<Int> { | |
switch self { | |
case .Amex: | |
return [15] | |
case .DinersClub: | |
return [14, 15, 16] | |
case .Discover: | |
return [16] | |
case .JCB: | |
return [16] | |
case .MasterCard: | |
return [16] | |
case .Visa: | |
return [13, 16] | |
case .Unknown: | |
return [] | |
} | |
} | |
init(number: String) { | |
if let first = number.prefixAsInt(1) where first == 4 { | |
self = .Visa | |
return | |
} | |
if let firstTwo = number.prefixAsInt(2) { | |
if firstTwo == 35 { | |
self = .JCB | |
return | |
} | |
if contains([30, 36, 38, 39], firstTwo) { | |
self = .DinersClub | |
return | |
} | |
if 50...55 ~= firstTwo { | |
self = .MasterCard | |
return | |
} | |
if firstTwo == 34 || firstTwo == 37 { | |
self = .Amex | |
return | |
} | |
if firstTwo == 65 { | |
self = .Discover | |
return | |
} | |
} | |
if let firstThree = number.prefixAsInt(3) where 644...649 ~= firstThree { | |
self = .Discover | |
return | |
} | |
if let firstFour = number.prefixAsInt(4) where firstFour == 6011 { | |
self = .Discover | |
return | |
} | |
if let firstSix = number.prefixAsInt(6) where 622126...622925 ~= firstSix { | |
self = .Discover | |
return | |
} | |
self = .Unknown | |
} | |
} | |
private extension String { | |
/** | |
The first `length` characters of the String as an Int. | |
:param: length The number of characters to return | |
:returns: The first `length` characters of the String as an Int. `nil` if `length` exceed the length of the String or is not representable as Int. | |
*/ | |
func prefixAsInt(length: Int) -> Int? { | |
if count(self) < length { | |
return nil | |
} | |
return substringWithRange(startIndex..<advance(startIndex, length)).toInt() | |
} | |
} | |
/// Represents a String as a Credit Card | |
struct CreditCard { | |
/// The credit card number represented as a String | |
let number: String | |
/// The type of credit card, this is generally accurate once the first two numbers are provided | |
var type: CreditCardType { return CreditCardType(number: number) } | |
/// The last 4 numbers of the card, nil if the length is < 4 | |
var last4: String? { | |
if count(number) < 4 { | |
return nil | |
} | |
return number.substringWithRange(advance(number.endIndex, -4)..<number.endIndex) | |
} | |
/// A display version of the credit card number | |
var formattedString: String { | |
return number | |
} | |
/// True when both `isValidLength` and `isValidLuhn` are true | |
var isValid: Bool { | |
return isValidLength && isValidLuhn | |
} | |
/// True when the length of the card number meets a required length for the card type | |
var isValidLength: Bool { | |
return contains(type.validLengths, count(number)) | |
} | |
/// True when the Luhn algorithm https://en.wikipedia.org/wiki/Luhn_algorithm succeeds | |
var isValidLuhn: Bool { | |
var sum = 0 | |
let digitStrings = reverse(number).map { String($0) } | |
for tuple in enumerate(digitStrings) { | |
if let digit = tuple.element.toInt() { | |
let odd = tuple.index % 2 == 1 | |
switch (odd, digit) { | |
case (true, 9): | |
sum += 9 | |
case (true, 0...8): | |
sum += (digit * 2) % 9 | |
default: | |
sum += digit | |
} | |
} else { | |
return false | |
} | |
} | |
return sum % 10 == 0 | |
} | |
init(string: String) { | |
self.number = string | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
not work properly