credit_card_validations 1.5.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](http://img.shields.io/travis/Fivell/credit_card_validations.svg)](https://travis-ci.org/Fivell/credit_card_validations)
|
4
|
+
[![Dependency Status](http://img.shields.io/gemnasium/Fivell/credit_card_validations.svg)](https://gemnasium.com/Fivell/credit_card_validations)
|
5
|
+
[![Coverage Status](http://img.shields.io/coveralls/Fivell/credit_card_validations.svg)](https://coveralls.io/r/Fivell/credit_card_validations)
|
6
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/Fivell/credit_card_validations.svg)](https://codeclimate.com/github/Fivell/credit_card_validations)
|
7
|
+
[![Gem Version](http://img.shields.io/gem/v/credit_card_validations.svg)](https://rubygems.org/gems/credit_card_validations)
|
8
|
+
[![License](http://img.shields.io/:license-mit-blue.svg)](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
|
-
[![Build Status](https://travis-ci.org/Fivell/credit_card_validations.png)](https://travis-ci.org/Fivell/credit_card_validations)
|
8
|
-
[![Coverage Status](https://coveralls.io/repos/Fivell/credit_card_validations/badge.png)](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
|