activemerchant 1.0.3 → 1.1.0
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.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
|