credit_card_validations 1.4.7 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +52 -34
- data/lib/active_model/credit_card_number_validator.rb +1 -1
- data/lib/credit_card_validations.rb +10 -9
- data/lib/credit_card_validations/card_rules.rb +13 -12
- data/lib/credit_card_validations/detector.rb +9 -8
- data/lib/credit_card_validations/mmi.rb +17 -15
- data/lib/credit_card_validations/string.rb +8 -5
- data/lib/credit_card_validations/version.rb +1 -1
- data/test/credit_card_validations_test.rb +29 -29
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cc121a408a642f75dfcfc595f63c58299895dfc
|
4
|
+
data.tar.gz: c30600f8e3925bce9cd79c1e30d0cb954696882f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b2c71d6eeef539a4adc6eb3aa3d971a5f0db812f2bf396de7e1d82be082243c657f43b5dcf4221b2d1e5e856dca2414160c567bedb5b651e8f63e15ff1d1f0e
|
7
|
+
data.tar.gz: d9979cffa4dec73c7f01b26c80ce654ede26fc4c4aa1458e932282867913d78d5685dbc74594096e8d7e67beca97849f4c068e99773bb2cb076388792d7f5c62
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Gem adds validator to check whether or not a given number actually falls within the ranges of possible numbers prior to performing such verification, and, as such, CreditCardValidations simply verifies that the credit card number provided is well-formed.
|
4
4
|
|
5
|
+
More info about card BIN numbers http://en.wikipedia.org/wiki/Bank_card_number
|
6
|
+
|
5
7
|
[![Build Status](https://travis-ci.org/Fivell/credit_card_validations.png)](https://travis-ci.org/Fivell/credit_card_validations)
|
6
8
|
[![Coverage Status](https://coveralls.io/repos/Fivell/credit_card_validations/badge.png)](https://coveralls.io/r/Fivell/credit_card_validations)
|
7
9
|
|
@@ -24,33 +26,55 @@ Or install it yourself as:
|
|
24
26
|
|
25
27
|
The following issuing institutes are accepted:
|
26
28
|
|
27
|
-
name | key
|
28
|
-
|
29
|
-
American Express | :amex
|
30
|
-
China UnionPay | :unionpay
|
31
|
-
Dankrot | :dankrot
|
32
|
-
Diners Club | :diners
|
33
|
-
Dinner Club US | :diners_us
|
34
|
-
Discover | :discover
|
35
|
-
JCB | :jcb
|
36
|
-
Laser | :laser
|
37
|
-
Maestro | :maestro
|
38
|
-
MasterCard | :mastercard
|
39
|
-
|
40
|
-
|
29
|
+
name | key |
|
30
|
+
-------------------------------------------------------------------
|
31
|
+
American Express | :amex | http://en.wikipedia.org/wiki/American_Express
|
32
|
+
China UnionPay | :unionpay | http://en.wikipedia.org/wiki/China_UnionPay
|
33
|
+
Dankrot | :dankrot | http://en.wikipedia.org/wiki/Dankort
|
34
|
+
Diners Club | :diners | http://en.wikipedia.org/wiki/Diners_Club_International
|
35
|
+
Dinner Club US | :diners_us | http://en.wikipedia.org/wiki/Diners_Club_International#MasterCard_alliance
|
36
|
+
Discover | :discover | http://en.wikipedia.org/wiki/Discover_Card
|
37
|
+
JCB | :jcb | http://en.wikipedia.org/wiki/Japan_Credit_Bureau
|
38
|
+
Laser | :laser | http://en.wikipedia.org/wiki/Laser_%28debit_card%29
|
39
|
+
Maestro | :maestro | http://en.wikipedia.org/wiki/Maestro_%28debit_card%29
|
40
|
+
MasterCard | :mastercard | http://en.wikipedia.org/wiki/MasterCard
|
41
|
+
Rupay | :rupay | http://en.wikipedia.org/wiki/RuPay
|
42
|
+
Solo | :solo | http://en.wikipedia.org/wiki/Solo_(debit_card)
|
43
|
+
Switch | :switch | http://en.wikipedia.org/wiki/Switch_(debit_card)
|
44
|
+
Visa | :visa | http://en.wikipedia.org/wiki/Visa_Inc.
|
41
45
|
|
42
46
|
|
43
47
|
|
44
48
|
Examples using string monkey patch
|
45
49
|
|
50
|
+
```ruby
|
46
51
|
require 'credit_card_validations/string'
|
47
52
|
'5274 5763 9425 9961'.credit_card_brand
|
48
53
|
'5274 5763 9425 9961'.valid_credit_card_brand?(:mastercard, :visa)
|
49
54
|
'5274 5763 9425 9961'.valid_credit_card_brand?(:amex)
|
55
|
+
```
|
56
|
+
|
57
|
+
ActiveModel support
|
58
|
+
|
59
|
+
only for certain brads
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class CreditCardModel
|
63
|
+
attr_accessor :number
|
64
|
+
include ActiveModel::Validations
|
65
|
+
validates :number, credit_card_number: {brands: [:amex, :maestro]}
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
for all known brands
|
50
70
|
|
71
|
+
```ruby
|
72
|
+
validates :number, presence: true, credit_card_number: true
|
73
|
+
```
|
51
74
|
|
52
75
|
Examples using CreditCardValidations::Detector class
|
53
76
|
|
77
|
+
```ruby
|
54
78
|
number = "4111111111111111"
|
55
79
|
detector = CreditCardValidations::Detector.new(number)
|
56
80
|
detector.brand #:visa
|
@@ -58,38 +82,32 @@ Examples using CreditCardValidations::Detector class
|
|
58
82
|
detector.valid?(:mastercard,:maestro) #false
|
59
83
|
detector.valid?(:visa, :mastercard) #true
|
60
84
|
detector.issuer_category #"Banking and financial"
|
85
|
+
```
|
61
86
|
|
62
87
|
Also You can add your own rules to detect other credit card brands/types
|
63
88
|
passing name,length(integer/array of integers) and prefix(string/array of strings)
|
64
89
|
Example
|
65
90
|
|
66
|
-
|
91
|
+
```ruby
|
92
|
+
CreditCardValidations.add_rule(:voyager, {length: 15, prefixes: '86'})
|
93
|
+
CreditCardValidations.add_rule(:en_route, {length: 15, prefixes: ['2014', '2149'], skip_luhn: true}) #skip luhn
|
94
|
+
|
67
95
|
voyager_test_card_number = '869926275400212'
|
68
96
|
CreditCardValidations::Detector.new(voyager_test_card_number).brand #:voyager
|
69
97
|
CreditCardValidations::Detector.new(voyager_test_card_number).voyager? #true
|
70
|
-
|
71
|
-
|
98
|
+
|
99
|
+
en_route_test_card_number = '2014-0000-0000-001'
|
100
|
+
CreditCardValidations::Detector.new(en_route_test_card_number).brand #:en_route
|
101
|
+
CreditCardValidations::Detector.new(en_route_test_card_number).en_route? #true
|
102
|
+
```
|
72
103
|
|
73
104
|
Check luhn
|
74
105
|
|
106
|
+
```ruby
|
75
107
|
CreditCardValidations::Detector.new(@credit_card_number).valid_luhn?
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
only for certain brads
|
81
|
-
|
82
|
-
class CreditCardModel
|
83
|
-
attr_accessor :number
|
84
|
-
include ActiveModel::Validations
|
85
|
-
validates :number, credit_card_number: {brands: [:amex, :maestro]}
|
86
|
-
end
|
87
|
-
|
88
|
-
for all known brands
|
89
|
-
|
90
|
-
validates :number, presence: true, credit_card_number: true
|
91
|
-
|
92
|
-
|
108
|
+
#or
|
109
|
+
CreditCardValidations::Luhn.valid?(@credit_card_number)
|
110
|
+
```
|
93
111
|
|
94
112
|
|
95
113
|
## Contributing
|
@@ -4,7 +4,7 @@ module ActiveModel
|
|
4
4
|
|
5
5
|
def validate_each(record, attribute, value)
|
6
6
|
brands = options.fetch(:brands, :any)
|
7
|
-
record.errors.add(attribute) unless credit_card_valid?(value, brands == :any ? [] : Array.wrap(brands)
|
7
|
+
record.errors.add(attribute) unless credit_card_valid?(value, brands == :any ? [] : Array.wrap(brands))
|
8
8
|
end
|
9
9
|
|
10
10
|
def credit_card_valid?(number, brands = [])
|
@@ -11,16 +11,17 @@ module CreditCardValidations
|
|
11
11
|
autoload :CardRules , 'credit_card_validations/card_rules'
|
12
12
|
autoload :Detector , 'credit_card_validations/detector'
|
13
13
|
autoload :Mmi, 'credit_card_validations/mmi'
|
14
|
+
|
15
|
+
def self.add_rule(name, options)
|
16
|
+
CreditCardValidations::Detector.add_rule(name, options.fetch(:length), options.fetch(:prefixes), options.fetch(:skip_luhn, false))
|
17
|
+
end
|
18
|
+
|
19
|
+
CardRules.rules.each do |name, rules|
|
20
|
+
rules.each do |rule_value|
|
21
|
+
add_rule(name, rule_value)
|
22
|
+
end
|
23
|
+
end
|
14
24
|
end
|
15
25
|
|
16
26
|
|
17
|
-
CreditCardValidations::CardRules.rules.each do |name, rules|
|
18
|
-
rules.each do |rule_value|
|
19
|
-
CreditCardValidations::Detector.add_rule(name,
|
20
|
-
rule_value[:length],
|
21
|
-
rule_value[:prefixes],
|
22
|
-
rule_value[:skip_validation])
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
27
|
|
@@ -10,11 +10,11 @@ module CreditCardValidations
|
|
10
10
|
mastercard: [
|
11
11
|
{length: [16], prefixes: ['51', '52', '53', '54', '55']}
|
12
12
|
],
|
13
|
-
|
13
|
+
|
14
14
|
amex: [
|
15
15
|
{length: [15], prefixes: ['34', '37']}
|
16
16
|
],
|
17
|
-
|
17
|
+
######## other brands ########
|
18
18
|
diners: [
|
19
19
|
{length: [14], prefixes: ['300', '301', '302', '303', '304', '305', '36', '38']},
|
20
20
|
],
|
@@ -43,12 +43,12 @@ module CreditCardValidations
|
|
43
43
|
solo: [
|
44
44
|
{length: [16, 18, 19], prefixes: ['6334', '6767']}
|
45
45
|
],
|
46
|
-
|
46
|
+
|
47
47
|
switch: [
|
48
|
-
|
49
|
-
|
48
|
+
{length: [16, 18, 19], prefixes: ['633110', '633312', '633304', '633303', '633301', '633300']}
|
49
|
+
|
50
50
|
],
|
51
|
-
|
51
|
+
|
52
52
|
maestro: [
|
53
53
|
{length: [12, 13, 14, 15, 16, 17, 18, 19], prefixes: ['5010', '5011', '5012', '5013', '5014', '5015', '5016', '5017', '5018',
|
54
54
|
'502', '503', '504', '505', '506', '507', '508',
|
@@ -56,20 +56,21 @@ module CreditCardValidations
|
|
56
56
|
'602', '603', '604', '605', '6060',
|
57
57
|
'621', '627', '629',
|
58
58
|
'677', '675', '674', '673', '672', '671', '670',
|
59
|
-
'6760', '6761', '6762', '6763', '6764', '6765', '6766', '6768', '6769'
|
60
|
-
]}
|
59
|
+
'6760', '6761', '6762', '6763', '6764', '6765', '6766', '6768', '6769']}
|
61
60
|
],
|
62
|
-
|
61
|
+
|
63
62
|
# Luhn validation are skipped for union pay cards because they have unknown generation algoritm
|
64
63
|
unionpay: [
|
65
|
-
{length: [16, 17, 18, 19], prefixes: ['622', '624', '625', '626', '628'],
|
64
|
+
{length: [16, 17, 18, 19], prefixes: ['622', '624', '625', '626', '628'], skip_luhn: true}
|
66
65
|
],
|
67
66
|
|
68
67
|
dankrot: [
|
69
68
|
{length: [16], prefixes: ['5019']}
|
69
|
+
],
|
70
|
+
|
71
|
+
rupay: [
|
72
|
+
{length: [16], prefixes: ['6061', '6062', '6063', '6064', '6065', '6066', '6067', '6068', '6069', '607', '608'], skip_luhn: true}
|
70
73
|
]
|
71
|
-
|
72
|
-
|
73
74
|
|
74
75
|
}
|
75
76
|
end
|
@@ -9,26 +9,26 @@ module CreditCardValidations
|
|
9
9
|
attr_reader :number
|
10
10
|
|
11
11
|
def initialize(number)
|
12
|
-
@number = number.to_s.tr('- ','')
|
12
|
+
@number = number.to_s.tr('- ', '')
|
13
13
|
end
|
14
14
|
|
15
15
|
# credit card number
|
16
16
|
def valid?(*brands)
|
17
|
-
|
17
|
+
!!valid_number?(*brands)
|
18
18
|
end
|
19
19
|
|
20
20
|
#brand name
|
21
21
|
def brand(*brands)
|
22
|
-
|
22
|
+
valid_number?(*brands)
|
23
23
|
end
|
24
24
|
|
25
25
|
def valid_number?(*brands)
|
26
26
|
number_length = number.length
|
27
|
-
brand_rules = brands.blank? ? self.rules : self.rules.slice(*brands.map{ |el| el.downcase })
|
27
|
+
brand_rules = brands.blank? ? self.rules : self.rules.slice(*brands.map { |el| el.downcase })
|
28
28
|
unless brand_rules.blank?
|
29
29
|
brand_rules.each do |brand_name, rules|
|
30
30
|
rules.each do |rule|
|
31
|
-
return brand_name if (
|
31
|
+
return brand_name if ((rule[:skip_luhn] || valid_luhn?) and rule[:length].include?(number_length) and number.match(rule[:regexp]))
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -44,21 +44,22 @@ module CreditCardValidations
|
|
44
44
|
|
45
45
|
#create regexp by array of prefixes
|
46
46
|
def compile_regexp(prefixes)
|
47
|
-
|
47
|
+
Regexp.new("^((#{prefixes.join(")|(")}))")
|
48
48
|
end
|
49
49
|
|
50
50
|
#create rule for detecting brand
|
51
|
-
def add_rule(brand, length, prefixes,
|
51
|
+
def add_rule(brand, length, prefixes, skip_luhn = false)
|
52
52
|
prefixes = Array.wrap(prefixes)
|
53
53
|
length = Array.wrap(length)
|
54
54
|
rules[brand] = [] if rules[brand].blank?
|
55
|
-
rules[brand] << {length: length,
|
55
|
+
rules[brand] << {length: length, regexp: compile_regexp(prefixes), prefixes: prefixes, skip_luhn: skip_luhn}
|
56
56
|
#create methods like visa? mastercard? etc
|
57
57
|
class_eval <<-BOOLEAN_RULE, __FILE__, __LINE__
|
58
58
|
def #{brand}?
|
59
59
|
valid?(:#{brand})
|
60
60
|
end
|
61
61
|
BOOLEAN_RULE
|
62
|
+
rules[brand]
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
@@ -14,23 +14,25 @@
|
|
14
14
|
#For example, American Express, Diner's Club, Carte Blanche, and JCB are in the travel and entertainment category; VISA, MasterCard, and Discover are in the banking and financial category (Discover being in the Merchandising and banking/financial category); and Sun Oil and Exxon are in the petroleum category.
|
15
15
|
|
16
16
|
|
17
|
-
module CreditCardValidations
|
17
|
+
module CreditCardValidations
|
18
|
+
module Mmi
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
ISSUER_CATEGORIES = {
|
21
|
+
'0' => 'ISO/TC 68 nd other industry assignments',
|
22
|
+
'1' => 'Airlines',
|
23
|
+
'2' => 'Airlines and other industry assignments',
|
24
|
+
'3' => 'Travel and entertainment and banking/financial',
|
25
|
+
'4' => 'Banking and financial',
|
26
|
+
'5' => 'Banking and financial',
|
27
|
+
'6' => 'Merchandising and banking/financial',
|
28
|
+
'7' => 'Petroleum and other industry assignments',
|
29
|
+
'8' => 'Healthcare, telecommunications and other industry assignments',
|
30
|
+
'9' => 'National assignment'
|
30
31
|
|
31
|
-
|
32
|
+
}
|
32
33
|
|
33
|
-
|
34
|
-
|
34
|
+
def issuer_category
|
35
|
+
ISSUER_CATEGORIES[@number.to_s[0]]
|
36
|
+
end
|
35
37
|
end
|
36
38
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
#
|
1
4
|
class String
|
2
5
|
def credit_card_brand
|
3
|
-
|
4
|
-
end
|
5
|
-
|
6
|
+
CreditCardValidations::Detector.new(self).brand
|
7
|
+
end
|
8
|
+
|
6
9
|
def valid_credit_card_brand?(*brands)
|
7
|
-
|
8
|
-
end
|
10
|
+
CreditCardValidations::Detector.new(self).valid?(*brands)
|
11
|
+
end
|
9
12
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
|
4
3
|
class CreditCardValidationsTest < MiniTest::Test
|
5
4
|
|
6
5
|
class CreditCardModel
|
@@ -18,20 +17,21 @@ class CreditCardValidationsTest < MiniTest::Test
|
|
18
17
|
def initialize name
|
19
18
|
super name
|
20
19
|
@test_numbers = {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
20
|
+
visa: ['4012 8888 8888 1881', '4111111111111111'],
|
21
|
+
mastercard: ['5274 5763 9425 9961', '5555-5555-5555-4444'],
|
22
|
+
diners: ['3020 4169 3226 43', '30569309025904'],
|
23
|
+
amex: ['3782 8224 6310 005', '371449635398431'],
|
24
|
+
discover: ['6011 1111 1111 1117', '6011000990139424'],
|
25
|
+
maestro: ['6759 6498 2643 8453'],
|
26
|
+
jcb: ['3575 7591 5225 4876', '3566002020360505'],
|
27
|
+
solo: ['6767 6222 2222 2222 222'],
|
28
|
+
unionpay: ['6264-1852-1292-2132-067', '6288997715452584', '6269 9920 5813 4322'],
|
29
|
+
dankrot: ['5019717010103742'],
|
30
|
+
switch: ['6331101999990016'],
|
31
|
+
rupay: ['6076-6000-0619-9992']
|
32
32
|
}
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def test_card_brand_detection
|
36
36
|
@test_numbers.each do |key, value|
|
37
37
|
value.each do |card_number|
|
@@ -40,21 +40,21 @@ class CreditCardValidationsTest < MiniTest::Test
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
def test_card_brand_is_nil_if_credit_card_invalid
|
45
45
|
assert_nil detector('1111111111111111').brand
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
def test_card_brand_detection_with_restriction
|
49
49
|
@test_numbers.slice(:visa, :mastercard).each do |key, value|
|
50
50
|
assert_equal key, detector(value.first).brand(:visa, :mastercard)
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
@test_numbers.except(:visa, :mastercard).each do |key, value|
|
54
54
|
assert_nil detector(value.first).brand(:visa, :mastercard)
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def test_card_valid_method
|
59
59
|
@test_numbers.each do |key, value|
|
60
60
|
value.each do |card_number|
|
@@ -90,14 +90,14 @@ class CreditCardValidationsTest < MiniTest::Test
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def test_active_model_validator
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
cc = CreditCardModel.new
|
94
|
+
cc.number = @test_numbers[:maestro].first
|
95
|
+
assert cc.valid?
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
cc = CreditCardModel.new
|
98
|
+
cc.number = @test_numbers[:mastercard].first
|
99
|
+
assert !cc.valid?
|
100
|
+
assert cc.errors[:number].include?(cc.errors.generate_message(:number, :invalid))
|
101
101
|
|
102
102
|
|
103
103
|
cc = CreditCardModelAny.new
|
@@ -110,13 +110,13 @@ class CreditCardValidationsTest < MiniTest::Test
|
|
110
110
|
|
111
111
|
def test_string_extension
|
112
112
|
require 'credit_card_validations/string'
|
113
|
-
assert_equal
|
114
|
-
assert
|
113
|
+
assert_equal @test_numbers[:mastercard].first.credit_card_brand, :mastercard
|
114
|
+
assert @test_numbers[:mastercard].first.valid_credit_card_brand?(:mastercard)
|
115
115
|
assert !@test_numbers[:mastercard].first.valid_credit_card_brand?(:visa, :amex)
|
116
116
|
end
|
117
117
|
|
118
118
|
|
119
|
-
def
|
119
|
+
def test_skip_luhn
|
120
120
|
d = detector(@test_numbers[:unionpay].first)
|
121
121
|
|
122
122
|
d.expects(:valid_luhn?).never
|
@@ -133,8 +133,8 @@ class CreditCardValidationsTest < MiniTest::Test
|
|
133
133
|
end
|
134
134
|
|
135
135
|
def test_mmi
|
136
|
-
|
137
|
-
|
136
|
+
d = detector(@test_numbers[:visa])
|
137
|
+
assert_equal d.issuer_category, CreditCardValidations::Mmi::ISSUER_CATEGORIES[@test_numbers[:visa][0]]
|
138
138
|
end
|
139
139
|
|
140
140
|
protected
|