activemerchant 1.29.1 → 1.31.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/CHANGELOG +58 -0
- data/CONTRIBUTORS +27 -0
- data/README.md +45 -41
- data/lib/active_merchant/billing/check.rb +11 -11
- data/lib/active_merchant/billing/credit_card.rb +1 -1
- data/lib/active_merchant/billing/credit_card_formatting.rb +8 -8
- data/lib/active_merchant/billing/gateway.rb +2 -2
- data/lib/active_merchant/billing/gateways/authorize_net.rb +9 -1
- data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +15 -4
- data/lib/active_merchant/billing/gateways/balanced.rb +9 -3
- data/lib/active_merchant/billing/gateways/banwire.rb +15 -1
- data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +6 -2
- data/lib/active_merchant/billing/gateways/beanstream.rb +26 -24
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +5 -2
- data/lib/active_merchant/billing/gateways/cyber_source.rb +55 -22
- data/lib/active_merchant/billing/gateways/evo_ca.rb +308 -0
- data/lib/active_merchant/billing/gateways/eway.rb +114 -171
- data/lib/active_merchant/billing/gateways/eway_managed.rb +52 -22
- data/lib/active_merchant/billing/gateways/firstdata_e4.rb +232 -0
- data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +13 -2
- data/lib/active_merchant/billing/gateways/litle.rb +50 -19
- data/lib/active_merchant/billing/gateways/merchant_ware.rb +44 -9
- data/lib/active_merchant/billing/gateways/merchant_warrior.rb +190 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +3 -5
- data/lib/active_merchant/billing/gateways/moneris_us.rb +1 -1
- data/lib/active_merchant/billing/gateways/nab_transact.rb +20 -3
- data/lib/active_merchant/billing/gateways/netbilling.rb +1 -0
- data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
- data/lib/active_merchant/billing/gateways/ogone.rb +6 -4
- data/lib/active_merchant/billing/gateways/optimal_payment.rb +18 -3
- data/lib/active_merchant/billing/gateways/orbital.rb +9 -5
- data/lib/active_merchant/billing/gateways/payment_express.rb +62 -1
- data/lib/active_merchant/billing/gateways/paymill.rb +161 -0
- data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
- data/lib/active_merchant/billing/gateways/paypal_express.rb +17 -11
- data/lib/active_merchant/billing/gateways/pin.rb +157 -0
- data/lib/active_merchant/billing/gateways/qbms.rb +3 -2
- data/lib/active_merchant/billing/gateways/quickpay.rb +66 -28
- data/lib/active_merchant/billing/gateways/sage_pay.rb +6 -0
- data/lib/active_merchant/billing/gateways/smart_ps.rb +1 -1
- data/lib/active_merchant/billing/gateways/spreedly_core.rb +235 -0
- data/lib/active_merchant/billing/gateways/stripe.rb +1 -0
- data/lib/active_merchant/billing/gateways/wirecard.rb +15 -9
- data/lib/active_merchant/billing/gateways/worldpay.rb +19 -5
- data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +4 -1
- data/lib/active_merchant/billing/integrations/paypal/notification.rb +39 -31
- data/lib/active_merchant/billing/integrations/quickpay/helper.rb +13 -10
- data/lib/active_merchant/billing/integrations/quickpay/notification.rb +14 -14
- data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +2 -2
- data/lib/active_merchant/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +32 -24
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
|
2
|
+
module Billing #:nodoc:
|
|
3
|
+
class FirstdataE4Gateway < Gateway
|
|
4
|
+
self.test_url = "https://api.demo.globalgatewaye4.firstdata.com/transaction"
|
|
5
|
+
self.live_url = "https://api.globalgatewaye4.firstdata.com/transaction"
|
|
6
|
+
|
|
7
|
+
TRANSACTIONS = {
|
|
8
|
+
:sale => "00",
|
|
9
|
+
:authorization => "01",
|
|
10
|
+
:capture => "32",
|
|
11
|
+
:void => "33",
|
|
12
|
+
:credit => "34"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
POST_HEADERS = {
|
|
16
|
+
"Accepts" => "application/xml",
|
|
17
|
+
"Content-Type" => "application/xml"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
SUCCESS = "true"
|
|
21
|
+
|
|
22
|
+
SENSITIVE_FIELDS = [:verification_str2, :expiry_date, :card_number]
|
|
23
|
+
|
|
24
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover]
|
|
25
|
+
self.supported_countries = ["CA", "US"]
|
|
26
|
+
self.default_currency = "USD"
|
|
27
|
+
self.homepage_url = "http://www.firstdata.com"
|
|
28
|
+
self.display_name = "FirstData Global Gateway e4"
|
|
29
|
+
|
|
30
|
+
# Create a new FirstdataE4Gateway
|
|
31
|
+
#
|
|
32
|
+
# The gateway requires that a valid login and password be passed
|
|
33
|
+
# in the +options+ hash.
|
|
34
|
+
#
|
|
35
|
+
# ==== Options
|
|
36
|
+
#
|
|
37
|
+
# * <tt>:login</tt> -- The EXACT ID. Also known as the Gateway ID.
|
|
38
|
+
# (Found in your administration terminal settings)
|
|
39
|
+
# * <tt>:password</tt> -- The terminal password (not your account password)
|
|
40
|
+
def initialize(options = {})
|
|
41
|
+
requires!(options, :login, :password)
|
|
42
|
+
@options = options
|
|
43
|
+
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def authorize(money, credit_card, options = {})
|
|
48
|
+
commit(:authorization, build_sale_or_authorization_request(money, credit_card, options))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def purchase(money, credit_card, options = {})
|
|
52
|
+
commit(:sale, build_sale_or_authorization_request(money, credit_card, options))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def capture(money, authorization, options = {})
|
|
56
|
+
commit(:capture, build_capture_or_credit_request(money, authorization, options))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def void(authorization, options = {})
|
|
60
|
+
commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def refund(money, authorization, options = {})
|
|
64
|
+
commit(:credit, build_capture_or_credit_request(money, authorization, options))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def build_request(action, body)
|
|
70
|
+
xml = Builder::XmlMarkup.new
|
|
71
|
+
|
|
72
|
+
xml.instruct!
|
|
73
|
+
xml.tag! "Transaction" do
|
|
74
|
+
add_credentials(xml)
|
|
75
|
+
add_transaction_type(xml, action)
|
|
76
|
+
xml << body
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
xml.target!
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_sale_or_authorization_request(money, credit_card, options)
|
|
83
|
+
xml = Builder::XmlMarkup.new
|
|
84
|
+
|
|
85
|
+
add_amount(xml, money)
|
|
86
|
+
add_credit_card(xml, credit_card)
|
|
87
|
+
add_customer_data(xml, options)
|
|
88
|
+
add_invoice(xml, options)
|
|
89
|
+
|
|
90
|
+
xml.target!
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_capture_or_credit_request(money, identification, options)
|
|
94
|
+
xml = Builder::XmlMarkup.new
|
|
95
|
+
|
|
96
|
+
add_identification(xml, identification)
|
|
97
|
+
add_amount(xml, money)
|
|
98
|
+
add_customer_data(xml, options)
|
|
99
|
+
|
|
100
|
+
xml.target!
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def add_credentials(xml)
|
|
104
|
+
xml.tag! "ExactID", @options[:login]
|
|
105
|
+
xml.tag! "Password", @options[:password]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def add_transaction_type(xml, action)
|
|
109
|
+
xml.tag! "Transaction_Type", TRANSACTIONS[action]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def add_identification(xml, identification)
|
|
113
|
+
authorization_num, transaction_tag, _ = identification.split(";")
|
|
114
|
+
|
|
115
|
+
xml.tag! "Authorization_Num", authorization_num
|
|
116
|
+
xml.tag! "Transaction_Tag", transaction_tag
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def add_amount(xml, money)
|
|
120
|
+
xml.tag! "DollarAmount", amount(money)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def add_credit_card(xml, credit_card)
|
|
124
|
+
xml.tag! "Card_Number", credit_card.number
|
|
125
|
+
xml.tag! "Expiry_Date", expdate(credit_card)
|
|
126
|
+
xml.tag! "CardHoldersName", credit_card.name
|
|
127
|
+
xml.tag! "CardType", credit_card.brand
|
|
128
|
+
|
|
129
|
+
if credit_card.verification_value?
|
|
130
|
+
xml.tag! "CVD_Presence_Ind", "1"
|
|
131
|
+
xml.tag! "VerificationStr2", credit_card.verification_value
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def add_customer_data(xml, options)
|
|
136
|
+
xml.tag! "Customer_Ref", options[:customer] if options[:customer]
|
|
137
|
+
xml.tag! "Client_IP", options[:ip] if options[:ip]
|
|
138
|
+
xml.tag! "Client_Email", options[:email] if options[:email]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def add_address(xml, options)
|
|
142
|
+
if address = (options[:billing_address] || options[:address])
|
|
143
|
+
xml.tag! "ZipCode", address[:zip]
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def add_invoice(xml, options)
|
|
148
|
+
xml.tag! "Reference_No", options[:order_id]
|
|
149
|
+
xml.tag! "Reference_3", options[:description] if options[:description]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def expdate(credit_card)
|
|
153
|
+
"#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def commit(action, request)
|
|
157
|
+
url = (test? ? self.test_url : self.live_url)
|
|
158
|
+
begin
|
|
159
|
+
response = parse(ssl_post(url, build_request(action, request), POST_HEADERS))
|
|
160
|
+
rescue ResponseError => e
|
|
161
|
+
response = parse_error(e.response)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
Response.new(successful?(response), message_from(response), response,
|
|
165
|
+
:test => test?,
|
|
166
|
+
:authorization => authorization_from(response),
|
|
167
|
+
:avs_result => {:code => response[:avs]},
|
|
168
|
+
:cvv_result => response[:cvv2]
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def successful?(response)
|
|
173
|
+
response[:transaction_approved] == SUCCESS
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def authorization_from(response)
|
|
177
|
+
if response[:authorization_num] && response[:transaction_tag]
|
|
178
|
+
[
|
|
179
|
+
response[:authorization_num],
|
|
180
|
+
response[:transaction_tag],
|
|
181
|
+
(response[:dollar_amount].to_f * 100).to_i
|
|
182
|
+
].join(";")
|
|
183
|
+
else
|
|
184
|
+
""
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def money_from_authorization(auth)
|
|
189
|
+
_, _, amount = auth.split(/;/, 3)
|
|
190
|
+
amount.to_i # return the # of cents, no need to divide
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def message_from(response)
|
|
194
|
+
if(response[:faultcode] && response[:faultstring])
|
|
195
|
+
response[:faultstring]
|
|
196
|
+
elsif(response[:error_number] != "0")
|
|
197
|
+
response[:error_description]
|
|
198
|
+
else
|
|
199
|
+
result = (response[:exact_message] || "")
|
|
200
|
+
result << " - #{response[:bank_message]}" if response[:bank_message].present?
|
|
201
|
+
result
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def parse_error(error)
|
|
206
|
+
{
|
|
207
|
+
:transaction_approved => "false",
|
|
208
|
+
:error_number => error.code,
|
|
209
|
+
:error_description => error.body
|
|
210
|
+
}
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def parse(xml)
|
|
214
|
+
response = {}
|
|
215
|
+
xml = REXML::Document.new(xml)
|
|
216
|
+
|
|
217
|
+
if root = REXML::XPath.first(xml, "//TransactionResult")
|
|
218
|
+
parse_elements(response, root)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def parse_elements(response, root)
|
|
225
|
+
root.elements.to_a.each do |node|
|
|
226
|
+
response[node.name.gsub(/EXact/, "Exact").underscore.to_sym] = (node.text || "").strip
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
@@ -9,12 +9,23 @@ module ActiveMerchant #:nodoc:
|
|
|
9
9
|
#
|
|
10
10
|
# ActiveMerchant expects the amounts to be given as an Integer in cents. In this case, 10 EUR becomes 1000.
|
|
11
11
|
#
|
|
12
|
+
# Create certificates for authentication:
|
|
13
|
+
#
|
|
14
|
+
# The PEM file expected should contain both the certificate and the generated PEM file.
|
|
15
|
+
# Some sample shell commands to generate the certificates:
|
|
16
|
+
#
|
|
17
|
+
# openssl genrsa -aes128 -out priv.pem -passout pass:[YOUR PASSWORD] 1024
|
|
18
|
+
# openssl req -x509 -new -key priv.pem -passin pass:[YOUR PASSWORD] -days 3000 -out cert.cer
|
|
19
|
+
# cat cert.cer priv.pem > ideal.pem
|
|
20
|
+
#
|
|
21
|
+
# Following the steps above, upload cert.cer to the ideal web interface and pass the path of ideal.pem to the :pem option.
|
|
22
|
+
#
|
|
12
23
|
# Configure the gateway using your iDEAL bank account info and security settings:
|
|
13
24
|
#
|
|
14
25
|
# Create gateway:
|
|
15
26
|
# gateway = ActiveMerchant::Billing::IdealRabobankGateway.new(
|
|
16
|
-
# :login => '123456789', # merchant number
|
|
17
|
-
# :pem => File.read(
|
|
27
|
+
# :login => '123456789', # 9 digit merchant number
|
|
28
|
+
# :pem => File.read(Rails.root + 'config/ideal.pem'),
|
|
18
29
|
# :password => 'password' # password for the PEM key
|
|
19
30
|
# )
|
|
20
31
|
#
|
|
@@ -71,13 +71,13 @@ module ActiveMerchant #:nodoc:
|
|
|
71
71
|
super
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
def authorize(money,
|
|
75
|
-
to_pass =
|
|
74
|
+
def authorize(money, creditcard_or_token, options = {})
|
|
75
|
+
to_pass = build_authorize_request(money, creditcard_or_token, options)
|
|
76
76
|
build_response(:authorization, @litle.authorization(to_pass))
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def purchase(money,
|
|
80
|
-
to_pass =
|
|
79
|
+
def purchase(money, creditcard_or_token, options = {})
|
|
80
|
+
to_pass = build_purchase_request(money, creditcard_or_token, options)
|
|
81
81
|
build_response(:sale, @litle.sale(to_pass))
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -98,7 +98,7 @@ module ActiveMerchant #:nodoc:
|
|
|
98
98
|
|
|
99
99
|
def store(creditcard, options = {})
|
|
100
100
|
to_pass = create_token_hash(creditcard, options)
|
|
101
|
-
build_response(:registerToken, @litle.register_token_request(to_pass), %w(801 802))
|
|
101
|
+
build_response(:registerToken, @litle.register_token_request(to_pass), %w(000 801 802))
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
private
|
|
@@ -142,11 +142,17 @@ module ActiveMerchant #:nodoc:
|
|
|
142
142
|
if response['response'] == "0"
|
|
143
143
|
detail = response["#{kind}Response"]
|
|
144
144
|
fraud = fraud_result(detail)
|
|
145
|
+
authorization = case kind
|
|
146
|
+
when :registerToken
|
|
147
|
+
response['registerTokenResponse']['litleToken']
|
|
148
|
+
else
|
|
149
|
+
detail['litleTxnId']
|
|
150
|
+
end
|
|
145
151
|
Response.new(
|
|
146
152
|
valid_responses.include?(detail['response']),
|
|
147
153
|
detail['message'],
|
|
148
154
|
{:litleOnlineResponse => response},
|
|
149
|
-
:authorization =>
|
|
155
|
+
:authorization => authorization,
|
|
150
156
|
:avs_result => {:code => fraud['avs']},
|
|
151
157
|
:cvv_result => fraud['cvv'],
|
|
152
158
|
:test => test?
|
|
@@ -156,27 +162,52 @@ module ActiveMerchant #:nodoc:
|
|
|
156
162
|
end
|
|
157
163
|
end
|
|
158
164
|
|
|
159
|
-
def
|
|
160
|
-
|
|
165
|
+
def build_authorize_request(money, creditcard_or_token, options)
|
|
166
|
+
hash = create_hash(money, options)
|
|
167
|
+
|
|
168
|
+
add_credit_card_or_token_hash(hash, creditcard_or_token)
|
|
169
|
+
|
|
170
|
+
hash
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def build_purchase_request(money, creditcard_or_token, options)
|
|
174
|
+
hash = create_hash(money, options)
|
|
175
|
+
|
|
176
|
+
add_credit_card_or_token_hash(hash, creditcard_or_token)
|
|
161
177
|
|
|
162
|
-
|
|
178
|
+
hash
|
|
179
|
+
end
|
|
163
180
|
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
def add_credit_card_or_token_hash(hash, creditcard_or_token)
|
|
182
|
+
if creditcard_or_token.is_a?(String)
|
|
183
|
+
add_token_hash(hash, creditcard_or_token)
|
|
166
184
|
else
|
|
167
|
-
|
|
185
|
+
add_credit_card_hash(hash, creditcard_or_token)
|
|
168
186
|
end
|
|
187
|
+
end
|
|
169
188
|
|
|
170
|
-
|
|
189
|
+
def add_token_hash(hash, creditcard_or_token)
|
|
190
|
+
token_info = {
|
|
191
|
+
'litleToken' => creditcard_or_token
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
hash['token'] = token_info
|
|
195
|
+
hash
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def add_credit_card_hash(hash, creditcard)
|
|
199
|
+
cc_type = CARD_TYPE[creditcard.brand]
|
|
200
|
+
exp_date_yr = creditcard.year.to_s[2..3]
|
|
201
|
+
exp_date_mo = '%02d' % creditcard.month.to_i
|
|
202
|
+
exp_date = exp_date_mo + exp_date_yr
|
|
171
203
|
|
|
172
204
|
card_info = {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
205
|
+
'type' => cc_type,
|
|
206
|
+
'number' => creditcard.number,
|
|
207
|
+
'expDate' => exp_date,
|
|
208
|
+
'cardValidationNum' => creditcard.verification_value
|
|
177
209
|
}
|
|
178
210
|
|
|
179
|
-
hash = create_hash(money, options)
|
|
180
211
|
hash['card'] = card_info
|
|
181
212
|
hash
|
|
182
213
|
end
|
|
@@ -258,7 +289,7 @@ module ActiveMerchant #:nodoc:
|
|
|
258
289
|
'customerId' => options[:customer],
|
|
259
290
|
'reportGroup' => (options[:merchant] || @options[:merchant]),
|
|
260
291
|
'merchantId' => (options[:merchant_id] || @options[:merchant_id]),
|
|
261
|
-
'orderSource' => 'ecommerce',
|
|
292
|
+
'orderSource' => (options[:order_source] || 'ecommerce'),
|
|
262
293
|
'enhancedData' => enhanced_data,
|
|
263
294
|
'fraudCheckType' => fraud_check_type,
|
|
264
295
|
'user' => (options[:user] || @options[:user]),
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
module ActiveMerchant #:nodoc:
|
|
2
2
|
module Billing #:nodoc:
|
|
3
3
|
class MerchantWareGateway < Gateway
|
|
4
|
+
class_attribute :v4_live_url
|
|
5
|
+
|
|
4
6
|
self.live_url = self.test_url = 'https://ps1.merchantware.net/MerchantWARE/ws/RetailTransaction/TXRetail.asmx'
|
|
7
|
+
self.v4_live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx'
|
|
5
8
|
|
|
6
9
|
self.supported_countries = ['US']
|
|
7
10
|
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
|
|
@@ -12,13 +15,19 @@ module ActiveMerchant #:nodoc:
|
|
|
12
15
|
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
|
13
16
|
"xmlns:env" => "http://schemas.xmlsoap.org/soap/envelope/"
|
|
14
17
|
}
|
|
18
|
+
ENV_NAMESPACES_V4 = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
|
|
19
|
+
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
|
20
|
+
"xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/"
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
TX_NAMESPACE = "http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail"
|
|
24
|
+
TX_NAMESPACE_V4 = "http://schemas.merchantwarehouse.com/merchantware/40/Credit/"
|
|
16
25
|
|
|
17
26
|
ACTIONS = {
|
|
18
27
|
:purchase => "IssueKeyedSale",
|
|
19
28
|
:authorize => "IssueKeyedPreAuth",
|
|
20
29
|
:capture => "IssuePostAuth",
|
|
21
|
-
:void => "
|
|
30
|
+
:void => "VoidPreAuthorization",
|
|
22
31
|
:credit => "IssueKeyedRefund",
|
|
23
32
|
:reference_credit => "IssueRefundByReference"
|
|
24
33
|
}
|
|
@@ -80,11 +89,10 @@ module ActiveMerchant #:nodoc:
|
|
|
80
89
|
# * <tt>authorization</tt> - The authorization string returned from the initial authorization or purchase.
|
|
81
90
|
def void(authorization, options = {})
|
|
82
91
|
reference, options[:order_id] = split_reference(authorization)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
add_reference(xml, reference)
|
|
92
|
+
request = v4_soap_request(:void) do |xml|
|
|
93
|
+
add_reference_token(xml, reference)
|
|
86
94
|
end
|
|
87
|
-
commit(:void, request)
|
|
95
|
+
commit(:void, request, true)
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
# Refund an amount back a cardholder
|
|
@@ -108,7 +116,6 @@ module ActiveMerchant #:nodoc:
|
|
|
108
116
|
perform_reference_credit(money, reference, options)
|
|
109
117
|
end
|
|
110
118
|
|
|
111
|
-
|
|
112
119
|
private
|
|
113
120
|
|
|
114
121
|
def soap_request(action)
|
|
@@ -125,6 +132,22 @@ module ActiveMerchant #:nodoc:
|
|
|
125
132
|
xml.target!
|
|
126
133
|
end
|
|
127
134
|
|
|
135
|
+
def v4_soap_request(action)
|
|
136
|
+
xml = Builder::XmlMarkup.new :indent => 2
|
|
137
|
+
xml.instruct!
|
|
138
|
+
xml.tag! "soap:Envelope", ENV_NAMESPACES_V4 do
|
|
139
|
+
xml.tag! "soap:Body" do
|
|
140
|
+
xml.tag! ACTIONS[:void], "xmlns" => TX_NAMESPACE_V4 do
|
|
141
|
+
xml.tag! "merchantName", @options[:name]
|
|
142
|
+
xml.tag! "merchantSiteId", @options[:login]
|
|
143
|
+
xml.tag! "merchantKey", @options[:password]
|
|
144
|
+
yield xml
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
xml.target!
|
|
149
|
+
end
|
|
150
|
+
|
|
128
151
|
def build_purchase_request(action, money, credit_card, options)
|
|
129
152
|
requires!(options, :order_id)
|
|
130
153
|
|
|
@@ -195,6 +218,10 @@ module ActiveMerchant #:nodoc:
|
|
|
195
218
|
xml.tag! "strReferenceCode", reference
|
|
196
219
|
end
|
|
197
220
|
|
|
221
|
+
def add_reference_token(xml, reference)
|
|
222
|
+
xml.tag! "token", reference
|
|
223
|
+
end
|
|
224
|
+
|
|
198
225
|
def add_address(xml, options)
|
|
199
226
|
if address = options[:billing_address] || options[:address]
|
|
200
227
|
xml.tag! "strAVSStreetAddress", address[:address1]
|
|
@@ -258,11 +285,19 @@ module ActiveMerchant #:nodoc:
|
|
|
258
285
|
response
|
|
259
286
|
end
|
|
260
287
|
|
|
261
|
-
def
|
|
288
|
+
def soap_action(action, v4 = false)
|
|
289
|
+
v4 ? "#{TX_NAMESPACE_V4}#{ACTIONS[action]}" : "#{TX_NAMESPACE}/#{ACTIONS[action]}"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def url(v4 = false)
|
|
293
|
+
v4 ? v4_live_url : live_url
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def commit(action, request, v4 = false)
|
|
262
297
|
begin
|
|
263
|
-
data = ssl_post(
|
|
298
|
+
data = ssl_post(url(v4), request,
|
|
264
299
|
"Content-Type" => 'text/xml; charset=utf-8',
|
|
265
|
-
"SOAPAction" =>
|
|
300
|
+
"SOAPAction" => soap_action(action, v4)
|
|
266
301
|
)
|
|
267
302
|
response = parse(action, data)
|
|
268
303
|
rescue ActiveMerchant::ResponseError => e
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
require 'rexml/document'
|
|
3
|
+
|
|
4
|
+
module ActiveMerchant #:nodoc:
|
|
5
|
+
module Billing #:nodoc:
|
|
6
|
+
class MerchantWarriorGateway < Gateway
|
|
7
|
+
TOKEN_TEST_URL = 'https://base.merchantwarrior.com/token/'
|
|
8
|
+
TOKEN_LIVE_URL = 'https://api.merchantwarrior.com/token/'
|
|
9
|
+
|
|
10
|
+
POST_TEST_URL = 'https://base.merchantwarrior.com/post/'
|
|
11
|
+
POST_LIVE_URL = 'https://api.merchantwarrior.com/post/'
|
|
12
|
+
|
|
13
|
+
self.supported_countries = ['AU']
|
|
14
|
+
self.supported_cardtypes = [:visa, :master, :american_express,
|
|
15
|
+
:diners_club, :discover]
|
|
16
|
+
self.homepage_url = 'http://www.merchantwarrior.com/'
|
|
17
|
+
self.display_name = 'MerchantWarrior'
|
|
18
|
+
|
|
19
|
+
self.money_format = :dollars
|
|
20
|
+
self.default_currency = 'AUD'
|
|
21
|
+
|
|
22
|
+
def initialize(options = {})
|
|
23
|
+
requires!(options, :merchant_uuid, :api_key, :api_passphrase)
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def authorize(money, payment_method, options = {})
|
|
28
|
+
post = {}
|
|
29
|
+
add_amount(post, money, options)
|
|
30
|
+
add_product(post, options)
|
|
31
|
+
add_address(post, options)
|
|
32
|
+
add_payment_method(post, payment_method)
|
|
33
|
+
commit('processAuth', post)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def purchase(money, payment_method, options = {})
|
|
37
|
+
post = {}
|
|
38
|
+
add_amount(post, money, options)
|
|
39
|
+
add_product(post, options)
|
|
40
|
+
add_address(post, options)
|
|
41
|
+
add_payment_method(post, payment_method)
|
|
42
|
+
commit('processCard', post)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def capture(money, identification)
|
|
46
|
+
post = {}
|
|
47
|
+
add_amount(post, money, options)
|
|
48
|
+
add_transaction(post, identification)
|
|
49
|
+
post.merge!('captureAmount' => money.to_s)
|
|
50
|
+
commit('processCapture', post)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def refund(money, identification)
|
|
54
|
+
post = {}
|
|
55
|
+
add_amount(post, money, options)
|
|
56
|
+
add_transaction(post, identification)
|
|
57
|
+
post['refundAmount'] = money
|
|
58
|
+
commit('refundCard', post)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def store(creditcard, options = {})
|
|
62
|
+
post = {
|
|
63
|
+
'cardName' => creditcard.name,
|
|
64
|
+
'cardNumber' => creditcard.number,
|
|
65
|
+
'cardExpiryMonth' => sprintf('%02d', creditcard.month),
|
|
66
|
+
'cardExpiryYear' => sprintf('%02d', creditcard.year)
|
|
67
|
+
}
|
|
68
|
+
commit('addCard', post)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def add_transaction(post, identification)
|
|
74
|
+
post['transactionID'] = identification
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def add_address(post, options)
|
|
78
|
+
return unless(address = options[:address])
|
|
79
|
+
|
|
80
|
+
post['customerName'] = address[:name]
|
|
81
|
+
post['customerCountry'] = address[:country]
|
|
82
|
+
post['customerState'] = address[:state]
|
|
83
|
+
post['customerCity'] = address[:city]
|
|
84
|
+
post['customerAddress'] = address[:address1]
|
|
85
|
+
post['customerPostCode'] = address[:zip]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def add_product(post, options)
|
|
89
|
+
post['transactionProduct'] = options[:transaction_product]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def add_payment_method(post, payment_method)
|
|
93
|
+
if payment_method.respond_to?(:number)
|
|
94
|
+
add_creditcard(post, payment_method)
|
|
95
|
+
else
|
|
96
|
+
add_token(post, payment_method)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def add_token(post, token)
|
|
101
|
+
post['cardID'] = token
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def add_creditcard(post, creditcard)
|
|
105
|
+
post['paymentCardNumber'] = creditcard.number
|
|
106
|
+
post['paymentCardName'] = creditcard.name
|
|
107
|
+
post['paymentCardExpiry'] = creditcard.expiry_date.expiration.strftime("%m%y")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def add_amount(post, money, options)
|
|
111
|
+
currency = (options[:currency] || currency(money))
|
|
112
|
+
|
|
113
|
+
post['transactionAmount'] = money.to_s
|
|
114
|
+
post['transactionCurrency'] = currency
|
|
115
|
+
post['hash'] = verification_hash(money, currency)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def verification_hash(money, currency)
|
|
119
|
+
Digest::MD5.hexdigest(
|
|
120
|
+
(
|
|
121
|
+
@options[:api_passphrase].to_s +
|
|
122
|
+
@options[:merchant_uuid].to_s +
|
|
123
|
+
money.to_s +
|
|
124
|
+
currency
|
|
125
|
+
).downcase
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def parse(body)
|
|
130
|
+
xml = REXML::Document.new(body)
|
|
131
|
+
|
|
132
|
+
response = {}
|
|
133
|
+
xml.root.elements.to_a.each do |node|
|
|
134
|
+
parse_element(response, node)
|
|
135
|
+
end
|
|
136
|
+
response
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parse_element(response, node)
|
|
140
|
+
if node.has_elements?
|
|
141
|
+
node.elements.each{|element| parse_element(response, element)}
|
|
142
|
+
else
|
|
143
|
+
response[node.name.underscore.to_sym] = node.text
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def commit(action, post)
|
|
148
|
+
add_auth(action, post)
|
|
149
|
+
|
|
150
|
+
response = parse(ssl_post(url_for(action, post), post_data(post)))
|
|
151
|
+
|
|
152
|
+
Response.new(
|
|
153
|
+
success?(response),
|
|
154
|
+
response[:response_message],
|
|
155
|
+
response,
|
|
156
|
+
:test => test?,
|
|
157
|
+
:authorization => (response[:card_id] || response[:transaction_id])
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def add_auth(action, post)
|
|
162
|
+
post['merchantUUID'] = @options[:merchant_uuid]
|
|
163
|
+
post['apiKey'] = @options[:api_key]
|
|
164
|
+
unless token?(post)
|
|
165
|
+
post['method'] = action
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def url_for(action, post)
|
|
170
|
+
if token?(post)
|
|
171
|
+
[(test? ? TOKEN_TEST_URL : TOKEN_LIVE_URL), action].join("/")
|
|
172
|
+
else
|
|
173
|
+
(test? ? POST_TEST_URL : POST_LIVE_URL)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def token?(post)
|
|
178
|
+
(post["cardID"] || post["cardName"])
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def success?(response)
|
|
182
|
+
(response[:response_code] == '0')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def post_data(post)
|
|
186
|
+
post.collect{|k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|