credit_card_support 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.txt CHANGED
@@ -1,3 +1,8 @@
1
+ 2.0.0
2
+
3
+ Rewrite.
4
+
5
+
1
6
  1.0.2
2
7
 
3
8
  Readme improvement.
data/README.md CHANGED
@@ -12,18 +12,19 @@ Basic usage
12
12
  -----------
13
13
 
14
14
  ```ruby
15
- credit_card = CreditCardSupport::Instrument.new(
16
- number: '4222222222222',
17
- expiry_year: 13,
18
- expiry_month: 11,
19
- holder_name: 'A B', # optional
20
- verification: '1234' # optional
21
- )
22
-
23
- credit_card.is_expired? # returns false
24
- credit_card.expiration_date # Date (last day of the month for expire month)
25
- credit_card.issuer # :visa
26
- credit_card.is_testcard? # true
15
+ # for a String object
16
+ credit_card_number = "5500005555555559"
17
+ credit_card_number.extend(CreditCardSupport::CreditCardNumber)
18
+
19
+ # or for all the Strings
20
+ class String
21
+ include CreditCardSupport::CreditCardNumber)
22
+ end
23
+
24
+ # Usage
25
+ credit_card_number.luhn? # true
26
+ credit_card_number.issuer # :visa
27
+ credit_card_number.test? # true
27
28
  ```
28
29
 
29
30
  For better understanding read the source and RSpec.
@@ -32,28 +33,36 @@ For better understanding read the source and RSpec.
32
33
  Validations support
33
34
  -------------------
34
35
 
36
+ * validates Luhn (http://en.wikipedia.org/wiki/Luhn_algorithm)
37
+ * validates issuer
38
+ * validates that number is not a test number
39
+ * for presence, length etc, please use Rails already existing validators :)
40
+
35
41
  ### Define
36
42
 
37
- ##### ActiveRecord or Mongoid
43
+ #### ActiveRecord or Mongoid
38
44
 
39
45
  ```ruby
40
46
  class CreditCard < ActiveRecord::Base
41
47
 
42
- attr_accessible :number, :expiry_year, :expiry_month
48
+ attr_accessible :number
43
49
 
44
- validates :number, credit_card: {
45
- expiry_year: :expiry_year, # default: :expiry_year
46
- expiry_month: :expiry_month, # default: :expiry_month
47
- allowed_issuers: [:visa, :mastercard], # default: all known issuers
48
- allow_testcards: true # default: False
49
- }
50
+ validates :number, presence: true,
51
+ length: {
52
+ minimum: 13,
53
+ maximum: 19
54
+ },
55
+ credit_card_number: {
56
+ allow_testcards: true,
57
+ allow_issuers: [:visa, :master_card]
58
+ }
50
59
 
51
60
  end
52
61
  ```
53
62
 
54
- ##### Only ActiveModel
63
+ #### Only ActiveModel
55
64
 
56
- ###### Rails 3
65
+ ##### Rails 3
57
66
 
58
67
  ```ruby
59
68
  class CreditCard
@@ -62,23 +71,27 @@ class CreditCard
62
71
  include ActiveModel::Validations
63
72
  include ActiveModel::Conversion
64
73
 
65
- attr_accessor :number, :expiry_year, :expiry_month
74
+ attr_accessor :number, :expiry_year, :expiry_month, :errors
66
75
 
67
76
  def initialize(opts={})
77
+ @errors = ActiveModel::Errors.new(self)
68
78
  opts.each {|key, value| send(:"#{key}=", value) }
69
79
  end
70
80
 
71
- validates :number, credit_card: {
72
- expiry_year: :expiry_year, # default: :expiry_year
73
- expiry_month: :expiry_month, # default: :expiry_month
74
- allowed_issuers: [:visa, :mastercard], # default: all known issuers
75
- allow_testcards: true # default: False
76
- }
81
+ validates :number, presence: true,
82
+ length: {
83
+ minimum: 13,
84
+ maximum: 19
85
+ },
86
+ credit_card_number: {
87
+ allow_testcards: true,
88
+ allow_issuers: [:visa, :master_card]
89
+ }
77
90
 
78
91
  end
79
92
  ```
80
93
 
81
- ###### Rails 4
94
+ ##### Rails 4
82
95
 
83
96
  ```ruby
84
97
  class CreditCard
@@ -86,12 +99,15 @@ class CreditCard
86
99
 
87
100
  attr_accessor :number, :expiry_year, :expiry_month
88
101
 
89
- validates :number, credit_card: {
90
- expiry_year: :expiry_year, # default: :expiry_year
91
- expiry_month: :expiry_month, # default: :expiry_month
92
- allowed_issuers: [:visa, :mastercard], # default: all known issuers
93
- allow_testcards: true # default: False
94
- }
102
+ validates :number, presence: true,
103
+ length: {
104
+ minimum: 13,
105
+ maximum: 19
106
+ },
107
+ credit_card_number: {
108
+ allow_testcards: true,
109
+ allow_issuers: [:visa, :master_card]
110
+ }
95
111
 
96
112
  end
97
113
  ```
@@ -105,20 +121,12 @@ today = Date.today
105
121
  ```
106
122
 
107
123
  ```ruby
108
- # RAILS_ENV == 'test'
109
- creditcard = CreditCard.new(number: '4000111111111115', expiry_year: today.year, expiry_month: today.month)
110
- creditcard.valid? # True
111
-
112
- # RAILS_ENV == 'production'
113
- creditcard = CreditCard.new(number: '4000111111111115', expiry_year: today.year, expiry_month: today.month)
114
- creditcard.valid? # False
115
- creditcard.errors # {number: ["is a test number"]}
116
-
117
- # RAILS_ENV == 'test'
118
- creditcard = CreditCard.new(number: '3566003566003566', expiry_year: today.year-1, expiry_month: today.month)
119
- creditcard.valid? # False
120
- creditcard.errors # {number: ["is a JCB card number and is not supported", expiry_year: ["is in the past"], expiry_month: ["is in the past"]]}
124
+ creditcard = CreditCard.new(number: '4000111111111115')
125
+ creditcard.valid? # true
121
126
 
127
+ # in case of allow_testcards is set to false
128
+ creditcard.valid? # false
129
+ creditcard.errors # <ActiveModel::Errors: ... @messages={:number=>["testcards not supported"]}>
122
130
  ```
123
131
 
124
132
 
@@ -1,3 +1,3 @@
1
1
  require 'credit_card_support/luhn_number'
2
- require 'credit_card_support/instrument'
3
- require 'credit_card_support/credit_card_validator' if Object.const_defined?(:ActiveModel)
2
+ require 'credit_card_support/credit_card_number'
3
+ require 'credit_card_support/validators/credit_card_number_validator' if Object.const_defined?(:ActiveModel)
@@ -0,0 +1,86 @@
1
+ require 'date'
2
+
3
+ module CreditCardSupport
4
+ module CreditCardNumber
5
+ include CreditCardSupport::LuhnNumber
6
+
7
+ ISSUERS = {
8
+ amex: /^3[47][0-9]{13}$/,
9
+ diners_club: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
10
+ discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
11
+ enroute: /^2(014|149)\d{11}$/,
12
+ maestro: /(^6759[0-9]{2}([0-9]{10})$)|(^6759[0-9]{2}([0-9]{12})$)|(^6759[0-9]{2}([0-9]{13})$)/,
13
+ master_card: /^5[1-5][0-9]{14}$/,
14
+ jcb: /^35[0-9]{14}$/,
15
+ solo: /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/,
16
+ visa: /^4[0-9]{12}(?:[0-9]{3})?$/
17
+ }.freeze
18
+
19
+ TESTCARDS = {
20
+ amex: [
21
+ '378282246310005',
22
+ '371449635398431',
23
+ '378734493671000',
24
+ '343434343434343',
25
+ '371144371144376',
26
+ '341134113411347'
27
+ ],
28
+ diners_club: [
29
+ '30569309025904',
30
+ '38520000023237',
31
+ '36438936438936'
32
+ ],
33
+ discover: [
34
+ '6011000990139424',
35
+ '6011111111111117',
36
+ '6011016011016011',
37
+ '6011000000000004',
38
+ '6011000995500000',
39
+ '6500000000000002'
40
+ ],
41
+ master_card: [
42
+ '5555555555554444',
43
+ '5105105105105100',
44
+ '5500005555555559',
45
+ '5555555555555557',
46
+ '5454545454545454',
47
+ '5555515555555551',
48
+ '5405222222222226',
49
+ '5478050000000007',
50
+ '5111005111051128',
51
+ '5112345112345114'
52
+ ],
53
+ visa: [
54
+ '4111111111111111',
55
+ '4012888888881881',
56
+ '4222222222222',
57
+ '4005519200000004',
58
+ '4009348888881881',
59
+ '4012000033330026',
60
+ '4012000077777777',
61
+ '4217651111111119',
62
+ '4500600000000061',
63
+ '4000111111111115'
64
+ ],
65
+ jcb: [
66
+ '3566003566003566',
67
+ '3528000000000007'
68
+ ]
69
+ }.freeze
70
+
71
+ def issuer
72
+ ISSUERS.each do |issuer, number_match|
73
+ return issuer if to_s.match(number_match)
74
+ end
75
+ nil
76
+ end
77
+
78
+ def testcard?
79
+ TESTCARDS.each do |issuer, numbers|
80
+ return true if numbers.include?(to_s)
81
+ end
82
+ false
83
+ end
84
+
85
+ end
86
+ end
@@ -1,10 +1,9 @@
1
1
  en:
2
- credit_card_validator:
3
- messages:
4
- luhn_not_valid: "is not valid"
5
- issuer_not_known: "not known issuer"
6
- issuer_not_supported: "%{issuer} not supported"
7
- testcard_not_supported: "testcards not supported"
8
- cant_be_blank: "can't be blank"
9
- is_expired: "is expired"
10
- cant_be_so_far_in_the_future: "can't be so far in the future"
2
+ credit_card_support:
3
+ validators:
4
+ number:
5
+ messages:
6
+ luhn_not_valid: "is not valid"
7
+ issuer_not_known: "not known issuer"
8
+ issuer_not_supported: "%{issuer} not supported"
9
+ testcard_not_supported: "testcards not supported"
@@ -1,27 +1,10 @@
1
1
  module CreditCardSupport
2
+ module LuhnNumber
2
3
 
3
- class LuhnNumber
4
-
5
- attr_accessor :full_number
6
-
7
- def initialize(full_number)
8
- self.full_number = full_number
9
- end
10
-
11
- def full_number=(full_number)
12
- @full_number = full_number.gsub(/[^0-9]/, '').to_s
13
- end
14
-
15
- def number_part
16
- full_number[0, full_number.length-1] if full_number
17
- end
18
-
19
- def check_digit_part
20
- full_number[-1, 1] if full_number
21
- end
22
-
23
- def valid?
4
+ def luhn?
5
+ full_number = to_s.gsub(/[^0-9]/, '')
24
6
  return false unless full_number and full_number.length > 2
7
+
25
8
  parts = full_number.split(//).map(&:to_i)
26
9
 
27
10
  double = parts.length % 2 == 0
@@ -35,10 +18,5 @@ module CreditCardSupport
35
18
  sum % 10 == 0
36
19
  end
37
20
 
38
- def invalid?
39
- !valid?
40
- end
41
-
42
21
  end
43
-
44
22
  end
@@ -0,0 +1,48 @@
1
+ I18n.load_path << File.dirname(__FILE__) + '/../locale/en.yml'
2
+
3
+ class CreditCardNumberValidator < ActiveModel::EachValidator
4
+
5
+ def validate_each(record, attribute, value)
6
+ @record = record
7
+ @attribute = attribute
8
+ @value = value
9
+
10
+ @value.extend(CreditCardSupport::CreditCardNumber)
11
+
12
+ validates_luhn &&
13
+ validates_testcard &&
14
+ validates_issuer_allowed
15
+ end
16
+
17
+ def validates_luhn
18
+ if !@value.luhn?
19
+ @record.errors.add(@attribute, t(:luhn_not_valid))
20
+ false
21
+ else
22
+ true
23
+ end
24
+ end
25
+
26
+ def validates_testcard
27
+ if !options[:allow_testcards] && @value.testcard?
28
+ @record.errors.add(@attribute, t(:testcard_not_supported))
29
+ false
30
+ else
31
+ true
32
+ end
33
+ end
34
+
35
+ def validates_issuer_allowed
36
+ if options[:allow_issuers] && !options[:allow_issuers].include?(@value.issuer)
37
+ @record.errors.add(@attribute, t(:issuer_not_supported, issuer: @value.issuer))
38
+ false
39
+ else
40
+ true
41
+ end
42
+ end
43
+
44
+ def t(code, *args)
45
+ I18n.t("credit_card_support.validators.number.messages.#{code}", *args)
46
+ end
47
+
48
+ end
@@ -1,8 +1,8 @@
1
1
  module CreditCardSupport
2
2
  module Version
3
- MAJOR = 1
3
+ MAJOR = 2
4
4
  MINOR = 0
5
- PATCH = 2
5
+ PATCH = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'date'
3
+
4
+ describe CreditCardSupport::CreditCardNumber do
5
+
6
+ describe "#testcard?" do
7
+ context "a testcard" do
8
+ it "returns true" do
9
+ number = "378282246310005"
10
+ number.extend(described_class)
11
+ number.should be_testcard
12
+ end
13
+ end
14
+ context "not a testcard" do
15
+ it "returns false" do
16
+ number = "4916119578780242"
17
+ number.extend(described_class)
18
+ number.should_not be_testcard
19
+ end
20
+ end
21
+ end
22
+
23
+ describe "#issuer" do
24
+ described_class::TESTCARDS.each do |issuer, numbers|
25
+ context "#{issuer}" do
26
+ it "is recognized" do
27
+ numbers.each do |number|
28
+ number.extend(described_class)
29
+ number.issuer.should == issuer
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -2,59 +2,38 @@ require 'spec_helper'
2
2
 
3
3
  describe CreditCardSupport::LuhnNumber do
4
4
 
5
- subject { described_class.new("4485071359608368") }
6
-
7
- describe "#initialize" do
8
- it "sets full_number" do
9
- described_class.any_instance.should_receive(:full_number=).with('123')
10
- described_class.new('123')
11
- end
12
- end
13
-
14
- describe "#full_number" do
15
- it "returns only number part of set value" do
16
- subject.full_number = '123 abc-'
17
- subject.full_number.should == '123'
18
- end
19
- end
20
-
21
- describe "#number_part" do
22
- it "returns number part of full_number" do
23
- subject.number_part.should == "448507135960836"
24
- end
25
- end
26
-
27
- describe "#check_digit_part" do
28
- it "returns number part of full_number" do
29
- subject.check_digit_part.should == "8"
30
- end
31
- end
32
-
33
- describe "#valid?" do
34
- it "returns if luhn algorytm % 10 is true" do
35
- subject.should be_valid
36
- end
37
- end
38
-
39
- describe "#valid?" do
5
+ VALID_LUHNS = [
6
+ "5500005555555559",
7
+ "5555555555555557",
8
+ "5454545454545454",
9
+ "5555515555555551",
10
+ "5405222222222226",
11
+ "5478050000000007"
12
+ ]
13
+ INVALID_LUHNS = [
14
+ "5500005555555558",
15
+ "5555555555555556",
16
+ "5454545454545453",
17
+ "5555515555555550",
18
+ "5405222222222225",
19
+ "5478050000000006"
20
+ ]
21
+
22
+ describe "#luhn?" do
40
23
  context "valid luhn" do
41
24
  it "returns true" do
42
- described_class.new("5500005555555559").should be_valid
43
- described_class.new("5555555555555557").should be_valid
44
- described_class.new("5454545454545454").should be_valid
45
- described_class.new("5555515555555551").should be_valid
46
- described_class.new("5405222222222226").should be_valid
47
- described_class.new("5478050000000007").should be_valid
25
+ VALID_LUHNS.each do |number|
26
+ number.extend(described_class)
27
+ number.should be_luhn
28
+ end
48
29
  end
49
30
  end
50
31
  context "invalid luhn" do
51
32
  it "returns false" do
52
- described_class.new("5500005555555558").should be_invalid
53
- described_class.new("5555555555555556").should be_invalid
54
- described_class.new("5454545454545453").should be_invalid
55
- described_class.new("5555515555555550").should be_invalid
56
- described_class.new("5405222222222225").should be_invalid
57
- described_class.new("5478050000000006").should be_invalid
33
+ INVALID_LUHNS.each do |number|
34
+ number.extend(described_class)
35
+ number.should_not be_luhn
36
+ end
58
37
  end
59
38
  end
60
39
  end
@@ -0,0 +1,81 @@
1
+ require 'active_model'
2
+ require 'spec_helper'
3
+ require 'credit_card_support/validators/credit_card_number_validator' if Object.const_defined?(:ActiveModel)
4
+
5
+ class CreditCard
6
+ extend ActiveModel::Naming
7
+ extend ActiveModel::Translation
8
+ include ActiveModel::Validations
9
+ include ActiveModel::Conversion
10
+
11
+ attr_accessor :number
12
+
13
+ def errors
14
+ @errors ||= ActiveModel::Errors.new(self)
15
+ end
16
+
17
+ def initialize(opts={})
18
+ opts.each do |k,v|
19
+ send(:"#{k}=", v)
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ class CreditCardTest < CreditCard
26
+ validates :number, presence: true,
27
+ length: {
28
+ minimum: 13,
29
+ maximum: 19
30
+ },
31
+ credit_card_number: {
32
+ allow_testcards: true,
33
+ allow_issuers: [:visa, :master_card]
34
+ }
35
+ end
36
+
37
+ class CreditCardProduction < CreditCard
38
+ validates :number, presence: true,
39
+ length: {
40
+ minimum: 13,
41
+ maximum: 19
42
+ },
43
+ credit_card_number: {
44
+ allow_testcards: false,
45
+ allow_issuers: [:visa, :master_card]
46
+ }
47
+ end
48
+
49
+
50
+ describe CreditCardNumberValidator do
51
+ subject { CreditCardTest.new(number: '4012888888881881') }
52
+
53
+ it "is valid" do
54
+ subject.should be_valid
55
+ end
56
+
57
+ describe "#number" do
58
+ it "must exist" do
59
+ subject.number = nil
60
+ subject.should_not be_valid
61
+ end
62
+ it "must be luhn" do
63
+ subject.number = '4012888888881882'
64
+ subject.should_not be_valid
65
+ end
66
+ context "production" do
67
+ subject { CreditCardProduction.new(number: '4485071359608368') }
68
+ context "testnumber" do
69
+ it "is invalid" do
70
+ subject.number = '4012888888881881'
71
+ subject.should be_invalid
72
+ end
73
+ end
74
+ context "valid number" do
75
+ it "is valid" do
76
+ subject.should be_valid
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: credit_card_support
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -22,15 +22,15 @@ files:
22
22
  - README.md
23
23
  - LICENSE.txt
24
24
  - HISTORY.txt
25
- - lib/credit_card_support/credit_card_validator.rb
26
- - lib/credit_card_support/instrument.rb
25
+ - lib/credit_card_support/credit_card_number.rb
27
26
  - lib/credit_card_support/locale/en.yml
28
27
  - lib/credit_card_support/luhn_number.rb
28
+ - lib/credit_card_support/validators/credit_card_number_validator.rb
29
29
  - lib/credit_card_support/version.rb
30
30
  - lib/credit_card_support.rb
31
- - spec/lib/credit_card_support/credit_card_validator_spec.rb
32
- - spec/lib/credit_card_support/instrument_spec.rb
31
+ - spec/lib/credit_card_support/credit_card_number_spec.rb
33
32
  - spec/lib/credit_card_support/luhn_number_spec.rb
33
+ - spec/lib/credit_card_support/validators/credit_card_number_validator_spec.rb
34
34
  - spec/spec_helper.rb
35
35
  homepage: https://github.com/tione/credit_card_support
36
36
  licenses: []
@@ -1,87 +0,0 @@
1
- I18n.load_path << File.dirname(__FILE__) + '/locale/en.yml'
2
-
3
- class CreditCardValidator < ActiveModel::EachValidator
4
-
5
- def validate_each(record, attribute, value)
6
- @record = record
7
-
8
- opts = options.dup
9
- opts[:number] ||= attribute
10
-
11
- @number_name = opts[:number] || :number
12
- @expiry_year_name = opts[:expiry_year] || :expiry_year
13
- @expiry_month_name = opts[:expiry_month] || :expiry_month
14
-
15
- @number = record.send(@number_name)
16
- @expiry_year = record.send(@expiry_year_name)
17
- @expiry_month = record.send(@expiry_month_name)
18
- @allowed_issuers = opts[:allowed_issuers]
19
- @allow_testcards = opts[:allow_testcards]
20
-
21
- @instrument = CreditCardSupport::Instrument.new(number: @number, expiry_year: @expiry_year, expiry_month: @expiry_month)
22
-
23
- validate_number
24
- validate_expiration
25
- end
26
-
27
-
28
- private
29
-
30
- def errors
31
- @record.errors
32
- end
33
-
34
- def instrument
35
- @instrument
36
- end
37
-
38
- def validate_number
39
- validate_number_luhn
40
- validate_number_issuer
41
- validate_number_testcard
42
- end
43
-
44
- def validate_expiration
45
- validate_expiry_year
46
- validate_expiry_month
47
- validate_expiration_date rescue nil
48
- end
49
-
50
- def validate_number_luhn
51
- errors.add(@number_name, t(:luhn_not_valid)) unless @instrument.has_valid_luhn?
52
- end
53
-
54
- def validate_number_issuer
55
- errors.add(@number_name, t(:issuer_not_known)) unless @instrument.issuer
56
- if @allowed_issuers and @instrument.issuer and !@allowed_issuers.map(&:to_sym).include?(@instrument.issuer)
57
- errors.add(@number_name, t(:issuer_not_supported, issuer: @instrument.issuer.to_s.upcase))
58
- end
59
- end
60
-
61
- def validate_number_testcard
62
- errors.add(@number_name, t(:testcard_not_supported)) if !@allow_testcards && @instrument.is_testcard?
63
- end
64
-
65
- def validate_expiry_year
66
- errors.add(@expiry_year_name, t(:cant_be_blank)) unless @instrument.expiry_year
67
- end
68
-
69
- def validate_expiry_month
70
- errors.add(@expiry_month_name, t(:cant_be_blank)) unless @instrument.expiry_month
71
- end
72
-
73
- def validate_expiration_date
74
- if @instrument.is_expired?
75
- errors.add(@expiry_year_name, t(:cant_be_expired))
76
- errors.add(@expiry_month_name, t(:cant_be_expired))
77
- end
78
- if @instrument.expiration_date > Date.new(Date.today.year+10)
79
- errors.add(@expiry_year_name, t(:cant_be_so_far_in_the_future))
80
- end
81
- end
82
-
83
- def t(code, *args)
84
- I18n.t("credit_card_validator.messages.#{code}", *args)
85
- end
86
-
87
- end
@@ -1,164 +0,0 @@
1
- require 'date'
2
-
3
- module CreditCardSupport
4
-
5
- # Usage:
6
- #
7
- # credit_card = CreditCardSupport.Instrument.new(
8
- # number: '4222222222222',
9
- # expiry_year: 13,
10
- # expiry_month: 11,
11
- # holder_name: 'A B'
12
- # verification: '1234' # optional!
13
- # )
14
- #
15
- # credit_card.expired? # returns false
16
- # credit_card.expiration_date # Date (last day of the month for expiry month)
17
- # credit_card.issuer # VISA
18
- # credit_card.is_testcard? # true
19
-
20
-
21
- class Instrument
22
-
23
- NUMBERS = {
24
- amex: /^3[47][0-9]{13}$/,
25
- diners_club: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
26
- discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
27
- enroute: /^2(014|149)\d{11}$/,
28
- maestro: /(^6759[0-9]{2}([0-9]{10})$)|(^6759[0-9]{2}([0-9]{12})$)|(^6759[0-9]{2}([0-9]{13})$)/,
29
- master_card: /^5[1-5][0-9]{14}$/,
30
- jcb: /^35[0-9]{14}$/,
31
- solo: /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/,
32
- visa: /^4[0-9]{12}(?:[0-9]{3})?$/
33
- }.freeze
34
-
35
- TESTCARD_NUMBERS = {
36
- amex: [
37
- '378282246310005',
38
- '371449635398431',
39
- '378734493671000',
40
- '343434343434343',
41
- '371144371144376',
42
- '341134113411347'
43
- ],
44
- diners_club: [
45
- '30569309025904',
46
- '38520000023237',
47
- '36438936438936'
48
- ],
49
- discover: [
50
- '6011000990139424',
51
- '6011111111111117',
52
- '6011016011016011',
53
- '6011000000000004',
54
- '6011000995500000',
55
- '6500000000000002'
56
- ],
57
- master_card: [
58
- '5555555555554444',
59
- '5105105105105100',
60
- '5500005555555559',
61
- '5555555555555557',
62
- '5454545454545454',
63
- '5555515555555551',
64
- '5405222222222226',
65
- '5478050000000007',
66
- '5111005111051128',
67
- '5112345112345114'
68
- ],
69
- visa: [
70
- '4111111111111111',
71
- '4012888888881881',
72
- '4222222222222',
73
- '4005519200000004',
74
- '4009348888881881',
75
- '4012000033330026',
76
- '4012000077777777',
77
- '4217651111111119',
78
- '4500600000000061',
79
- '4000111111111115'
80
- ],
81
- jcb: [
82
- '3566003566003566',
83
- '3528000000000007'
84
- ]
85
- }.freeze
86
-
87
- def self.numbers
88
- self::NUMBERS
89
- end
90
-
91
- def self.testcard_numbers
92
- self::TESTCARD_NUMBERS
93
- end
94
-
95
- def self.determine_issuer(number)
96
- numbers.each do |issuer, issuer_regexp|
97
- return issuer if number.match(issuer_regexp)
98
- end
99
- nil
100
- end
101
-
102
- def self.is_testcard?(number)
103
- testcard_numbers.values.flatten.include?(number)
104
- end
105
-
106
- def self.has_valid_luhn?(number)
107
- CreditCardSupport::LuhnNumber.new(number).valid?
108
- end
109
-
110
- attr_accessor :number,
111
- :expiry_year,
112
- :expiry_month,
113
- :holder_name,
114
- :verification
115
-
116
- attr_reader :issuer
117
-
118
- def initialize(opts={}, &block)
119
- self.number = opts[:number]
120
- self.expiry_year = opts[:expiry_year]
121
- self.expiry_month = opts[:expiry_month]
122
- self.holder_name = opts[:holder_name]
123
- self.verification = opts[:verification]
124
-
125
- block.call(self) if block
126
- end
127
-
128
- def number=(number)
129
- @number = number.to_s.gsub(/[^0-9]/, '')
130
- @issuer = self.class.determine_issuer(self.number)
131
- @is_testcard = self.class.is_testcard?(self.number)
132
- @has_valid_luhn = self.class.has_valid_luhn?(self.number)
133
- end
134
-
135
- def expiry_year=(expiry_year)
136
- @expiry_year = expiry_year.to_i if expiry_year
137
- end
138
-
139
- def expiry_month=(expiry_month)
140
- @expiry_month = expiry_month.to_i if expiry_month
141
- end
142
-
143
- def expiry_year
144
- (@expiry_year < 1900 ? 2000 + @expiry_year : @expiry_year) if @expiry_year
145
- end
146
-
147
- def expiration_date
148
- Date.new(expiry_year, expiry_month, -1) rescue nil
149
- end
150
-
151
- def is_expired?
152
- expiration_date < Date.today
153
- end
154
-
155
- def is_testcard?
156
- @is_testcard
157
- end
158
-
159
- def has_valid_luhn?
160
- @has_valid_luhn
161
- end
162
-
163
- end
164
- end
@@ -1,127 +0,0 @@
1
- require 'active_model'
2
- require 'spec_helper'
3
-
4
-
5
- class CreditCard
6
- extend ActiveModel::Naming
7
- extend ActiveModel::Translation
8
- include ActiveModel::Validations
9
- include ActiveModel::Conversion
10
-
11
- attr_accessor :number, :expiry_year, :expiry_month
12
-
13
- def initialize(opts={})
14
- @errors = ActiveModel::Errors.new(self)
15
- opts.each do |k,v|
16
- send(:"#{k}=", v)
17
- end
18
- end
19
-
20
- end
21
-
22
- class CreditCardTest < CreditCard
23
- validates :number, credit_card: {
24
- expiry_year: :expiry_year,
25
- expiry_month: :expiry_month,
26
- allowed_issuers: [:visa, :mastercard],
27
- allow_testcards: true
28
- }
29
- end
30
-
31
- class CreditCardProduction < CreditCard
32
- validates :number, credit_card: {
33
- expiry_year: :expiry_year,
34
- expiry_month: :expiry_month,
35
- allowed_issuers: [:visa, :mastercard],
36
- allow_testcards: false
37
- }
38
- end
39
-
40
-
41
-
42
- describe CreditCardValidator do
43
- let(:today) { Date.today }
44
-
45
- describe "validatable" do
46
-
47
- subject { CreditCardTest.new(number: '4012888888881881', expiry_year: today.year, expiry_month: today.month) }
48
-
49
- it "is valid" do
50
- subject.should be_valid
51
- end
52
-
53
- describe "#number" do
54
- it "must exist" do
55
- subject.number = nil
56
- subject.should_not be_valid
57
- end
58
- it "must be luhn" do
59
- subject.number = '4000111111111116'
60
- subject.should_not be_valid
61
- end
62
- context "production" do
63
- subject { CreditCardProduction.new(number: '4485071359608368', expiry_year: today.year, expiry_month: today.month) }
64
- context "testnumber" do
65
- it "is invalid" do
66
- subject.number = '4012888888881881'
67
- subject.should be_invalid
68
- end
69
- end
70
- context "valid number" do
71
- it "is valid" do
72
- subject.should be_valid
73
- end
74
- end
75
- end
76
- end
77
-
78
- describe "#expiry_year" do
79
- it "must exist" do
80
- subject.expiry_year = nil
81
- subject.should_not be_valid
82
- end
83
- context "in the future" do
84
- it "is valid" do
85
- subject.expiry_year = today.year + 1
86
- subject.should be_valid
87
- end
88
- end
89
- context "in the future too much" do
90
- it "is valid" do
91
- subject.expiry_year = today.year + 11
92
- subject.should be_invalid
93
- end
94
- end
95
- context "in the past" do
96
- it "is invalid" do
97
- subject.expiry_year = today.year - 1
98
- subject.should be_invalid
99
- end
100
- end
101
- end
102
-
103
- describe "#expiry_month" do
104
- it "must exist" do
105
- subject.expiry_month = nil
106
- subject.should_not be_valid
107
- end
108
- context "in the future" do
109
- it "is valid" do
110
- subject.expiry_month = today.month + 1
111
- subject.valid?
112
- puts subject.errors.inspect
113
- subject.should be_valid
114
- end
115
- end
116
- context "in the past" do
117
- it "is invalid" do
118
- subject.expiry_month = today.month - 1
119
- subject.should be_invalid
120
- end
121
- end
122
- end
123
-
124
- end
125
-
126
- end
127
-
@@ -1,125 +0,0 @@
1
- require 'spec_helper'
2
- require 'date'
3
-
4
- describe CreditCardSupport::Instrument do
5
- let(:today) { Date.today }
6
- subject { described_class.new(number: 4222222222222, expiry_year: "#{today.year}", expiry_month: "12", holder_name: 'A B', verification: '1234') }
7
-
8
- describe ".numbers" do
9
- it "returns hash containing of issuer: number_regexp" do
10
- described_class.numbers.should be_a(Hash)
11
- end
12
- end
13
-
14
- describe ".testcard_numbers" do
15
- it "returns hash containing of testcard numbers" do
16
- described_class.testcard_numbers.should be_a(Hash)
17
- end
18
- end
19
-
20
- describe ".determine_issuer" do
21
- described_class.testcard_numbers.each do |issuer, numbers|
22
- context "#{issuer}" do
23
- it "knows the numbers" do
24
- numbers.each do |number|
25
- described_class.determine_issuer(number).should == issuer
26
- end
27
- end
28
- end
29
- end
30
- end
31
-
32
- describe ".is_testcard?" do
33
- it "determines if number is testnumber" do
34
- described_class.is_testcard?("4000111111111115").should be_true
35
- described_class.is_testcard?("4000111111111116").should be_false
36
- end
37
- end
38
-
39
- describe "#initialize" do
40
- it "supports a hash" do
41
- instrument = described_class.new(number: "123")
42
- instrument.number.should == "123"
43
- end
44
- it "supports a block" do
45
- instrument = described_class.new do |instrument_object|
46
- instrument_object.number = "123"
47
- end
48
- instrument.number.should == "123"
49
- end
50
- end
51
-
52
- describe "#number" do
53
- it "returns number as a string" do
54
- subject.number = "1-2-3-412345"
55
- subject.number.should == "123412345"
56
- end
57
- end
58
-
59
- describe "#expiry_year" do
60
- it "returns expiry year as NNNN integer" do
61
- subject.expiry_year = "#{today.year-2000}"
62
- subject.expiry_year.should == today.year
63
- end
64
- end
65
-
66
- describe "#expiry_month" do
67
- it "returns expiry month as NN integer" do
68
- subject.expiry_month = "#{today.month}"
69
- subject.expiry_month.should == today.month
70
- end
71
- end
72
-
73
- describe "#expiration_date" do
74
- it "returns last day of the month when expiring" do
75
- subject.expiration_date.should == Date.new(today.year, 12, 31)
76
- end
77
- end
78
-
79
- describe "#is_expired?" do
80
- context "not expired" do
81
- it "returns false" do
82
- subject.is_expired?.should be_false
83
- end
84
- end
85
- context "expired" do
86
- it "returns true" do
87
- subject.expiry_year = today.year
88
- subject.expiry_month = today.month-1
89
- subject.is_expired?.should be_true
90
- end
91
- end
92
- end
93
-
94
- describe "#is_testcard?" do
95
- context "not a testcard" do
96
- it "returns false" do
97
- subject.number = 4222222222223
98
- subject.is_testcard?.should be_false
99
- end
100
- end
101
- context "a testcard" do
102
- it "returns true" do
103
- subject.number = 4222222222222
104
- subject.is_testcard?.should be_true
105
- end
106
- end
107
- end
108
-
109
- describe "has_valid_luhn?" do
110
- context "valid luhn" do
111
- it "returns true" do
112
- subject.number = 4222222222222
113
- subject.is_testcard?.should be_true
114
- end
115
- end
116
- context "invalid luhn" do
117
- it "returns false" do
118
- subject.number = 4222222222223
119
- subject.is_testcard?.should be_false
120
- end
121
- end
122
- end
123
-
124
-
125
- end