credit_officer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -11,7 +11,7 @@ Use this library so that you can validate credit card information before sending
11
11
  Checks credit card number formats, checksums, and other required details. Supports i18n for better message
12
12
  customization
13
13
 
14
- cc = CreditOfficer::CreditCard.new(
14
+ cc = CreditOfficer::CreditCard.new({
15
15
  :number => "411111111111111",
16
16
  :provider_name => "visa",
17
17
  :name_on_card => "John Doe",
@@ -29,7 +29,7 @@ customization
29
29
  If you want to turn requiring verification values off, make it so:
30
30
 
31
31
  CreditOfficer::CreditCard.require_verification_value = false
32
- cc = CreditOfficer::CreditCard.new(
32
+ cc = CreditOfficer::CreditCard.new({
33
33
  :number => "411111111111111",
34
34
  :provider_name => "visa",
35
35
  :name_on_card => "John Doe",
@@ -48,7 +48,7 @@ Want to only support certain credit cards and card number formats? Make it so:
48
48
  'amex'
49
49
  ]
50
50
 
51
- cc = CreditOfficer::CreditCard.new(
51
+ cc = CreditOfficer::CreditCard.new({
52
52
  :number => "411111111111111",
53
53
  :provider_name => "visa",
54
54
  :name_on_card => "John Doe",
@@ -57,6 +57,29 @@ Want to only support certain credit cards and card number formats? Make it so:
57
57
  :verification_value => ""
58
58
  }).valid? => false
59
59
 
60
+ == Deriving provider names
61
+
62
+ Most of the time, you can derive the credit card provider (Mastercard, AMEX, Visa, etc) based on the format
63
+ of the card number. By default, credit officer attempts to derive this provider name automatically
64
+
65
+ cc = CreditOfficer::CreditCard.new({
66
+ :number => "411111111111111",
67
+ :name_on_card => "John Doe",
68
+ :expiration_year => 2010,
69
+ :expiration_month => 1,
70
+ }).valid? => true
71
+
72
+ cc.provider_name => visa
73
+
74
+
75
+ You can toggle this so that the user must provide a valid provider name like so:
76
+
77
+ CreditOfficer::CreditCard.automatically_derive_provider_name = false
78
+
79
+ You can manually attempt the provider name like so (this will set the provider name according to your number):
80
+
81
+ cc.derive_provider_name
82
+
60
83
  == i18n
61
84
 
62
85
  Error messages can be customized with i18n translations
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -0,0 +1,109 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{credit_officer}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Dan Pickett"]
12
+ s.date = %q{2010-12-22}
13
+ s.description = %q{An upgrade/port of ActiveMerchant's credit card class}
14
+ s.email = %q{dpickett@enlightsolutions.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ ".rvmrc",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.rdoc",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "credit_officer.gemspec",
30
+ "lib/credit_officer.rb",
31
+ "lib/credit_officer/base.rb",
32
+ "lib/credit_officer/credit_card.rb",
33
+ "lib/credit_officer/month_year_pair.rb",
34
+ "spec/credit_officer/base_spec.rb",
35
+ "spec/credit_officer/credit_card_spec.rb",
36
+ "spec/credit_officer/month_year_pair_spec.rb",
37
+ "spec/credit_officer_spec.rb",
38
+ "spec/spec_helper.rb",
39
+ "spec/support/active_model_shared_examples.rb",
40
+ "spec/support/factories.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/dpickett/credit_officer}
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.7}
46
+ s.summary = %q{An activemodel compliant credit card validator}
47
+ s.test_files = [
48
+ "spec/credit_officer/base_spec.rb",
49
+ "spec/credit_officer/credit_card_spec.rb",
50
+ "spec/credit_officer/month_year_pair_spec.rb",
51
+ "spec/credit_officer_spec.rb",
52
+ "spec/spec_helper.rb",
53
+ "spec/support/active_model_shared_examples.rb",
54
+ "spec/support/factories.rb"
55
+ ]
56
+
57
+ if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
+ s.specification_version = 3
60
+
61
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
62
+ s.add_runtime_dependency(%q<activemodel>, [">= 3.0.3"])
63
+ s.add_runtime_dependency(%q<luhney_bin>, [">= 0"])
64
+ s.add_development_dependency(%q<timecop>, ["= 0.3.5"])
65
+ s.add_development_dependency(%q<ruby-debug>, [">= 0"])
66
+ s.add_development_dependency(%q<factory_girl>, [">= 0"])
67
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
68
+ s.add_development_dependency(%q<remarkable_activemodel>, [">= 4.0.0.alpha4"])
69
+ s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
70
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
71
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
72
+ s.add_development_dependency(%q<rcov>, [">= 0"])
73
+ s.add_development_dependency(%q<reek>, ["~> 1.2.8"])
74
+ s.add_development_dependency(%q<roodi>, ["~> 2.1.0"])
75
+ s.add_development_dependency(%q<fuubar>, [">= 0"])
76
+ else
77
+ s.add_dependency(%q<activemodel>, [">= 3.0.3"])
78
+ s.add_dependency(%q<luhney_bin>, [">= 0"])
79
+ s.add_dependency(%q<timecop>, ["= 0.3.5"])
80
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
81
+ s.add_dependency(%q<factory_girl>, [">= 0"])
82
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
83
+ s.add_dependency(%q<remarkable_activemodel>, [">= 4.0.0.alpha4"])
84
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
85
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
86
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
87
+ s.add_dependency(%q<rcov>, [">= 0"])
88
+ s.add_dependency(%q<reek>, ["~> 1.2.8"])
89
+ s.add_dependency(%q<roodi>, ["~> 2.1.0"])
90
+ s.add_dependency(%q<fuubar>, [">= 0"])
91
+ end
92
+ else
93
+ s.add_dependency(%q<activemodel>, [">= 3.0.3"])
94
+ s.add_dependency(%q<luhney_bin>, [">= 0"])
95
+ s.add_dependency(%q<timecop>, ["= 0.3.5"])
96
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
97
+ s.add_dependency(%q<factory_girl>, [">= 0"])
98
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
99
+ s.add_dependency(%q<remarkable_activemodel>, [">= 4.0.0.alpha4"])
100
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
101
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
102
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
103
+ s.add_dependency(%q<rcov>, [">= 0"])
104
+ s.add_dependency(%q<reek>, ["~> 1.2.8"])
105
+ s.add_dependency(%q<roodi>, ["~> 2.1.0"])
106
+ s.add_dependency(%q<fuubar>, [">= 0"])
107
+ end
108
+ end
109
+
@@ -11,7 +11,7 @@ module CreditOfficer
11
11
  #store this private information
12
12
  #
13
13
  #@example
14
- # cc = CreditOfficer::CreditCard.new(
14
+ # cc = CreditOfficer::CreditCard.new({
15
15
  # :number => "411111111111111",
16
16
  # :provider_name => "visa",
17
17
  # :name_on_card => "John Doe",
@@ -91,7 +91,8 @@ module CreditOfficer
91
91
 
92
92
  validate :expiration_date_is_in_future
93
93
  validate :expiration_date_is_in_recent_future
94
- validate :number_is_valid
94
+ validate :number_is_valid
95
+
95
96
  validate :provider_name_is_supported
96
97
 
97
98
  #SOLO or Switch validations
@@ -107,9 +108,17 @@ module CreditOfficer
107
108
  validate :start_date_is_in_the_past,
108
109
  :if => proc{|cc| cc.switch_or_solo? }
109
110
 
110
- #set this flag accordingly to enable/disable validating verification codes (CVV/CVV2)
111
+ #set this flag accordingly to enable/disable validating verification codes
112
+ #(CVV/CVV2)
113
+ #@note defaults to true
111
114
  cattr_accessor :require_verification_value
112
115
  self.require_verification_value = true
116
+
117
+ #set this flag accordingly if you want CreditOfficer to attempt to derive
118
+ #the provider name before validation takes place
119
+ #@note defaults to true
120
+ cattr_accessor :automatically_derive_provider_name
121
+ self.automatically_derive_provider_name = true
113
122
 
114
123
  #checks the configuration setting require_verification_value to see if
115
124
  #verification is required
@@ -161,7 +170,27 @@ module CreditOfficer
161
170
  SWITCH_OR_SOLO_PROVIDERS.include?(provider_name)
162
171
  end
163
172
 
173
+ def derive_provider_name
174
+ self.class.supported_providers_and_formats.each do |name, format|
175
+ if number =~ format
176
+ self.provider_name = name
177
+ return
178
+ end
179
+ end
180
+ end
181
+
182
+ def masked_number
183
+ if number.present? && number.size >= 4
184
+ "X" * (number.size - 4) + number[-4..-1]
185
+ end
186
+ end
187
+
164
188
  protected
189
+ def run_validations!
190
+ derive_provider_name if self.class.automatically_derive_provider_name
191
+ super
192
+ end
193
+
165
194
  I18N_ERROR_SCOPE = [:credit_officer, :errors, :messages]
166
195
 
167
196
  def expiration_date_is_in_future
@@ -182,7 +211,8 @@ module CreditOfficer
182
211
  end
183
212
 
184
213
  def number_is_valid
185
- if provider_name.present? && number.present?
214
+ if (provider_name.present? || self.class.automatically_derive_provider_name) &&
215
+ number.present?
186
216
  if self.class.supported_providers_and_formats[provider_name].nil? ||
187
217
  !(number =~ self.class.supported_providers_and_formats[provider_name]) ||
188
218
  !checksum_valid?
@@ -195,7 +225,9 @@ module CreditOfficer
195
225
  end
196
226
 
197
227
  def provider_name_is_supported
198
- unless self.class.supported_providers.include?(provider_name.downcase)
228
+ if !self.class.automatically_derive_provider_name &&
229
+ !self.class.supported_providers.include?(provider_name.try(:downcase))
230
+
199
231
  errors.add(:provider_name, translate(:unsupported_provider,
200
232
  :scope => I18N_ERROR_SCOPE,
201
233
  :default => "is not supported"))
@@ -1,6 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe CreditOfficer::CreditCard do
4
+ TEST_AMEX = "378282246310005"
5
+ TEST_MASTER = "5555555555554444"
6
+
4
7
  subject { Factory.build(:credit_card) }
5
8
  it_should_behave_like "ActiveModel"
6
9
 
@@ -65,14 +68,14 @@ describe CreditOfficer::CreditCard do
65
68
  old_supported_providers = subject.class.supported_providers.dup
66
69
  subject.class.supported_providers = ['master']
67
70
  subject.should_not be_valid
68
- subject.errors[:provider_name].should_not be_blank
71
+ subject.errors[:number].should_not be_blank
69
72
 
70
73
  #reset supported providers
71
74
  subject.class.supported_providers = old_supported_providers
72
75
  end
73
76
 
74
77
  it "rejects a provider that is not in the whitelist" do
75
- old_supported_providers = subject.class.supported_providers
78
+ old_supported_providers = subject.class.supported_providers.dup
76
79
  subject.class.supported_providers = ["gaga", "ohlala"]
77
80
  subject.class.supported_providers.should be_empty
78
81
 
@@ -124,4 +127,52 @@ describe CreditOfficer::CreditCard do
124
127
  subject.errors[:start_year].should_not be_blank
125
128
  end
126
129
  end
130
+
131
+ it "reveals the last 4 digits in your masked card number" do
132
+ subject.masked_number.should =~ /#{subject.number[-4..-1]}$/
133
+ end
134
+
135
+ it "obfuscates all the digits except the last for in your masked card numbers" do
136
+ subject.masked_number.should =~ /^#{"X" * (subject.number.size - 4)}/
137
+ end
138
+
139
+ it "returns a nil masked number if I don't have more than 4 digits in my credit card number" do
140
+ subject.number = ""
141
+ subject.masked_number.should be_nil
142
+ end
143
+
144
+ context "deriving provider name" do
145
+ it "derives visa from a visa formatted card number" do
146
+ subject.provider_name = ""
147
+ subject.derive_provider_name
148
+ subject.provider_name.should eql("visa")
149
+ end
150
+
151
+ it "derives master from a mastercard formatted number" do
152
+ subject.provider_name = ""
153
+ subject.number = TEST_MASTER
154
+ subject.derive_provider_name
155
+ subject.provider_name.should eql('master')
156
+ end
157
+
158
+ it "derives american express from an amex formatted number" do
159
+ subject.provider_name = ""
160
+ subject.number = TEST_AMEX
161
+ subject.derive_provider_name
162
+ subject.provider_name.should eql('american_express')
163
+ end
164
+
165
+ it "does not validate the provider name if provider name derivation is on" do
166
+ subject.provider_name = ""
167
+ subject.number = TEST_AMEX
168
+ subject.should be_valid
169
+ subject.provider_name.should eql('american_express')
170
+ end
171
+
172
+ it "should not set an error on provider name if the credit card number is invalid" do
173
+ subject.number = "fasdfas"
174
+ subject.should_not be_valid
175
+ subject.errors[:provider_name].should be_blank
176
+ end
177
+ end
127
178
  end
@@ -1,6 +1,5 @@
1
1
  Factory.define :credit_card, :class => CreditOfficer::CreditCard do |c|
2
2
  c.number "4111111111111111"
3
- c.provider_name 'visa'
4
3
  c.expiration_month 1
5
4
  c.expiration_year { Time.now.advance(:year => 1) }
6
5
  c.name_on_card "John Smith"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: credit_officer
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dan Pickett
@@ -252,6 +252,7 @@ files:
252
252
  - README.rdoc
253
253
  - Rakefile
254
254
  - VERSION
255
+ - credit_officer.gemspec
255
256
  - lib/credit_officer.rb
256
257
  - lib/credit_officer/base.rb
257
258
  - lib/credit_officer/credit_card.rb