cw_credit_card_validations 3.4.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.
@@ -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'