am_credit_card 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # See: am_credit_card.gemspec
4
+ gemspec
@@ -0,0 +1,50 @@
1
+ am_credit_card
2
+ ==============
3
+
4
+ [ActiveMerchant::Billing::CreditCard][1], without [ActiveMerchant][2].
5
+
6
+ [1]: https://github.com/Shopify/active_merchant/blob/master/lib/active_merchant/billing/credit_card.rb
7
+ [2]: http://activemerchant.rubyforge.org/
8
+
9
+
10
+ Why?
11
+ ----
12
+
13
+ ActiveMerchant has [nice credit card validations][1], but also lots of [dependencies][2] and other code. That's fine for those using the rest of ActiveMerchant, but if you're just after it's credit card model/validations, this is for you.
14
+
15
+ [1]: https://github.com/Shopify/active_merchant/blob/master/lib/active_merchant/billing/credit_card_methods.rb
16
+ [2]: https://github.com/Shopify/active_merchant/blob/master/activemerchant.gemspec#L21
17
+
18
+
19
+ Usage
20
+ -----
21
+
22
+ ```ruby
23
+ # Gemfile
24
+ gem "am_credit_card"
25
+ ```
26
+
27
+ ```ruby
28
+ # Instantiation
29
+ card = ActiveMerchant::Billing::CreditCard.new(
30
+ :first_name => "Bob",
31
+ :last_name => "Bobsen",
32
+ :number => "4242424242424242",
33
+ :month => "8",
34
+ :year => "2012",
35
+ :verification_value => "123"
36
+ )
37
+
38
+ # Validation
39
+ card.valid?
40
+ card.errors
41
+ ```
42
+
43
+
44
+ License
45
+ -------
46
+
47
+ ActiveMerchant is Copyright © 2005-2010 Tobias Luetke.
48
+ He has released it open-source under the [MIT license][1]
49
+
50
+ [1]: https://github.com/Shopify/active_merchant/blob/master/MIT-LICENSE
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+ desc "Run the unit test suite"
8
+ task :default => 'test:units'
9
+
10
+ namespace :test do
11
+
12
+ Rake::TestTask.new(:units) do |t|
13
+ t.pattern = 'test/unit/**/*_test.rb'
14
+ t.ruby_opts << '-rubygems'
15
+ t.libs << 'test'
16
+ t.verbose = true
17
+ end
18
+
19
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/am_credit_card_version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Paul Annesley"]
6
+ gem.email = ["paul@annesley.cc"]
7
+ gem.description = %q{ActiveMerchant::Billing::CreditCard, without ActiveMerchant}
8
+ gem.summary = %q{ActiveMerchant has nice credit card validations, but also lots of dependencies and other code. That's fine for those using the rest of ActiveMerchant, but if you're just after it's credit card model/validations, this is for you.
9
+ }
10
+ gem.homepage = "https://github.com/pda/am_credit_card"
11
+
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
+ gem.files = `git ls-files`.split("\n")
14
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ gem.name = "am_credit_card"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = AmCreditCard::VERSION
18
+
19
+ gem.add_dependency('activesupport', '>= 2.3.11')
20
+ gem.add_dependency('i18n')
21
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ module Base
4
+ # Set ActiveMerchant gateways in test mode.
5
+ #
6
+ # ActiveMerchant::Billing::Base.gateway_mode = :test
7
+ mattr_accessor :gateway_mode
8
+
9
+ # Set both the mode of both the gateways and integrations
10
+ # at once
11
+ mattr_reader :mode
12
+
13
+ def self.mode=(mode)
14
+ @@mode = mode
15
+ self.gateway_mode = mode
16
+ end
17
+
18
+ self.mode = :production
19
+
20
+ # A check to see if we're in test mode
21
+ def self.test?
22
+ self.gateway_mode == :test
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,260 @@
1
+ require 'time'
2
+ require 'date'
3
+ require 'active_merchant/billing/expiry_date'
4
+
5
+ module ActiveMerchant #:nodoc:
6
+ module Billing #:nodoc:
7
+ # A +CreditCard+ object represents a physical credit card, and is capable of validating the various
8
+ # data associated with these.
9
+ #
10
+ # At the moment, the following credit card types are supported:
11
+ #
12
+ # * Visa
13
+ # * MasterCard
14
+ # * Discover
15
+ # * American Express
16
+ # * Diner's Club
17
+ # * JCB
18
+ # * Switch
19
+ # * Solo
20
+ # * Dankort
21
+ # * Maestro
22
+ # * Forbrugsforeningen
23
+ # * Laser
24
+ #
25
+ # For testing purposes, use the 'bogus' credit card type. This skips the vast majority of
26
+ # validations, allowing you to focus on your core concerns until you're ready to be more concerned
27
+ # with the details of particular credit cards or your gateway.
28
+ #
29
+ # == Testing With CreditCard
30
+ # Often when testing we don't care about the particulars of a given card type. When using the 'test'
31
+ # mode in your {Gateway}, there are six different valid card numbers: 1, 2, 3, 'success', 'fail',
32
+ # and 'error'.
33
+ #
34
+ # For details, see {CreditCardMethods::ClassMethods#valid_number?}
35
+ #
36
+ # == Example Usage
37
+ # cc = CreditCard.new(
38
+ # :first_name => 'Steve',
39
+ # :last_name => 'Smith',
40
+ # :month => '9',
41
+ # :year => '2010',
42
+ # :type => 'visa',
43
+ # :number => '4242424242424242'
44
+ # )
45
+ #
46
+ # cc.valid? # => true
47
+ # cc.display_number # => XXXX-XXXX-XXXX-4242
48
+ #
49
+ class CreditCard
50
+ include CreditCardMethods
51
+ include Validateable
52
+
53
+ cattr_accessor :require_verification_value
54
+ self.require_verification_value = true
55
+
56
+ # Returns or sets the credit card number.
57
+ #
58
+ # @return [String]
59
+ attr_accessor :number
60
+
61
+ # Returns or sets the expiry month for the card.
62
+ #
63
+ # @return [Integer]
64
+ attr_accessor :month
65
+
66
+ # Returns or sets the expiry year for the card.
67
+ #
68
+ # @return [Integer]
69
+ attr_accessor :year
70
+
71
+ # Returns or sets the credit card type.
72
+ #
73
+ # Valid card types are
74
+ #
75
+ # * +'visa'+
76
+ # * +'master'+
77
+ # * +'discover'+
78
+ # * +'american_express'+
79
+ # * +'diners_club'+
80
+ # * +'jcb'+
81
+ # * +'switch'+
82
+ # * +'solo'+
83
+ # * +'dankort'+
84
+ # * +'maestro'+
85
+ # * +'forbrugsforeningen'+
86
+ # * +'laser'+
87
+ #
88
+ # Or, if you wish to test your implementation, +'bogus'+.
89
+ #
90
+ # @return (String) the credit card type
91
+ attr_accessor :type
92
+
93
+ # Returns or sets the first name of the card holder.
94
+ #
95
+ # @return [String]
96
+ attr_accessor :first_name
97
+
98
+ # Returns or sets the last name of the card holder.
99
+ #
100
+ # @return [String]
101
+ attr_accessor :last_name
102
+
103
+ # Required for Switch / Solo cards
104
+ attr_accessor :start_month, :start_year, :issue_number
105
+
106
+ # Returns or sets the card verification value.
107
+ #
108
+ # This attribute is optional but recommended. The verification value is
109
+ # a {card security code}[http://en.wikipedia.org/wiki/Card_security_code]. If provided,
110
+ # the gateway will attempt to validate the value.
111
+ #
112
+ # @return [String] the verification value
113
+ attr_accessor :verification_value
114
+
115
+ alias_method :brand, :type
116
+
117
+ # Provides proxy access to an expiry date object
118
+ #
119
+ # @return [ExpiryDate]
120
+ def expiry_date
121
+ ExpiryDate.new(@month, @year)
122
+ end
123
+
124
+ # Returns whether the credit card has expired.
125
+ #
126
+ # @return +true+ if the card has expired, +false+ otherwise
127
+ def expired?
128
+ expiry_date.expired?
129
+ end
130
+
131
+ # Returns whether either the +first_name+ or the +last_name+ attributes has been set.
132
+ def name?
133
+ first_name? || last_name?
134
+ end
135
+
136
+ # Returns whether the +first_name+ attribute has been set.
137
+ def first_name?
138
+ @first_name.present?
139
+ end
140
+
141
+ # Returns whether the +last_name+ attribute has been set.
142
+ def last_name?
143
+ @last_name.present?
144
+ end
145
+
146
+ # Returns the full name of the card holder.
147
+ #
148
+ # @return [String] the full name of the card holder
149
+ def name
150
+ [@first_name, @last_name].compact.join(' ')
151
+ end
152
+
153
+ def name=(full_name)
154
+ names = full_name.split
155
+ self.last_name = names.pop
156
+ self.first_name = names.join(" ")
157
+ end
158
+
159
+ def verification_value?
160
+ !@verification_value.blank?
161
+ end
162
+
163
+ # Returns a display-friendly version of the card number.
164
+ #
165
+ # All but the last 4 numbers are replaced with an "X", and hyphens are
166
+ # inserted in order to improve legibility.
167
+ #
168
+ # @example
169
+ # credit_card = CreditCard.new(:number => "2132542376824338")
170
+ # credit_card.display_number # "XXXX-XXXX-XXXX-4338"
171
+ #
172
+ # @return [String] a display-friendly version of the card number
173
+ def display_number
174
+ self.class.mask(number)
175
+ end
176
+
177
+ def last_digits
178
+ self.class.last_digits(number)
179
+ end
180
+
181
+ # Validates the credit card details.
182
+ #
183
+ # Any validation errors are added to the {#errors} attribute.
184
+ def validate
185
+ validate_essential_attributes
186
+
187
+ # Bogus card is pretty much for testing purposes. Lets just skip these extra tests if its used
188
+ return if type == 'bogus'
189
+
190
+ validate_card_type
191
+ validate_card_number
192
+ validate_verification_value
193
+ validate_switch_or_solo_attributes
194
+ end
195
+
196
+ def self.requires_verification_value?
197
+ require_verification_value
198
+ end
199
+
200
+ private
201
+
202
+ def before_validate #:nodoc:
203
+ self.month = month.to_i
204
+ self.year = year.to_i
205
+ self.start_month = start_month.to_i unless start_month.nil?
206
+ self.start_year = start_year.to_i unless start_year.nil?
207
+ self.number = number.to_s.gsub(/[^\d]/, "")
208
+ self.type.downcase! if type.respond_to?(:downcase)
209
+ self.type = self.class.type?(number) if type.blank?
210
+ end
211
+
212
+ def validate_card_number #:nodoc:
213
+ if number.blank?
214
+ errors.add :number, "is required"
215
+ elsif !CreditCard.valid_number?(number)
216
+ errors.add :number, "is not a valid credit card number"
217
+ end
218
+
219
+ unless errors.on(:number) || errors.on(:type)
220
+ errors.add :type, "is not the correct card type" unless CreditCard.matching_type?(number, type)
221
+ end
222
+ end
223
+
224
+ def validate_card_type #:nodoc:
225
+ errors.add :type, "is required" if type.blank? && number.present?
226
+ errors.add :type, "is invalid" unless type.blank? || CreditCard.card_companies.keys.include?(type)
227
+ end
228
+
229
+ def validate_essential_attributes #:nodoc:
230
+ errors.add :first_name, "cannot be empty" if @first_name.blank?
231
+ errors.add :last_name, "cannot be empty" if @last_name.blank?
232
+
233
+ if @month.to_i.zero? || @year.to_i.zero?
234
+ errors.add :month, "is required" if @month.to_i.zero?
235
+ errors.add :year, "is required" if @year.to_i.zero?
236
+ else
237
+ errors.add :month, "is not a valid month" unless valid_month?(@month)
238
+ errors.add :year, "expired" if expired?
239
+ errors.add :year, "is not a valid year" unless expired? || valid_expiry_year?(@year)
240
+ end
241
+ end
242
+
243
+ def validate_switch_or_solo_attributes #:nodoc:
244
+ if %w[switch solo].include?(type)
245
+ unless valid_month?(@start_month) && valid_start_year?(@start_year) || valid_issue_number?(@issue_number)
246
+ errors.add :start_month, "is invalid" unless valid_month?(@start_month)
247
+ errors.add :start_year, "is invalid" unless valid_start_year?(@start_year)
248
+ errors.add :issue_number, "cannot be empty" unless valid_issue_number?(@issue_number)
249
+ end
250
+ end
251
+ end
252
+
253
+ def validate_verification_value #:nodoc:
254
+ if CreditCard.requires_verification_value?
255
+ errors.add :verification_value, "is required" unless verification_value?
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,125 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ # Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object.
4
+ module CreditCardMethods
5
+ CARD_COMPANIES = {
6
+ 'visa' => /^4\d{12}(\d{3})?$/,
7
+ 'master' => /^(5[1-5]\d{4}|677189)\d{10}$/,
8
+ 'discover' => /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/,
9
+ 'american_express' => /^3[47]\d{13}$/,
10
+ 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
11
+ 'jcb' => /^35(28|29|[3-8]\d)\d{12}$/,
12
+ 'switch' => /^6759\d{12}(\d{2,3})?$/,
13
+ 'solo' => /^6767\d{12}(\d{2,3})?$/,
14
+ 'dankort' => /^5019\d{12}$/,
15
+ 'maestro' => /^(5[06-8]|6\d)\d{10,17}$/,
16
+ 'forbrugsforeningen' => /^600722\d{10}$/,
17
+ 'laser' => /^(6304|6706|6771|6709)\d{8}(\d{4}|\d{6,7})?$/
18
+ }
19
+
20
+ def self.included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+
24
+ def valid_month?(month)
25
+ (1..12).include?(month.to_i)
26
+ end
27
+
28
+ def valid_expiry_year?(year)
29
+ (Time.now.year..Time.now.year + 20).include?(year.to_i)
30
+ end
31
+
32
+ def valid_start_year?(year)
33
+ year.to_s =~ /^\d{4}$/ && year.to_i > 1987
34
+ end
35
+
36
+ def valid_issue_number?(number)
37
+ number.to_s =~ /^\d{1,2}$/
38
+ end
39
+
40
+ module ClassMethods
41
+ # Returns true if it validates. Optionally, you can pass a card type as an argument and
42
+ # make sure it is of the correct type.
43
+ #
44
+ # References:
45
+ # - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
46
+ # - http://www.beachnet.com/~hstiles/cardtype.html
47
+ def valid_number?(number)
48
+ valid_test_mode_card_number?(number) ||
49
+ valid_card_number_length?(number) &&
50
+ valid_checksum?(number)
51
+ end
52
+
53
+ # Regular expressions for the known card companies.
54
+ #
55
+ # References:
56
+ # - http://en.wikipedia.org/wiki/Credit_card_number
57
+ # - http://www.barclaycardbusiness.co.uk/information_zone/processing/bin_rules.html
58
+ def card_companies
59
+ CARD_COMPANIES
60
+ end
61
+
62
+ # Returns a string containing the type of card from the list of known information below.
63
+ # Need to check the cards in a particular order, as there is some overlap of the allowable ranges
64
+ #--
65
+ # TODO Refactor this method. We basically need to tighten up the Maestro Regexp.
66
+ #
67
+ # Right now the Maestro regexp overlaps with the MasterCard regexp (IIRC). If we can tighten
68
+ # things up, we can boil this whole thing down to something like...
69
+ #
70
+ # def type?(number)
71
+ # return 'visa' if valid_test_mode_card_number?(number)
72
+ # card_companies.find([nil]) { |type, regexp| number =~ regexp }.first.dup
73
+ # end
74
+ #
75
+ def type?(number)
76
+ return 'bogus' if valid_test_mode_card_number?(number)
77
+
78
+ card_companies.reject { |c,p| c == 'maestro' }.each do |company, pattern|
79
+ return company.dup if number =~ pattern
80
+ end
81
+
82
+ return 'maestro' if number =~ card_companies['maestro']
83
+
84
+ return nil
85
+ end
86
+
87
+ def last_digits(number)
88
+ number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1)
89
+ end
90
+
91
+ def mask(number)
92
+ "XXXX-XXXX-XXXX-#{last_digits(number)}"
93
+ end
94
+
95
+ # Checks to see if the calculated type matches the specified type
96
+ def matching_type?(number, type)
97
+ type?(number) == type
98
+ end
99
+
100
+ private
101
+
102
+ def valid_card_number_length?(number) #:nodoc:
103
+ number.to_s.length >= 12
104
+ end
105
+
106
+ def valid_test_mode_card_number?(number) #:nodoc:
107
+ ActiveMerchant::Billing::Base.test? &&
108
+ %w[1 2 3 success failure error].include?(number.to_s)
109
+ end
110
+
111
+ # Checks the validity of a card number by use of the the Luhn Algorithm.
112
+ # Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details.
113
+ def valid_checksum?(number) #:nodoc:
114
+ sum = 0
115
+ for i in 0..number.length
116
+ weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
117
+ sum += (weight < 10) ? weight : weight - 9
118
+ end
119
+
120
+ (number[-1,1].to_i == (10 - sum % 10) % 10)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end