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,70 @@
|
|
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
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'Any Brand' do
|
19
|
+
it 'should be valid for all prepared valid numbers' do
|
20
|
+
VALID_NUMBERS.each do |_, numbers|
|
21
|
+
numbers.each do |number|
|
22
|
+
card = model
|
23
|
+
card.number4 = number
|
24
|
+
card.number5 = number
|
25
|
+
card.valid?.must_equal true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'Except Amex and Maestro brand' do
|
32
|
+
it 'should reject all other valid numbers' do
|
33
|
+
VALID_NUMBERS.except(:amex, :maestro).each do |_, numbers|
|
34
|
+
card = model
|
35
|
+
card.number = numbers.first
|
36
|
+
card.valid?.must_equal false
|
37
|
+
card.errors[:number].include?(card.errors.generate_message(:number, :invalid)).must_equal true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should accept using except options' do
|
42
|
+
VALID_NUMBERS.except(:amex, :maestro).each do |_, numbers|
|
43
|
+
card = model
|
44
|
+
card.number3 = numbers.first
|
45
|
+
card.valid?.must_equal true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'Only Amex and Mestro brands' do
|
51
|
+
it 'should accept amex and maestro brand if valid' do
|
52
|
+
VALID_NUMBERS.slice(:amex, :maestro).each do |_, numbers|
|
53
|
+
card = model
|
54
|
+
card.number = numbers.first
|
55
|
+
card.number2 = numbers.first
|
56
|
+
card.valid?.must_equal true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'Custom error message' do
|
62
|
+
it 'should allow custom message' do
|
63
|
+
card = model
|
64
|
+
card.number7 = 'wrong'
|
65
|
+
card.valid?.must_equal false
|
66
|
+
card.errors[:number7].must_equal ['Custom message']
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe CreditCardValidations do
|
4
|
+
|
5
|
+
|
6
|
+
before do
|
7
|
+
CreditCardValidations.reload!
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'MMI' do
|
11
|
+
it 'should detect issuer category' do
|
12
|
+
d = detector(VALID_NUMBERS[:visa].first)
|
13
|
+
d.issuer_category.must_equal CreditCardValidations::Mmi::ISSUER_CATEGORIES[d.number[0]]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'Luhn#valid?' do
|
18
|
+
let(:card_detector) {
|
19
|
+
detector(VALID_NUMBERS[:unionpay].first)
|
20
|
+
}
|
21
|
+
it 'should call Luhn.valid? once' do
|
22
|
+
CreditCardValidations::Luhn.expects(:valid?).with(card_detector.number).once
|
23
|
+
card_detector.valid?(:visa, :unionpay).must_equal true
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should call Luhn.valid? twice' do
|
27
|
+
CreditCardValidations::Luhn.expects(:valid?).with(card_detector.number).twice
|
28
|
+
card_detector.valid?(:visa, :mastercard).must_equal false
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should not call Luhn.valid?' do
|
32
|
+
CreditCardValidations::Luhn.expects(:valid?).never
|
33
|
+
card_detector.valid?(:unionpay).must_equal true
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
it 'should check luhn' do
|
40
|
+
VALID_NUMBERS.each do |brand, card_numbers|
|
41
|
+
if has_luhn_check_rule?(brand)
|
42
|
+
card_numbers.each do |number|
|
43
|
+
luhn_valid?(detector(number).number).must_equal true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should check valid brand' do
|
50
|
+
VALID_NUMBERS.each do |brand, card_numbers|
|
51
|
+
card_numbers.each do |card_number|
|
52
|
+
detector(card_number).send("#{brand}?").must_equal true
|
53
|
+
detector(card_number).brand.must_equal brand
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should check if card invalid' do
|
59
|
+
INVALID_NUMBERS.each do |card_number|
|
60
|
+
detector(card_number).valid?.must_equal false
|
61
|
+
detector(card_number).brand.must_be_nil
|
62
|
+
VALID_NUMBERS.keys.each do |brand|
|
63
|
+
detector(card_number).send("#{brand}?").must_equal false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should detect by full brand name' do
|
69
|
+
amex = CreditCardValidations::Factory.random(:amex)
|
70
|
+
detector(amex).valid?('American Express').must_equal true
|
71
|
+
visa = CreditCardValidations::Factory.random(:visa)
|
72
|
+
detector(visa).valid?('American Express').must_equal false
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should support multiple brands for single check' do
|
76
|
+
VALID_NUMBERS.slice(:visa, :mastercard).each do |key, value|
|
77
|
+
detector(value.first).brand(:visa, :mastercard).must_equal key
|
78
|
+
end
|
79
|
+
|
80
|
+
VALID_NUMBERS.except(:visa, :mastercard).each do |_, value|
|
81
|
+
detector(value.first).brand(:visa, :mastercard).must_be_nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should check if valid brand without arguments' do
|
86
|
+
VALID_NUMBERS.each do |key, value|
|
87
|
+
value.each do |card_number|
|
88
|
+
detector(card_number).valid?(key).must_equal true
|
89
|
+
assert detector(card_number).valid?.must_equal true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should not be valid? if wrong brand' do
|
95
|
+
detector(VALID_NUMBERS[:visa].first).valid?(:mastercard).must_equal false
|
96
|
+
detector(VALID_NUMBERS[:mastercard].first).valid?(:visa).must_equal false
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should be valid? if right brand' do
|
100
|
+
detector(VALID_NUMBERS[:visa].first).valid?(:mastercard, :visa).must_equal true
|
101
|
+
detector(VALID_NUMBERS[:visa].first).valid?(:mastercard, :amex).must_equal false
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
describe 'adding/removing brand' do
|
106
|
+
|
107
|
+
describe 'adding rules' do
|
108
|
+
|
109
|
+
let(:voyager_number) { '869926275400212' }
|
110
|
+
|
111
|
+
it 'should validate number as voyager' do
|
112
|
+
CreditCardValidations::Detector.add_brand(:voyager, length: 15, prefixes: '86')
|
113
|
+
detector(voyager_number).valid?(:voyager).must_equal true
|
114
|
+
detector(voyager_number).voyager?.must_equal true
|
115
|
+
detector(voyager_number).brand.must_equal :voyager
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'Add voyager rule' do
|
119
|
+
before do
|
120
|
+
CreditCardValidations::Detector.add_brand(:voyager, length: 15, prefixes: '86')
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should validate number as voyager' do
|
124
|
+
detector(voyager_number).valid?(:voyager).must_equal true
|
125
|
+
detector(voyager_number).voyager?.must_equal true
|
126
|
+
detector(voyager_number).brand.must_equal :voyager
|
127
|
+
end
|
128
|
+
|
129
|
+
describe 'Remove voyager rule' do
|
130
|
+
before do
|
131
|
+
CreditCardValidations::Detector.delete_brand(:voyager)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should not validate number as voyager' do
|
135
|
+
detector(voyager_number).respond_to?(:voyager?).must_equal false
|
136
|
+
detector(voyager_number).brand.must_be_nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'plugins' do
|
143
|
+
[:diners_us, :en_route, :laser].each do |brand|
|
144
|
+
it "should support #{brand}" do
|
145
|
+
-> { CreditCardValidations::Factory.random(brand) }.
|
146
|
+
must_raise(CreditCardValidations::Error)
|
147
|
+
custom_number = 'some_number'
|
148
|
+
detector(custom_number).respond_to?("#{brand}?").must_equal false
|
149
|
+
require "credit_card_validations/plugins/#{brand}"
|
150
|
+
number = CreditCardValidations::Factory.random(brand)
|
151
|
+
detector(number).valid?("#{brand}".to_sym).must_equal true
|
152
|
+
detector(custom_number).respond_to?("#{brand}?").must_equal true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should raise Error if no brand added before' do
|
158
|
+
-> { CreditCardValidations::Detector::add_rule(:undefined_brand, 20, [20]) }.
|
159
|
+
must_raise(CreditCardValidations::Error)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def luhn_valid?(number)
|
164
|
+
CreditCardValidations::Luhn.valid?(number)
|
165
|
+
end
|
166
|
+
|
167
|
+
def detector(number)
|
168
|
+
CreditCardValidations::Detector.new(number)
|
169
|
+
end
|
170
|
+
|
171
|
+
def has_luhn_check_rule?(key)
|
172
|
+
CreditCardValidations::Detector.has_luhn_check_rule?(key)
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe CreditCardValidations::Factory do
|
4
|
+
|
5
|
+
it 'should generate random brand' do
|
6
|
+
number = CreditCardValidations::Factory.random
|
7
|
+
CreditCardValidations::Detector.new(number).valid?.must_equal true
|
8
|
+
end
|
9
|
+
|
10
|
+
CreditCardValidations::Detector.brands.keys.sort.each do |key|
|
11
|
+
describe "#{key}" do
|
12
|
+
it "should generate valid #{key}" do
|
13
|
+
number = CreditCardValidations::Factory.random(key)
|
14
|
+
CreditCardValidations::Detector.new(number).valid?(key).must_equal true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
---
|
2
|
+
:visa:
|
3
|
+
- 4012 8888 8888 1881
|
4
|
+
- 4111 1111 1111 1111
|
5
|
+
- 4222 2222 2222 2
|
6
|
+
- 4917 6100 0000 0000
|
7
|
+
:mastercard:
|
8
|
+
- 5274 5763 9425 9961
|
9
|
+
- 5555 5555 5555 4444
|
10
|
+
- 5105 1051 0510 5100
|
11
|
+
- 2720 1700 0000 0006
|
12
|
+
- 2223 4800 0000 0001
|
13
|
+
- 2223 0400 0000 0003
|
14
|
+
- 2223 0700 0000 0000
|
15
|
+
- 2223 2700 0000 0006
|
16
|
+
- 2720 3500 0000 0004
|
17
|
+
- 2223 1000 0000 0005
|
18
|
+
- 2720 0500 0000 0000
|
19
|
+
:diners:
|
20
|
+
- 3020 4169 3226 43
|
21
|
+
- 3021 8047 1965 57
|
22
|
+
- 3022 1511 5632 52
|
23
|
+
- 3600 0000 0000 08
|
24
|
+
- 3614 8900 6479 13
|
25
|
+
- 3670 0102 0000 00
|
26
|
+
- 3852 0000 0232 37
|
27
|
+
- 3056 9309 0259 04
|
28
|
+
- 3020 4169 3226 43
|
29
|
+
:amex:
|
30
|
+
- 3714 4963 5398 431
|
31
|
+
- 3787 3449 3671 000
|
32
|
+
- 3400 0000 0000 009
|
33
|
+
- 3411 1111 1111 111
|
34
|
+
- 3434 3434 3434 343
|
35
|
+
- 3468 2763 0435 344
|
36
|
+
- 3700 0000 0000 002
|
37
|
+
- 3700 0020 0000 000
|
38
|
+
- 3704 0726 9909 809
|
39
|
+
- 3705 5601 9309 221
|
40
|
+
- 3714 4963 5398 431
|
41
|
+
- 3742 0000 0000 004
|
42
|
+
- 3764 6228 0921 451
|
43
|
+
- 3777 5274 9896 404
|
44
|
+
- 3782 8224 6310 005
|
45
|
+
:discover:
|
46
|
+
- 6011 1111 1111 1117
|
47
|
+
- 6011 0009 9013 9424
|
48
|
+
- 6011 0000 0000 0004
|
49
|
+
- 6011 0004 0000 0000
|
50
|
+
- 6011 1532 1637 1980
|
51
|
+
- 6011 6011 6011 6611
|
52
|
+
- 6011 6874 8256 4166
|
53
|
+
- 6011 8148 3690 5651
|
54
|
+
:maestro:
|
55
|
+
- 6759 6498 2643 8453
|
56
|
+
- 5641 8200 0000 0005
|
57
|
+
- 5033 9619 8909 17
|
58
|
+
- 5868 2416 0825 5333 38
|
59
|
+
- 6799 9901 0000 0000 019
|
60
|
+
- 6390 0200 0000 000003
|
61
|
+
- 6304 9506 0000 0000 00
|
62
|
+
- 6304 9000 1774 0292 441
|
63
|
+
:jcb:
|
64
|
+
- 3575 7591 5225 4876
|
65
|
+
- 3566 0020 2036 0505
|
66
|
+
- 1800 0163 8277 392
|
67
|
+
- 3569 9900 0000 0009
|
68
|
+
- 3530 1113 3330 0000
|
69
|
+
- 3572 6600 0000 0000 006
|
70
|
+
:solo:
|
71
|
+
- 6767 6222 2222 2222 222
|
72
|
+
- 6334 5805 0000 0000
|
73
|
+
- 6334 9000 0000 0005
|
74
|
+
- 6334 7306 0000 0000 00
|
75
|
+
- 6767 6767 6767 6767 671
|
76
|
+
:unionpay:
|
77
|
+
- 6264 1852 1292 2132 067
|
78
|
+
- 6288 9977 1545 2584
|
79
|
+
- 6269 9920 5813 4322
|
80
|
+
:dankort:
|
81
|
+
- 5019 7170 1010 3742
|
82
|
+
:switch:
|
83
|
+
- 6331 1019 9999 0016
|
84
|
+
:rupay:
|
85
|
+
- 6076 6000 0619 9992
|
86
|
+
- 6070 5500 5000 0047
|
87
|
+
:hipercard:
|
88
|
+
- 3841 0058 9908 8180 330
|
89
|
+
:elo:
|
90
|
+
- '4011 7841 3850 9070'
|
91
|
+
- '4389 3555 4728 7502'
|
92
|
+
- '4573 9320 5642 0699'
|
93
|
+
- '4576 3198 7564 9522'
|
94
|
+
- '5041 7572 2687 1008'
|
95
|
+
- '5066 9917 1703 9876'
|
96
|
+
- '5067 5391 6526 0388'
|
97
|
+
- '5090 4071 4078 7707'
|
98
|
+
- '5090 4270 1119 7080'
|
99
|
+
- '5090 4344 4788 5021'
|
100
|
+
- '5090 4514 6675 8555'
|
101
|
+
- '5090 4673 1437 9707'
|
102
|
+
- '5090 4791 2220 0924'
|
103
|
+
- '5090 4854 1570 5059'
|
104
|
+
- '5090 4968 1280 8652'
|
105
|
+
- '5090 5097 0678 9193'
|
106
|
+
- '5090 5157 9762 1083'
|
107
|
+
- '5090 5225 6947 6964'
|
108
|
+
- '5090 6473 8030 4070'
|
109
|
+
- '5090 6644 0324 2820'
|
110
|
+
- '5090 6724 8089 9317'
|
111
|
+
- '5090 6888 2173 4488'
|
112
|
+
- '5090 6948 3784 4758'
|
113
|
+
- '5090 7463 8779 6948'
|
114
|
+
- '6362 9745 2190 7765'
|
115
|
+
- '6363 6850 2897 2643'
|
116
|
+
- '6504 9799 9991 0279'
|
117
|
+
- '6516 5299 9991 0328'
|
118
|
+
:mir:
|
119
|
+
- 2202 1234 1234 1234
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreditCard
|
2
|
+
attr_accessor :number, :number2, :number3, :number4, :number5, :number6, :number7, :card_type
|
3
|
+
include ActiveModel::Validations
|
4
|
+
validates :number, credit_card_number: { brands: [:amex, :maestro] }, allow_blank: true
|
5
|
+
validates :number2, credit_card_number: { only: [:amex, :maestro] }, allow_blank: true
|
6
|
+
validates :number3, credit_card_number: { except: [:amex, :maestro] }, allow_blank: true
|
7
|
+
validates :number4, credit_card_number: { brands: :any }, allow_blank: true
|
8
|
+
validates :number5, credit_card_number: true, allow_blank: true
|
9
|
+
validates :number6, credit_card_number: { brands: ->(record) { record.supported_brand } }, allow_blank: true
|
10
|
+
validates :number7, credit_card_number: { message: 'Custom message' }, allow_blank: true
|
11
|
+
|
12
|
+
def supported_brand
|
13
|
+
{
|
14
|
+
'Master Card' => :mastercard,
|
15
|
+
'Visa' => :visa
|
16
|
+
}[self.card_type]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/spec/string_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'credit_card_validations/string'
|
3
|
+
|
4
|
+
describe 'String ext' do
|
5
|
+
|
6
|
+
let(:mastercard) { CreditCardValidations::Factory.random(:mastercard) }
|
7
|
+
let(:visa) { CreditCardValidations::Factory.random(:visa) }
|
8
|
+
let(:invalid) { INVALID_NUMBERS.sample }
|
9
|
+
|
10
|
+
it 'should allow detect brand for mastercard' do
|
11
|
+
mastercard.credit_card_brand.must_equal :mastercard
|
12
|
+
mastercard.credit_card_brand_name.must_equal 'MasterCard'
|
13
|
+
mastercard.valid_credit_card_brand?(:mastercard).must_equal true
|
14
|
+
mastercard.valid_credit_card_brand?('MasterCard').must_equal true
|
15
|
+
mastercard.valid_credit_card_brand?(:visa, :amex).must_equal false
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should allow detect brand for visa' do
|
19
|
+
visa.credit_card_brand.must_equal :visa
|
20
|
+
visa.credit_card_brand_name.must_equal 'Visa'
|
21
|
+
visa.valid_credit_card_brand?(:mastercard).must_equal false
|
22
|
+
visa.valid_credit_card_brand?(:visa, :amex).must_equal true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should not allow detect brand for invalid card' do
|
26
|
+
invalid.credit_card_brand.must_be_nil
|
27
|
+
invalid.credit_card_brand_name.must_be_nil
|
28
|
+
invalid.valid_credit_card_brand?(:mastercard).must_equal false
|
29
|
+
invalid.valid_credit_card_brand?(:visa, :amex).must_equal false
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|