-
-
Save unnamedd/70521a1e8f59b056988e9b3b2eb6c76c 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
// | |
// Created by Chris Wagner | |
// https://gist.github.com/cwagdev/e66d4806c1f63fe9387a | |
// | |
// Modified by Thiago Holanda (August/2016) | |
// | |
// These modifications was made to support the latest version of Swift | |
// before the major version (3.0) is released. This code will compile | |
// without any problem on Swift 2.2. | |
// | |
// If you have any big issue, please, contact the original author or | |
// contact me to talk about this specific modification at twitter.com/tholanda | |
/// Describes a type of credit card for a subset of known types. | |
enum CreditCardType: String, CustomStringConvertible { | |
case Amex = "Amex" | |
case DinersClub = "Diners" | |
case Discover = "Discover" | |
case JCB = "JCB" | |
case MasterCard = "MasterCard" | |
case Visa = "Visa" | |
case Unknown = "Unknown" | |
var description: String { | |
return self.rawValue | |
} | |
/// 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.prefix(1) where first == 4 { | |
self = .Visa | |
return | |
} | |
if let firstTwo = number.prefix(2) { | |
if firstTwo == 35 { | |
self = .JCB | |
return | |
} | |
if [30, 36, 38, 39].contains(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.prefix(3) where 644...649 ~= firstThree { | |
self = .Discover | |
return | |
} | |
if let firstFour = number.prefix(4) where firstFour == 6011 { | |
self = .Discover | |
return | |
} | |
if let firstSix = number.prefix(6) where 622126...622925 ~= firstSix { | |
self = .Discover | |
return | |
} | |
if let firstFour = number.prefix(4) where 3528...3589 ~= firstFour { | |
self = .JCB | |
return | |
} | |
self = .Unknown | |
} | |
} | |
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 prefix(length: Int) -> Int? { | |
let range = startIndex..<startIndex.advancedBy(length) | |
return Int(self.substringWithRange(range)) | |
} | |
} | |
/// Represents a String as a Credit Card | |
struct CreditCard { | |
init(card: String) { | |
self.number = card.stringByReplacingOccurrencesOfString(" ", withString: "") | |
} | |
/// The credit card number represented as a String | |
var 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 number.characters.count < 4 { | |
return nil | |
} | |
let range = number.endIndex.advancedBy(-4)..<number.endIndex | |
return number.substringWithRange(range) | |
} | |
/// 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 type.validLengths.contains(number.characters.count) | |
} | |
/// True when the Luhn algorithm https://en.wikipedia.org/wiki/Luhn_algorithm succeeds | |
var isValidLuhn: Bool { | |
var sum = 0 | |
let digitStrings = number.characters.reverse().map { Int(String($0)) } | |
for tuple in digitStrings.enumerate() { | |
if let digit = tuple.element { | |
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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment