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 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