credit_officer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +79 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +102 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/lib/credit_officer/base.rb +34 -0
- data/lib/credit_officer/credit_card.rb +235 -0
- data/lib/credit_officer/month_year_pair.rb +62 -0
- data/lib/credit_officer.rb +6 -0
- data/spec/credit_officer/base_spec.rb +0 -0
- data/spec/credit_officer/credit_card_spec.rb +127 -0
- data/spec/credit_officer/month_year_pair_spec.rb +52 -0
- data/spec/credit_officer_spec.rb +5 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/active_model_shared_examples.rb +21 -0
- data/spec/support/factories.rb +21 -0
- metadata +307 -0
data/.document
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm gemset use credit_officer --create
|
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
gem "activemodel", ">= 3.0.3"
|
9
|
+
gem "luhney_bin"
|
10
|
+
|
11
|
+
group :development do
|
12
|
+
gem "timecop", "0.3.5"
|
13
|
+
gem "ruby-debug"
|
14
|
+
gem "factory_girl"
|
15
|
+
gem "rspec", "~> 2.3.0"
|
16
|
+
gem "remarkable_activemodel", ">=4.0.0.alpha4"
|
17
|
+
gem "yard", "~> 0.6.0"
|
18
|
+
gem "bundler", "~> 1.0.0"
|
19
|
+
gem "jeweler", "~> 1.5.1"
|
20
|
+
gem "rcov", ">= 0"
|
21
|
+
gem "reek", "~> 1.2.8"
|
22
|
+
gem "roodi", "~> 2.1.0"
|
23
|
+
gem "fuubar"
|
24
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.0.3)
|
5
|
+
activesupport (= 3.0.3)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
i18n (~> 0.4)
|
8
|
+
activesupport (3.0.3)
|
9
|
+
builder (2.1.2)
|
10
|
+
columnize (0.3.1)
|
11
|
+
diff-lcs (1.1.2)
|
12
|
+
factory_girl (1.3.2)
|
13
|
+
fuubar (0.0.3)
|
14
|
+
rspec (~> 2.0)
|
15
|
+
rspec-instafail (~> 0.1.4)
|
16
|
+
ruby-progressbar (~> 0.0.9)
|
17
|
+
git (1.2.5)
|
18
|
+
i18n (0.5.0)
|
19
|
+
jeweler (1.5.2)
|
20
|
+
bundler (~> 1.0.0)
|
21
|
+
git (>= 1.2.5)
|
22
|
+
rake
|
23
|
+
linecache (0.43)
|
24
|
+
luhney_bin (0.1.0)
|
25
|
+
rake (0.8.7)
|
26
|
+
rcov (0.9.9)
|
27
|
+
reek (1.2.8)
|
28
|
+
ruby2ruby (~> 1.2)
|
29
|
+
ruby_parser (~> 2.0)
|
30
|
+
sexp_processor (~> 3.0)
|
31
|
+
remarkable (4.0.0.alpha4)
|
32
|
+
rspec (>= 2.0.0.alpha11)
|
33
|
+
remarkable_activemodel (4.0.0.alpha4)
|
34
|
+
remarkable (~> 4.0.0.alpha4)
|
35
|
+
rspec (>= 2.0.0.alpha11)
|
36
|
+
roodi (2.1.0)
|
37
|
+
ruby_parser
|
38
|
+
rspec (2.3.0)
|
39
|
+
rspec-core (~> 2.3.0)
|
40
|
+
rspec-expectations (~> 2.3.0)
|
41
|
+
rspec-mocks (~> 2.3.0)
|
42
|
+
rspec-core (2.3.1)
|
43
|
+
rspec-expectations (2.3.0)
|
44
|
+
diff-lcs (~> 1.1.2)
|
45
|
+
rspec-instafail (0.1.5)
|
46
|
+
rspec-mocks (2.3.0)
|
47
|
+
ruby-debug (0.10.4)
|
48
|
+
columnize (>= 0.1)
|
49
|
+
ruby-debug-base (~> 0.10.4.0)
|
50
|
+
ruby-debug-base (0.10.4)
|
51
|
+
linecache (>= 0.3)
|
52
|
+
ruby-progressbar (0.0.9)
|
53
|
+
ruby2ruby (1.2.5)
|
54
|
+
ruby_parser (~> 2.0)
|
55
|
+
sexp_processor (~> 3.0)
|
56
|
+
ruby_parser (2.0.5)
|
57
|
+
sexp_processor (~> 3.0)
|
58
|
+
sexp_processor (3.0.5)
|
59
|
+
timecop (0.3.5)
|
60
|
+
yard (0.6.3)
|
61
|
+
|
62
|
+
PLATFORMS
|
63
|
+
ruby
|
64
|
+
|
65
|
+
DEPENDENCIES
|
66
|
+
activemodel (>= 3.0.3)
|
67
|
+
bundler (~> 1.0.0)
|
68
|
+
factory_girl
|
69
|
+
fuubar
|
70
|
+
jeweler (~> 1.5.1)
|
71
|
+
luhney_bin
|
72
|
+
rcov
|
73
|
+
reek (~> 1.2.8)
|
74
|
+
remarkable_activemodel (>= 4.0.0.alpha4)
|
75
|
+
roodi (~> 2.1.0)
|
76
|
+
rspec (~> 2.3.0)
|
77
|
+
ruby-debug
|
78
|
+
timecop (= 0.3.5)
|
79
|
+
yard (~> 0.6.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Dan Pickett
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
= credit_officer
|
2
|
+
|
3
|
+
An ActiveModel port of ActiveMerchant's credit card validations.
|
4
|
+
|
5
|
+
Use only with Rails 3/ActiveModel supported applications
|
6
|
+
|
7
|
+
== The Basics
|
8
|
+
|
9
|
+
Use this library so that you can validate credit card information before sending it to your payment processor
|
10
|
+
|
11
|
+
Checks credit card number formats, checksums, and other required details. Supports i18n for better message
|
12
|
+
customization
|
13
|
+
|
14
|
+
cc = CreditOfficer::CreditCard.new(
|
15
|
+
:number => "411111111111111",
|
16
|
+
:provider_name => "visa",
|
17
|
+
:name_on_card => "John Doe",
|
18
|
+
:expiration_year => 2010,
|
19
|
+
:expiration_month => 1,
|
20
|
+
:verification_value => "343"
|
21
|
+
}).valid? => true
|
22
|
+
|
23
|
+
cc.number = ""
|
24
|
+
cc.valid? => false
|
25
|
+
cc.errors.full_messages => ["Number can't be blank"]
|
26
|
+
|
27
|
+
== Configuring verification
|
28
|
+
|
29
|
+
If you want to turn requiring verification values off, make it so:
|
30
|
+
|
31
|
+
CreditOfficer::CreditCard.require_verification_value = false
|
32
|
+
cc = CreditOfficer::CreditCard.new(
|
33
|
+
:number => "411111111111111",
|
34
|
+
:provider_name => "visa",
|
35
|
+
:name_on_card => "John Doe",
|
36
|
+
:expiration_year => 2010,
|
37
|
+
:expiration_month => 1,
|
38
|
+
:verification_value => ""
|
39
|
+
}).valid? => true
|
40
|
+
|
41
|
+
|
42
|
+
== Configuring Providers
|
43
|
+
|
44
|
+
Want to only support certain credit cards and card number formats? Make it so:
|
45
|
+
|
46
|
+
CreditOfficer::CreditCard.supported_providers = [
|
47
|
+
'mastercard',
|
48
|
+
'amex'
|
49
|
+
]
|
50
|
+
|
51
|
+
cc = CreditOfficer::CreditCard.new(
|
52
|
+
:number => "411111111111111",
|
53
|
+
:provider_name => "visa",
|
54
|
+
:name_on_card => "John Doe",
|
55
|
+
:expiration_year => 2010,
|
56
|
+
:expiration_month => 1,
|
57
|
+
:verification_value => ""
|
58
|
+
}).valid? => false
|
59
|
+
|
60
|
+
== i18n
|
61
|
+
|
62
|
+
Error messages can be customized with i18n translations
|
63
|
+
|
64
|
+
credit_officer:
|
65
|
+
errors:
|
66
|
+
messages:
|
67
|
+
expired: "is expired"
|
68
|
+
exceeds_recent_future: "is not a valid year"
|
69
|
+
invalid_format: "is not a valid card number"
|
70
|
+
unsupported_provider: "is not supported"
|
71
|
+
invalid_issue_number: "is not valid"
|
72
|
+
futuristic_start_date: "is in the future"
|
73
|
+
|
74
|
+
== Why?
|
75
|
+
|
76
|
+
ActiveMerchant has a ton of functionality and it's a great library. I just wanted the ability to validate
|
77
|
+
information before sending it to a payment processor. I don't want all the payment processor logic, etc
|
78
|
+
bloating my applications. Lean, simple, extensible, and up to date - that's how I like it!
|
79
|
+
|
80
|
+
I've also added some of the niceties that an activemodel compliant architecture provides (i18n, for example)
|
81
|
+
|
82
|
+
== A note on persistence
|
83
|
+
|
84
|
+
Do not store this data yourself unless you absolutely must. Your payment processors should have a way for you
|
85
|
+
to transmit this information and have them store it for you. Otherwise, you're beholden to all kinds of
|
86
|
+
PCI compliance issues.
|
87
|
+
|
88
|
+
== Contributing to credit_officer
|
89
|
+
|
90
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
91
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
92
|
+
* Fork the project
|
93
|
+
* Start a feature/bugfix branch
|
94
|
+
* Commit and push until you are happy with your contribution
|
95
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
96
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
97
|
+
|
98
|
+
== Copyright
|
99
|
+
|
100
|
+
Copyright (c) 2010 Dan Pickett. See LICENSE.txt for
|
101
|
+
further details.
|
102
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "credit_officer"
|
16
|
+
gem.homepage = "http://github.com/dpickett/credit_officer"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{An activemodel compliant credit card validator}
|
19
|
+
gem.description = %Q{An upgrade/port of ActiveMerchant's credit card class}
|
20
|
+
gem.email = "dpickett@enlightsolutions.com"
|
21
|
+
gem.authors = ["Dan Pickett"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'reek/rake/task'
|
41
|
+
Reek::Rake::Task.new do |t|
|
42
|
+
t.fail_on_error = true
|
43
|
+
t.verbose = false
|
44
|
+
t.source_files = 'lib/**/*.rb'
|
45
|
+
end
|
46
|
+
|
47
|
+
require 'roodi'
|
48
|
+
require 'roodi_task'
|
49
|
+
RoodiTask.new do |t|
|
50
|
+
t.verbose = false
|
51
|
+
end
|
52
|
+
|
53
|
+
task :default => :spec
|
54
|
+
|
55
|
+
require 'yard'
|
56
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "active_model"
|
2
|
+
|
3
|
+
module CreditOfficer
|
4
|
+
class Base
|
5
|
+
extend ActiveModel::Naming
|
6
|
+
include ActiveModel::Validations
|
7
|
+
extend ActiveModel::Translation
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_model
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def persisted?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_key
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_param
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
def translate(key, options = {})
|
31
|
+
I18n.t key, options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require "active_support/core_ext/class/attribute_accessors"
|
2
|
+
require "active_support/ordered_hash"
|
3
|
+
require "luhney_bin"
|
4
|
+
|
5
|
+
module CreditOfficer
|
6
|
+
#ActiveModel compliant class that represents credit card information
|
7
|
+
#Use this to populate and validate credit card details
|
8
|
+
#
|
9
|
+
#It is not recommended that you persist credit card information unless
|
10
|
+
#you absolutely must. Many payment processors proviider you with a mechanism to
|
11
|
+
#store this private information
|
12
|
+
#
|
13
|
+
#@example
|
14
|
+
# cc = CreditOfficer::CreditCard.new(
|
15
|
+
# :number => "411111111111111",
|
16
|
+
# :provider_name => "visa",
|
17
|
+
# :name_on_card => "John Doe",
|
18
|
+
# :expiration_year => 2010,
|
19
|
+
# :expiration_month => 1
|
20
|
+
# }).valid? => true
|
21
|
+
#
|
22
|
+
# cc.number = ""
|
23
|
+
# cc.valid? => false
|
24
|
+
# cc.errors.full_messages => ["Number can't be blank"]
|
25
|
+
class CreditCard < Base
|
26
|
+
PROVIDERS_AND_FORMATS = [
|
27
|
+
['visa' , /^4\d{12}(\d{3})?$/],
|
28
|
+
['master' , /^(5[1-5]\d{4}|677189)\d{10}$/],
|
29
|
+
['discover' , /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/],
|
30
|
+
['american_express' , /^3[47]\d{13}$/],
|
31
|
+
['diners_club' , /^3(0[0-5]|[68]\d)\d{11}$/],
|
32
|
+
['jcb' , /^35(28|29|[3-8]\d)\d{12}$/],
|
33
|
+
['switch' , /^6759\d{12}(\d{2,3})?$/],
|
34
|
+
['solo' , /^6767\d{12}(\d{2,3})?$/],
|
35
|
+
['dankort' , /^5019\d{12}$/],
|
36
|
+
['maestro' , /^(5[06-8]|6\d)\d{10,17}$/],
|
37
|
+
['forbrugsforeningen' , /^600722\d{10}$/],
|
38
|
+
['laser' , /^(6304|6706|6771|6709)\d{8}(\d{4}|\d{6,7})?$/]
|
39
|
+
].inject(ActiveSupport::OrderedHash.new) do |ordered_hash, name_format_pair|
|
40
|
+
ordered_hash[name_format_pair[0]] = name_format_pair[1]
|
41
|
+
ordered_hash
|
42
|
+
end
|
43
|
+
|
44
|
+
SWITCH_OR_SOLO_PROVIDERS = [
|
45
|
+
'switch',
|
46
|
+
'solo'
|
47
|
+
]
|
48
|
+
|
49
|
+
#[String] the number found on card
|
50
|
+
attr_accessor :number
|
51
|
+
|
52
|
+
#[String] the name found on the front of the card
|
53
|
+
attr_accessor :name_on_card
|
54
|
+
|
55
|
+
#[Integer] the integer based representation of the month when the credit card expires (1-12 e.g)
|
56
|
+
attr_accessor :expiration_month
|
57
|
+
|
58
|
+
#[Integer] the year when the credit card expires
|
59
|
+
#@note paired with the month, this must be in the future for the credit card to be valid
|
60
|
+
attr_accessor :expiration_year
|
61
|
+
|
62
|
+
#[String] the CVV/CVV2 value found on the back or front of cards depending on their brand
|
63
|
+
#validation of this string can be turned off via class setting require_verification_value
|
64
|
+
attr_accessor :verification_value
|
65
|
+
|
66
|
+
#[String] downcased name of the credit card provider
|
67
|
+
#(see {PROVIDERS_AND_FORMATS} for a valid list
|
68
|
+
attr_accessor :provider_name
|
69
|
+
|
70
|
+
#[Integer] Solo or Switch card attribute representing the start date found on the card
|
71
|
+
attr_accessor :start_month
|
72
|
+
|
73
|
+
#[Integer] Solo or Switch card attribute representing the start year found on the card
|
74
|
+
attr_accessor :start_year
|
75
|
+
|
76
|
+
#[String] Solo or Switch Card attribute representing the issue number found on the card
|
77
|
+
attr_accessor :issue_number
|
78
|
+
|
79
|
+
alias_method :brand, :provider_name
|
80
|
+
|
81
|
+
validates_presence_of :number
|
82
|
+
validates_presence_of :name_on_card
|
83
|
+
|
84
|
+
validates_presence_of :verification_value,
|
85
|
+
:if => proc{|p| p.class.verification_value_required? }
|
86
|
+
|
87
|
+
validates_inclusion_of :expiration_month,
|
88
|
+
:in => 1..12
|
89
|
+
|
90
|
+
validates_presence_of :expiration_year
|
91
|
+
|
92
|
+
validate :expiration_date_is_in_future
|
93
|
+
validate :expiration_date_is_in_recent_future
|
94
|
+
validate :number_is_valid
|
95
|
+
validate :provider_name_is_supported
|
96
|
+
|
97
|
+
#SOLO or Switch validations
|
98
|
+
validates_presence_of :start_month,
|
99
|
+
:if => proc{|cc| cc.switch_or_solo? }
|
100
|
+
|
101
|
+
validates_presence_of :start_year,
|
102
|
+
:if => proc{|cc| cc.switch_or_solo? }
|
103
|
+
|
104
|
+
validate :issue_number_is_valid,
|
105
|
+
:if => proc{|cc| cc.switch_or_solo? }
|
106
|
+
|
107
|
+
validate :start_date_is_in_the_past,
|
108
|
+
:if => proc{|cc| cc.switch_or_solo? }
|
109
|
+
|
110
|
+
#set this flag accordingly to enable/disable validating verification codes (CVV/CVV2)
|
111
|
+
cattr_accessor :require_verification_value
|
112
|
+
self.require_verification_value = true
|
113
|
+
|
114
|
+
#checks the configuration setting require_verification_value to see if
|
115
|
+
#verification is required
|
116
|
+
def self.verification_value_required?
|
117
|
+
require_verification_value
|
118
|
+
end
|
119
|
+
|
120
|
+
#@return [CreditOfficer::MonthYearPair] month year pair that represents the expiration date
|
121
|
+
def expiration_date
|
122
|
+
CreditOfficer::MonthYearPair.new(:month => expiration_month,
|
123
|
+
:year => expiration_year)
|
124
|
+
end
|
125
|
+
|
126
|
+
#@return [CreditOfficer::MonthYearPair] month year pair that represents the start date
|
127
|
+
#@note this applies to switch and solo cards only
|
128
|
+
def start_date
|
129
|
+
CreditOfficer::MonthYearPair.new(:month => start_month,
|
130
|
+
:year => start_year)
|
131
|
+
end
|
132
|
+
|
133
|
+
#sets the provider name
|
134
|
+
#@param [String] the provider name you wish to set
|
135
|
+
#sets the provider name to its downcased equivalent
|
136
|
+
#@example note the downcase
|
137
|
+
# credit_card.provider_name = "VISA" => "visa"
|
138
|
+
def provider_name=(provider)
|
139
|
+
unless provider.nil?
|
140
|
+
@provider_name = provider.downcase
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
#configure your list of supported providers
|
145
|
+
#@param Array<String> providers you wish to support (amex, visa, etc) (refer to {PROVIDERS_AND_FORMATS})
|
146
|
+
#@note matches specified providers against the supported whitelist {PROVIDERS_AND_FORMATS}
|
147
|
+
def self.supported_providers=(providers)
|
148
|
+
@supported_providers = providers.collect{|i| i.downcase} & PROVIDERS_AND_FORMATS.keys
|
149
|
+
end
|
150
|
+
|
151
|
+
#@return [Array<String>] list of providers
|
152
|
+
#@note defaults to {PROVIDERS_AND_FORMATS}.keys
|
153
|
+
def self.supported_providers
|
154
|
+
@supported_providers
|
155
|
+
end
|
156
|
+
|
157
|
+
self.supported_providers = PROVIDERS_AND_FORMATS.keys
|
158
|
+
|
159
|
+
#@return [Boolean] whether or not the provider name indicates the card is a switch or solo card
|
160
|
+
def switch_or_solo?
|
161
|
+
SWITCH_OR_SOLO_PROVIDERS.include?(provider_name)
|
162
|
+
end
|
163
|
+
|
164
|
+
protected
|
165
|
+
I18N_ERROR_SCOPE = [:credit_officer, :errors, :messages]
|
166
|
+
|
167
|
+
def expiration_date_is_in_future
|
168
|
+
if expiration_date.valid? && expiration_date.end_is_in_past?
|
169
|
+
errors.add(:expiration_year,
|
170
|
+
translate(:expired,
|
171
|
+
:scope => I18N_ERROR_SCOPE,
|
172
|
+
:default => "is expired"))
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def expiration_date_is_in_recent_future
|
177
|
+
if expiration_date.valid? && expiration_date.exceeds_recent_future?
|
178
|
+
errors.add(:expiration_year, translate(:exceeds_recent_future,
|
179
|
+
:scope => I18N_ERROR_SCOPE,
|
180
|
+
:default => "is not a valid year"))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def number_is_valid
|
185
|
+
if provider_name.present? && number.present?
|
186
|
+
if self.class.supported_providers_and_formats[provider_name].nil? ||
|
187
|
+
!(number =~ self.class.supported_providers_and_formats[provider_name]) ||
|
188
|
+
!checksum_valid?
|
189
|
+
|
190
|
+
errors.add(:number, translate(:invalid_format,
|
191
|
+
:scope => I18N_ERROR_SCOPE,
|
192
|
+
:default => "is not a valid card number"))
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def provider_name_is_supported
|
198
|
+
unless self.class.supported_providers.include?(provider_name.downcase)
|
199
|
+
errors.add(:provider_name, translate(:unsupported_provider,
|
200
|
+
:scope => I18N_ERROR_SCOPE,
|
201
|
+
:default => "is not supported"))
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def issue_number_is_valid
|
206
|
+
unless issue_number =~ /^\d{1,2}$/
|
207
|
+
errors.add(:issue_number, translate(:invalid_issue_number,
|
208
|
+
:scope => I18N_ERROR_SCOPE,
|
209
|
+
:default => "is not valid"))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def start_date_is_in_the_past
|
214
|
+
if start_date.valid? && start_date.start_is_in_future?
|
215
|
+
errors.add(:start_year, translate(:futuristic_start_date,
|
216
|
+
:scope => I18N_ERROR_SCOPE,
|
217
|
+
:default => "is in the future"))
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def checksum_valid?
|
222
|
+
LuhneyBin.validate(number)
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.supported_providers_and_formats
|
226
|
+
#match supported providers against constant's whitelist
|
227
|
+
valid_supported_providers = supported_providers & PROVIDERS_AND_FORMATS.keys
|
228
|
+
|
229
|
+
supported_providers.inject(ActiveSupport::OrderedHash.new) do |ordered_hash, provider_name|
|
230
|
+
ordered_hash[provider_name] = PROVIDERS_AND_FORMATS[provider_name]
|
231
|
+
ordered_hash
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'active_support/core_ext/time/calculations'
|
2
|
+
|
3
|
+
module CreditOfficer
|
4
|
+
#ActiveModel compliant abstraction representing the month/year pairs often found on credit cards
|
5
|
+
#
|
6
|
+
#For most, the only one found on the front is an expiration date
|
7
|
+
#
|
8
|
+
#For switch and solo cards, an additional start date might be specified
|
9
|
+
class MonthYearPair < Base
|
10
|
+
#[Integer] the year (required for validity)
|
11
|
+
attr_accessor :year
|
12
|
+
|
13
|
+
#[Integer] the numberic representation of the month (1-12) (required for validity)
|
14
|
+
attr_accessor :month
|
15
|
+
|
16
|
+
validates_inclusion_of :month,
|
17
|
+
:in => 1..12
|
18
|
+
|
19
|
+
validates_presence_of :year
|
20
|
+
|
21
|
+
#@param [Hash] hash of attributes to set
|
22
|
+
def initialize(attrs = {})
|
23
|
+
self.year = attrs[:year].to_i
|
24
|
+
self.month = attrs[:month].to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
#@return [Boolean] whether the last minute of the month is in the past
|
28
|
+
def end_is_in_past? #:nodoc:
|
29
|
+
Time.now.utc > end_of_month
|
30
|
+
end
|
31
|
+
|
32
|
+
#@return [Boolean] whether the first minute of the month is in the future
|
33
|
+
def start_is_in_future?
|
34
|
+
Time.now.utc < start_of_month
|
35
|
+
end
|
36
|
+
|
37
|
+
RECENT_FUTURE_YEAR_LIMIT = 20
|
38
|
+
|
39
|
+
#@return [Boolean] whether the last minute of the month is within the bound of {RECENT_FUTURE_YEAR_LIMIT}
|
40
|
+
def exceeds_recent_future?
|
41
|
+
end_of_month <= Time.now.utc.advance(:years => RECENT_FUTURE_YEAR_LIMIT)
|
42
|
+
end
|
43
|
+
|
44
|
+
#@return [Time, nil] the first minute of the month in UTC or nil if an invalid pair was specified
|
45
|
+
def start_of_month
|
46
|
+
begin
|
47
|
+
Time.utc(year, month, 1, 0, 0, 1)
|
48
|
+
rescue ArgumentError
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#@return [Time, nil] the last minute of the month in UTC or nil if an invalid pair was specified
|
54
|
+
def end_of_month
|
55
|
+
begin
|
56
|
+
Time.utc(year, month, Time.days_in_month(month, year), 23, 59, 59)
|
57
|
+
rescue ArgumentError
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
File without changes
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CreditOfficer::CreditCard do
|
4
|
+
subject { Factory.build(:credit_card) }
|
5
|
+
it_should_behave_like "ActiveModel"
|
6
|
+
|
7
|
+
should_validate_presence_of :number
|
8
|
+
should_validate_presence_of :name_on_card
|
9
|
+
should_validate_inclusion_of :expiration_month, :in => 1..12
|
10
|
+
should_validate_presence_of :expiration_year
|
11
|
+
|
12
|
+
it "validates that the expiration year is on or after this year" do
|
13
|
+
subject.expiration_year = Time.now.year - 1
|
14
|
+
subject.should_not be_valid
|
15
|
+
subject.errors[:expiration_year].should_not be_blank
|
16
|
+
end
|
17
|
+
|
18
|
+
it "validates that the expiration year is within 20 years from now" do
|
19
|
+
subject.expiration_year = Time.now.utc.year + 20
|
20
|
+
subject.should_not be_valid
|
21
|
+
subject.errors[:expiration_year].should_not be_blank
|
22
|
+
end
|
23
|
+
|
24
|
+
it "validates that the expiration month and year is in the future" do
|
25
|
+
Timecop.freeze(Time.utc(2010, 11, 1, 1)) do
|
26
|
+
subject.expiration_year = "2010"
|
27
|
+
subject.expiration_month = "10"
|
28
|
+
subject.should_not be_valid
|
29
|
+
subject.errors[:expiration_year].should_not be_blank
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "validates the presence of a verification value if it's enabled" do
|
34
|
+
old_setting = CreditOfficer::CreditCard.require_verification_value
|
35
|
+
CreditOfficer::CreditCard.require_verification_value = true
|
36
|
+
subject.verification_value = ""
|
37
|
+
subject.should_not be_valid
|
38
|
+
subject.errors[:verification_value].should_not be_blank
|
39
|
+
|
40
|
+
CreditOfficer::CreditCard.require_verification_value = old_setting
|
41
|
+
end
|
42
|
+
|
43
|
+
it "does not validate the presence of a verification if it's not enabled" do
|
44
|
+
old_setting = subject.class.require_verification_value
|
45
|
+
subject.class.require_verification_value = false
|
46
|
+
subject.verification_value = ""
|
47
|
+
subject.should be_valid
|
48
|
+
end
|
49
|
+
|
50
|
+
it "validates the credit card number based on its provider name's format" do
|
51
|
+
subject.provider_name = 'visa'
|
52
|
+
subject.number = '68293421'
|
53
|
+
subject.should_not be_valid
|
54
|
+
subject.errors[:number].should_not be_blank
|
55
|
+
end
|
56
|
+
|
57
|
+
it "checks the checksum of the number" do
|
58
|
+
subject.number = "4123456789012345"
|
59
|
+
subject.should_not be_valid
|
60
|
+
subject.errors[:number].should_not be_blank
|
61
|
+
end
|
62
|
+
|
63
|
+
context "supported providers" do
|
64
|
+
it "validates that my provider is in the list of supported providers" do
|
65
|
+
old_supported_providers = subject.class.supported_providers.dup
|
66
|
+
subject.class.supported_providers = ['master']
|
67
|
+
subject.should_not be_valid
|
68
|
+
subject.errors[:provider_name].should_not be_blank
|
69
|
+
|
70
|
+
#reset supported providers
|
71
|
+
subject.class.supported_providers = old_supported_providers
|
72
|
+
end
|
73
|
+
|
74
|
+
it "rejects a provider that is not in the whitelist" do
|
75
|
+
old_supported_providers = subject.class.supported_providers
|
76
|
+
subject.class.supported_providers = ["gaga", "ohlala"]
|
77
|
+
subject.class.supported_providers.should be_empty
|
78
|
+
|
79
|
+
#reset supported providers
|
80
|
+
subject.class.supported_providers = old_supported_providers
|
81
|
+
end
|
82
|
+
|
83
|
+
it "defaults to the large list of providers" do
|
84
|
+
subject.class.supported_providers.should eql(subject.class::PROVIDERS_AND_FORMATS.keys)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "switch or solo cards" do
|
89
|
+
subject { Factory.build(:switch_credit_card) }
|
90
|
+
|
91
|
+
it { should be_valid }
|
92
|
+
|
93
|
+
it "is switch or solo if the provider name reflects that" do
|
94
|
+
[
|
95
|
+
"switch",
|
96
|
+
"solo"
|
97
|
+
].each do |provider_name|
|
98
|
+
subject.provider_name = provider_name
|
99
|
+
subject.should be_switch_or_solo
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "requires a start month" do
|
104
|
+
subject.start_month = ""
|
105
|
+
subject.should_not be_valid
|
106
|
+
subject.errors[:start_month].should_not be_blank
|
107
|
+
end
|
108
|
+
|
109
|
+
it "requires a start year" do
|
110
|
+
subject.start_year = ""
|
111
|
+
subject.should_not be_valid
|
112
|
+
subject.errors[:start_year].should_not be_blank
|
113
|
+
end
|
114
|
+
|
115
|
+
it "requires a valid issue number" do
|
116
|
+
subject.issue_number = ""
|
117
|
+
subject.should_not be_valid
|
118
|
+
subject.errors[:issue_number].should_not be_blank
|
119
|
+
end
|
120
|
+
|
121
|
+
it "requires that the start month is not in the future" do
|
122
|
+
subject.start_year = Time.now.year + 1
|
123
|
+
subject.should_not be_valid
|
124
|
+
subject.errors[:start_year].should_not be_blank
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CreditOfficer::MonthYearPair do
|
4
|
+
should_validate_presence_of :year
|
5
|
+
should_validate_inclusion_of :month, :in => 1..12
|
6
|
+
|
7
|
+
subject { Factory.build(:month_year_pair) }
|
8
|
+
|
9
|
+
it "indicates when the end of the month is in the past" do
|
10
|
+
freeze_time do
|
11
|
+
subject.year = 2009
|
12
|
+
subject.end_is_in_past?.should be_true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "indicates when the end of hte month is not in the past" do
|
17
|
+
freeze_time do
|
18
|
+
subject.year = 2011
|
19
|
+
subject.end_is_in_past?.should be_false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "indicates when the start of the month is in the future" do
|
24
|
+
freeze_time do
|
25
|
+
subject.year = 2011
|
26
|
+
subject.start_is_in_future?.should be_true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "indicates when the start of the month is not in the future" do
|
31
|
+
freeze_time do
|
32
|
+
subject.year = 2009
|
33
|
+
subject.start_is_in_future?.should be_false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has a nil start of month if the date doesn't make sense" do
|
38
|
+
subject.month = 49
|
39
|
+
subject.start_of_month.should be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "has a nil end of month if the date doesn't make sense" do
|
43
|
+
subject.month = 49
|
44
|
+
subject.end_of_month.should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def freeze_time(time = Time.utc(2010, 1, 1, 0, 0, 1), &block)
|
48
|
+
Timecop.freeze(time) do
|
49
|
+
yield
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'credit_officer'
|
5
|
+
require 'remarkable/active_model'
|
6
|
+
require 'timecop'
|
7
|
+
require 'factory_girl'
|
8
|
+
|
9
|
+
# Requires supporting files with custom matchers and macros, etc,
|
10
|
+
# in ./support/ and its subdirectories.
|
11
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# activemodel compliance test courtesy of https://gist.github.com/665629
|
2
|
+
|
3
|
+
# adapted from rspec-rails http://github.com/rspec/rspec-rails/blob/master/spec/rspec/rails/mocks/mock_model_spec.rb
|
4
|
+
|
5
|
+
shared_examples_for "ActiveModel" do
|
6
|
+
require 'test/unit/assertions'
|
7
|
+
require 'active_model/lint'
|
8
|
+
include Test::Unit::Assertions
|
9
|
+
include ActiveModel::Lint::Tests
|
10
|
+
|
11
|
+
# to_s is to support ruby-1.9
|
12
|
+
ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
|
13
|
+
example m.gsub('_',' ') do
|
14
|
+
send m
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def model
|
19
|
+
subject
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Factory.define :credit_card, :class => CreditOfficer::CreditCard do |c|
|
2
|
+
c.number "4111111111111111"
|
3
|
+
c.provider_name 'visa'
|
4
|
+
c.expiration_month 1
|
5
|
+
c.expiration_year { Time.now.advance(:year => 1) }
|
6
|
+
c.name_on_card "John Smith"
|
7
|
+
c.verification_value "1434"
|
8
|
+
end
|
9
|
+
|
10
|
+
Factory.define :switch_credit_card, :parent => :credit_card do |c|
|
11
|
+
c.number '675900000000000000'
|
12
|
+
c.provider_name 'switch'
|
13
|
+
c.start_month "01"
|
14
|
+
c.start_year "1990"
|
15
|
+
c.issue_number "01"
|
16
|
+
end
|
17
|
+
|
18
|
+
Factory.define :month_year_pair, :class => CreditOfficer::MonthYearPair do |c|
|
19
|
+
c.month 1
|
20
|
+
c.year 2010
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,307 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: credit_officer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Dan Pickett
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-22 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
type: :runtime
|
24
|
+
name: activemodel
|
25
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 1
|
31
|
+
segments:
|
32
|
+
- 3
|
33
|
+
- 0
|
34
|
+
- 3
|
35
|
+
version: 3.0.3
|
36
|
+
requirement: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
type: :runtime
|
40
|
+
name: luhney_bin
|
41
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
version: "0"
|
50
|
+
requirement: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
prerelease: false
|
53
|
+
type: :development
|
54
|
+
name: timecop
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - "="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 25
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
- 3
|
64
|
+
- 5
|
65
|
+
version: 0.3.5
|
66
|
+
requirement: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
prerelease: false
|
69
|
+
type: :development
|
70
|
+
name: ruby-debug
|
71
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirement: *id004
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
prerelease: false
|
83
|
+
type: :development
|
84
|
+
name: factory_girl
|
85
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirement: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
prerelease: false
|
97
|
+
type: :development
|
98
|
+
name: rspec
|
99
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ~>
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 2
|
107
|
+
- 3
|
108
|
+
- 0
|
109
|
+
version: 2.3.0
|
110
|
+
requirement: *id006
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
prerelease: false
|
113
|
+
type: :development
|
114
|
+
name: remarkable_activemodel
|
115
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
hash: -1710980466
|
121
|
+
segments:
|
122
|
+
- 4
|
123
|
+
- 0
|
124
|
+
- 0
|
125
|
+
- alpha4
|
126
|
+
version: 4.0.0.alpha4
|
127
|
+
requirement: *id007
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
prerelease: false
|
130
|
+
type: :development
|
131
|
+
name: yard
|
132
|
+
version_requirements: &id008 !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ~>
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 7
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
- 6
|
141
|
+
- 0
|
142
|
+
version: 0.6.0
|
143
|
+
requirement: *id008
|
144
|
+
- !ruby/object:Gem::Dependency
|
145
|
+
prerelease: false
|
146
|
+
type: :development
|
147
|
+
name: bundler
|
148
|
+
version_requirements: &id009 !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ~>
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
hash: 23
|
154
|
+
segments:
|
155
|
+
- 1
|
156
|
+
- 0
|
157
|
+
- 0
|
158
|
+
version: 1.0.0
|
159
|
+
requirement: *id009
|
160
|
+
- !ruby/object:Gem::Dependency
|
161
|
+
prerelease: false
|
162
|
+
type: :development
|
163
|
+
name: jeweler
|
164
|
+
version_requirements: &id010 !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ~>
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
hash: 1
|
170
|
+
segments:
|
171
|
+
- 1
|
172
|
+
- 5
|
173
|
+
- 1
|
174
|
+
version: 1.5.1
|
175
|
+
requirement: *id010
|
176
|
+
- !ruby/object:Gem::Dependency
|
177
|
+
prerelease: false
|
178
|
+
type: :development
|
179
|
+
name: rcov
|
180
|
+
version_requirements: &id011 !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
hash: 3
|
186
|
+
segments:
|
187
|
+
- 0
|
188
|
+
version: "0"
|
189
|
+
requirement: *id011
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
prerelease: false
|
192
|
+
type: :development
|
193
|
+
name: reek
|
194
|
+
version_requirements: &id012 !ruby/object:Gem::Requirement
|
195
|
+
none: false
|
196
|
+
requirements:
|
197
|
+
- - ~>
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
hash: 15
|
200
|
+
segments:
|
201
|
+
- 1
|
202
|
+
- 2
|
203
|
+
- 8
|
204
|
+
version: 1.2.8
|
205
|
+
requirement: *id012
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
prerelease: false
|
208
|
+
type: :development
|
209
|
+
name: roodi
|
210
|
+
version_requirements: &id013 !ruby/object:Gem::Requirement
|
211
|
+
none: false
|
212
|
+
requirements:
|
213
|
+
- - ~>
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
hash: 11
|
216
|
+
segments:
|
217
|
+
- 2
|
218
|
+
- 1
|
219
|
+
- 0
|
220
|
+
version: 2.1.0
|
221
|
+
requirement: *id013
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
prerelease: false
|
224
|
+
type: :development
|
225
|
+
name: fuubar
|
226
|
+
version_requirements: &id014 !ruby/object:Gem::Requirement
|
227
|
+
none: false
|
228
|
+
requirements:
|
229
|
+
- - ">="
|
230
|
+
- !ruby/object:Gem::Version
|
231
|
+
hash: 3
|
232
|
+
segments:
|
233
|
+
- 0
|
234
|
+
version: "0"
|
235
|
+
requirement: *id014
|
236
|
+
description: An upgrade/port of ActiveMerchant's credit card class
|
237
|
+
email: dpickett@enlightsolutions.com
|
238
|
+
executables: []
|
239
|
+
|
240
|
+
extensions: []
|
241
|
+
|
242
|
+
extra_rdoc_files:
|
243
|
+
- LICENSE.txt
|
244
|
+
- README.rdoc
|
245
|
+
files:
|
246
|
+
- .document
|
247
|
+
- .rspec
|
248
|
+
- .rvmrc
|
249
|
+
- Gemfile
|
250
|
+
- Gemfile.lock
|
251
|
+
- LICENSE.txt
|
252
|
+
- README.rdoc
|
253
|
+
- Rakefile
|
254
|
+
- VERSION
|
255
|
+
- lib/credit_officer.rb
|
256
|
+
- lib/credit_officer/base.rb
|
257
|
+
- lib/credit_officer/credit_card.rb
|
258
|
+
- lib/credit_officer/month_year_pair.rb
|
259
|
+
- spec/credit_officer/base_spec.rb
|
260
|
+
- spec/credit_officer/credit_card_spec.rb
|
261
|
+
- spec/credit_officer/month_year_pair_spec.rb
|
262
|
+
- spec/credit_officer_spec.rb
|
263
|
+
- spec/spec_helper.rb
|
264
|
+
- spec/support/active_model_shared_examples.rb
|
265
|
+
- spec/support/factories.rb
|
266
|
+
has_rdoc: true
|
267
|
+
homepage: http://github.com/dpickett/credit_officer
|
268
|
+
licenses:
|
269
|
+
- MIT
|
270
|
+
post_install_message:
|
271
|
+
rdoc_options: []
|
272
|
+
|
273
|
+
require_paths:
|
274
|
+
- lib
|
275
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
276
|
+
none: false
|
277
|
+
requirements:
|
278
|
+
- - ">="
|
279
|
+
- !ruby/object:Gem::Version
|
280
|
+
hash: 3
|
281
|
+
segments:
|
282
|
+
- 0
|
283
|
+
version: "0"
|
284
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
285
|
+
none: false
|
286
|
+
requirements:
|
287
|
+
- - ">="
|
288
|
+
- !ruby/object:Gem::Version
|
289
|
+
hash: 3
|
290
|
+
segments:
|
291
|
+
- 0
|
292
|
+
version: "0"
|
293
|
+
requirements: []
|
294
|
+
|
295
|
+
rubyforge_project:
|
296
|
+
rubygems_version: 1.3.7
|
297
|
+
signing_key:
|
298
|
+
specification_version: 3
|
299
|
+
summary: An activemodel compliant credit card validator
|
300
|
+
test_files:
|
301
|
+
- spec/credit_officer/base_spec.rb
|
302
|
+
- spec/credit_officer/credit_card_spec.rb
|
303
|
+
- spec/credit_officer/month_year_pair_spec.rb
|
304
|
+
- spec/credit_officer_spec.rb
|
305
|
+
- spec/spec_helper.rb
|
306
|
+
- spec/support/active_model_shared_examples.rb
|
307
|
+
- spec/support/factories.rb
|