cardia 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.
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +28 -0
- data/README.txt +8 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +17 -0
- data/lib/cardia/credit_card_transaction.rb +207 -0
- data/lib/cardia/customer.rb +15 -0
- data/lib/cardia/merchant.rb +23 -0
- data/lib/cardia/order.rb +37 -0
- data/lib/cardia/transaction_status.rb +60 -0
- data/lib/cardia/version.rb +9 -0
- data/lib/cardia.rb +19 -0
- data/lib/credit_card.rb +189 -0
- data/lib/enum.rb +78 -0
- data/lib/validateable.rb +80 -0
- data/log/debug.log +0 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +27 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/test/test_cardia.rb +11 -0
- data/test/test_credit_card_transaction.rb +76 -0
- data/test/test_helper.rb +33 -0
- data/test/test_transaction_status.rb +15 -0
- metadata +80 -0
data/lib/credit_card.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'validateable'
|
3
|
+
|
4
|
+
module ActiveMerchant
|
5
|
+
module Billing
|
6
|
+
|
7
|
+
# Represents a credit card
|
8
|
+
class CreditCard
|
9
|
+
include Validateable
|
10
|
+
|
11
|
+
attr_accessor :number, :month, :year, :first_name, :last_name, :type
|
12
|
+
|
13
|
+
# Creates a new Credit card instance
|
14
|
+
# Valid options are
|
15
|
+
# * :number
|
16
|
+
# * :month
|
17
|
+
# * :year
|
18
|
+
# * :first_name
|
19
|
+
# * :last_name
|
20
|
+
# * :verification_value
|
21
|
+
# * :type
|
22
|
+
def initialize(options = {})
|
23
|
+
options.each do |key, value|
|
24
|
+
self.send("#{key}=", value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Optional verification_value (CVV, CVV2 etc)
|
29
|
+
attr_accessor :verification_value
|
30
|
+
|
31
|
+
def to_param #:nodoc:
|
32
|
+
return {
|
33
|
+
:number => self.number,
|
34
|
+
:month => self.month,
|
35
|
+
:year => self.year,
|
36
|
+
:type => self.type,
|
37
|
+
:first_name => self.first_name,
|
38
|
+
:last_name => self.last_name,
|
39
|
+
:verification_value => self.verification_value}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a valid credit card (visa) for testing purposes.
|
43
|
+
def self.valid_card_for_testing
|
44
|
+
result = self.new
|
45
|
+
result.number = "4569971388844836"
|
46
|
+
result.verification_value = "081"
|
47
|
+
result.expiry_date = Date.parse("2017-03-01")
|
48
|
+
result.type = "visa"
|
49
|
+
result.first_name = "John"
|
50
|
+
result.last_name = "Doe"
|
51
|
+
return result
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns an invalid credit card for testing purposes
|
55
|
+
def self.invalid_card_for_testing
|
56
|
+
result = self.valid_card_for_testing
|
57
|
+
result.number = "45699713888448360"
|
58
|
+
return result
|
59
|
+
end
|
60
|
+
|
61
|
+
def before_validate #:nodoc:
|
62
|
+
self.type.downcase! if type.respond_to?(:downcase)
|
63
|
+
self.month = month.to_i
|
64
|
+
self.year = year.to_i
|
65
|
+
self.number.to_s.gsub!(/[^\d]/, "")
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate #:nodoc:
|
69
|
+
@errors.add "year", "expired" if expired?
|
70
|
+
#@errors.add "first_name", "cannot be empty" unless @first_name
|
71
|
+
#@errors.add "last_name", "cannot be empty" unless @last_name
|
72
|
+
@errors.add "month", "cannot be empty" unless (1..12).include?(month.to_i)
|
73
|
+
@errors.add "year", "cannot be empty" unless (Time.now.year..Time.now.year+10).include?(year.to_i)
|
74
|
+
|
75
|
+
# Bogus card is pretty much for testing purposes. Lets just skip these extra tests if its used
|
76
|
+
return if type == 'bogus'
|
77
|
+
|
78
|
+
@errors.add "number", "is not a vaild credit card number" unless CreditCard.valid_number?(number)
|
79
|
+
@errors.add "type", "is invalid." unless CreditCard.card_companies.include?(type)
|
80
|
+
@errors.add "type", "is not the correct card type" unless CreditCard.type?(number) == type
|
81
|
+
end
|
82
|
+
|
83
|
+
# Expiry date in mmyy format
|
84
|
+
def expires
|
85
|
+
expiry_date.strftime("%m%y")
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sets the expiry date, takes a Date instance
|
89
|
+
def expiry_date=(a_date)
|
90
|
+
self.year = a_date.year
|
91
|
+
self.month = a_date.month
|
92
|
+
end
|
93
|
+
|
94
|
+
def expiry_date #:nodoc:
|
95
|
+
Date.parse("#{year}-#{month}-01")
|
96
|
+
end
|
97
|
+
|
98
|
+
def expired? #:nodoc:
|
99
|
+
Time.now > Time.parse("#{month}/28/#{year}") rescue true
|
100
|
+
end
|
101
|
+
|
102
|
+
def name? #:nodoc:
|
103
|
+
@first_name != nil and @last_name != nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def first_name? #:nodoc:
|
107
|
+
@first_name != nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def last_name? #:nodoc:
|
111
|
+
@last_name != nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def name #:nodoc:
|
115
|
+
"#{@first_name} #{@last_name}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def payment_type
|
119
|
+
"1000"
|
120
|
+
end
|
121
|
+
|
122
|
+
def verification_value? #:nodoc:
|
123
|
+
@verification_value != nil
|
124
|
+
end
|
125
|
+
|
126
|
+
# Get the regexps for different card companies
|
127
|
+
# == Known card types
|
128
|
+
# *Card Type* *Prefix* *Length*
|
129
|
+
# mastercard 51-55 16
|
130
|
+
# visa 4 13, 16
|
131
|
+
# american_express 34, 37 15
|
132
|
+
# diners_club 300-305, 36, 38 14
|
133
|
+
# enroute 2014, 2149 15
|
134
|
+
# discover 6011 16
|
135
|
+
# jcb 3 16
|
136
|
+
# jcb 2131, 1800 15
|
137
|
+
# bankcard 5610, 56022[1-5] 16
|
138
|
+
# switch various 16,18,19
|
139
|
+
# solo 63, 6767 16,18,19
|
140
|
+
def self.card_companies_to_pattern #:nodoc:
|
141
|
+
{
|
142
|
+
'visa' => /^4\d{12}(\d{3})?$/,
|
143
|
+
'master' => /^5[1-5]\d{14}$/,
|
144
|
+
'discover' => /^6011\d{12}$/,
|
145
|
+
'american_express' => /^3[47]\d{13}$/,
|
146
|
+
'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
|
147
|
+
'enroute' => /^2(014|149)\d{11}$/,
|
148
|
+
'jcb' => /^(3\d{4}|2131|1800)\d{11}$/,
|
149
|
+
'bankcard' => /^56(10\d\d|022[1-5])\d{10}$/,
|
150
|
+
'switch' => [/^49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\d{10}(\d{2,3})?$/, /^564182\d{10}(\d{2,3})?$/, /^6(3(33[0-4][0-9])|759[0-9]{2})\d{10}(\d{2,3})?$/],
|
151
|
+
'solo' => /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.card_companies #:nodoc:
|
156
|
+
return card_companies_to_pattern.keys
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns a string containing the type of card from the list of known information below.
|
160
|
+
def self.type?(number) #:nodoc:
|
161
|
+
card_companies_to_pattern.each do |company, patterns|
|
162
|
+
return company if [patterns].flatten.any? { |pattern| number =~ pattern }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns true if it validates. Optionally, you can pass a card type as an argument and make sure it is of the correct type.
|
167
|
+
# == References
|
168
|
+
# - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
|
169
|
+
# - http://www.beachnet.com/~hstiles/cardtype.html
|
170
|
+
def self.valid_number?(number)
|
171
|
+
return false unless number.to_s.length >= 13
|
172
|
+
|
173
|
+
sum = 0
|
174
|
+
for i in 0..number.length
|
175
|
+
weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
|
176
|
+
sum += (weight < 10) ? weight : weight - 9
|
177
|
+
end
|
178
|
+
|
179
|
+
(number[-1,1].to_i == (10 - sum % 10) % 10)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Show the card number, with all but last 4 numbers replace with "x". (xxxxxxxxxxxx-4338)
|
183
|
+
def display_number
|
184
|
+
return @number if @number.blank? || @number.length < 5
|
185
|
+
"#{'x' * (@number.length - 4)}-#{@number[-4, 4]}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
data/lib/enum.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Provides simple enumerability to classes
|
2
|
+
class Object #:nodoc:
|
3
|
+
|
4
|
+
# The hidden singleton lurks behind everyone
|
5
|
+
def metaclass; class << self; self; end; end
|
6
|
+
def meta_eval(&blk); metaclass.instance_eval &blk; end
|
7
|
+
|
8
|
+
# Adds methods to a metaclass
|
9
|
+
def meta_def name, &blk
|
10
|
+
meta_eval { define_method name, &blk }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Defines an instance method within a class
|
14
|
+
def class_def name, &blk
|
15
|
+
class_eval { define_method name, &blk }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Enum #:nodoc:
|
20
|
+
attr_reader :code
|
21
|
+
|
22
|
+
def initialize(a_code)
|
23
|
+
@code = a_code.to_i unless a_code.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(an_object)
|
27
|
+
self.class == an_object.class and self.code == an_object.code
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.enums
|
31
|
+
@enums ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Adds a new enum code.
|
35
|
+
# If description isn't provided, name is used instead
|
36
|
+
def self.map(name, a_code, description=nil)
|
37
|
+
enum_description = (description || name.to_s)
|
38
|
+
self.enums[a_code] = enum_description
|
39
|
+
self.define_constructor(name, a_code)
|
40
|
+
define_method "#{name}?" do
|
41
|
+
return @code == a_code
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.define_constructor(name, a_code)
|
46
|
+
meta_def(name.to_sym) do
|
47
|
+
return self.new(a_code)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
description
|
53
|
+
end
|
54
|
+
|
55
|
+
def description
|
56
|
+
self.class.enums[@code]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
module Enumerable #:nodoc:
|
62
|
+
class Mapper #:nodoc:
|
63
|
+
def initialize( collection )
|
64
|
+
@collection = collection
|
65
|
+
end
|
66
|
+
def method_missing( meth_id, *args)
|
67
|
+
@collection.map{ |a| a.send(meth_id, *args) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def mapped
|
72
|
+
Mapper.new( self )
|
73
|
+
end
|
74
|
+
|
75
|
+
alias :collected :mapped
|
76
|
+
|
77
|
+
end
|
78
|
+
|
data/lib/validateable.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
|
3
|
+
# Implements the standard ActiveRecord stuff for non-AR classes
|
4
|
+
module Validateable #:nodoc:
|
5
|
+
def valid?
|
6
|
+
errors.clear
|
7
|
+
|
8
|
+
before_validate if respond_to?(:before_validate)
|
9
|
+
validate if respond_to?(:validate)
|
10
|
+
|
11
|
+
errors.empty?
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(attributes = {})
|
15
|
+
self.attributes = attributes
|
16
|
+
end
|
17
|
+
|
18
|
+
def errors
|
19
|
+
@errors ||= Error.new(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def attributes=(attributes)
|
25
|
+
unless attributes.nil?
|
26
|
+
for key, value in attributes
|
27
|
+
send("#{key}=", value )
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# This hash keeps the errors of the object
|
33
|
+
class Error < Hash #:nodoc:
|
34
|
+
|
35
|
+
def initialize(base)
|
36
|
+
@base = base
|
37
|
+
end
|
38
|
+
|
39
|
+
def count
|
40
|
+
size
|
41
|
+
end
|
42
|
+
|
43
|
+
# returns a specific fields error message.
|
44
|
+
# if more than one error is available we will only return the first. If no error is available
|
45
|
+
# we return an empty string
|
46
|
+
def on(field)
|
47
|
+
self[field].to_a.first
|
48
|
+
end
|
49
|
+
|
50
|
+
def add(field, error)
|
51
|
+
self[field] ||= []
|
52
|
+
self[field] << error
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_to_base(error)
|
56
|
+
add(:base, error)
|
57
|
+
end
|
58
|
+
|
59
|
+
def each_full
|
60
|
+
full_messages.each { |msg| yield msg }
|
61
|
+
end
|
62
|
+
|
63
|
+
def full_messages
|
64
|
+
result = []
|
65
|
+
|
66
|
+
self.each do |key, messages|
|
67
|
+
if key == :base
|
68
|
+
result << "#{messages.first}"
|
69
|
+
else
|
70
|
+
result << "#{key.to_s.humanize} #{messages.first}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
end
|
data/log/debug.log
ADDED
File without changes
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|