-
Notifications
You must be signed in to change notification settings - Fork 3k
/
CreditCardValidator.swift
102 lines (90 loc) · 3.4 KB
/
CreditCardValidator.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import Foundation
enum CreditCardType: String, Equatable, CaseIterable {
case visa = "VISA"
case mastercard = "MASTERCARD"
case amex = "AMEX"
case diners = "DINERS"
case jcb = "JCB"
case discover = "DISCOVER"
case mir = "MIR"
case unionpay = "UNIONPAY"
var image: UIImage? {
switch self {
case .visa:
return UIImage(named: ImageIdentifiers.logoVisa)
case .mastercard:
return UIImage(named: ImageIdentifiers.logoMastercard)
case .amex:
return UIImage(named: ImageIdentifiers.logoAmex)
case .diners:
return UIImage(named: ImageIdentifiers.logoDiners)
case .jcb:
return UIImage(named: ImageIdentifiers.logoJcb)
case .discover:
return UIImage(named: ImageIdentifiers.logoDiscover)
case .mir:
return UIImage(named: ImageIdentifiers.logoMir)
case .unionpay:
return UIImage(named: ImageIdentifiers.logoUnionpay)
}
}
var validNumberLength: IndexSet {
switch self {
case .visa:
return IndexSet([13, 16])
case .amex:
return IndexSet(integer: 15)
case .diners:
return IndexSet(integersIn: 14...19)
case .jcb, .discover, .unionpay, .mir:
return IndexSet(integersIn: 16...19)
default:
return IndexSet(integer: 16)
}
}
}
struct CreditCardValidator {
let regEx: [CreditCardType: String] = [
.visa: "^4[0-9]{6,}$",
.mastercard: "^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$",
.amex: "^3[47][0-9]{5,}$",
.diners: "^3(?:0[0-5]|[68][0-9])[0-9]{4,}$",
.jcb: "^(?:2131|1800|35[0-9]{3})[0-9]{3,}$",
.discover: "^6(?:011|5[0-9]{2})[0-9]{3,}$",
.mir: "^2[0-9]{6,}$",
.unionpay: "^62[0-5]\\d{13,16}$"
]
func cardTypeFor(_ card: String) -> CreditCardType? {
let val = regEx.first { _, regex in
let result = "\(card)".range(of: regex, options: .regularExpression)
return (result != nil)
}
return val?.key
}
func isCardNumberValidFor(card: String) -> Bool {
guard let cardType = cardTypeFor(card) else { return false }
return cardType.validNumberLength.contains(String(card).count)
}
func isExpirationValidFor(date: String) -> Bool {
guard date.count == 4,
let inputMonth = Int(date.prefix(2)),
let inputYear = Int(date.suffix(2)),
let currentYear = Calendar(identifier: .gregorian).dateComponents([.year], from: Date()).year,
inputMonth < 13 && inputMonth > 0,
(inputYear + 2000) < currentYear + 100 && (inputYear + 2000) >= currentYear else {
return false
}
var userInputtedDate = DateComponents()
userInputtedDate.year = inputYear + 2000
userInputtedDate.month = inputMonth
let userCalendar = Calendar(identifier: .gregorian)
guard let userCardExpirationDate = userCalendar.date(from: userInputtedDate),
userCardExpirationDate >= Date() else {
return false
}
return true
}
}