credit_card_validations 1.5.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/Changelog.md +85 -0
- data/README.md +32 -9
- data/Rakefile +4 -4
- data/credit_card_validations.gemspec +2 -2
- data/lib/active_model/credit_card_number_validator.rb +67 -2
- data/lib/credit_card_validations.rb +25 -17
- data/lib/credit_card_validations/detector.rb +110 -27
- data/lib/credit_card_validations/factory.rb +66 -0
- data/lib/credit_card_validations/luhn.rb +5 -0
- data/lib/credit_card_validations/mmi.rb +18 -13
- data/lib/credit_card_validations/string.rb +11 -1
- data/lib/credit_card_validations/version.rb +1 -1
- data/lib/data/brands.yaml +238 -0
- data/spec/active_model_spec.rb +69 -0
- data/spec/credit_card_validations_spec.rb +168 -0
- data/spec/factory_spec.rb +21 -0
- data/{test → spec}/fixtures/invalid_cards.yml +0 -0
- data/{test → spec}/fixtures/valid_cards.yml +1 -0
- data/spec/models/credit_card.rb +19 -0
- data/spec/string_spec.rb +43 -0
- data/{test → spec}/test_helper.rb +5 -4
- metadata +25 -17
- data/lib/credit_card_validations/card_rules.rb +0 -84
- data/test/credit_card_validations_test.rb +0 -151
- data/test/models/any_credit_card.rb +0 -6
- data/test/models/credit_card.rb +0 -5
@@ -0,0 +1,66 @@
|
|
1
|
+
# == CreditCardValidations Factory
|
2
|
+
# Generates card number that passes validation
|
3
|
+
#
|
4
|
+
# #random
|
5
|
+
# CreditCardValidations::Factory.random
|
6
|
+
# #or particular brand
|
7
|
+
# CreditCardValidations::Factory.random(:maestro) # "6010430241237266856"
|
8
|
+
#
|
9
|
+
#
|
10
|
+
#
|
11
|
+
module CreditCardValidations
|
12
|
+
class Factory
|
13
|
+
class << self
|
14
|
+
def random(brand = nil)
|
15
|
+
if brand.nil?
|
16
|
+
brand = Detector.brands.keys.sample
|
17
|
+
else
|
18
|
+
raise RuntimeError.new("Unsupported brand") if Detector.brands[brand].nil?
|
19
|
+
end
|
20
|
+
generate(Detector.brands[brand][:rules].sample)
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate(rule)
|
25
|
+
number(rule[:prefixes].sample, rule[:length].sample, rule.fetch(:options, {})[:skip_luhn])
|
26
|
+
end
|
27
|
+
|
28
|
+
def number(prefix, length, skip_luhn = false)
|
29
|
+
number = prefix.dup
|
30
|
+
1.upto(length - (prefix.length + 1)) do
|
31
|
+
number << "#{rand(9)}"
|
32
|
+
end
|
33
|
+
#if skip luhn
|
34
|
+
if skip_luhn
|
35
|
+
number += "#{rand(9)}"
|
36
|
+
else
|
37
|
+
number += last_digit(number).to_s
|
38
|
+
end
|
39
|
+
number
|
40
|
+
end
|
41
|
+
|
42
|
+
#extracted from darkcoding-credit-card
|
43
|
+
|
44
|
+
def last_digit(number)
|
45
|
+
# Calculate sum
|
46
|
+
sum, pos = 0, 0
|
47
|
+
length = number.length + 1
|
48
|
+
|
49
|
+
reversed_number = number.reverse
|
50
|
+
while pos < length do
|
51
|
+
odd = reversed_number[pos].to_i * 2
|
52
|
+
odd -= 9 if odd > 9
|
53
|
+
|
54
|
+
sum += odd
|
55
|
+
|
56
|
+
sum += reversed_number[pos + 1].to_i if pos != (length - 2)
|
57
|
+
|
58
|
+
pos += 2
|
59
|
+
end
|
60
|
+
|
61
|
+
(((sum / 10).floor + 1) * 10 - sum) % 10
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,17 +1,22 @@
|
|
1
|
-
#
|
1
|
+
# == CreditCardValidations Mmi
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
3
|
+
# Implements Major Industry Identifier (MII) detection
|
4
|
+
#
|
5
|
+
# The first digit of a credit card number is the Major Industry Identifier (MII), which represents the category of entity which issued the card. MII digits represent the following issuer categories:
|
6
|
+
# 0 – ISO/TC 68 and other future industry assignments
|
7
|
+
# 1 – Airlines
|
8
|
+
# 2 – Airlines and other future industry assignments
|
9
|
+
# 3 – Travel and entertainment and banking/financial
|
10
|
+
# 4 – Banking and financial
|
11
|
+
# 5 – Banking and financial
|
12
|
+
# 6 – Merchandising and banking/financial
|
13
|
+
# 7 – Petroleum and other future industry assignments
|
14
|
+
# 8 – Healthcare, telecommunications and other future industry assignments
|
15
|
+
# 9 – National assignment
|
16
|
+
# For example, American Express, Diner's Club, Carte Blanche,
|
17
|
+
# and JCB are in the travel and entertainment category;
|
18
|
+
# VISA, MasterCard, and Discover are in the banking and financial category (Discover being in the Merchandising and banking/financial category);
|
19
|
+
# and Sun Oil and Exxon are in the petroleum category.
|
15
20
|
|
16
21
|
|
17
22
|
module CreditCardValidations
|
@@ -1,5 +1,10 @@
|
|
1
|
+
# String extension for brand detecting and number validation
|
1
2
|
#
|
2
|
-
#
|
3
|
+
# require 'credit_card_validations/string'
|
4
|
+
# '5274 5763 9425 9961'.credit_card_brand
|
5
|
+
# '5274 5763 9425 9961'.credit_card_brand_name
|
6
|
+
# '5274 5763 9425 9961'.valid_credit_card_brand?(:mastercard, :visa)
|
7
|
+
# '5274 5763 9425 9961'.valid_credit_card_brand?(:amex)
|
3
8
|
#
|
4
9
|
class String
|
5
10
|
def credit_card_brand
|
@@ -9,4 +14,9 @@ class String
|
|
9
14
|
def valid_credit_card_brand?(*brands)
|
10
15
|
CreditCardValidations::Detector.new(self).valid?(*brands)
|
11
16
|
end
|
17
|
+
|
18
|
+
def credit_card_brand_name
|
19
|
+
CreditCardValidations::Detector.new(self).brand_name
|
20
|
+
end
|
21
|
+
|
12
22
|
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
---
|
2
|
+
:visa:
|
3
|
+
:rules:
|
4
|
+
- :length:
|
5
|
+
- 13
|
6
|
+
- 16
|
7
|
+
:prefixes:
|
8
|
+
- '4'
|
9
|
+
:mastercard:
|
10
|
+
:rules:
|
11
|
+
- :length:
|
12
|
+
- 16
|
13
|
+
:prefixes:
|
14
|
+
- '51'
|
15
|
+
- '52'
|
16
|
+
- '53'
|
17
|
+
- '54'
|
18
|
+
- '55'
|
19
|
+
:options:
|
20
|
+
:brand_name: MasterCard
|
21
|
+
:amex:
|
22
|
+
:rules:
|
23
|
+
- :length:
|
24
|
+
- 15
|
25
|
+
:prefixes:
|
26
|
+
- '34'
|
27
|
+
- '37'
|
28
|
+
:options:
|
29
|
+
:brand_name: American Express
|
30
|
+
:diners:
|
31
|
+
:rules:
|
32
|
+
- :length:
|
33
|
+
- 14
|
34
|
+
:prefixes:
|
35
|
+
- '300'
|
36
|
+
- '301'
|
37
|
+
- '302'
|
38
|
+
- '303'
|
39
|
+
- '304'
|
40
|
+
- '305'
|
41
|
+
- '36'
|
42
|
+
- '38'
|
43
|
+
:options:
|
44
|
+
:brand_name: Diners Club
|
45
|
+
:diners_us:
|
46
|
+
:rules:
|
47
|
+
- :length:
|
48
|
+
- 16
|
49
|
+
:prefixes:
|
50
|
+
- '54'
|
51
|
+
- '55'
|
52
|
+
:options:
|
53
|
+
:brand_name: Diners Club US
|
54
|
+
:discover:
|
55
|
+
:rules:
|
56
|
+
- :length:
|
57
|
+
- 16
|
58
|
+
:prefixes:
|
59
|
+
- '6011'
|
60
|
+
- '644'
|
61
|
+
- '645'
|
62
|
+
- '646'
|
63
|
+
- '647'
|
64
|
+
- '648'
|
65
|
+
- '649'
|
66
|
+
- '65'
|
67
|
+
:jcb:
|
68
|
+
:rules:
|
69
|
+
- :length:
|
70
|
+
- 15
|
71
|
+
- 16
|
72
|
+
:prefixes:
|
73
|
+
- '3528'
|
74
|
+
- '3529'
|
75
|
+
- '353'
|
76
|
+
- '354'
|
77
|
+
- '355'
|
78
|
+
- '356'
|
79
|
+
- '357'
|
80
|
+
- '358'
|
81
|
+
- :length:
|
82
|
+
- 15
|
83
|
+
:prefixes:
|
84
|
+
- '1800'
|
85
|
+
- '2131'
|
86
|
+
- :length:
|
87
|
+
- 19
|
88
|
+
:prefixes:
|
89
|
+
- '357266'
|
90
|
+
:options:
|
91
|
+
:brand_name: JCB
|
92
|
+
:laser:
|
93
|
+
:rules:
|
94
|
+
- :length:
|
95
|
+
- 16
|
96
|
+
- 17
|
97
|
+
- 18
|
98
|
+
- 19
|
99
|
+
:prefixes:
|
100
|
+
- '6304'
|
101
|
+
- '6706'
|
102
|
+
- '6771'
|
103
|
+
:solo:
|
104
|
+
:rules:
|
105
|
+
- :length:
|
106
|
+
- 16
|
107
|
+
- 18
|
108
|
+
- 19
|
109
|
+
:prefixes:
|
110
|
+
- '6334'
|
111
|
+
- '6767'
|
112
|
+
:switch:
|
113
|
+
:rules:
|
114
|
+
- :length:
|
115
|
+
- 16
|
116
|
+
- 18
|
117
|
+
- 19
|
118
|
+
:prefixes:
|
119
|
+
- '633110'
|
120
|
+
- '633312'
|
121
|
+
- '633304'
|
122
|
+
- '633303'
|
123
|
+
- '633301'
|
124
|
+
- '633300'
|
125
|
+
:maestro:
|
126
|
+
:rules:
|
127
|
+
- :length:
|
128
|
+
- 12
|
129
|
+
- 13
|
130
|
+
- 14
|
131
|
+
- 15
|
132
|
+
- 16
|
133
|
+
- 17
|
134
|
+
- 18
|
135
|
+
- 19
|
136
|
+
:prefixes:
|
137
|
+
- '500'
|
138
|
+
- '5010'
|
139
|
+
- '5011'
|
140
|
+
- '5012'
|
141
|
+
- '5013'
|
142
|
+
- '5014'
|
143
|
+
- '5015'
|
144
|
+
- '5016'
|
145
|
+
- '5017'
|
146
|
+
- '5018'
|
147
|
+
- '502'
|
148
|
+
- '503'
|
149
|
+
- '504'
|
150
|
+
- '505'
|
151
|
+
- '506'
|
152
|
+
- '507'
|
153
|
+
- '508'
|
154
|
+
- '509'
|
155
|
+
- '56'
|
156
|
+
- '57'
|
157
|
+
- '58'
|
158
|
+
- '59'
|
159
|
+
- '6010'
|
160
|
+
- '6012'
|
161
|
+
- '6013'
|
162
|
+
- '6014'
|
163
|
+
- '6015'
|
164
|
+
- '6016'
|
165
|
+
- '6017'
|
166
|
+
- '6018'
|
167
|
+
- '6019'
|
168
|
+
- '602'
|
169
|
+
- '603'
|
170
|
+
- '604'
|
171
|
+
- '605'
|
172
|
+
- '6060'
|
173
|
+
- '621'
|
174
|
+
- '627'
|
175
|
+
- '629'
|
176
|
+
- '670'
|
177
|
+
- '671'
|
178
|
+
- '672'
|
179
|
+
- '673'
|
180
|
+
- '674'
|
181
|
+
- '675'
|
182
|
+
- '677'
|
183
|
+
- '6760'
|
184
|
+
- '6761'
|
185
|
+
- '6762'
|
186
|
+
- '6763'
|
187
|
+
- '6764'
|
188
|
+
- '6765'
|
189
|
+
- '6766'
|
190
|
+
- '6768'
|
191
|
+
- '6769'
|
192
|
+
- '679'
|
193
|
+
:unionpay:
|
194
|
+
:rules:
|
195
|
+
- :length:
|
196
|
+
- 16
|
197
|
+
- 17
|
198
|
+
- 18
|
199
|
+
- 19
|
200
|
+
:prefixes:
|
201
|
+
- '622'
|
202
|
+
- '624'
|
203
|
+
- '625'
|
204
|
+
- '626'
|
205
|
+
- '628'
|
206
|
+
:options:
|
207
|
+
:skip_luhn: true
|
208
|
+
:brand_name: China UnionPay
|
209
|
+
:dankrot:
|
210
|
+
:rules:
|
211
|
+
- :length:
|
212
|
+
- 16
|
213
|
+
:prefixes:
|
214
|
+
- '5019'
|
215
|
+
:rupay:
|
216
|
+
:rules:
|
217
|
+
- :length:
|
218
|
+
- 16
|
219
|
+
:prefixes:
|
220
|
+
- '6061'
|
221
|
+
- '6062'
|
222
|
+
- '6063'
|
223
|
+
- '6064'
|
224
|
+
- '6065'
|
225
|
+
- '6066'
|
226
|
+
- '6067'
|
227
|
+
- '6068'
|
228
|
+
- '6069'
|
229
|
+
- '607'
|
230
|
+
- '608'
|
231
|
+
:options:
|
232
|
+
:skip_luhn: true
|
233
|
+
:hipercard:
|
234
|
+
:rules:
|
235
|
+
- :length:
|
236
|
+
- 19
|
237
|
+
:prefixes:
|
238
|
+
- '384'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe "ActiveModel Validator" do
|
4
|
+
|
5
|
+
let(:model) { CreditCard.new }
|
6
|
+
|
7
|
+
describe "Proc support" do
|
8
|
+
it "should be valid if brands from proc valid" do
|
9
|
+
card = model.dup
|
10
|
+
card.card_type = "Master Card"
|
11
|
+
card.number6 = CreditCardValidations::Factory.random(:visa)
|
12
|
+
card.valid?.must_equal false
|
13
|
+
card.number6 = CreditCardValidations::Factory.random(:mastercard)
|
14
|
+
card.valid?.must_equal true
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "Any Brand" do
|
21
|
+
it "should be valid for all prepared valid numbers" do
|
22
|
+
VALID_NUMBERS.each do |_, numbers|
|
23
|
+
|
24
|
+
numbers.each do |number|
|
25
|
+
card = model
|
26
|
+
card.number4 = number
|
27
|
+
card.number5 = number
|
28
|
+
card.valid?.must_equal true
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
describe "Except Amex and Maestro brand" do
|
37
|
+
it "should reject all other valid numbers" do
|
38
|
+
VALID_NUMBERS.except(:amex, :maestro) do |_, numbers|
|
39
|
+
card = model
|
40
|
+
card.number = numbers.first
|
41
|
+
card.valid?.must_equal false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should accept using except options" do
|
46
|
+
VALID_NUMBERS.except(:amex, :maestro) do |_, numbers|
|
47
|
+
card = model
|
48
|
+
card.number3 = numbers.first
|
49
|
+
card.valid?.must_equal true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "Only Amex and Mestro brands" do
|
56
|
+
|
57
|
+
|
58
|
+
it "should accept amex and maestro brand if valid" do
|
59
|
+
VALID_NUMBERS.slice(:amex, :maestro) do |_, numbers|
|
60
|
+
card = model
|
61
|
+
card.number = numbers.first
|
62
|
+
card.number2 = numbers.first
|
63
|
+
card.valid?.must_equal true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|