Skip to content

Instantly share code, notes, and snippets.

@cwagdev
Last active June 27, 2019 10:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cwagdev/e66d4806c1f63fe9387a to your computer and use it in GitHub Desktop.
Save cwagdev/e66d4806c1f63fe9387a to your computer and use it in GitHub Desktop.
Credit Cards represented in Swift
/// 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
}
}
@yildirimatcioglu
Copy link

not work properly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment