cw_credit_card_validations 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.hound.yml +2 -0
- data/.travis.yml +26 -0
- data/Changelog.md +115 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +162 -0
- data/Rakefile +13 -0
- data/credit_card_validations.gemspec +29 -0
- data/gemfiles/rails1-4.gemfile +11 -0
- data/gemfiles/rails5.gemfile +10 -0
- data/lib/active_model/credit_card_number_validator.rb +81 -0
- data/lib/credit_card_validations.rb +36 -0
- data/lib/credit_card_validations/detector.rb +148 -0
- data/lib/credit_card_validations/error.rb +4 -0
- data/lib/credit_card_validations/factory.rb +64 -0
- data/lib/credit_card_validations/luhn.rb +22 -0
- data/lib/credit_card_validations/mmi.rb +43 -0
- data/lib/credit_card_validations/plugins/diners_us.rb +6 -0
- data/lib/credit_card_validations/plugins/en_route.rb +5 -0
- data/lib/credit_card_validations/plugins/laser.rb +4 -0
- data/lib/credit_card_validations/string.rb +22 -0
- data/lib/credit_card_validations/version.rb +3 -0
- data/lib/data/brands.yaml +409 -0
- data/spec/active_model_spec.rb +70 -0
- data/spec/credit_card_validations_spec.rb +175 -0
- data/spec/factory_spec.rb +18 -0
- data/spec/fixtures/invalid_cards.yml +6 -0
- data/spec/fixtures/valid_cards.yml +119 -0
- data/spec/models/credit_card.rb +19 -0
- data/spec/string_spec.rb +32 -0
- data/spec/test_helper.rb +20 -0
- metadata +153 -0
@@ -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,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,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,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'
|