am_credit_card 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ require 'date'
2
+
3
+ module ActiveMerchant
4
+ module Billing
5
+ class CreditCard
6
+ class ExpiryDate #:nodoc:
7
+ attr_reader :month, :year
8
+ def initialize(month, year)
9
+ @month = month.to_i
10
+ @year = year.to_i
11
+ end
12
+
13
+ def expired? #:nodoc:
14
+ Time.now.utc > expiration
15
+ end
16
+
17
+ def expiration #:nodoc:
18
+ begin
19
+ Time.utc(year, month, month_days, 23, 59, 59)
20
+ rescue ArgumentError
21
+ Time.at(0).utc
22
+ end
23
+ end
24
+
25
+ private
26
+ def month_days
27
+ mdays = [nil,31,28,31,30,31,30,31,31,30,31,30,31]
28
+ mdays[2] = 29 if Date.leap?(year)
29
+ mdays[month]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,81 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Validateable #:nodoc:
3
+ def valid?
4
+ errors.clear
5
+
6
+ before_validate if respond_to?(:before_validate, true)
7
+ validate if respond_to?(:validate, true)
8
+
9
+ errors.empty?
10
+ end
11
+
12
+ def initialize(attributes = {})
13
+ self.attributes = attributes
14
+ end
15
+
16
+ def errors
17
+ @errors ||= Errors.new(self)
18
+ end
19
+
20
+ private
21
+
22
+ def attributes=(attributes)
23
+ unless attributes.nil?
24
+ for key, value in attributes
25
+ send("#{key}=", value )
26
+ end
27
+ end
28
+ end
29
+
30
+ # This hash keeps the errors of the object
31
+ class Errors < HashWithIndifferentAccess
32
+
33
+ def initialize(base)
34
+ super() { |h, k| h[k] = [] ; h[k] }
35
+ @base = base
36
+ end
37
+
38
+ def count
39
+ size
40
+ end
41
+
42
+ def empty?
43
+ all? { |k, v| v && v.empty? }
44
+ end
45
+
46
+ # returns a specific fields error message.
47
+ # if more than one error is available we will only return the first. If no error is available
48
+ # we return an empty string
49
+ def on(field)
50
+ self[field].to_a.first
51
+ end
52
+
53
+ def add(field, error)
54
+ self[field] << error
55
+ end
56
+
57
+ def add_to_base(error)
58
+ add(:base, error)
59
+ end
60
+
61
+ def each_full
62
+ full_messages.each { |msg| yield msg }
63
+ end
64
+
65
+ def full_messages
66
+ result = []
67
+
68
+ self.each do |key, messages|
69
+ next if messages.blank?
70
+ if key == 'base'
71
+ result << "#{messages.first}"
72
+ else
73
+ result << "#{key.to_s.humanize} #{messages.first}"
74
+ end
75
+ end
76
+
77
+ result
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright (c) 2005-2010 Tobias Luetke
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'active_support/core_ext/class/attribute_accessors'
25
+ require 'active_support/core_ext/hash/indifferent_access'
26
+ require 'active_support/core_ext/object/conversions'
27
+ require 'active_support/core_ext/object/blank'
28
+
29
+ require 'active_merchant/validateable'
30
+ require 'active_merchant/billing/base'
31
+ require 'active_merchant/billing/credit_card_methods'
32
+ require 'active_merchant/billing/credit_card'
@@ -0,0 +1,3 @@
1
+ module AmCreditCard
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ begin
5
+ require 'rubygems'
6
+ require 'bundler'
7
+ Bundler.setup
8
+ rescue LoadError => e
9
+ puts "Error loading bundler (#{e.message}): \"gem install bundler\" for bundler support."
10
+ end
11
+
12
+ require 'test/unit'
13
+ require "am_credit_card"
14
+
15
+ module ActiveMerchant
16
+ module Assertions
17
+ AssertionClass = RUBY_VERSION > '1.9' ? MiniTest::Assertion : Test::Unit::AssertionFailedError
18
+
19
+ def assert_field(field, value)
20
+ clean_backtrace do
21
+ assert_equal value, @helper.fields[field]
22
+ end
23
+ end
24
+
25
+ # Allows the testing of you to check for negative assertions:
26
+ #
27
+ # # Instead of
28
+ # assert !something_that_is_false
29
+ #
30
+ # # Do this
31
+ # assert_false something_that_should_be_false
32
+ #
33
+ # An optional +msg+ parameter is available to help you debug.
34
+ def assert_false(boolean, message = nil)
35
+ message = build_message message, '<?> is not false or nil.', boolean
36
+
37
+ clean_backtrace do
38
+ assert_block message do
39
+ not boolean
40
+ end
41
+ end
42
+ end
43
+
44
+ # A handy little assertion to check for a successful response:
45
+ #
46
+ # # Instead of
47
+ # assert_success response
48
+ #
49
+ # # DRY that up with
50
+ # assert_success response
51
+ #
52
+ # A message will automatically show the inspection of the response
53
+ # object if things go afoul.
54
+ def assert_success(response)
55
+ clean_backtrace do
56
+ assert response.success?, "Response failed: #{response.inspect}"
57
+ end
58
+ end
59
+
60
+ # The negative of +assert_success+
61
+ def assert_failure(response)
62
+ clean_backtrace do
63
+ assert_false response.success?, "Response expected to fail: #{response.inspect}"
64
+ end
65
+ end
66
+
67
+ def assert_valid(validateable)
68
+ clean_backtrace do
69
+ assert validateable.valid?, "Expected to be valid"
70
+ end
71
+ end
72
+
73
+ def assert_not_valid(validateable)
74
+ clean_backtrace do
75
+ assert_false validateable.valid?, "Expected to not be valid"
76
+ end
77
+ end
78
+
79
+ def assert_deprecation_warning(message, target)
80
+ target.expects(:deprecated).with(message)
81
+ yield
82
+ end
83
+
84
+ private
85
+ def clean_backtrace(&block)
86
+ yield
87
+ rescue AssertionClass => e
88
+ path = File.expand_path(__FILE__)
89
+ raise AssertionClass, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
90
+ end
91
+ end
92
+
93
+ module Fixtures
94
+ HOME_DIR = RUBY_PLATFORM =~ /mswin32/ ? ENV['HOMEPATH'] : ENV['HOME'] unless defined?(HOME_DIR)
95
+ LOCAL_CREDENTIALS = File.join(HOME_DIR.to_s, '.active_merchant/fixtures.yml') unless defined?(LOCAL_CREDENTIALS)
96
+ DEFAULT_CREDENTIALS = File.join(File.dirname(__FILE__), 'fixtures.yml') unless defined?(DEFAULT_CREDENTIALS)
97
+
98
+ private
99
+ def credit_card(number = '4242424242424242', options = {})
100
+ defaults = {
101
+ :number => number,
102
+ :month => 9,
103
+ :year => Time.now.year + 1,
104
+ :first_name => 'Longbob',
105
+ :last_name => 'Longsen',
106
+ :verification_value => '123',
107
+ :type => 'visa'
108
+ }.update(options)
109
+
110
+ Billing::CreditCard.new(defaults)
111
+ end
112
+
113
+ def check(options = {})
114
+ defaults = {
115
+ :name => 'Jim Smith',
116
+ :routing_number => '244183602',
117
+ :account_number => '15378535',
118
+ :account_holder_type => 'personal',
119
+ :account_type => 'checking',
120
+ :number => '1'
121
+ }.update(options)
122
+
123
+ Billing::Check.new(defaults)
124
+ end
125
+
126
+ def address(options = {})
127
+ {
128
+ :name => 'Jim Smith',
129
+ :address1 => '1234 My Street',
130
+ :address2 => 'Apt 1',
131
+ :company => 'Widgets Inc',
132
+ :city => 'Ottawa',
133
+ :state => 'ON',
134
+ :zip => 'K1C2N6',
135
+ :country => 'CA',
136
+ :phone => '(555)555-5555',
137
+ :fax => '(555)555-6666'
138
+ }.update(options)
139
+ end
140
+
141
+ def all_fixtures
142
+ @@fixtures ||= load_fixtures
143
+ end
144
+
145
+ def fixtures(key)
146
+ data = all_fixtures[key] || raise(StandardError, "No fixture data was found for '#{key}'")
147
+
148
+ data.dup
149
+ end
150
+
151
+ def load_fixtures
152
+ file = File.exists?(LOCAL_CREDENTIALS) ? LOCAL_CREDENTIALS : DEFAULT_CREDENTIALS
153
+ yaml_data = YAML.load(File.read(file))
154
+ symbolize_keys(yaml_data)
155
+
156
+ yaml_data
157
+ end
158
+
159
+ def symbolize_keys(hash)
160
+ return unless hash.is_a?(Hash)
161
+
162
+ hash.symbolize_keys!
163
+ hash.each{|k,v| symbolize_keys(v)}
164
+ end
165
+ end
166
+ end
167
+
168
+ ActiveMerchant::Billing::Base.mode = :test
169
+
170
+ Test::Unit::TestCase.class_eval do
171
+ include ActiveMerchant::Billing
172
+ include ActiveMerchant::Assertions
173
+ include ActiveMerchant::Fixtures
174
+ end
@@ -0,0 +1,195 @@
1
+ require 'test_helper'
2
+
3
+ class CreditCardMethodsTest < Test::Unit::TestCase
4
+ include ActiveMerchant::Billing::CreditCardMethods
5
+
6
+ class CreditCard
7
+ include ActiveMerchant::Billing::CreditCardMethods
8
+ end
9
+
10
+ def maestro_card_numbers
11
+ %w[
12
+ 5000000000000000 5099999999999999 5600000000000000
13
+ 5899999999999999 6000000000000000 6999999999999999
14
+ 6761999999999999 6763000000000000 5038999999999999
15
+ ]
16
+ end
17
+
18
+ def non_maestro_card_numbers
19
+ %w[
20
+ 4999999999999999 5100000000000000 5599999999999999
21
+ 5900000000000000 5999999999999999 7000000000000000
22
+ ]
23
+ end
24
+
25
+ def test_should_be_able_to_identify_valid_expiry_months
26
+ assert_false valid_month?(-1)
27
+ assert_false valid_month?(13)
28
+ assert_false valid_month?(nil)
29
+ assert_false valid_month?('')
30
+
31
+ 1.upto(12) { |m| assert valid_month?(m) }
32
+ end
33
+
34
+ def test_should_be_able_to_identify_valid_expiry_years
35
+ assert_false valid_expiry_year?(-1)
36
+ assert_false valid_expiry_year?(Time.now.year + 21)
37
+
38
+ 0.upto(20) { |n| assert valid_expiry_year?(Time.now.year + n) }
39
+ end
40
+
41
+ def test_should_be_able_to_identify_valid_start_years
42
+ assert valid_start_year?(1988)
43
+ assert valid_start_year?(2007)
44
+ assert valid_start_year?(3000)
45
+
46
+ assert_false valid_start_year?(1987)
47
+ end
48
+
49
+ def test_valid_start_year_can_handle_strings
50
+ assert valid_start_year?("2009")
51
+ end
52
+
53
+ def test_valid_month_can_handle_strings
54
+ assert valid_month?("1")
55
+ end
56
+
57
+ def test_valid_expiry_year_can_handle_strings
58
+ year = Time.now.year + 1
59
+ assert valid_expiry_year?(year.to_s)
60
+ end
61
+
62
+ def test_should_be_able_to_identify_valid_issue_numbers
63
+ assert valid_issue_number?(1)
64
+ assert valid_issue_number?(10)
65
+ assert valid_issue_number?('12')
66
+ assert valid_issue_number?(0)
67
+
68
+ assert_false valid_issue_number?(-1)
69
+ assert_false valid_issue_number?(123)
70
+ assert_false valid_issue_number?('CAT')
71
+ end
72
+
73
+ def test_should_ensure_type_from_credit_card_class_is_not_frozen
74
+ assert_false CreditCard.type?('4242424242424242').frozen?
75
+ end
76
+
77
+ def test_should_be_dankort_card_type
78
+ assert_equal 'dankort', CreditCard.type?('5019717010103742')
79
+ end
80
+
81
+ def test_should_detect_visa_dankort_as_visa
82
+ assert_equal 'visa', CreditCard.type?('4571100000000000')
83
+ end
84
+
85
+ def test_should_detect_electron_dk_as_visa
86
+ assert_equal 'visa', CreditCard.type?('4175001000000000')
87
+ end
88
+
89
+ def test_should_detect_diners_club
90
+ assert_equal 'diners_club', CreditCard.type?('36148010000000')
91
+ end
92
+
93
+ def test_should_detect_diners_club_dk
94
+ assert_equal 'diners_club', CreditCard.type?('30401000000000')
95
+ end
96
+
97
+ def test_should_detect_maestro_dk_as_maestro
98
+ assert_equal 'maestro', CreditCard.type?('6769271000000000')
99
+ end
100
+
101
+ def test_should_detect_maestro_cards
102
+ assert_equal 'maestro', CreditCard.type?('5020100000000000')
103
+
104
+ maestro_card_numbers.each { |number| assert_equal 'maestro', CreditCard.type?(number) }
105
+ non_maestro_card_numbers.each { |number| assert_not_equal 'maestro', CreditCard.type?(number) }
106
+ end
107
+
108
+ def test_should_detect_mastercard
109
+ assert_equal 'master', CreditCard.type?('6771890000000000')
110
+ assert_equal 'master', CreditCard.type?('5413031000000000')
111
+ end
112
+
113
+ def test_should_detect_forbrugsforeningen
114
+ assert_equal 'forbrugsforeningen', CreditCard.type?('6007221000000000')
115
+ end
116
+
117
+ def test_should_detect_laser_card
118
+ # 16 digits
119
+ assert_equal 'laser', CreditCard.type?('6304985028090561')
120
+
121
+ # 18 digits
122
+ assert_equal 'laser', CreditCard.type?('630498502809056151')
123
+
124
+ # 19 digits
125
+ assert_equal 'laser', CreditCard.type?('6304985028090561515')
126
+
127
+ # 17 digits
128
+ assert_not_equal 'laser', CreditCard.type?('63049850280905615')
129
+
130
+ # 15 digits
131
+ assert_not_equal 'laser', CreditCard.type?('630498502809056')
132
+
133
+ # Alternate format
134
+ assert_equal 'laser', CreditCard.type?('6706950000000000000')
135
+
136
+ # Alternate format (16 digits)
137
+ assert_equal 'laser', CreditCard.type?('6706123456789012')
138
+
139
+ # New format (16 digits)
140
+ assert_equal 'laser', CreditCard.type?('6709123456789012')
141
+
142
+ # Ulster bank (Ireland) with 12 digits
143
+ assert_equal 'laser', CreditCard.type?('677117111234')
144
+ end
145
+
146
+ def test_should_detect_when_an_argument_type_does_not_match_calculated_type
147
+ assert CreditCard.matching_type?('4175001000000000', 'visa')
148
+ assert_false CreditCard.matching_type?('4175001000000000', 'master')
149
+ end
150
+
151
+ def test_detecting_full_range_of_maestro_card_numbers
152
+ maestro = '50000000000'
153
+
154
+ assert_equal 11, maestro.length
155
+ assert_not_equal 'maestro', CreditCard.type?(maestro)
156
+
157
+ while maestro.length < 19
158
+ maestro << '0'
159
+ assert_equal 'maestro', CreditCard.type?(maestro)
160
+ end
161
+
162
+ assert_equal 19, maestro.length
163
+
164
+ maestro << '0'
165
+ assert_not_equal 'maestro', CreditCard.type?(maestro)
166
+ end
167
+
168
+ def test_matching_discover_card
169
+ assert_equal 'discover', CreditCard.type?('6011000000000000')
170
+ assert_equal 'discover', CreditCard.type?('6500000000000000')
171
+ assert_equal 'discover', CreditCard.type?('6221260000000000')
172
+ assert_equal 'discover', CreditCard.type?('6450000000000000')
173
+
174
+ assert_not_equal 'discover', CreditCard.type?('6010000000000000')
175
+ assert_not_equal 'discover', CreditCard.type?('6600000000000000')
176
+ end
177
+
178
+ def test_16_digit_maestro_uk
179
+ number = '6759000000000000'
180
+ assert_equal 16, number.length
181
+ assert_equal 'switch', CreditCard.type?(number)
182
+ end
183
+
184
+ def test_18_digit_maestro_uk
185
+ number = '675900000000000000'
186
+ assert_equal 18, number.length
187
+ assert_equal 'switch', CreditCard.type?(number)
188
+ end
189
+
190
+ def test_19_digit_maestro_uk
191
+ number = '6759000000000000000'
192
+ assert_equal 19, number.length
193
+ assert_equal 'switch', CreditCard.type?(number)
194
+ end
195
+ end