credit_card_validations 1.5.1 → 2.0.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/Changelog.md +85 -0
- data/README.md +32 -9
- data/Rakefile +4 -4
- data/credit_card_validations.gemspec +2 -2
- data/lib/active_model/credit_card_number_validator.rb +67 -2
- data/lib/credit_card_validations.rb +25 -17
- data/lib/credit_card_validations/detector.rb +110 -27
- data/lib/credit_card_validations/factory.rb +66 -0
- data/lib/credit_card_validations/luhn.rb +5 -0
- data/lib/credit_card_validations/mmi.rb +18 -13
- data/lib/credit_card_validations/string.rb +11 -1
- data/lib/credit_card_validations/version.rb +1 -1
- data/lib/data/brands.yaml +238 -0
- data/spec/active_model_spec.rb +69 -0
- data/spec/credit_card_validations_spec.rb +168 -0
- data/spec/factory_spec.rb +21 -0
- data/{test → spec}/fixtures/invalid_cards.yml +0 -0
- data/{test → spec}/fixtures/valid_cards.yml +1 -0
- data/spec/models/credit_card.rb +19 -0
- data/spec/string_spec.rb +43 -0
- data/{test → spec}/test_helper.rb +5 -4
- metadata +25 -17
- data/lib/credit_card_validations/card_rules.rb +0 -84
- data/test/credit_card_validations_test.rb +0 -151
- data/test/models/any_credit_card.rb +0 -6
- data/test/models/credit_card.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9132f1754e530b96267778bd96cecaf3838ec575
|
4
|
+
data.tar.gz: 7b805a21857451ce13ad3d86776c0c192e8ac5fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61bfa339a1c364fcededa512391ac3a43a57d102d41e95bb47782d920c6835a430b27ebd169323b22e8a555bcecff0150ba758f495ca778fb92c822c53184ea7
|
7
|
+
data.tar.gz: 7bcbc2632129f06dc3c429264b5d56820cfacae1fa5b709b636eabc0a5e343d0ffd24389dbd65e34d16308b27aba3da35a35f2729968fe3c9732dc2b3c91d345
|
data/.travis.yml
CHANGED
data/Changelog.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# 2.0.0
|
2
|
+
|
3
|
+
* added support for full brand names
|
4
|
+
* added possibility to remove card brands globally
|
5
|
+
* support for procs using :brand option for CreditCardNumberValidator
|
6
|
+
* :only and :except options for CreditCardNumberValidator
|
7
|
+
* credit card generator
|
8
|
+
* test unit -> specs migration
|
9
|
+
* card rules structure changed to allow custom options
|
10
|
+
* yaml storage implemented
|
11
|
+
* Maestro detection fix
|
12
|
+
* JCB detection fix
|
13
|
+
|
14
|
+
|
15
|
+
# 1.5.1
|
16
|
+
|
17
|
+
* Maestro detection fix
|
18
|
+
|
19
|
+
# 1.5.0
|
20
|
+
|
21
|
+
* Rupay detection support
|
22
|
+
* Hipercard detection support
|
23
|
+
|
24
|
+
# 1.4.7
|
25
|
+
|
26
|
+
* Maestro and Switch detection fix
|
27
|
+
|
28
|
+
# 1.4.6
|
29
|
+
|
30
|
+
* JCB detection fix
|
31
|
+
|
32
|
+
# 1.4.5
|
33
|
+
|
34
|
+
* Diners detection fix
|
35
|
+
|
36
|
+
# 1.4.4
|
37
|
+
|
38
|
+
* Visa detection fix
|
39
|
+
|
40
|
+
# 1.4.3
|
41
|
+
|
42
|
+
* fix for Rails 4.+ dependency
|
43
|
+
* Maestro detection fixes
|
44
|
+
|
45
|
+
# 1.4.2
|
46
|
+
|
47
|
+
* fix for Rails 4.1 dependency
|
48
|
+
* UnionPay and Discover detection fixes
|
49
|
+
|
50
|
+
# 1.4.1
|
51
|
+
|
52
|
+
* fixed ActiveModel Validator syntax
|
53
|
+
|
54
|
+
# 1.4.0
|
55
|
+
|
56
|
+
* Dankrot brand support
|
57
|
+
|
58
|
+
# 1.3.0
|
59
|
+
|
60
|
+
* MMI detection support
|
61
|
+
|
62
|
+
# 1.2.0
|
63
|
+
|
64
|
+
* Allow brand restriction when detecting brand
|
65
|
+
* fix for Rails 4.0 dependency
|
66
|
+
* Allow to skip Luhn validation for certain brands
|
67
|
+
|
68
|
+
# 1.1.2
|
69
|
+
|
70
|
+
* Credit card number sanitizing fix
|
71
|
+
* :any instead of brand name can be used with ActiveModel Validator
|
72
|
+
|
73
|
+
# 1.1.1
|
74
|
+
|
75
|
+
* ActiveModel Validator was moved from root namespace
|
76
|
+
|
77
|
+
# 1.1.0
|
78
|
+
|
79
|
+
* Added rules for Maestro brand
|
80
|
+
* String extension added
|
81
|
+
|
82
|
+
# 1.0.1
|
83
|
+
|
84
|
+
* Dependency fixes
|
85
|
+
* Added usage instructions to README
|
data/README.md
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# CreditCardValidations
|
2
2
|
|
3
|
+
[](https://travis-ci.org/Fivell/credit_card_validations)
|
4
|
+
[](https://gemnasium.com/Fivell/credit_card_validations)
|
5
|
+
[](https://coveralls.io/r/Fivell/credit_card_validations)
|
6
|
+
[](https://codeclimate.com/github/Fivell/credit_card_validations)
|
7
|
+
[](https://rubygems.org/gems/credit_card_validations)
|
8
|
+
[](http://Fivell.mit-license.org)
|
9
|
+
|
10
|
+
|
3
11
|
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
12
|
|
5
13
|
More info about card BIN numbers http://en.wikipedia.org/wiki/Bank_card_number
|
6
14
|
|
7
|
-
[](https://travis-ci.org/Fivell/credit_card_validations)
|
8
|
-
[](https://coveralls.io/r/Fivell/credit_card_validations)
|
9
|
-
|
10
15
|
## Installation
|
11
16
|
|
12
17
|
Add this line to your application's Gemfile:
|
@@ -50,9 +55,11 @@ Examples using string monkey patch
|
|
50
55
|
|
51
56
|
```ruby
|
52
57
|
require 'credit_card_validations/string'
|
53
|
-
'5274 5763 9425 9961'.credit_card_brand
|
54
|
-
'5274 5763 9425 9961'.
|
55
|
-
'5274 5763 9425 9961'.valid_credit_card_brand?(:
|
58
|
+
'5274 5763 9425 9961'.credit_card_brand #=> :mastercard
|
59
|
+
'5274 5763 9425 9961'.credit_card_brand_name #=> "MasterCard"
|
60
|
+
'5274 5763 9425 9961'.valid_credit_card_brand?(:mastercard, :visa) #=> true
|
61
|
+
'5274 5763 9425 9961'.valid_credit_card_brand?(:amex) #=> false
|
62
|
+
'5274 5763 9425 9961'.valid_credit_card_brand?('MasterCard') #=> true
|
56
63
|
```
|
57
64
|
|
58
65
|
ActiveModel support
|
@@ -85,13 +92,13 @@ Examples using CreditCardValidations::Detector class
|
|
85
92
|
detector.issuer_category #"Banking and financial"
|
86
93
|
```
|
87
94
|
|
88
|
-
Also You can add your own rules to detect other credit card brands/types
|
95
|
+
Also You can add your own brand rules to detect other credit card brands/types
|
89
96
|
passing name,length(integer/array of integers) and prefix(string/array of strings)
|
90
97
|
Example
|
91
98
|
|
92
99
|
```ruby
|
93
|
-
CreditCardValidations.
|
94
|
-
CreditCardValidations.
|
100
|
+
CreditCardValidations.add_brand(:voyager, {length: 15, prefixes: '86'})
|
101
|
+
CreditCardValidations.add_brand(:en_route, {length: 15, prefixes: ['2014', '2149']}, {skip_luhn: true}) #skip luhn
|
95
102
|
|
96
103
|
voyager_test_card_number = '869926275400212'
|
97
104
|
CreditCardValidations::Detector.new(voyager_test_card_number).brand #:voyager
|
@@ -102,6 +109,14 @@ Example
|
|
102
109
|
CreditCardValidations::Detector.new(en_route_test_card_number).en_route? #true
|
103
110
|
```
|
104
111
|
|
112
|
+
Remove brands also supported
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
CreditCardValidations.delete_brand(:maestro)
|
116
|
+
```
|
117
|
+
|
118
|
+
|
119
|
+
|
105
120
|
Check luhn
|
106
121
|
|
107
122
|
```ruby
|
@@ -110,6 +125,14 @@ Check luhn
|
|
110
125
|
CreditCardValidations::Luhn.valid?(@credit_card_number)
|
111
126
|
```
|
112
127
|
|
128
|
+
Generate credit card numbers that pass validation
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
CreditCardValidations::Factory.random(:amex)
|
132
|
+
# => "348051773827666"
|
133
|
+
CreditCardValidations::Factory.random(:maestro)
|
134
|
+
# => "6010430241237266856"
|
135
|
+
```
|
113
136
|
|
114
137
|
## Contributing
|
115
138
|
|
data/Rakefile
CHANGED
@@ -3,11 +3,11 @@ require 'bundler/gem_tasks'
|
|
3
3
|
require 'rake'
|
4
4
|
require 'rake/testtask'
|
5
5
|
|
6
|
-
task :default => [:
|
6
|
+
task :default => [:specs]
|
7
7
|
|
8
|
-
desc "Run basic
|
9
|
-
Rake::TestTask.new("
|
10
|
-
t.pattern = '
|
8
|
+
desc "Run basic specs"
|
9
|
+
Rake::TestTask.new("specs") { |t|
|
10
|
+
t.pattern = 'spec/*_spec.rb'
|
11
11
|
t.verbose = true
|
12
12
|
t.warning = true
|
13
13
|
}
|
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = CreditCardValidations::VERSION
|
9
9
|
gem.authors = ["Igor"]
|
10
10
|
gem.email = ["fedoronchuk@gmail.com"]
|
11
|
-
gem.description = %q{A ruby gem for validating credit card numbers
|
12
|
-
gem.summary = "gem for credit card numbers validation, card brands detections"
|
11
|
+
gem.description = %q{A ruby gem for validating credit card numbers}
|
12
|
+
gem.summary = "gem should be used for credit card numbers validation, card brands detections, luhn checks"
|
13
13
|
gem.homepage = "https://github.com/Fivell/credit_card_validations"
|
14
14
|
gem.license = 'MIT'
|
15
15
|
|
@@ -1,15 +1,80 @@
|
|
1
|
+
# == ActiveModel Validations CreditCardNumberValidator
|
2
|
+
# Can be used in combination with the +validates+ method
|
3
|
+
#
|
4
|
+
# Only Amex and Maestro
|
5
|
+
#
|
6
|
+
# class CreditCard
|
7
|
+
# attr_accessor :number
|
8
|
+
# include ActiveModel::Validations
|
9
|
+
# validates :number, credit_card_number: {only: [:amex, :maestro]}
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# All numbers are valid except Maestro
|
13
|
+
#
|
14
|
+
# class CreditCard
|
15
|
+
# attr_accessor :number
|
16
|
+
# include ActiveModel::Validations
|
17
|
+
# validates :number, credit_card_number: {except: [:maestro]}
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Proc can be used as well
|
21
|
+
#
|
22
|
+
# class CreditCard
|
23
|
+
# attr_accessor :number, :card_type
|
24
|
+
# include ActiveModel::Validations
|
25
|
+
# validates :number, credit_card_number: {brands: ->{|record| Array(record.accepted_brands) } }
|
26
|
+
#
|
27
|
+
# def accepted_brands
|
28
|
+
# if card_type == 'Maestro'
|
29
|
+
# :maestro
|
30
|
+
# elsif card_type == 'American Express'
|
31
|
+
# :amex
|
32
|
+
# else
|
33
|
+
# :visa
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
#
|
40
|
+
|
1
41
|
module ActiveModel
|
2
42
|
module Validations
|
3
43
|
class CreditCardNumberValidator < EachValidator
|
4
44
|
|
5
45
|
def validate_each(record, attribute, value)
|
6
|
-
|
7
|
-
record.errors.add(attribute) unless credit_card_valid?(value, brands == :any ? [] : Array.wrap(brands))
|
46
|
+
record.errors.add(attribute) unless credit_card_valid?(value, extract_brands(record, options))
|
8
47
|
end
|
9
48
|
|
10
49
|
def credit_card_valid?(number, brands = [])
|
11
50
|
CreditCardValidations::Detector.new(number).valid?(*brands)
|
12
51
|
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def extract_brands(record, options)
|
56
|
+
if options.has_key?(:brands)
|
57
|
+
with_brands(record, options[:brands])
|
58
|
+
elsif options.has_key?(:only)
|
59
|
+
Array(options[:only])
|
60
|
+
elsif options.has_key?(:except)
|
61
|
+
Array(CreditCardValidations::Detector.brands.keys) - Array(options[:except])
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_brands(record, brands)
|
69
|
+
if brands.is_a?(Proc)
|
70
|
+
brands.call(record)
|
71
|
+
elsif options[:brands] == :any
|
72
|
+
[]
|
73
|
+
else
|
74
|
+
Array(options[:brands])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
13
78
|
end
|
14
79
|
end
|
15
80
|
end
|
@@ -3,25 +3,33 @@ require 'active_model'
|
|
3
3
|
require 'active_support/core_ext'
|
4
4
|
require 'active_model/validations'
|
5
5
|
require 'active_model/credit_card_number_validator'
|
6
|
+
require 'yaml'
|
6
7
|
|
7
8
|
module CreditCardValidations
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
autoload :VERSION, 'credit_card_validations/version'
|
11
|
+
autoload :Luhn, 'credit_card_validations/luhn'
|
12
|
+
autoload :Detector, 'credit_card_validations/detector'
|
13
|
+
autoload :Factory, 'credit_card_validations/factory'
|
14
|
+
autoload :Mmi, 'credit_card_validations/mmi'
|
15
|
+
|
16
|
+
|
17
|
+
def self.add_brand(key, rules, options = {})
|
18
|
+
Detector.add_brand(key, rules, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
DATA = YAML.load_file(File.join(File.dirname(__FILE__), 'data', 'brands.yaml')) || {}
|
22
|
+
|
23
|
+
def self.reload!
|
24
|
+
Detector.brands = {}
|
25
|
+
DATA.each do |key, data|
|
26
|
+
add_brand(key, data.fetch(:rules), data.fetch(:options, {}))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
CreditCardValidations.reload!
|
25
33
|
|
26
34
|
|
27
35
|
|
@@ -1,10 +1,13 @@
|
|
1
|
+
# == CreditCardValidations Detector
|
2
|
+
#
|
3
|
+
# class provides credit card number validations
|
1
4
|
module CreditCardValidations
|
2
5
|
class Detector
|
3
6
|
|
4
7
|
include Mmi
|
5
8
|
|
6
|
-
class_attribute :
|
7
|
-
self.
|
9
|
+
class_attribute :brands
|
10
|
+
self.brands = {}
|
8
11
|
|
9
12
|
attr_reader :number
|
10
13
|
|
@@ -12,24 +15,21 @@ module CreditCardValidations
|
|
12
15
|
@number = number.to_s.tr('- ', '')
|
13
16
|
end
|
14
17
|
|
15
|
-
# credit card number
|
18
|
+
# credit card number validation
|
16
19
|
def valid?(*brands)
|
17
20
|
!!valid_number?(*brands)
|
18
21
|
end
|
19
22
|
|
20
23
|
#brand name
|
21
|
-
def brand(*
|
22
|
-
valid_number?(*
|
24
|
+
def brand(*keys)
|
25
|
+
valid_number?(*keys)
|
23
26
|
end
|
24
27
|
|
25
|
-
def valid_number?(*
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
rules.each do |rule|
|
31
|
-
return brand_name if ((rule[:skip_luhn] || valid_luhn?) and rule[:length].include?(number_length) and number.match(rule[:regexp]))
|
32
|
-
end
|
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
33
|
end
|
34
34
|
end
|
35
35
|
nil
|
@@ -40,30 +40,113 @@ module CreditCardValidations
|
|
40
40
|
@valid_luhn ||= Luhn.valid?(number)
|
41
41
|
end
|
42
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
|
+
|
43
74
|
class << self
|
44
75
|
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def brand_key(brand_name)
|
108
|
+
brands.detect do |_, brand|
|
109
|
+
brand[:options][:brand_name] == brand_name
|
110
|
+
end.try(:first)
|
111
|
+
end
|
112
|
+
|
113
|
+
# CreditCardValidations.delete_brand(:en_route)
|
114
|
+
def delete_brand(key)
|
115
|
+
key = key.to_sym
|
116
|
+
undef_brand_method(key)
|
117
|
+
brands.reject! { |k, _| k == key }
|
48
118
|
end
|
49
119
|
|
50
120
|
#create rule for detecting brand
|
51
|
-
def add_rule(
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
121
|
+
def add_rule(key, length, prefixes)
|
122
|
+
unless brands.has_key?(key)
|
123
|
+
raise RuntimeError.new("brand #{key} is undefined, please use #add_brand method")
|
124
|
+
end
|
125
|
+
length, prefixes = Array(length), Array(prefixes)
|
126
|
+
brands[key][:rules] << {length: length, regexp: compile_regexp(prefixes), prefixes: prefixes}
|
127
|
+
end
|
56
128
|
|
57
|
-
|
58
|
-
valid?(brand)
|
59
|
-
end unless method_defined? "#{brand}?".to_sym
|
129
|
+
protected
|
60
130
|
|
61
|
-
|
131
|
+
# create methods like visa?, maestro? etc
|
132
|
+
def define_brand_method(key)
|
133
|
+
define_method "#{key}?".to_sym do
|
134
|
+
valid?(key)
|
135
|
+
end unless method_defined? "#{key}?".to_sym
|
62
136
|
end
|
63
|
-
end
|
64
137
|
|
138
|
+
def undef_brand_method(key)
|
139
|
+
undef_method "#{key}?".to_sym if method_defined? "#{key}?".to_sym
|
140
|
+
end
|
65
141
|
|
66
|
-
end
|
67
142
|
|
143
|
+
#create regexp by array of prefixes
|
144
|
+
def compile_regexp(prefixes)
|
145
|
+
Regexp.new("^((#{prefixes.join(")|(")}))")
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
68
151
|
|
69
152
|
end
|