activemerchant 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +18 -0
- data/lib/active_merchant.rb +2 -0
- data/lib/active_merchant/billing/credit_card.rb +25 -113
- data/lib/active_merchant/billing/credit_card_formatting.rb +18 -0
- data/lib/active_merchant/billing/credit_card_methods.rb +83 -0
- data/lib/active_merchant/billing/gateway.rb +1 -0
- data/lib/active_merchant/billing/gateways.rb +2 -1
- data/lib/active_merchant/billing/gateways/data_cash.rb +475 -0
- data/lib/active_merchant/billing/gateways/linkpoint.rb +9 -24
- data/lib/active_merchant/billing/gateways/moneris.rb +1 -1
- data/lib/active_merchant/billing/gateways/payflow.rb +14 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -14
- data/lib/active_merchant/billing/gateways/paypal.rb +11 -3
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +47 -22
- data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -3
- data/lib/active_merchant/billing/gateways/psigate.rb +4 -19
- data/lib/active_merchant/billing/gateways/usa_epay.rb +5 -6
- data/lib/active_merchant/billing/integrations.rb +1 -0
- data/lib/active_merchant/billing/integrations/helper.rb +13 -6
- data/lib/active_merchant/billing/integrations/notification.rb +2 -1
- data/lib/active_merchant/billing/integrations/paypal/helper.rb +2 -0
- data/lib/active_merchant/billing/integrations/two_checkout.rb +18 -0
- data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +59 -0
- data/lib/active_merchant/billing/integrations/two_checkout/notification.rb +114 -0
- data/lib/active_merchant/lib/posts_data.rb +18 -9
- data/lib/active_merchant/lib/validateable.rb +2 -2
- data/lib/certs/cacert.pem +7815 -0
- metadata +11 -2
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
# ---
|
3
|
+
# * Mark 1.1.0 release
|
4
|
+
#
|
5
|
+
# * Add unique_id option to PayPal mass payments [Haig]
|
6
|
+
#
|
7
|
+
# * Fix expiry date in USA ePay [cody]
|
8
|
+
#
|
9
|
+
# * Fix PayPal Payments Pro UK with Switch & Solo cards [cody]
|
10
|
+
#
|
11
|
+
# * Add reauthorization to PaypalGateway and PaypalExpressGateway [dorrenchen]
|
12
|
+
#
|
13
|
+
# * Update DataCash tests and format merchant reference number to meet DataCash's requirements [MoneySpyder, cody]
|
14
|
+
#
|
15
|
+
# * Add Datacash gateway [MoneySpyder, cody]
|
16
|
+
#
|
17
|
+
# * VERIFY_PEER on all SSL requests [cody]
|
18
|
+
#
|
19
|
+
# * Add support for 2Checkout [cody]
|
20
|
+
#
|
3
21
|
# * Add support for PayPal mass payments to the PaypalGateway and the PaypalExpressGateway [Brandon Keepers]
|
4
22
|
#
|
5
23
|
# * Add a credit method to Authorize.net [cody]
|
data/lib/active_merchant.rb
CHANGED
@@ -48,6 +48,8 @@ require 'active_merchant/lib/requires_parameters'
|
|
48
48
|
require 'active_merchant/lib/country'
|
49
49
|
|
50
50
|
# CreditCard Utility class.
|
51
|
+
require 'active_merchant/billing/credit_card_methods'
|
52
|
+
require 'active_merchant/billing/credit_card_formatting'
|
51
53
|
require 'active_merchant/billing/credit_card'
|
52
54
|
|
53
55
|
require 'active_merchant/billing/base'
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'time'
|
2
|
-
require 'delegate'
|
3
2
|
require 'date'
|
4
3
|
|
5
4
|
module ActiveMerchant #:nodoc:
|
6
5
|
module Billing #:nodoc:
|
7
|
-
|
8
6
|
# This credit card object can be used as a stand alone object. It acts just like a active record object
|
9
7
|
# but doesn't support the .save method as its not backed by a database.
|
10
8
|
class CreditCard
|
9
|
+
include CreditCardMethods
|
10
|
+
|
11
11
|
cattr_accessor :require_verification_value
|
12
12
|
self.require_verification_value = false
|
13
13
|
|
@@ -17,47 +17,11 @@ module ActiveMerchant #:nodoc:
|
|
17
17
|
|
18
18
|
include Validateable
|
19
19
|
|
20
|
-
class ExpiryMonth < DelegateClass(Fixnum)#:nodoc:
|
21
|
-
def to_s(format = :default) #:nodoc:
|
22
|
-
case format
|
23
|
-
when :default
|
24
|
-
__getobj__.to_s
|
25
|
-
when :two_digit
|
26
|
-
sprintf("%.2i", self)[-2..-1]
|
27
|
-
else
|
28
|
-
super
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def valid? #:nodoc:
|
33
|
-
(1..12).include?(self)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class ExpiryYear < DelegateClass(Fixnum)#:nodoc:
|
38
|
-
def to_s(format = :default) #:nodoc:
|
39
|
-
case format
|
40
|
-
when :default
|
41
|
-
__getobj__.to_s
|
42
|
-
when :two_digit
|
43
|
-
sprintf("%.2i", self)[-2..-1]
|
44
|
-
when :four_digit
|
45
|
-
sprintf("%.4i", self)
|
46
|
-
else
|
47
|
-
super
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def valid? #:nodoc:
|
52
|
-
(Time.now.year..Time.now.year + 20).include?(self)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
20
|
class ExpiryDate #:nodoc:
|
57
21
|
attr_reader :month, :year
|
58
22
|
def initialize(month, year)
|
59
|
-
@month =
|
60
|
-
@year =
|
23
|
+
@month = month
|
24
|
+
@year = year
|
61
25
|
end
|
62
26
|
|
63
27
|
def expired? #:nodoc:
|
@@ -78,6 +42,9 @@ module ActiveMerchant #:nodoc:
|
|
78
42
|
|
79
43
|
# required
|
80
44
|
attr_accessor :number, :month, :year, :type, :first_name, :last_name
|
45
|
+
|
46
|
+
# required for Switch / Solo
|
47
|
+
attr_accessor :start_month, :start_year, :issue_number
|
81
48
|
|
82
49
|
# Optional verification_value (CVV, CVV2 etc)
|
83
50
|
#
|
@@ -93,23 +60,31 @@ module ActiveMerchant #:nodoc:
|
|
93
60
|
end
|
94
61
|
|
95
62
|
def validate
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
63
|
+
errors.add "year", "expired" if expired?
|
64
|
+
|
65
|
+
errors.add "first_name", "cannot be empty" if @first_name.blank?
|
66
|
+
errors.add "last_name", "cannot be empty" if @last_name.blank?
|
67
|
+
errors.add "month", "cannot be empty" unless valid_month?(@month)
|
68
|
+
errors.add "year", "cannot be empty" unless valid_expiry_year?(@year)
|
102
69
|
|
103
70
|
# Bogus card is pretty much for testing purposes. Lets just skip these extra tests if its used
|
104
71
|
|
105
72
|
return if type == 'bogus'
|
106
73
|
|
107
|
-
|
108
|
-
|
109
|
-
|
74
|
+
errors.add "number", "is not a valid credit card number" unless CreditCard.valid_number?(number)
|
75
|
+
errors.add "type", "is invalid" unless CreditCard.card_companies.keys.include?(type)
|
76
|
+
errors.add "type", "is not the correct card type" unless CreditCard.type?(number) == type
|
110
77
|
|
111
78
|
if CreditCard.requires_verification_value?
|
112
|
-
|
79
|
+
errors.add "verification_value", "is required" unless verification_value?
|
80
|
+
end
|
81
|
+
|
82
|
+
if [ 'switch', 'solo' ].include?(type)
|
83
|
+
unless valid_month?(@start_month) && valid_start_year?(@start_year) || valid_issue_number?(@issue_number)
|
84
|
+
errors.add "start_month", "is invalid" unless valid_month?(@start_month)
|
85
|
+
errors.add "start_year", "is invalid" unless valid_start_year?(@start_year)
|
86
|
+
errors.add "issue_number", "cannot be empty" unless valid_issue_number?(@issue_number)
|
87
|
+
end
|
113
88
|
end
|
114
89
|
end
|
115
90
|
|
@@ -137,61 +112,6 @@ module ActiveMerchant #:nodoc:
|
|
137
112
|
!@verification_value.blank?
|
138
113
|
end
|
139
114
|
|
140
|
-
# Regular expressions for the known card companies
|
141
|
-
# == Known card types
|
142
|
-
# Card Type Prefix Length
|
143
|
-
# --------------------------------------------------------------------------
|
144
|
-
# master 51-55 16
|
145
|
-
# visa 4 13, 16
|
146
|
-
# american_express 34, 37 15
|
147
|
-
# diners_club 300-305, 36, 38 14
|
148
|
-
# discover 6011 16
|
149
|
-
# jcb 3 16
|
150
|
-
# jcb 2131, 1800 15
|
151
|
-
# switch various 16,18,19
|
152
|
-
# solo 63, 6767 16,18,19
|
153
|
-
def self.card_companies
|
154
|
-
{
|
155
|
-
'visa' => /^4\d{12}(\d{3})?$/,
|
156
|
-
'master' => /^5[1-5]\d{14}$/,
|
157
|
-
'discover' => /^6011\d{12}$/,
|
158
|
-
'american_express' => /^3[47]\d{13}$/,
|
159
|
-
'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
|
160
|
-
'jcb' => /^(3\d{4}|2131|1800)\d{11}$/,
|
161
|
-
'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})?$/],
|
162
|
-
'solo' => /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/
|
163
|
-
}
|
164
|
-
end
|
165
|
-
|
166
|
-
# Returns a string containing the type of card from the list of known information below.
|
167
|
-
def self.type?(number)
|
168
|
-
return 'visa' if Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
|
169
|
-
|
170
|
-
card_companies.each do |company, patterns|
|
171
|
-
return company if [patterns].flatten.any? { |pattern| number =~ pattern }
|
172
|
-
end
|
173
|
-
|
174
|
-
return nil
|
175
|
-
end
|
176
|
-
|
177
|
-
# Returns true if it validates. Optionally, you can pass a card type as an argument and make sure it is of the correct type.
|
178
|
-
# == References
|
179
|
-
# - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
|
180
|
-
# - http://www.beachnet.com/~hstiles/cardtype.html
|
181
|
-
def self.valid_number?(number)
|
182
|
-
return true if Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
|
183
|
-
|
184
|
-
return false unless number.to_s.length >= 13
|
185
|
-
|
186
|
-
sum = 0
|
187
|
-
for i in 0..number.length
|
188
|
-
weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
|
189
|
-
sum += (weight < 10) ? weight : weight - 9
|
190
|
-
end
|
191
|
-
|
192
|
-
(number[-1,1].to_i == (10 - sum % 10) % 10)
|
193
|
-
end
|
194
|
-
|
195
115
|
# Show the card number, with all but last 4 numbers replace with "X". (XXXX-XXXX-XXXX-4338)
|
196
116
|
def display_number
|
197
117
|
"XXXX-XXXX-XXXX-#{last_digits}"
|
@@ -201,14 +121,6 @@ module ActiveMerchant #:nodoc:
|
|
201
121
|
number.nil? ? "" : number.last(4)
|
202
122
|
end
|
203
123
|
|
204
|
-
def month
|
205
|
-
expiry_date.month
|
206
|
-
end
|
207
|
-
|
208
|
-
def year
|
209
|
-
expiry_date.year
|
210
|
-
end
|
211
|
-
|
212
124
|
def expiry_date
|
213
125
|
ExpiryDate.new(@month, @year)
|
214
126
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
module CreditCardFormatting
|
4
|
+
def format(number, format)
|
5
|
+
return '' if number.blank?
|
6
|
+
|
7
|
+
case format
|
8
|
+
when :two_digits
|
9
|
+
sprintf("%.2i", number)[-2..-1]
|
10
|
+
when :four_digits
|
11
|
+
sprintf("%.4i", number)[-4..-1]
|
12
|
+
else
|
13
|
+
number
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,83 @@
|
|
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
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid_month?(month)
|
10
|
+
(1..12).include?(month)
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_expiry_year?(year)
|
14
|
+
(Time.now.year..Time.now.year + 20).include?(year)
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid_start_year?(year)
|
18
|
+
year.to_s =~ /^\d{4}$/ && year.to_i > 1987
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_issue_number?(number)
|
22
|
+
number.to_s =~ /^\d{1,2}$/
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
# Returns true if it validates. Optionally, you can pass a card type as an argument and make sure it is of the correct type.
|
27
|
+
# == References
|
28
|
+
# - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
|
29
|
+
# - http://www.beachnet.com/~hstiles/cardtype.html
|
30
|
+
def valid_number?(number)
|
31
|
+
return true if ActiveMerchant::Billing::Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
|
32
|
+
|
33
|
+
return false unless number.to_s.length >= 13
|
34
|
+
|
35
|
+
sum = 0
|
36
|
+
for i in 0..number.length
|
37
|
+
weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
|
38
|
+
sum += (weight < 10) ? weight : weight - 9
|
39
|
+
end
|
40
|
+
|
41
|
+
(number[-1,1].to_i == (10 - sum % 10) % 10)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Regular expressions for the known card companies
|
45
|
+
# == Known card types
|
46
|
+
# Card Type Prefix Length
|
47
|
+
# --------------------------------------------------------------------------
|
48
|
+
# master 51-55 16
|
49
|
+
# visa 4 13, 16
|
50
|
+
# american_express 34, 37 15
|
51
|
+
# diners_club 300-305, 36, 38 14
|
52
|
+
# discover 6011 16
|
53
|
+
# jcb 3 16
|
54
|
+
# jcb 2131, 1800 15
|
55
|
+
# switch various 16,18,19
|
56
|
+
# solo 63, 6767 16,18,19
|
57
|
+
def card_companies
|
58
|
+
{
|
59
|
+
'visa' => /^4\d{12}(\d{3})?$/,
|
60
|
+
'master' => /^5[1-5]\d{14}$/,
|
61
|
+
'discover' => /^6011\d{12}$/,
|
62
|
+
'american_express' => /^3[47]\d{13}$/,
|
63
|
+
'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
|
64
|
+
'jcb' => /^(3\d{4}|2131|1800)\d{11}$/,
|
65
|
+
'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})?$/],
|
66
|
+
'solo' => /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a string containing the type of card from the list of known information below.
|
71
|
+
def type?(number)
|
72
|
+
return 'visa' if ActiveMerchant::Billing::Base.gateway_mode == :test and ['1','2','3','success','failure','error'].include?(number.to_s)
|
73
|
+
|
74
|
+
card_companies.each do |company, patterns|
|
75
|
+
return company if [patterns].flatten.any? { |pattern| number =~ pattern }
|
76
|
+
end
|
77
|
+
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -12,4 +12,5 @@ require 'active_merchant/billing/gateways/usa_epay'
|
|
12
12
|
require 'active_merchant/billing/gateways/payflow'
|
13
13
|
require 'active_merchant/billing/gateways/payflow_express'
|
14
14
|
require 'active_merchant/billing/gateways/payflow_uk'
|
15
|
-
require 'active_merchant/billing/gateways/payflow_express_uk'
|
15
|
+
require 'active_merchant/billing/gateways/payflow_express_uk'
|
16
|
+
require 'active_merchant/billing/gateways/data_cash'
|
@@ -0,0 +1,475 @@
|
|
1
|
+
# Author:: MoneySpyder, http://moneyspyder.co.uk
|
2
|
+
|
3
|
+
module ActiveMerchant
|
4
|
+
module Billing
|
5
|
+
# ActiveMerchant Datacash Gateway
|
6
|
+
#
|
7
|
+
# Datacash allows a policy for CV2 checks. There is currently no way
|
8
|
+
# to modify this programatically. The policy may be changed in the
|
9
|
+
# add_credit_card method.
|
10
|
+
#
|
11
|
+
class DataCashGateway < Gateway
|
12
|
+
|
13
|
+
# Datacash server URLs
|
14
|
+
TEST_URL = 'https://testserver.datacash.com/Transaction'
|
15
|
+
LIVE_URL = 'https://mars.transaction.datacash.com/Transaction'
|
16
|
+
|
17
|
+
# Different Card Transaction Types
|
18
|
+
AUTH_TYPE = 'auth'
|
19
|
+
CANCEL_TYPE = 'cancel'
|
20
|
+
FULFILL_TYPE = 'fulfill'
|
21
|
+
PRE_TYPE = 'pre'
|
22
|
+
|
23
|
+
# Constant strings for use in the ExtendedPolicy complex element for
|
24
|
+
# CV2 checks
|
25
|
+
POLICY_ACCEPT = 'accept'
|
26
|
+
POLICY_REJECT = 'reject'
|
27
|
+
|
28
|
+
#Datacash success code
|
29
|
+
DATACASH_SUCCESS = '1'
|
30
|
+
|
31
|
+
# Class attributes
|
32
|
+
attr_reader :url
|
33
|
+
attr_reader :response
|
34
|
+
attr_reader :options
|
35
|
+
|
36
|
+
# Create a new DataCashGateway
|
37
|
+
#
|
38
|
+
# The gateway requires that a valid :login and :password be passed
|
39
|
+
# in the options hash
|
40
|
+
#
|
41
|
+
# Parameters:
|
42
|
+
# -options:
|
43
|
+
# :login - the Datacash account login
|
44
|
+
# :password - the Datacash account password
|
45
|
+
# :test - boolean, use the test or live Datacash url
|
46
|
+
#
|
47
|
+
def initialize(options = {})
|
48
|
+
requires!(options, :login, :password)
|
49
|
+
@options = options
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
# Purchase the item straight away
|
54
|
+
#
|
55
|
+
# Parameters:
|
56
|
+
# -money: Money object for the total to be charged
|
57
|
+
# -credit_card: ActiveMerchant::Billing::CreditCard details for the transaction
|
58
|
+
# -options:
|
59
|
+
#
|
60
|
+
# Returns:
|
61
|
+
# -ActiveRecord::Billing::Response object
|
62
|
+
#
|
63
|
+
def purchase(money, credit_card, options = {})
|
64
|
+
if result = test_result_from_cc_number(credit_card.number)
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
|
68
|
+
request = build_purchase_or_authorization_request(AUTH_TYPE, money, credit_card, options)
|
69
|
+
|
70
|
+
commit(request)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Authorize the transaction
|
74
|
+
#
|
75
|
+
# Reserves the funds on the customer's credit card, but does not
|
76
|
+
# charge the card.
|
77
|
+
#
|
78
|
+
# Parameters:
|
79
|
+
# -money: Money object for the total to be charged
|
80
|
+
# -credit_card: ActiveMerchant::Billing::CreditCard details for the transaction
|
81
|
+
# -options:
|
82
|
+
#
|
83
|
+
# Returns:
|
84
|
+
# -ActiveRecord::Billing::Response object
|
85
|
+
#
|
86
|
+
def authorize(money, credit_card, options = {})
|
87
|
+
if result = test_result_from_cc_number(credit_card.number)
|
88
|
+
return result
|
89
|
+
end
|
90
|
+
|
91
|
+
request = build_purchase_or_authorization_request(PRE_TYPE, money, credit_card, options)
|
92
|
+
|
93
|
+
commit(request)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Datacash requires both the reference and the authcode of the original
|
97
|
+
# authorization. To maintain the same interface as the other
|
98
|
+
# gateways the two numbers are concatenated together with an ; separator as
|
99
|
+
# the authorization number returned by authorization
|
100
|
+
|
101
|
+
# Captures the funds from an authorized transaction.
|
102
|
+
# authorization must be a valid Datacash reference and :authcode must be
|
103
|
+
# a valid Datacash authcode from a prior authorized transaction.
|
104
|
+
#
|
105
|
+
# This needs to create a 'historic txn' to fulfill
|
106
|
+
#
|
107
|
+
# Parameters:
|
108
|
+
# -money: Money object for the total to be charged
|
109
|
+
# -authorization: the Datacash reference and authcode from the previous authorization
|
110
|
+
#
|
111
|
+
# Returns:
|
112
|
+
# -ActiveRecord::Billing::Response object
|
113
|
+
#
|
114
|
+
def capture(money, authorization, options = {})
|
115
|
+
request = build_void_or_capture_request(FULFILL_TYPE, money, authorization, options)
|
116
|
+
|
117
|
+
commit(request)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Void a previous transaction
|
121
|
+
#
|
122
|
+
# This needs to create a 'historic txn' to fulfil
|
123
|
+
#
|
124
|
+
# Parameters:
|
125
|
+
# -authorization: the Datacash reference from the previous authorization
|
126
|
+
#
|
127
|
+
# Returns:
|
128
|
+
# -ActiveRecord::Billing::Response object
|
129
|
+
#
|
130
|
+
def void(authorization, options = {})
|
131
|
+
request = build_void_or_capture_request(CANCEL_TYPE, nil, authorization, options)
|
132
|
+
|
133
|
+
commit(request)
|
134
|
+
end
|
135
|
+
|
136
|
+
# From the DataCash docs; Page 13, the following cards are
|
137
|
+
# usable
|
138
|
+
#
|
139
|
+
# American Express, ATM, Carte Blanche, Diners Club, Discover,
|
140
|
+
# EnRoute, GE Capital, JCB, Laser, Maestro, Mastercard, Solo,
|
141
|
+
# Switch, Unknown, Visa, Visa Delta, VISA Electron, Visa Purchasing
|
142
|
+
#
|
143
|
+
# Parameters:
|
144
|
+
# -none
|
145
|
+
#
|
146
|
+
# Returns:
|
147
|
+
# -the list of all supported cards
|
148
|
+
#
|
149
|
+
def self.supported_cardtypes
|
150
|
+
[ :visa, :master, :american_express, :discover, :diners_club, :jcb,
|
151
|
+
:switch, :solo ]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Return whether or not the gateway is in test mode
|
155
|
+
#
|
156
|
+
# Parameters:
|
157
|
+
# -none
|
158
|
+
#
|
159
|
+
# Returns:
|
160
|
+
# -boolean
|
161
|
+
#
|
162
|
+
def test?
|
163
|
+
@options[:test] || Base.gateway_mode == :test
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
# Create the xml document for a 'cancel' or 'fulfill' transaction.
|
168
|
+
#
|
169
|
+
# Final XML should look like:
|
170
|
+
# <Request>
|
171
|
+
# <Authentication>
|
172
|
+
# <client>99000001</client>
|
173
|
+
# <password>******</password>
|
174
|
+
# </Authentication>
|
175
|
+
# <Transaction>
|
176
|
+
# <TxnDetails>
|
177
|
+
# <amount>25.00</amount>
|
178
|
+
# </TxnDetails>
|
179
|
+
# <HistoricTxn>
|
180
|
+
# <reference>4900200000000001</reference>
|
181
|
+
# <authcode>A6</authcode>
|
182
|
+
# <method>fulfill</method>
|
183
|
+
# </HistoricTxn>
|
184
|
+
# </Transaction>
|
185
|
+
# </Request>
|
186
|
+
#
|
187
|
+
# Parameters:
|
188
|
+
# -type must be FULFILL_TYPE or CANCEL_TYPE
|
189
|
+
# -money - optional - A money object with the price and currency
|
190
|
+
# -authorization - the Datacash reference number from a previous
|
191
|
+
# succesful authorize transaction
|
192
|
+
# -authcode - the Datacash authcode
|
193
|
+
# -order_id - The merchants reference
|
194
|
+
#
|
195
|
+
# Returns:
|
196
|
+
# -Builder xml document
|
197
|
+
#
|
198
|
+
def build_void_or_capture_request(type, money, authorization, options)
|
199
|
+
reference, auth_code = authorization.to_s.split(';')
|
200
|
+
|
201
|
+
xml = Builder::XmlMarkup.new :indent => 2
|
202
|
+
xml.instruct!
|
203
|
+
xml.tag! :Request do
|
204
|
+
add_authentication(xml)
|
205
|
+
|
206
|
+
xml.tag! :Transaction do
|
207
|
+
xml.tag! :HistoricTxn do
|
208
|
+
xml.tag! :reference, reference
|
209
|
+
xml.tag! :authcode, auth_code
|
210
|
+
xml.tag! :method, type
|
211
|
+
end
|
212
|
+
|
213
|
+
if money
|
214
|
+
xml.tag! :TxnDetails do
|
215
|
+
xml.tag! :merchantreference, format_reference_number(options[:order_id])
|
216
|
+
xml.tag! :amount, amount(money), :currency => currency(money)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
xml.target!
|
222
|
+
end
|
223
|
+
|
224
|
+
# Create the xml document for an 'auth' or 'pre' transaction.
|
225
|
+
#
|
226
|
+
# Final XML should look like:
|
227
|
+
#
|
228
|
+
# <Request>
|
229
|
+
# <Authentication>
|
230
|
+
# <client>99000000</client>
|
231
|
+
# <password>*******</password>
|
232
|
+
# </Authentication>
|
233
|
+
# <Transaction>
|
234
|
+
# <TxnDetails>
|
235
|
+
# <merchantreference>123456</merchantreference>
|
236
|
+
# <amount currency="EUR">10.00</amount>
|
237
|
+
# </TxnDetails>
|
238
|
+
# <CardTxn>
|
239
|
+
# <Card>
|
240
|
+
# <pan>4444********1111</pan>
|
241
|
+
# <expirydate>03/04</expirydate>
|
242
|
+
# <Cv2Avs>
|
243
|
+
# <street_address1>Flat 7</street_address1>
|
244
|
+
# <street_address2>89 Jumble
|
245
|
+
# Street</street_address2>
|
246
|
+
# <street_address3>Mytown</street_address3>
|
247
|
+
# <postcode>AV12FR</postcode>
|
248
|
+
# <cv2>123</cv2>
|
249
|
+
# <ExtendedPolicy>
|
250
|
+
# <cv2_policy notprovided="reject"
|
251
|
+
# notchecked="accept"
|
252
|
+
# matched="accept"
|
253
|
+
# notmatched="reject"
|
254
|
+
# partialmatch="reject"/>
|
255
|
+
# <postcode_policy notprovided="reject"
|
256
|
+
# notchecked="accept"
|
257
|
+
# matched="accept"
|
258
|
+
# notmatched="reject"
|
259
|
+
# partialmatch="accept"/>
|
260
|
+
# <address_policy notprovided="reject"
|
261
|
+
# notchecked="accept"
|
262
|
+
# matched="accept"
|
263
|
+
# notmatched="reject"
|
264
|
+
# partialmatch="accept"/>
|
265
|
+
# </ExtendedPolicy>
|
266
|
+
# </Cv2Avs>
|
267
|
+
# </Card>
|
268
|
+
# <method>auth, </method>
|
269
|
+
# </CardTxn>
|
270
|
+
# </Transaction>
|
271
|
+
# </Request>
|
272
|
+
#
|
273
|
+
# Parameters:
|
274
|
+
# -type must be 'auth' or 'pre'
|
275
|
+
# -money - A money object with the price and currency
|
276
|
+
# -credit_card - The credit_card details to use
|
277
|
+
# -options:
|
278
|
+
# :order_id is the merchant reference number
|
279
|
+
# :billing_address is the billing address for the cc
|
280
|
+
# :address is the delivery address
|
281
|
+
#
|
282
|
+
# Returns:
|
283
|
+
# -xml: Builder document containing the markup
|
284
|
+
#
|
285
|
+
def build_purchase_or_authorization_request(type, money, credit_card, options)
|
286
|
+
xml = Builder::XmlMarkup.new :indent => 2
|
287
|
+
xml.instruct!
|
288
|
+
xml.tag! :Request do
|
289
|
+
add_authentication(xml)
|
290
|
+
|
291
|
+
xml.tag! :Transaction do
|
292
|
+
xml.tag! :CardTxn do
|
293
|
+
xml.tag! :method, type
|
294
|
+
|
295
|
+
add_credit_card(xml, credit_card, options[:billing_address] || options[:address])
|
296
|
+
end
|
297
|
+
xml.tag! :TxnDetails do
|
298
|
+
xml.tag! :merchantreference, format_reference_number(options[:order_id])
|
299
|
+
xml.tag! :amount, amount(money), :currency => currency(money)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
xml.target!
|
304
|
+
end
|
305
|
+
|
306
|
+
# Adds the authentication element to the passed builder xml doc
|
307
|
+
#
|
308
|
+
# Parameters:
|
309
|
+
# -xml: Builder document that is being built up
|
310
|
+
#
|
311
|
+
# Returns:
|
312
|
+
# -none: The results is stored in the passed xml document
|
313
|
+
#
|
314
|
+
def add_authentication(xml)
|
315
|
+
xml.tag! :Authentication do
|
316
|
+
xml.tag! :client, @options[:login]
|
317
|
+
xml.tag! :password, @options[:password]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Add credit_card detals to the passed XML Builder doc
|
322
|
+
#
|
323
|
+
# Parameters:
|
324
|
+
# -xml: Builder document that is being built up
|
325
|
+
# -credit_card: ActiveMerchant::Billing::CreditCard object
|
326
|
+
# -billing_address: Hash containing all of the billing address details
|
327
|
+
#
|
328
|
+
# Returns:
|
329
|
+
# -none: The results is stored in the passed xml document
|
330
|
+
#
|
331
|
+
def add_credit_card(xml, credit_card, address)
|
332
|
+
xml.tag! :Card do
|
333
|
+
|
334
|
+
# DataCash calls the CC number 'pan'
|
335
|
+
xml.tag! :pan, credit_card.number
|
336
|
+
xml.tag! :expirydate, format_date(credit_card.month, credit_card.year)
|
337
|
+
|
338
|
+
# optional values - for Solo etc
|
339
|
+
if [ 'switch', 'solo' ].include?(credit_card.type.to_s)
|
340
|
+
|
341
|
+
xml.tag! :issuenumber, credit_card.issue_number unless credit_card.issue_number.blank?
|
342
|
+
|
343
|
+
if !credit_card.start_month.blank? && !credit_card.start_year.blank?
|
344
|
+
xml.tag! :startdate, format_date(credit_card.start_month, credit_card.start_year)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
xml.tag! :Cv2Avs do
|
349
|
+
xml.tag! :cv2, credit_card.verification_value if credit_card.verification_value?
|
350
|
+
xml.tag! :street_address1, address[:address1] unless address[:address1].blank?
|
351
|
+
xml.tag! :street_address2, address[:address2] unless address[:address2].blank?
|
352
|
+
xml.tag! :street_address3, address[:address3] unless address[:address3].blank?
|
353
|
+
xml.tag! :street_address4, address[:address4] unless address[:address4].blank?
|
354
|
+
xml.tag! :postcode, address[:zip] unless address[:zip].blank?
|
355
|
+
|
356
|
+
# The ExtendedPolicy defines what to do when the passed data
|
357
|
+
# matches, or not...
|
358
|
+
#
|
359
|
+
# All of the following elements MUST be present for the
|
360
|
+
# xml to be valid (or can drop the ExtendedPolicy and use
|
361
|
+
# a predefined one
|
362
|
+
xml.tag! :ExtendedPolicy do
|
363
|
+
xml.tag! :cv2_policy,
|
364
|
+
:notprovided => POLICY_REJECT,
|
365
|
+
:notchecked => POLICY_REJECT,
|
366
|
+
:matched => POLICY_ACCEPT,
|
367
|
+
:notmatched => POLICY_REJECT,
|
368
|
+
:partialmatch => POLICY_REJECT
|
369
|
+
xml.tag! :postcode_policy,
|
370
|
+
:notprovided => POLICY_ACCEPT,
|
371
|
+
:notchecked => POLICY_ACCEPT,
|
372
|
+
:matched => POLICY_ACCEPT,
|
373
|
+
:notmatched => POLICY_REJECT,
|
374
|
+
:partialmatch => POLICY_ACCEPT
|
375
|
+
xml.tag! :address_policy,
|
376
|
+
:notprovided => POLICY_ACCEPT,
|
377
|
+
:notchecked => POLICY_ACCEPT,
|
378
|
+
:matched => POLICY_ACCEPT,
|
379
|
+
:notmatched => POLICY_REJECT,
|
380
|
+
:partialmatch => POLICY_ACCEPT
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Send the passed data to DataCash for processing
|
387
|
+
#
|
388
|
+
# Parameters:
|
389
|
+
# -request: The XML data that is to be sent to Datacash
|
390
|
+
#
|
391
|
+
# Returns:
|
392
|
+
# - ActiveMerchant::Billing::Response object
|
393
|
+
#
|
394
|
+
def commit(request)
|
395
|
+
url = test? ? TEST_URL : LIVE_URL
|
396
|
+
|
397
|
+
@response = parse(ssl_post(url, request))
|
398
|
+
success = @response[:status] == DATACASH_SUCCESS
|
399
|
+
message = @response[:reason]
|
400
|
+
|
401
|
+
Response.new(success, message, @response,
|
402
|
+
:test => test?,
|
403
|
+
:authorization => "#{@response[:datacash_reference]};#{@response[:authcode]}"
|
404
|
+
)
|
405
|
+
end
|
406
|
+
|
407
|
+
# Find the currency of the Money object passed
|
408
|
+
#
|
409
|
+
# Parameters:
|
410
|
+
# -money: The money object that we are looking at
|
411
|
+
#
|
412
|
+
# Returns:
|
413
|
+
# -string: The three digit currency code (These are
|
414
|
+
# ISO 4217 codes)
|
415
|
+
#
|
416
|
+
def currency(money)
|
417
|
+
money.respond_to?(:currency) ? money.currency : 'GBP'
|
418
|
+
end
|
419
|
+
|
420
|
+
# Returns a date string in the format Datacash expects
|
421
|
+
#
|
422
|
+
# Parameters:
|
423
|
+
# -month: integer, the month
|
424
|
+
# -year: integer, the year
|
425
|
+
#
|
426
|
+
# Returns:
|
427
|
+
# -String: date in MM/YY format
|
428
|
+
#
|
429
|
+
def format_date(month, year)
|
430
|
+
"#{format(month,:two_digits)}/#{format(year, :two_digits)}"
|
431
|
+
end
|
432
|
+
|
433
|
+
# Parse the datacash response and create a Response object
|
434
|
+
#
|
435
|
+
# Parameters:
|
436
|
+
# -body: The XML returned from Datacash
|
437
|
+
#
|
438
|
+
# Returns:
|
439
|
+
# -a hash with all of the values returned in the Datacash XML response
|
440
|
+
#
|
441
|
+
def parse(body)
|
442
|
+
|
443
|
+
response = {}
|
444
|
+
xml = REXML::Document.new(body)
|
445
|
+
root = REXML::XPath.first(xml, "//Response")
|
446
|
+
|
447
|
+
root.elements.to_a.each do |node|
|
448
|
+
parse_element(response, node)
|
449
|
+
end
|
450
|
+
|
451
|
+
response
|
452
|
+
end
|
453
|
+
|
454
|
+
# Parse an xml element
|
455
|
+
#
|
456
|
+
# Parameters:
|
457
|
+
# -response: The hash that the values are being returned in
|
458
|
+
# -node: The node that is currently being read
|
459
|
+
#
|
460
|
+
# Returns:
|
461
|
+
# - none (results are stored in the passed hash)
|
462
|
+
def parse_element(response, node)
|
463
|
+
if node.has_elements?
|
464
|
+
node.elements.each{|e| parse_element(response, e) }
|
465
|
+
else
|
466
|
+
response[node.name.underscore.to_sym] = node.text
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def format_reference_number(number)
|
471
|
+
number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, "0")
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|