cw_credit_card_validations 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ require 'credit_card_validations/version'
2
+ require 'credit_card_validations/error'
3
+ require 'active_model'
4
+ require 'active_support/core_ext'
5
+ require 'active_model/validations'
6
+ require 'active_model/credit_card_number_validator'
7
+ require 'yaml'
8
+
9
+ module CreditCardValidations
10
+ extend ActiveSupport::Autoload
11
+ autoload :VERSION, 'credit_card_validations/version'
12
+ autoload :Luhn, 'credit_card_validations/luhn'
13
+ autoload :Detector, 'credit_card_validations/detector'
14
+ autoload :Factory, 'credit_card_validations/factory'
15
+ autoload :Mmi, 'credit_card_validations/mmi'
16
+
17
+
18
+ def self.add_brand(key, rules, options = {})
19
+ Detector.add_brand(key, rules, options)
20
+ end
21
+
22
+ DATA = YAML.load_file(File.join(File.dirname(__FILE__), 'data', 'brands.yaml')) || {}
23
+
24
+ def self.reload!
25
+ Detector.brands = {}
26
+ DATA.each do |key, data|
27
+ add_brand(key, data.fetch(:rules), data.fetch(:options, {}))
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ CreditCardValidations.reload!
34
+
35
+
36
+
@@ -0,0 +1,148 @@
1
+ # == CreditCardValidations Detector
2
+ #
3
+ # class provides credit card number validations
4
+ module CreditCardValidations
5
+ class Detector
6
+
7
+ include Mmi
8
+
9
+ class_attribute :brands
10
+ self.brands = {}
11
+
12
+ attr_reader :number
13
+
14
+ def initialize(number)
15
+ @number = number.to_s.tr('- ', '')
16
+ end
17
+
18
+ # credit card number validation
19
+ def valid?(*brands)
20
+ !!valid_number?(*brands)
21
+ end
22
+
23
+ #brand name
24
+ def brand(*keys)
25
+ valid_number?(*keys)
26
+ end
27
+
28
+ def valid_number?(*keys)
29
+ selected_brands = keys.blank? ? self.brands : resolve_keys(*keys)
30
+ if selected_brands.any?
31
+ selected_brands.each do |key, brand|
32
+ return key if matches_brand?(brand)
33
+ end
34
+ end
35
+ nil
36
+ end
37
+
38
+ #check if luhn valid
39
+ def valid_luhn?
40
+ @valid_luhn ||= Luhn.valid?(number)
41
+ end
42
+
43
+ def brand_name
44
+ self.class.brand_name(brand)
45
+ end
46
+
47
+ protected
48
+
49
+ def resolve_keys(*keys)
50
+ brand_keys = keys.map do |el|
51
+ if el.is_a? String
52
+ #try to find key by name
53
+ el = (self.class.brand_key(el) || el).to_sym
54
+ end
55
+ el.downcase
56
+ end
57
+ self.brands.slice(*brand_keys)
58
+ end
59
+
60
+ def matches_brand?(brand)
61
+ rules = brand.fetch(:rules)
62
+ options = brand.fetch(:options, {})
63
+
64
+ rules.each do |rule|
65
+ if (options[:skip_luhn] || valid_luhn?) &&
66
+ rule[:length].include?(number.length) &&
67
+ number.match(rule[:regexp])
68
+ return true
69
+ end
70
+ end
71
+ false
72
+ end
73
+
74
+ class << self
75
+
76
+ def has_luhn_check_rule?(key)
77
+ !brands[key].fetch(:options, {}).fetch(:skip_luhn, false)
78
+ end
79
+
80
+ #
81
+ # add brand
82
+ #
83
+ # CreditCardValidations.add_brand(:en_route, {length: 15, prefixes: ['2014', '2149']}, {skip_luhn: true}) #skip luhn
84
+ #
85
+ def add_brand(key, rules, options = {})
86
+
87
+ brands[key] = {rules: [], options: options || {}}
88
+
89
+ Array.wrap(rules).each do |rule|
90
+ add_rule(key, rule[:length], rule[:prefixes])
91
+ end
92
+
93
+ define_brand_method(key)
94
+
95
+ end
96
+
97
+ def brand_name(brand_key)
98
+ brand = brands[brand_key]
99
+ if brand
100
+ brand.fetch(:options, {})[:brand_name] || brand_key.to_s.titleize
101
+ else
102
+ nil
103
+ end
104
+ end
105
+
106
+ def brand_key(brand_name)
107
+ brands.detect do |_, brand|
108
+ brand[:options][:brand_name] == brand_name
109
+ end.try(:first)
110
+ end
111
+
112
+ # CreditCardValidations.delete_brand(:en_route)
113
+ def delete_brand(key)
114
+ key = key.to_sym
115
+ undef_brand_method(key)
116
+ brands.reject! { |k, _| k == key }
117
+ end
118
+
119
+ #create rule for detecting brand
120
+ def add_rule(key, length, prefixes)
121
+ unless brands.has_key?(key)
122
+ raise Error.new("brand #{key} is undefined, please use #add_brand method")
123
+ end
124
+ length, prefixes = Array(length), Array(prefixes)
125
+ brands[key][:rules] << {length: length, regexp: compile_regexp(prefixes), prefixes: prefixes}
126
+ end
127
+
128
+ protected
129
+
130
+ # create methods like visa?, maestro? etc
131
+ def define_brand_method(key)
132
+ define_method "#{key}?".to_sym do
133
+ valid?(key)
134
+ end unless method_defined? "#{key}?".to_sym
135
+ end
136
+
137
+ def undef_brand_method(key)
138
+ undef_method "#{key}?".to_sym if method_defined? "#{key}?".to_sym
139
+ end
140
+
141
+ #create regexp by array of prefixes
142
+ def compile_regexp(prefixes)
143
+ Regexp.new("^((#{prefixes.join(")|(")}))")
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,4 @@
1
+ module CreditCardValidations
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,64 @@
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
+ brand = Detector.brands.keys.sample if brand.nil?
16
+ if Detector.brands[brand].nil?
17
+ raise Error.new('Unsupported brand')
18
+ end
19
+ generate(Detector.brands[brand][:rules].sample)
20
+ end
21
+
22
+ def generate(rule)
23
+ number(rule[:prefixes].sample, rule[:length].sample, rule.fetch(:options, {})[:skip_luhn])
24
+ end
25
+
26
+ def number(prefix, length, skip_luhn = false)
27
+ number = prefix.dup
28
+ 1.upto(length - (prefix.length + 1)) do
29
+ number << "#{rand(9)}"
30
+ end
31
+ #if skip luhn
32
+ if skip_luhn
33
+ number += "#{rand(9)}"
34
+ else
35
+ number += last_digit(number).to_s
36
+ end
37
+ number
38
+ end
39
+
40
+ #extracted from darkcoding-credit-card
41
+
42
+ def last_digit(number)
43
+ # Calculate sum
44
+ sum, pos = 0, 0
45
+ length = number.length + 1
46
+
47
+ reversed_number = number.reverse
48
+ while pos < length do
49
+ odd = reversed_number[pos].to_i * 2
50
+ odd -= 9 if odd > 9
51
+
52
+ sum += odd
53
+
54
+ sum += reversed_number[pos + 1].to_i if pos != (length - 2)
55
+
56
+ pos += 2
57
+ end
58
+
59
+ (((sum / 10).floor + 1) * 10 - sum) % 10
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,22 @@
1
+ # == CreditCardValidations Luhn
2
+ # simple class to validate Luhn numbers.
3
+ #
4
+ # Luhn.valid? 4111111111111111
5
+ #
6
+ module CreditCardValidations
7
+ class Luhn
8
+ def self.valid?(number)
9
+ s1 = s2 = 0
10
+ number.to_s.reverse.chars.each_slice(2) do |odd, even|
11
+ s1 += odd.to_i
12
+
13
+ double = even.to_i * 2
14
+ double -= 9 if double >= 10
15
+ s2 += double
16
+ end
17
+ (s1 + s2) % 10 == 0
18
+ end
19
+ end
20
+ end
21
+
22
+
@@ -0,0 +1,43 @@
1
+ # == CreditCardValidations Mmi
2
+ #
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.
20
+
21
+
22
+ module CreditCardValidations
23
+ module Mmi
24
+
25
+ ISSUER_CATEGORIES = {
26
+ '0' => 'ISO/TC 68 nd other industry assignments',
27
+ '1' => 'Airlines',
28
+ '2' => 'Airlines and other industry assignments',
29
+ '3' => 'Travel and entertainment and banking/financial',
30
+ '4' => 'Banking and financial',
31
+ '5' => 'Banking and financial',
32
+ '6' => 'Merchandising and banking/financial',
33
+ '7' => 'Petroleum and other industry assignments',
34
+ '8' => 'Healthcare, telecommunications and other industry assignments',
35
+ '9' => 'National assignment'
36
+
37
+ }
38
+
39
+ def issuer_category
40
+ ISSUER_CATEGORIES[@number.to_s[0]]
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ CreditCardValidations.add_brand(
2
+ :diners_us,
3
+ { length: 16, prefixes: %w(54 55) },
4
+ brand_name: 'Diners Club US'
5
+ )
6
+
@@ -0,0 +1,5 @@
1
+ CreditCardValidations.add_brand(
2
+ :en_route,
3
+ { length: 15, prefixes: %w(2014 2149) },
4
+ skip_luhn: true
5
+ )
@@ -0,0 +1,4 @@
1
+ CreditCardValidations.add_brand(
2
+ :laser,
3
+ length: [16, 17, 18, 19], prefixes: %w(6304 6706 6771)
4
+ )
@@ -0,0 +1,22 @@
1
+ # String extension for brand detecting and number validation
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)
8
+ #
9
+ class String
10
+ def credit_card_brand
11
+ CreditCardValidations::Detector.new(self).brand
12
+ end
13
+
14
+ def valid_credit_card_brand?(*brands)
15
+ CreditCardValidations::Detector.new(self).valid?(*brands)
16
+ end
17
+
18
+ def credit_card_brand_name
19
+ CreditCardValidations::Detector.new(self).brand_name
20
+ end
21
+
22
+ end
@@ -0,0 +1,3 @@
1
+ module CreditCardValidations
2
+ VERSION = '3.4.0'
3
+ end
@@ -0,0 +1,409 @@
1
+ ---
2
+ :visa:
3
+ :rules:
4
+ - :length:
5
+ - 13
6
+ - 16
7
+ - 19
8
+ :prefixes:
9
+ - '4'
10
+ :options:
11
+ :brand_name: Visa
12
+ :mastercard:
13
+ :rules:
14
+ - :length:
15
+ - 16
16
+ :prefixes:
17
+ - '2221'
18
+ - '2222'
19
+ - '2223'
20
+ - '2224'
21
+ - '2225'
22
+ - '2226'
23
+ - '2227'
24
+ - '2228'
25
+ - '2229'
26
+ - '223'
27
+ - '224'
28
+ - '225'
29
+ - '226'
30
+ - '227'
31
+ - '228'
32
+ - '229'
33
+ - '23'
34
+ - '24'
35
+ - '25'
36
+ - '26'
37
+ - '271'
38
+ - '2720'
39
+ - '51'
40
+ - '52'
41
+ - '53'
42
+ - '54'
43
+ - '55'
44
+ :options:
45
+ :brand_name: MasterCard
46
+ :amex:
47
+ :rules:
48
+ - :length:
49
+ - 15
50
+ :prefixes:
51
+ - '34'
52
+ - '37'
53
+ :options:
54
+ :brand_name: American Express
55
+ :diners:
56
+ :rules:
57
+ - :length:
58
+ - 14
59
+ :prefixes:
60
+ - '300'
61
+ - '301'
62
+ - '302'
63
+ - '303'
64
+ - '304'
65
+ - '305'
66
+ - '36'
67
+ - '38'
68
+ :options:
69
+ :brand_name: Diners Club
70
+ :jcb:
71
+ :rules:
72
+ - :length:
73
+ - 15
74
+ - 16
75
+ :prefixes:
76
+ - '3528'
77
+ - '3529'
78
+ - '353'
79
+ - '354'
80
+ - '355'
81
+ - '356'
82
+ - '357'
83
+ - '358'
84
+ - :length:
85
+ - 15
86
+ :prefixes:
87
+ - '1800'
88
+ - '2131'
89
+ - :length:
90
+ - 19
91
+ :prefixes:
92
+ - '357266'
93
+ :options:
94
+ :brand_name: JCB
95
+ :solo:
96
+ :rules:
97
+ - :length:
98
+ - 16
99
+ - 18
100
+ - 19
101
+ :prefixes:
102
+ - '6334'
103
+ - '6767'
104
+ :switch:
105
+ :rules:
106
+ - :length:
107
+ - 16
108
+ - 18
109
+ - 19
110
+ :prefixes:
111
+ - '633110'
112
+ - '633312'
113
+ - '633304'
114
+ - '633303'
115
+ - '633301'
116
+ - '633300'
117
+ :maestro:
118
+ :rules:
119
+ - :length:
120
+ - 12
121
+ - 13
122
+ - 14
123
+ - 15
124
+ - 16
125
+ - 17
126
+ - 18
127
+ - 19
128
+ :prefixes:
129
+ - '500'
130
+ - '5010'
131
+ - '5011'
132
+ - '5012'
133
+ - '5013'
134
+ - '5014'
135
+ - '5015'
136
+ - '5016'
137
+ - '5017'
138
+ - '5018'
139
+ - '502'
140
+ - '503'
141
+ - '504'
142
+ - '505'
143
+ - '506'
144
+ - '507'
145
+ - '508'
146
+ - '509'
147
+ - '56'
148
+ - '57'
149
+ - '58'
150
+ - '59'
151
+ - '6010'
152
+ - '6012'
153
+ - '6013'
154
+ - '6014'
155
+ - '6015'
156
+ - '6016'
157
+ - '6017'
158
+ - '6018'
159
+ - '6019'
160
+ - '602'
161
+ - '603'
162
+ - '604'
163
+ - '605'
164
+ - '6060'
165
+ - '621'
166
+ - '627'
167
+ - '629'
168
+ - '6304'
169
+ - '639'
170
+ - '670'
171
+ - '671'
172
+ - '672'
173
+ - '673'
174
+ - '674'
175
+ - '675'
176
+ - '677'
177
+ - '6760'
178
+ - '6761'
179
+ - '6762'
180
+ - '6763'
181
+ - '6764'
182
+ - '6765'
183
+ - '6766'
184
+ - '6768'
185
+ - '6769'
186
+ - '6771'
187
+ - '679'
188
+ :unionpay:
189
+ :rules:
190
+ - :length:
191
+ - 16
192
+ - 17
193
+ - 18
194
+ - 19
195
+ :prefixes:
196
+ - '622'
197
+ - '624'
198
+ - '625'
199
+ - '626'
200
+ - '628'
201
+ :options:
202
+ :skip_luhn: true
203
+ :brand_name: China UnionPay
204
+ :dankort:
205
+ :rules:
206
+ - :length:
207
+ - 16
208
+ :prefixes:
209
+ - '5019'
210
+ :rupay:
211
+ :rules:
212
+ - :length:
213
+ - 16
214
+ :prefixes:
215
+ - '6061'
216
+ - '6062'
217
+ - '6063'
218
+ - '6064'
219
+ - '6065'
220
+ - '6066'
221
+ - '6067'
222
+ - '6068'
223
+ - '6069'
224
+ - '607'
225
+ - '608'
226
+ :options:
227
+ :skip_luhn: true
228
+ :hipercard:
229
+ :rules:
230
+ - :length:
231
+ - 19
232
+ :prefixes:
233
+ - '384'
234
+ - '606282'
235
+ - '637095'
236
+ - '637568'
237
+ - '637599'
238
+ - '637609'
239
+ - '637612'
240
+ :elo:
241
+ :rules:
242
+ - :length:
243
+ - 16
244
+ :prefixes:
245
+ - '401178'
246
+ - '401179'
247
+ - '431274'
248
+ - '438935'
249
+ - '451416'
250
+ - '457393'
251
+ - '457631'
252
+ - '457632'
253
+ - '504175'
254
+ - '506699'
255
+ - '50670'
256
+ - '50671'
257
+ - '506717'
258
+ - '506718'
259
+ - '50672'
260
+ - '506720'
261
+ - '506721'
262
+ - '506724'
263
+ - '506725'
264
+ - '506726'
265
+ - '506727'
266
+ - '506728'
267
+ - '506729'
268
+ - '50673'
269
+ - '50674'
270
+ - '50675'
271
+ - '50676'
272
+ - '506770'
273
+ - '506771'
274
+ - '506772'
275
+ - '506773'
276
+ - '506774'
277
+ - '506775'
278
+ - '506776'
279
+ - '506777'
280
+ - '506778'
281
+ - '50900'
282
+ - '509013'
283
+ - '50902'
284
+ - '509031'
285
+ - '509033'
286
+ - '509035'
287
+ - '509036'
288
+ - '509037'
289
+ - '509038'
290
+ - '509039'
291
+ - '50904'
292
+ - '509050'
293
+ - '509051'
294
+ - '509052'
295
+ - '509053'
296
+ - '509064'
297
+ - '509066'
298
+ - '509067'
299
+ - '509068'
300
+ - '509069'
301
+ - '509072'
302
+ - '509074'
303
+ - '509076'
304
+ - '509077'
305
+ - '509078'
306
+ - '509079'
307
+ - '509080'
308
+ - '509081'
309
+ - '509082'
310
+ - '509083'
311
+ - '509085'
312
+ - '636297'
313
+ - '636368'
314
+ - '650031'
315
+ - '650032'
316
+ - '650033'
317
+ - '650035'
318
+ - '650036'
319
+ - '650037'
320
+ - '650038'
321
+ - '650039'
322
+ - '65004'
323
+ - '650050'
324
+ - '650051'
325
+ - '650405'
326
+ - '650406'
327
+ - '650407'
328
+ - '650408'
329
+ - '650409'
330
+ - '65041'
331
+ - '65042'
332
+ - '65043'
333
+ - '650485'
334
+ - '650486'
335
+ - '650487'
336
+ - '650488'
337
+ - '650489'
338
+ - '65049'
339
+ - '65050'
340
+ - '65051'
341
+ - '65052'
342
+ - '650530'
343
+ - '650531'
344
+ - '650532'
345
+ - '650533'
346
+ - '650534'
347
+ - '650535'
348
+ - '650536'
349
+ - '650537'
350
+ - '650538'
351
+ - '650541'
352
+ - '650700'
353
+ - '650720'
354
+ - '650901'
355
+ - '650921'
356
+ - '650922'
357
+ - '650923'
358
+ - '650924'
359
+ - '650925'
360
+ - '650926'
361
+ - '650927'
362
+ - '650928'
363
+ - '650929'
364
+ - '65093'
365
+ - '65094'
366
+ - '65095'
367
+ - '65096'
368
+ - '650970'
369
+ - '650971'
370
+ - '650972'
371
+ - '650973'
372
+ - '650974'
373
+ - '650975'
374
+ - '650976'
375
+ - '650977'
376
+ - '650978'
377
+ - '651652'
378
+ - '65500'
379
+ - '65501'
380
+ - '655021'
381
+ - '65503'
382
+ - '65504'
383
+ :options:
384
+ :skip_luhn: true
385
+ :brand_name: Elo
386
+ :mir:
387
+ :rules:
388
+ - :length:
389
+ - 16
390
+ :prefixes:
391
+ - '2200'
392
+ - '2201'
393
+ - '2202'
394
+ - '2203'
395
+ - '2204'
396
+ :discover:
397
+ :rules:
398
+ - :length:
399
+ - 16
400
+ - 19
401
+ :prefixes:
402
+ - '6011'
403
+ - '644'
404
+ - '645'
405
+ - '646'
406
+ - '647'
407
+ - '648'
408
+ - '649'
409
+ - '65'