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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9dc4aaa6683f8c4b1b0bcfea5ca31076fa3109f2
4
- data.tar.gz: 33aba134c4e4b5e445f51796740ffe8477d6fdac
3
+ metadata.gz: 9132f1754e530b96267778bd96cecaf3838ec575
4
+ data.tar.gz: 7b805a21857451ce13ad3d86776c0c192e8ac5fa
5
5
  SHA512:
6
- metadata.gz: 2bf428e04e75942465f12bec35b449943bd3825c20ecc709364a937f861041284c16909430afb74fd0e9cf76492c7b6f167580d6a8179f6273d60e8def127a65
7
- data.tar.gz: f164f228a43fd51410c03040b11f0b0cdb3c09dd1d9a3a2aa7e193f166eec3ad6d43dd9616b9ffef557d237d07089132a5a75c3ed6762eb1c40546fcdb98b030
6
+ metadata.gz: 61bfa339a1c364fcededa512391ac3a43a57d102d41e95bb47782d920c6835a430b27ebd169323b22e8a555bcecff0150ba758f495ca778fb92c822c53184ea7
7
+ data.tar.gz: 7bcbc2632129f06dc3c429264b5d56820cfacae1fa5b709b636eabc0a5e343d0ffd24389dbd65e34d16308b27aba3da35a35f2729968fe3c9732dc2b3c91d345
data/.travis.yml CHANGED
@@ -1,5 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
- - jruby-19mode
5
4
  - 2.0.0
5
+ - 2.1.5
6
+ - 2.2.0
7
+ - jruby-19mode # JRuby in 1.9 mode
8
+ - rbx-2.2.10
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'.valid_credit_card_brand?(:mastercard, :visa)
55
- '5274 5763 9425 9961'.valid_credit_card_brand?(:amex)
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.add_rule(:voyager, {length: 15, prefixes: '86'})
94
- CreditCardValidations.add_rule(:en_route, {length: 15, prefixes: ['2014', '2149'], skip_luhn: true}) #skip luhn
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 => [:test_units]
6
+ task :default => [:specs]
7
7
 
8
- desc "Run basic tests"
9
- Rake::TestTask.new("test_units") { |t|
10
- t.pattern = 'test/*_test.rb'
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 (a port of ZF2 Zend\Validator\CreditCard)}
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
- brands = options.fetch(:brands, :any)
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
- extend ActiveSupport::Autoload
9
- autoload :VERSION, 'credit_card_validations/version'
10
- autoload :Luhn, 'credit_card_validations/luhn'
11
- autoload :CardRules , 'credit_card_validations/card_rules'
12
- autoload :Detector , 'credit_card_validations/detector'
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
24
- end
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 :rules
7
- self.rules = {}
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(*brands)
22
- valid_number?(*brands)
24
+ def brand(*keys)
25
+ valid_number?(*keys)
23
26
  end
24
27
 
25
- def valid_number?(*brands)
26
- number_length = number.length
27
- brand_rules = brands.blank? ? self.rules : self.rules.slice(*brands.map { |el| el.downcase })
28
- unless brand_rules.blank?
29
- brand_rules.each do |brand_name, rules|
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
- #create regexp by array of prefixes
46
- def compile_regexp(prefixes)
47
- Regexp.new("^((#{prefixes.join(")|(")}))")
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(brand, length, prefixes, skip_luhn = false)
52
- prefixes = Array.wrap(prefixes)
53
- length = Array.wrap(length)
54
- rules[brand] = [] if rules[brand].blank?
55
- rules[brand] << {length: length, regexp: compile_regexp(prefixes), prefixes: prefixes, skip_luhn: skip_luhn}
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
- define_method "#{brand}?".to_sym do
58
- valid?(brand)
59
- end unless method_defined? "#{brand}?".to_sym
129
+ protected
60
130
 
61
- rules[brand]
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