credit_card_detector 0.3
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 +25 -0
- data/Changelog.md +8 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +74 -0
- data/Rakefile +14 -0
- data/credit_card_detector.gemspec +24 -0
- data/lib/credit_card_detector/brand.rb +26 -0
- data/lib/credit_card_detector/data.rb +22 -0
- data/lib/credit_card_detector/detector.rb +51 -0
- data/lib/credit_card_detector/error.rb +4 -0
- data/lib/credit_card_detector/luhn.rb +18 -0
- data/lib/credit_card_detector/rule.rb +26 -0
- data/lib/credit_card_detector/version.rb +4 -0
- data/lib/credit_card_detector.rb +6 -0
- data/lib/data/brands.yaml +281 -0
- data/spec/brand_spec.rb +70 -0
- data/spec/detector_spec.rb +78 -0
- data/spec/fixtures/invalid_cards.yml +6 -0
- data/spec/fixtures/valid_cards.yml +115 -0
- data/spec/luhn_spec.rb +20 -0
- data/spec/rule_spec.rb +26 -0
- data/spec/test_helper.rb +9 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b275b2204494c66c28dfb5eb23925b6ab6afc37b
|
4
|
+
data.tar.gz: 8baec80bedec36a582aab73f65b6f552255459d2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8d7bc3057b56ff82481011092797d445d651f63bb3e04a81e551da38fa54157cc3740b5f5624bd233e721e5869d1a5bb46a92d28363f9fdf6155ff4fef501d91
|
7
|
+
data.tar.gz: 762b2d41da9ff1779d406f9c6672e1c73addfd302ca240303c7b78c173566ee0f1c531a3d1e98eeae7cf9560afa9a9015072d4694fb04be4dd8a7e5b05b4d31d
|
data/.hound.yml
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9.3
|
4
|
+
- 2.0.0
|
5
|
+
- 2.1.9
|
6
|
+
- 2.2.5
|
7
|
+
- 2.3.1
|
8
|
+
- jruby-19mode # JRuby in 1.9 mode
|
9
|
+
gemfile:
|
10
|
+
- gemfiles/rails1-4.gemfile
|
11
|
+
- gemfiles/rails5.gemfile
|
12
|
+
matrix:
|
13
|
+
exclude:
|
14
|
+
- gemfile: gemfiles/rails5.gemfile
|
15
|
+
rvm: 1.9.3
|
16
|
+
- gemfile: gemfiles/rails5.gemfile
|
17
|
+
rvm: 2.0.0
|
18
|
+
- gemfile: gemfiles/rails5.gemfile
|
19
|
+
rvm: 2.1.9
|
20
|
+
- gemfile: gemfiles/rails5.gemfile
|
21
|
+
rvm: jruby-19mode
|
22
|
+
before_install:
|
23
|
+
- gem install bundler # use the very latest Bundler
|
24
|
+
- gem update --system
|
25
|
+
- gem --version
|
data/Changelog.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# 0.2
|
2
|
+
|
3
|
+
Refactor the source code completely to have cleaner objects that are easier to test.
|
4
|
+
|
5
|
+
# 0.1
|
6
|
+
|
7
|
+
Fork [credit_card_validations](https://github.com/Fivell/credit_card_validations) gem to have credit card validation and detector without any extra dependency (specifically, ActiveModel).
|
8
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Igor Fedoronchuk
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# CreditCardDetector
|
2
|
+
|
3
|
+
Gem provides credit card number validator and type detector.
|
4
|
+
|
5
|
+
It checks whether or not a given number actually falls within the ranges of possible numbers for given brands and provides an optional Luhn check.
|
6
|
+
|
7
|
+
More info about card BIN numbers http://en.wikipedia.org/wiki/Bank_card_number
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'credit_card_detector'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install credit_card_detector
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
|
26
|
+
The following issuing institutes are accepted:
|
27
|
+
|
28
|
+
Name | Key |
|
29
|
+
--------------------- | ------------|
|
30
|
+
[American Express](http://en.wikipedia.org/wiki/American_Express) | :amex
|
31
|
+
[China UnionPay](http://en.wikipedia.org/wiki/China_UnionPay) | :unionpay
|
32
|
+
[Dankort](http://en.wikipedia.org/wiki/Dankort) | :dankort
|
33
|
+
[Diners Club](http://en.wikipedia.org/wiki/Diners_Club_International) | :diners
|
34
|
+
[Elo](https://pt.wikipedia.org/wiki/Elo_Participa%C3%A7%C3%B5es_S/A) | :elo
|
35
|
+
[Discover](http://en.wikipedia.org/wiki/Discover_Card) | :discover
|
36
|
+
[Hipercard](http://pt.wikipedia.org/wiki/Hipercard) | :hipercard
|
37
|
+
[JCB](http://en.wikipedia.org/wiki/Japan_Credit_Bureau) | :jcb
|
38
|
+
[Maestro](http://en.wikipedia.org/wiki/Maestro_%28debit_card%29) | :maestro
|
39
|
+
[MasterCard](http://en.wikipedia.org/wiki/MasterCard) | :mastercard
|
40
|
+
[Rupay](http://en.wikipedia.org/wiki/RuPay) | :rupay
|
41
|
+
[Solo](http://en.wikipedia.org/wiki/Solo_(debit_card)) | :solo
|
42
|
+
[Switch](http://en.wikipedia.org/wiki/Switch_(debit_card)) | :switch
|
43
|
+
[Visa](http://en.wikipedia.org/wiki/Visa_Inc.) | :visa
|
44
|
+
|
45
|
+
|
46
|
+
### Examples
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
number = "4111111111111111"
|
50
|
+
detector = CreditCardDetector::Detector.new(number)
|
51
|
+
detector.brand # Visa brand
|
52
|
+
detector.brand_name # Visa
|
53
|
+
detector.valid?(:mastercard, :maestro) #false
|
54
|
+
detector.valid?(:visa, :mastercard) #true
|
55
|
+
```
|
56
|
+
|
57
|
+
### Check luhn
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
CreditCardDetector::Detector.new(number).valid_luhn?
|
61
|
+
```
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
|
65
|
+
1. Fork it
|
66
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
67
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
68
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
69
|
+
5. Create new Pull Request
|
70
|
+
|
71
|
+
## Original version
|
72
|
+
|
73
|
+
[credit_card_validations](https://github.com/Fivell/credit_card_validations) - provides more extended API, but dependes on activemodel and activesupport.
|
74
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
task default: [:specs]
|
7
|
+
|
8
|
+
desc "Run basic specs"
|
9
|
+
Rake::TestTask.new("specs") { |t|
|
10
|
+
t.pattern = "spec/*_spec.rb"
|
11
|
+
t.verbose = true
|
12
|
+
t.warning = true
|
13
|
+
t.libs << "lib" << "spec"
|
14
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require "credit_card_detector/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "credit_card_detector"
|
8
|
+
gem.version = CreditCardDetector::VERSION
|
9
|
+
gem.authors = ["Andrew Volozhanin", "Igor"]
|
10
|
+
gem.email = ["scarfacedeb@gmail.com", "fedoronchuk@gmail.com"]
|
11
|
+
gem.description = %q{A ruby gem for validating credit card numbers}
|
12
|
+
gem.summary = "Gem provides basic credit card number validation and type detection without any extra dependencies"
|
13
|
+
gem.homepage = ""
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_development_dependency "rake", "~> 10.5"
|
22
|
+
gem.add_development_dependency "minitest", "~> 5.9"
|
23
|
+
gem.add_development_dependency "pry"
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "credit_card_detector/rule"
|
2
|
+
|
3
|
+
module CreditCardDetector
|
4
|
+
class Brand
|
5
|
+
attr_reader :id, :name, :options
|
6
|
+
|
7
|
+
def initialize(id, data)
|
8
|
+
@id = id
|
9
|
+
@name = data.fetch(:name)
|
10
|
+
@raw_rules = data.fetch(:rules)
|
11
|
+
@options = data.fetch(:options, {})
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(number)
|
15
|
+
rules.any? { |rule| rule.matches? number }
|
16
|
+
end
|
17
|
+
|
18
|
+
def luhn?
|
19
|
+
!@options.fetch(:skip_luhn, false)
|
20
|
+
end
|
21
|
+
|
22
|
+
def rules
|
23
|
+
@rules ||= @raw_rules.map { |rule| Rule.new rule }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "credit_card_detector/brand"
|
3
|
+
|
4
|
+
module CreditCardDetector
|
5
|
+
module Data
|
6
|
+
DATA = File.expand_path("../../data/brands.yaml", __FILE__)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def brands
|
10
|
+
@data ||= raw_data.map { |key, data| Brand.new key, data }
|
11
|
+
end
|
12
|
+
|
13
|
+
def ids
|
14
|
+
@ids ||= brands.map(&:id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def raw_data
|
18
|
+
YAML.load_file DATA
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "credit_card_detector/data"
|
2
|
+
require "credit_card_detector/luhn"
|
3
|
+
|
4
|
+
module CreditCardDetector
|
5
|
+
class Detector
|
6
|
+
def initialize(number)
|
7
|
+
@number = number.to_s.tr('- ', '')
|
8
|
+
end
|
9
|
+
|
10
|
+
def valid?(*brands)
|
11
|
+
brands.compact!
|
12
|
+
|
13
|
+
if brands.empty?
|
14
|
+
!brand.nil?
|
15
|
+
else
|
16
|
+
validate_brands(brands)
|
17
|
+
brands.include?(brand.id)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_luhn?
|
22
|
+
@valid_luhn ||= Luhn.valid?(@number)
|
23
|
+
end
|
24
|
+
|
25
|
+
def brand
|
26
|
+
@brand ||= Data.brands.find { |brand| matches_brand? brand }
|
27
|
+
end
|
28
|
+
|
29
|
+
def brand_id
|
30
|
+
brand.id if brand
|
31
|
+
end
|
32
|
+
|
33
|
+
def brand_name
|
34
|
+
brand.name if brand
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def matches_brand?(brand)
|
40
|
+
return if brand.luhn? && !valid_luhn?
|
41
|
+
brand.matches?(@number)
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_brands(brands)
|
45
|
+
brands.each do |key|
|
46
|
+
next if Data.ids.include?(key)
|
47
|
+
fail Error, "brand #{key} is undefined"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CreditCardDetector
|
2
|
+
# Class to validate Luhn numbers.
|
3
|
+
class Luhn
|
4
|
+
def self.valid?(number)
|
5
|
+
s1 = s2 = 0
|
6
|
+
|
7
|
+
number.to_s.reverse.chars.each_slice(2) do |odd, even|
|
8
|
+
s1 += odd.to_i
|
9
|
+
|
10
|
+
double = even.to_i * 2
|
11
|
+
double -= 9 if double >= 10
|
12
|
+
s2 += double
|
13
|
+
end
|
14
|
+
|
15
|
+
(s1 + s2) % 10 == 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CreditCardDetector
|
2
|
+
class Rule
|
3
|
+
def initialize(length:, prefixes:)
|
4
|
+
@length = length
|
5
|
+
@regexp = compile_regexp(prefixes)
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(number)
|
9
|
+
matches_length?(number.to_s) && matches_prefix?(number.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches_length?(number)
|
13
|
+
@length.include? number.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches_prefix?(number)
|
17
|
+
!!(number =~ @regexp)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def compile_regexp(prefixes)
|
23
|
+
Regexp.new("^(#{prefixes.join("|")})")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
---
|
2
|
+
:visa:
|
3
|
+
:name: Visa
|
4
|
+
:rules:
|
5
|
+
- :length:
|
6
|
+
- 13
|
7
|
+
- 16
|
8
|
+
:prefixes:
|
9
|
+
- '4'
|
10
|
+
:mastercard:
|
11
|
+
:name: MasterCard
|
12
|
+
:rules:
|
13
|
+
- :length:
|
14
|
+
- 16
|
15
|
+
:prefixes:
|
16
|
+
- '2221'
|
17
|
+
- '2222'
|
18
|
+
- '2223'
|
19
|
+
- '2224'
|
20
|
+
- '2225'
|
21
|
+
- '2226'
|
22
|
+
- '2227'
|
23
|
+
- '2228'
|
24
|
+
- '2229'
|
25
|
+
- '223'
|
26
|
+
- '224'
|
27
|
+
- '225'
|
28
|
+
- '226'
|
29
|
+
- '227'
|
30
|
+
- '228'
|
31
|
+
- '229'
|
32
|
+
- '23'
|
33
|
+
- '24'
|
34
|
+
- '25'
|
35
|
+
- '26'
|
36
|
+
- '271'
|
37
|
+
- '2720'
|
38
|
+
- '51'
|
39
|
+
- '52'
|
40
|
+
- '53'
|
41
|
+
- '54'
|
42
|
+
- '55'
|
43
|
+
:amex:
|
44
|
+
:name: American Express
|
45
|
+
:rules:
|
46
|
+
- :length:
|
47
|
+
- 15
|
48
|
+
:prefixes:
|
49
|
+
- '34'
|
50
|
+
- '37'
|
51
|
+
:diners:
|
52
|
+
:name: Diners Club
|
53
|
+
:rules:
|
54
|
+
- :length:
|
55
|
+
- 14
|
56
|
+
:prefixes:
|
57
|
+
- '300'
|
58
|
+
- '301'
|
59
|
+
- '302'
|
60
|
+
- '303'
|
61
|
+
- '304'
|
62
|
+
- '305'
|
63
|
+
- '36'
|
64
|
+
- '38'
|
65
|
+
:discover:
|
66
|
+
:name: Discover
|
67
|
+
:rules:
|
68
|
+
- :length:
|
69
|
+
- 16
|
70
|
+
:prefixes:
|
71
|
+
- '6011'
|
72
|
+
- '644'
|
73
|
+
- '645'
|
74
|
+
- '646'
|
75
|
+
- '647'
|
76
|
+
- '648'
|
77
|
+
- '649'
|
78
|
+
- '65'
|
79
|
+
:jcb:
|
80
|
+
:name: JCB
|
81
|
+
:rules:
|
82
|
+
- :length:
|
83
|
+
- 15
|
84
|
+
- 16
|
85
|
+
:prefixes:
|
86
|
+
- '3528'
|
87
|
+
- '3529'
|
88
|
+
- '353'
|
89
|
+
- '354'
|
90
|
+
- '355'
|
91
|
+
- '356'
|
92
|
+
- '357'
|
93
|
+
- '358'
|
94
|
+
- :length:
|
95
|
+
- 15
|
96
|
+
:prefixes:
|
97
|
+
- '1800'
|
98
|
+
- '2131'
|
99
|
+
- :length:
|
100
|
+
- 19
|
101
|
+
:prefixes:
|
102
|
+
- '357266'
|
103
|
+
:solo:
|
104
|
+
:name: Solo
|
105
|
+
:rules:
|
106
|
+
- :length:
|
107
|
+
- 16
|
108
|
+
- 18
|
109
|
+
- 19
|
110
|
+
:prefixes:
|
111
|
+
- '6334'
|
112
|
+
- '6767'
|
113
|
+
:switch:
|
114
|
+
:name: Switch
|
115
|
+
:rules:
|
116
|
+
- :length:
|
117
|
+
- 16
|
118
|
+
- 18
|
119
|
+
- 19
|
120
|
+
:prefixes:
|
121
|
+
- '633110'
|
122
|
+
- '633312'
|
123
|
+
- '633304'
|
124
|
+
- '633303'
|
125
|
+
- '633301'
|
126
|
+
- '633300'
|
127
|
+
:maestro:
|
128
|
+
:name: Maestro
|
129
|
+
:rules:
|
130
|
+
- :length:
|
131
|
+
- 12
|
132
|
+
- 13
|
133
|
+
- 14
|
134
|
+
- 15
|
135
|
+
- 16
|
136
|
+
- 17
|
137
|
+
- 18
|
138
|
+
- 19
|
139
|
+
:prefixes:
|
140
|
+
- '500'
|
141
|
+
- '5010'
|
142
|
+
- '5011'
|
143
|
+
- '5012'
|
144
|
+
- '5013'
|
145
|
+
- '5014'
|
146
|
+
- '5015'
|
147
|
+
- '5016'
|
148
|
+
- '5017'
|
149
|
+
- '5018'
|
150
|
+
- '502'
|
151
|
+
- '503'
|
152
|
+
- '504'
|
153
|
+
- '505'
|
154
|
+
- '506'
|
155
|
+
- '507'
|
156
|
+
- '508'
|
157
|
+
- '509'
|
158
|
+
- '56'
|
159
|
+
- '57'
|
160
|
+
- '58'
|
161
|
+
- '59'
|
162
|
+
- '6010'
|
163
|
+
- '6012'
|
164
|
+
- '6013'
|
165
|
+
- '6014'
|
166
|
+
- '6015'
|
167
|
+
- '6016'
|
168
|
+
- '6017'
|
169
|
+
- '6018'
|
170
|
+
- '6019'
|
171
|
+
- '602'
|
172
|
+
- '603'
|
173
|
+
- '604'
|
174
|
+
- '605'
|
175
|
+
- '6060'
|
176
|
+
- '621'
|
177
|
+
- '627'
|
178
|
+
- '629'
|
179
|
+
- '6304'
|
180
|
+
- '6390'
|
181
|
+
- '670'
|
182
|
+
- '671'
|
183
|
+
- '672'
|
184
|
+
- '673'
|
185
|
+
- '674'
|
186
|
+
- '675'
|
187
|
+
- '677'
|
188
|
+
- '6760'
|
189
|
+
- '6761'
|
190
|
+
- '6762'
|
191
|
+
- '6763'
|
192
|
+
- '6764'
|
193
|
+
- '6765'
|
194
|
+
- '6766'
|
195
|
+
- '6768'
|
196
|
+
- '6769'
|
197
|
+
- '6771'
|
198
|
+
- '679'
|
199
|
+
:unionpay:
|
200
|
+
:name: China UnionPay
|
201
|
+
:rules:
|
202
|
+
- :length:
|
203
|
+
- 16
|
204
|
+
- 17
|
205
|
+
- 18
|
206
|
+
- 19
|
207
|
+
:prefixes:
|
208
|
+
- '622'
|
209
|
+
- '624'
|
210
|
+
- '625'
|
211
|
+
- '626'
|
212
|
+
- '628'
|
213
|
+
:options:
|
214
|
+
:skip_luhn: true
|
215
|
+
:dankort:
|
216
|
+
:name: Dankort
|
217
|
+
:rules:
|
218
|
+
- :length:
|
219
|
+
- 16
|
220
|
+
:prefixes:
|
221
|
+
- '5019'
|
222
|
+
:rupay:
|
223
|
+
:name: Rupay
|
224
|
+
:rules:
|
225
|
+
- :length:
|
226
|
+
- 16
|
227
|
+
:prefixes:
|
228
|
+
- '6061'
|
229
|
+
- '6062'
|
230
|
+
- '6063'
|
231
|
+
- '6064'
|
232
|
+
- '6065'
|
233
|
+
- '6066'
|
234
|
+
- '6067'
|
235
|
+
- '6068'
|
236
|
+
- '6069'
|
237
|
+
- '607'
|
238
|
+
- '608'
|
239
|
+
:options:
|
240
|
+
:skip_luhn: true
|
241
|
+
:hipercard:
|
242
|
+
:name: Hipercard
|
243
|
+
:rules:
|
244
|
+
- :length:
|
245
|
+
- 19
|
246
|
+
:prefixes:
|
247
|
+
- '384'
|
248
|
+
:elo:
|
249
|
+
:name: Elo
|
250
|
+
:rules:
|
251
|
+
- :length:
|
252
|
+
- 16
|
253
|
+
:prefixes:
|
254
|
+
- '4011'
|
255
|
+
- '438935'
|
256
|
+
- '451416'
|
257
|
+
- '4576'
|
258
|
+
- '504175'
|
259
|
+
- '506699'
|
260
|
+
- '5067'
|
261
|
+
- '509040'
|
262
|
+
- '509042'
|
263
|
+
- '509043'
|
264
|
+
- '509045'
|
265
|
+
- '509046'
|
266
|
+
- '509047'
|
267
|
+
- '509048'
|
268
|
+
- '509049'
|
269
|
+
- '509050'
|
270
|
+
- '509051'
|
271
|
+
- '509052'
|
272
|
+
- '509064'
|
273
|
+
- '509066'
|
274
|
+
- '509067'
|
275
|
+
- '509068'
|
276
|
+
- '509069'
|
277
|
+
- '509074'
|
278
|
+
- '636297'
|
279
|
+
- '63636'
|
280
|
+
:options:
|
281
|
+
:skip_luhn: true
|
data/spec/brand_spec.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "credit_card_detector/brand"
|
3
|
+
|
4
|
+
module CreditCardDetector
|
5
|
+
describe Brand do
|
6
|
+
describe "#matches?" do
|
7
|
+
let(:data) { { name: "Visa", rules: rules, options: {} } }
|
8
|
+
let(:brand) { Brand.new(:visa, data) }
|
9
|
+
|
10
|
+
describe "when brand doesn't have any rules" do
|
11
|
+
let(:rules) { [] }
|
12
|
+
it "doesn't matches any number" do
|
13
|
+
assert_equal brand.matches?("1234"), false
|
14
|
+
assert_equal brand.matches?(""), false
|
15
|
+
assert_equal brand.matches?("4012888888881881"), false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "when brand has a single rule" do
|
20
|
+
let(:rules) {
|
21
|
+
[ { length: [16, 13], prefixes: ['4'] } ]
|
22
|
+
}
|
23
|
+
|
24
|
+
it "matches valid numbers" do
|
25
|
+
numbers = VALID_NUMBERS.fetch(:visa)
|
26
|
+
numbers.each do |number|
|
27
|
+
assert_equal brand.matches?(number.tr("- ", "")), true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "doesn't match invalid numbers" do
|
32
|
+
assert_equal brand.matches?("42222222222221"), false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "when brand has multiple rules" do
|
37
|
+
let(:rules) {
|
38
|
+
[
|
39
|
+
{ length: [15, 16], prefixes: ['357', '356', '353'] },
|
40
|
+
{ length: [15], prefixes: ['1800'] },
|
41
|
+
{ length: [19], prefixes: ['357266'] }
|
42
|
+
]
|
43
|
+
}
|
44
|
+
|
45
|
+
it "matches valid numbers" do
|
46
|
+
numbers = VALID_NUMBERS.fetch(:jcb)
|
47
|
+
numbers.each do |number|
|
48
|
+
assert_equal brand.matches?(number.tr("- ", "")), true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "doesn't match invalid numbers" do
|
53
|
+
assert_equal brand.matches?("1800016382773921"), false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#luhn?" do
|
59
|
+
it "returns true when NO skip_luhn option is set on brand" do
|
60
|
+
brand = Brand.new(:maestro, name: "maestro", options: {}, rules: [])
|
61
|
+
assert_equal brand.luhn?, true
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns false when skip_luhn option is set on brand" do
|
65
|
+
brand = Brand.new(:maestro, name: "rupay", options: { skip_luhn: true }, rules: [])
|
66
|
+
assert_equal brand.luhn?, false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "credit_card_detector/detector"
|
3
|
+
|
4
|
+
module CreditCardDetector
|
5
|
+
describe Detector do
|
6
|
+
describe "#valid?" do
|
7
|
+
def perform(numbers, expected, *brands)
|
8
|
+
numbers.each do |number|
|
9
|
+
valid = Detector.new(number).valid?(*brands)
|
10
|
+
assert_equal valid, expected, number
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "validates valid numbers against all of the available brands" do
|
15
|
+
VALID_NUMBERS.each do |_, numbers|
|
16
|
+
perform numbers, true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "validates invalid numbers against all of the available brands" do
|
21
|
+
perform INVALID_NUMBERS, false
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns true when numbers belong to the given brand" do
|
25
|
+
visa = VALID_NUMBERS.fetch(:visa)
|
26
|
+
perform visa, true, :visa
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns false when numbers belong to another brand" do
|
30
|
+
mastercard = VALID_NUMBERS.fetch(:mastercard)
|
31
|
+
perform mastercard, false, :visa
|
32
|
+
end
|
33
|
+
|
34
|
+
it "validates number against multiple brands" do
|
35
|
+
visa = VALID_NUMBERS.fetch(:visa)
|
36
|
+
mastercard = VALID_NUMBERS.fetch(:mastercard)
|
37
|
+
maestro = VALID_NUMBERS.fetch(:maestro)
|
38
|
+
|
39
|
+
perform (visa + maestro), true, :visa, :maestro, :jcb
|
40
|
+
perform mastercard, false, :visa, :rupay
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "when brand doesn't support luhn check" do
|
44
|
+
it "returns true when number fails luhn check" do
|
45
|
+
unionpay = VALID_NUMBERS.fetch(:unionpay)
|
46
|
+
perform unionpay, true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#brand" do
|
52
|
+
it "returns matched brand for valid number" do
|
53
|
+
detector = Detector.new("4012888888881881")
|
54
|
+
assert_instance_of Brand, detector.brand
|
55
|
+
assert_equal detector.brand.id, :visa
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns nil when number is invalid" do
|
59
|
+
detector = Detector.new("40128888")
|
60
|
+
assert_equal detector.brand, nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#brand_id" do
|
65
|
+
it "returns brand id" do
|
66
|
+
detector = Detector.new("4012888888881881")
|
67
|
+
assert_equal detector.brand_id, :visa
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#brand_name" do
|
72
|
+
it "returns brand name" do
|
73
|
+
detector = Detector.new("4012888888881881")
|
74
|
+
assert_equal detector.brand_name, "Visa"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,115 @@
|
|
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 4141 3850 9070'
|
91
|
+
- '4389 3555 4728 7502'
|
92
|
+
- '4514 1620 5642 0699'
|
93
|
+
- '4576 5598 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'
|
data/spec/luhn_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "credit_card_detector/luhn"
|
3
|
+
|
4
|
+
module CreditCardDetector
|
5
|
+
describe Luhn do
|
6
|
+
describe ".valid?" do
|
7
|
+
it "returns true when number is valid" do
|
8
|
+
LUHN_ENABLED.each do |brand, card_numbers|
|
9
|
+
card_numbers.each do |number|
|
10
|
+
assert_equal Luhn.valid?(number.to_s.tr("- ", "")), true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns false when number is invalid" do
|
16
|
+
assert_equal Luhn.valid?("4111111111111110"), false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/rule_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "credit_card_detector/rule"
|
3
|
+
|
4
|
+
module CreditCardDetector
|
5
|
+
describe Rule do
|
6
|
+
describe "#matches?" do
|
7
|
+
let(:rule) { Rule.new(length: [8, 9], prefixes: [1234, 2345]) }
|
8
|
+
|
9
|
+
it "matches when number has correct length AND starts from any prefix" do
|
10
|
+
assert_equal rule.matches?(12345678), true
|
11
|
+
assert_equal rule.matches?(234567890), true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "doesn't match when number has incorrect length" do
|
15
|
+
assert_equal rule.matches?(123), false
|
16
|
+
assert_equal rule.matches?(""), false
|
17
|
+
assert_equal rule.matches?(1234567890123), false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "doesn't match when number doesn't start from any prefix" do
|
21
|
+
assert_equal rule.matches?(01234567), false
|
22
|
+
assert_equal rule.matches?(00000000), false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/test_helper.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "credit_card_detector"
|
3
|
+
|
4
|
+
VALID_NUMBERS = YAML.load_file File.join(File.dirname(__FILE__), "fixtures/valid_cards.yml")
|
5
|
+
INVALID_NUMBERS = YAML.load_file File.join(File.dirname(__FILE__), "fixtures/invalid_cards.yml")
|
6
|
+
|
7
|
+
SKIP_LUHN = [:rupay, :elo, :unionpay]
|
8
|
+
LUHN_ENABLED = VALID_NUMBERS.reject { |k, _| SKIP_LUHN.include?(k) }
|
9
|
+
LUHN_DISABLED = VALID_NUMBERS.select { |k, _| SKIP_LUHN.include?(k) }
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: credit_card_detector
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.3'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Volozhanin
|
8
|
+
- Igor
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-09-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '10.5'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '10.5'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: minitest
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '5.9'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '5.9'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: pry
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: A ruby gem for validating credit card numbers
|
57
|
+
email:
|
58
|
+
- scarfacedeb@gmail.com
|
59
|
+
- fedoronchuk@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".hound.yml"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Changelog.md
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- credit_card_detector.gemspec
|
72
|
+
- lib/credit_card_detector.rb
|
73
|
+
- lib/credit_card_detector/brand.rb
|
74
|
+
- lib/credit_card_detector/data.rb
|
75
|
+
- lib/credit_card_detector/detector.rb
|
76
|
+
- lib/credit_card_detector/error.rb
|
77
|
+
- lib/credit_card_detector/luhn.rb
|
78
|
+
- lib/credit_card_detector/rule.rb
|
79
|
+
- lib/credit_card_detector/version.rb
|
80
|
+
- lib/data/brands.yaml
|
81
|
+
- spec/brand_spec.rb
|
82
|
+
- spec/detector_spec.rb
|
83
|
+
- spec/fixtures/invalid_cards.yml
|
84
|
+
- spec/fixtures/valid_cards.yml
|
85
|
+
- spec/luhn_spec.rb
|
86
|
+
- spec/rule_spec.rb
|
87
|
+
- spec/test_helper.rb
|
88
|
+
homepage: ''
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.5.1
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Gem provides basic credit card number validation and type detection without
|
112
|
+
any extra dependencies
|
113
|
+
test_files:
|
114
|
+
- spec/brand_spec.rb
|
115
|
+
- spec/detector_spec.rb
|
116
|
+
- spec/fixtures/invalid_cards.yml
|
117
|
+
- spec/fixtures/valid_cards.yml
|
118
|
+
- spec/luhn_spec.rb
|
119
|
+
- spec/rule_spec.rb
|
120
|
+
- spec/test_helper.rb
|