activemerchant 1.24.0 → 1.25.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 +31 -0
- data/CONTRIBUTORS +12 -0
- data/README.md +1 -0
- data/lib/active_merchant/billing/base.rb +8 -9
- data/lib/active_merchant/billing/gateway.rb +31 -31
- data/lib/active_merchant/billing/gateways/authorize_net.rb +25 -24
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +45 -1
- data/lib/active_merchant/billing/gateways/cyber_source.rb +157 -29
- data/lib/active_merchant/billing/gateways/eway.rb +17 -8
- data/lib/active_merchant/billing/gateways/fat_zebra.rb +152 -0
- data/lib/active_merchant/billing/gateways/litle.rb +0 -1
- data/lib/active_merchant/billing/gateways/metrics_global.rb +323 -0
- data/lib/active_merchant/billing/gateways/orbital.rb +12 -9
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +7 -3
- data/lib/active_merchant/billing/gateways/wirecard.rb +79 -84
- data/lib/active_merchant/billing/integrations/easy_pay.rb +30 -0
- data/lib/active_merchant/billing/integrations/easy_pay/common.rb +40 -0
- data/lib/active_merchant/billing/integrations/easy_pay/helper.rb +40 -0
- data/lib/active_merchant/billing/integrations/easy_pay/notification.rb +51 -0
- data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +1 -1
- data/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb +20 -0
- data/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb +15 -0
- data/lib/active_merchant/version.rb +1 -1
- metadata +39 -25
- metadata.gz.sig +0 -0
@@ -0,0 +1,323 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
# For more information on the Metrics Global Payment Gateway, visit the {Metrics Global website}[www.metricsglobal.com].
|
4
|
+
# Further documentation on AVS and CVV response codes are available under the support section of the Metrics Global
|
5
|
+
# control panel.
|
6
|
+
#
|
7
|
+
# === Metrics Global Payment Gateway Authentication
|
8
|
+
#
|
9
|
+
# The login and password for the gateway are the same as the username and password used to log in to the Metrics Global
|
10
|
+
# control panel. Contact Metrics Global support to receive credentials for the control panel.
|
11
|
+
#
|
12
|
+
# === Demo Account
|
13
|
+
#
|
14
|
+
# There is a public demo account available with the following credentials:
|
15
|
+
#
|
16
|
+
# Login: demo
|
17
|
+
# Password: password
|
18
|
+
class MetricsGlobalGateway < Gateway
|
19
|
+
API_VERSION = '3.1'
|
20
|
+
|
21
|
+
class_attribute :test_url, :live_url
|
22
|
+
|
23
|
+
self.test_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll?testing=true"
|
24
|
+
self.live_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll"
|
25
|
+
|
26
|
+
class_attribute :duplicate_window
|
27
|
+
|
28
|
+
APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4
|
29
|
+
|
30
|
+
RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT = 0, 2, 3
|
31
|
+
AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38
|
32
|
+
|
33
|
+
self.supported_countries = ['US']
|
34
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
|
35
|
+
self.homepage_url = 'http://www.metricsglobal.com'
|
36
|
+
self.display_name = 'Metrics Global'
|
37
|
+
|
38
|
+
CARD_CODE_ERRORS = %w( N S )
|
39
|
+
AVS_ERRORS = %w( A E N R W Z )
|
40
|
+
AVS_REASON_CODES = %w(27 45)
|
41
|
+
|
42
|
+
# Creates a new MetricsGlobalGateway
|
43
|
+
#
|
44
|
+
# The gateway requires that a valid login and password be passed
|
45
|
+
# in the +options+ hash.
|
46
|
+
#
|
47
|
+
# ==== Options
|
48
|
+
#
|
49
|
+
# * <tt>:login</tt> -- The username required to access the Metrics Global control panel. (REQUIRED)
|
50
|
+
# * <tt>:password</tt> -- The password required to access the Metrics Global control panel. (REQUIRED)
|
51
|
+
# * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
|
52
|
+
# Otherwise, perform transactions against the production server.
|
53
|
+
def initialize(options = {})
|
54
|
+
requires!(options, :login, :password)
|
55
|
+
@options = options
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
# Performs an authorization, which reserves the funds on the customer's credit card, but does not
|
60
|
+
# charge the card.
|
61
|
+
#
|
62
|
+
# ==== Parameters
|
63
|
+
#
|
64
|
+
# * <tt>money</tt> -- The amount to be authorized as an Integer value in cents.
|
65
|
+
# * <tt>creditcard</tt> -- The CreditCard details for the transaction.
|
66
|
+
# * <tt>options</tt> -- A hash of optional parameters.
|
67
|
+
def authorize(money, creditcard, options = {})
|
68
|
+
post = {}
|
69
|
+
add_invoice(post, options)
|
70
|
+
add_creditcard(post, creditcard)
|
71
|
+
add_address(post, options)
|
72
|
+
add_customer_data(post, options)
|
73
|
+
add_duplicate_window(post)
|
74
|
+
|
75
|
+
commit('AUTH_ONLY', money, post)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Perform a purchase, which is essentially an authorization and capture in a single operation.
|
79
|
+
#
|
80
|
+
# ==== Parameters
|
81
|
+
#
|
82
|
+
# * <tt>money</tt> -- The amount to be purchased as an Integer value in cents.
|
83
|
+
# * <tt>creditcard</tt> -- The CreditCard details for the transaction.
|
84
|
+
# * <tt>options</tt> -- A hash of optional parameters.
|
85
|
+
def purchase(money, creditcard, options = {})
|
86
|
+
post = {}
|
87
|
+
add_invoice(post, options)
|
88
|
+
add_creditcard(post, creditcard)
|
89
|
+
add_address(post, options)
|
90
|
+
add_customer_data(post, options)
|
91
|
+
add_duplicate_window(post)
|
92
|
+
|
93
|
+
commit('AUTH_CAPTURE', money, post)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Captures the funds from an authorized transaction.
|
97
|
+
#
|
98
|
+
# ==== Parameters
|
99
|
+
#
|
100
|
+
# * <tt>money</tt> -- The amount to be captured as an Integer value in cents.
|
101
|
+
# * <tt>authorization</tt> -- The authorization returned from the previous authorize request.
|
102
|
+
def capture(money, authorization, options = {})
|
103
|
+
post = {:trans_id => authorization}
|
104
|
+
add_customer_data(post, options)
|
105
|
+
commit('PRIOR_AUTH_CAPTURE', money, post)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Void a previous transaction
|
109
|
+
#
|
110
|
+
# ==== Parameters
|
111
|
+
#
|
112
|
+
# * <tt>authorization</tt> - The authorization returned from the previous authorize request.
|
113
|
+
def void(authorization, options = {})
|
114
|
+
post = {:trans_id => authorization}
|
115
|
+
add_duplicate_window(post)
|
116
|
+
commit('VOID', nil, post)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Refund a transaction.
|
120
|
+
#
|
121
|
+
# This transaction indicates to the gateway that
|
122
|
+
# money should flow from the merchant to the customer.
|
123
|
+
#
|
124
|
+
# ==== Parameters
|
125
|
+
#
|
126
|
+
# * <tt>money</tt> -- The amount to be credited to the customer as an Integer value in cents.
|
127
|
+
# * <tt>identification</tt> -- The ID of the original transaction against which the refund is being issued.
|
128
|
+
# * <tt>options</tt> -- A hash of parameters.
|
129
|
+
#
|
130
|
+
# ==== Options
|
131
|
+
#
|
132
|
+
# * <tt>:card_number</tt> -- The credit card number the refund is being issued to. (REQUIRED)
|
133
|
+
# * <tt>:first_name</tt> -- The first name of the account being refunded.
|
134
|
+
# * <tt>:last_name</tt> -- The last name of the account being refunded.
|
135
|
+
# * <tt>:zip</tt> -- The postal code of the account being refunded.
|
136
|
+
def refund(money, identification, options = {})
|
137
|
+
requires!(options, :card_number)
|
138
|
+
|
139
|
+
post = { :trans_id => identification,
|
140
|
+
:card_num => options[:card_number]
|
141
|
+
}
|
142
|
+
|
143
|
+
post[:first_name] = options[:first_name] if options[:first_name]
|
144
|
+
post[:last_name] = options[:last_name] if options[:last_name]
|
145
|
+
post[:zip] = options[:zip] if options[:zip]
|
146
|
+
|
147
|
+
add_invoice(post, options)
|
148
|
+
add_duplicate_window(post)
|
149
|
+
|
150
|
+
commit('CREDIT', money, post)
|
151
|
+
end
|
152
|
+
|
153
|
+
def credit(money, identification, options = {})
|
154
|
+
deprecated CREDIT_DEPRECATION_MESSAGE
|
155
|
+
refund(money, identification, options)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def commit(action, money, parameters)
|
161
|
+
parameters[:amount] = amount(money) unless action == 'VOID'
|
162
|
+
|
163
|
+
# Only activate the test_request when the :test option is passed in
|
164
|
+
parameters[:test_request] = @options[:test] ? 'TRUE' : 'FALSE'
|
165
|
+
|
166
|
+
url = test? ? self.test_url : self.live_url
|
167
|
+
data = ssl_post url, post_data(action, parameters)
|
168
|
+
|
169
|
+
response = parse(data)
|
170
|
+
|
171
|
+
message = message_from(response)
|
172
|
+
|
173
|
+
# Return the response. The authorization can be taken out of the transaction_id
|
174
|
+
# Test Mode on/off is something we have to parse from the response text.
|
175
|
+
# It usually looks something like this
|
176
|
+
#
|
177
|
+
# (TESTMODE) Successful Sale
|
178
|
+
test_mode = test? || message =~ /TESTMODE/
|
179
|
+
|
180
|
+
Response.new(success?(response), message, response,
|
181
|
+
:test => test_mode,
|
182
|
+
:authorization => response[:transaction_id],
|
183
|
+
:fraud_review => fraud_review?(response),
|
184
|
+
:avs_result => { :code => response[:avs_result_code] },
|
185
|
+
:cvv_result => response[:card_code]
|
186
|
+
)
|
187
|
+
end
|
188
|
+
|
189
|
+
def success?(response)
|
190
|
+
response[:response_code] == APPROVED
|
191
|
+
end
|
192
|
+
|
193
|
+
def fraud_review?(response)
|
194
|
+
response[:response_code] == FRAUD_REVIEW
|
195
|
+
end
|
196
|
+
|
197
|
+
def parse(body)
|
198
|
+
fields = split(body)
|
199
|
+
|
200
|
+
results = {
|
201
|
+
:response_code => fields[RESPONSE_CODE].to_i,
|
202
|
+
:response_reason_code => fields[RESPONSE_REASON_CODE],
|
203
|
+
:response_reason_text => fields[RESPONSE_REASON_TEXT],
|
204
|
+
:avs_result_code => fields[AVS_RESULT_CODE],
|
205
|
+
:transaction_id => fields[TRANSACTION_ID],
|
206
|
+
:card_code => fields[CARD_CODE_RESPONSE_CODE]
|
207
|
+
}
|
208
|
+
results
|
209
|
+
end
|
210
|
+
|
211
|
+
def post_data(action, parameters = {})
|
212
|
+
post = {}
|
213
|
+
|
214
|
+
post[:version] = API_VERSION
|
215
|
+
post[:login] = @options[:login]
|
216
|
+
post[:tran_key] = @options[:password]
|
217
|
+
post[:relay_response] = "FALSE"
|
218
|
+
post[:type] = action
|
219
|
+
post[:delim_data] = "TRUE"
|
220
|
+
post[:delim_char] = ","
|
221
|
+
post[:encap_char] = "$"
|
222
|
+
post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant"
|
223
|
+
|
224
|
+
request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
225
|
+
request
|
226
|
+
end
|
227
|
+
|
228
|
+
def add_invoice(post, options)
|
229
|
+
post[:invoice_num] = options[:order_id]
|
230
|
+
post[:description] = options[:description]
|
231
|
+
end
|
232
|
+
|
233
|
+
def add_creditcard(post, creditcard)
|
234
|
+
post[:card_num] = creditcard.number
|
235
|
+
post[:card_code] = creditcard.verification_value if creditcard.verification_value?
|
236
|
+
post[:exp_date] = expdate(creditcard)
|
237
|
+
post[:first_name] = creditcard.first_name
|
238
|
+
post[:last_name] = creditcard.last_name
|
239
|
+
end
|
240
|
+
|
241
|
+
def add_customer_data(post, options)
|
242
|
+
if options.has_key? :email
|
243
|
+
post[:email] = options[:email]
|
244
|
+
post[:email_customer] = false
|
245
|
+
end
|
246
|
+
|
247
|
+
if options.has_key? :customer
|
248
|
+
post[:cust_id] = options[:customer]
|
249
|
+
end
|
250
|
+
|
251
|
+
if options.has_key? :ip
|
252
|
+
post[:customer_ip] = options[:ip]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# x_duplicate_window won't be sent by default, because sending it changes the response.
|
257
|
+
# "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent."
|
258
|
+
def add_duplicate_window(post)
|
259
|
+
unless duplicate_window.nil?
|
260
|
+
post[:duplicate_window] = duplicate_window
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def add_address(post, options)
|
265
|
+
if address = options[:billing_address] || options[:address]
|
266
|
+
post[:address] = address[:address1].to_s
|
267
|
+
post[:company] = address[:company].to_s
|
268
|
+
post[:phone] = address[:phone].to_s
|
269
|
+
post[:zip] = address[:zip].to_s
|
270
|
+
post[:city] = address[:city].to_s
|
271
|
+
post[:country] = address[:country].to_s
|
272
|
+
post[:state] = address[:state].blank? ? 'n/a' : address[:state]
|
273
|
+
end
|
274
|
+
|
275
|
+
if address = options[:shipping_address]
|
276
|
+
post[:ship_to_first_name] = address[:first_name].to_s
|
277
|
+
post[:ship_to_last_name] = address[:last_name].to_s
|
278
|
+
post[:ship_to_address] = address[:address1].to_s
|
279
|
+
post[:ship_to_company] = address[:company].to_s
|
280
|
+
post[:ship_to_phone] = address[:phone].to_s
|
281
|
+
post[:ship_to_zip] = address[:zip].to_s
|
282
|
+
post[:ship_to_city] = address[:city].to_s
|
283
|
+
post[:ship_to_country] = address[:country].to_s
|
284
|
+
post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Make a ruby type out of the response string
|
289
|
+
def normalize(field)
|
290
|
+
case field
|
291
|
+
when "true" then true
|
292
|
+
when "false" then false
|
293
|
+
when "" then nil
|
294
|
+
when "null" then nil
|
295
|
+
else field
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def message_from(results)
|
300
|
+
if results[:response_code] == DECLINED
|
301
|
+
return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code])
|
302
|
+
if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code])
|
303
|
+
return AVSResult.messages[ results[:avs_result_code] ]
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
(results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '')
|
308
|
+
end
|
309
|
+
|
310
|
+
def expdate(creditcard)
|
311
|
+
year = sprintf("%.4i", creditcard.year)
|
312
|
+
month = sprintf("%.2i", creditcard.month)
|
313
|
+
|
314
|
+
"#{month}#{year[-2..-1]}"
|
315
|
+
end
|
316
|
+
|
317
|
+
def split(response)
|
318
|
+
response[1..-2].split(/\$,\$/)
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
@@ -152,7 +152,6 @@ module ActiveMerchant #:nodoc:
|
|
152
152
|
if address = options[:billing_address] || options[:address]
|
153
153
|
add_avs_details(xml, address)
|
154
154
|
xml.tag! :AVSname, creditcard.name
|
155
|
-
xml.tag! :AVScountryCode, address[:country]
|
156
155
|
end
|
157
156
|
end
|
158
157
|
|
@@ -175,14 +174,18 @@ module ActiveMerchant #:nodoc:
|
|
175
174
|
end
|
176
175
|
|
177
176
|
def add_avs_details(xml, address)
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
177
|
+
if AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
|
178
|
+
xml.tag! :AVSzip, address[:zip]
|
179
|
+
xml.tag! :AVSaddress1, address[:address1]
|
180
|
+
xml.tag! :AVSaddress2, address[:address2]
|
181
|
+
xml.tag! :AVScity, address[:city]
|
182
|
+
xml.tag! :AVSstate, address[:state]
|
183
|
+
xml.tag! :AVSphoneNum, address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil
|
184
|
+
country_code = address[:country]
|
185
|
+
else
|
186
|
+
country_code = ''
|
187
|
+
end
|
188
|
+
xml.tag! :AVScountryCode, country_code
|
186
189
|
end
|
187
190
|
|
188
191
|
|
@@ -133,6 +133,7 @@ module ActiveMerchant #:nodoc:
|
|
133
133
|
|
134
134
|
def add_invoice(post, options)
|
135
135
|
post[:invoice] = options[:order_id]
|
136
|
+
post[:description] = options[:description]
|
136
137
|
end
|
137
138
|
|
138
139
|
def add_credit_card(post, credit_card)
|
@@ -167,12 +168,11 @@ module ActiveMerchant #:nodoc:
|
|
167
168
|
}.delete_if{|k, v| v.nil?}
|
168
169
|
end
|
169
170
|
|
170
|
-
|
171
171
|
def commit(action, parameters)
|
172
172
|
response = parse( ssl_post(URL, post_data(action, parameters)) )
|
173
173
|
|
174
174
|
Response.new(response[:status] == 'Approved', message_from(response), response,
|
175
|
-
:test =>
|
175
|
+
:test => test?,
|
176
176
|
:authorization => response[:ref_num],
|
177
177
|
:cvv_result => response[:cvv2_result_code],
|
178
178
|
:avs_result => { :code => response[:avs_result_code] }
|
@@ -192,10 +192,14 @@ module ActiveMerchant #:nodoc:
|
|
192
192
|
parameters[:command] = TRANSACTIONS[action]
|
193
193
|
parameters[:key] = @options[:login]
|
194
194
|
parameters[:software] = 'Active Merchant'
|
195
|
-
parameters[:testmode] = @options[:test] ? 1 : 0
|
195
|
+
parameters[:testmode] = (@options[:test] ? 1 : 0)
|
196
196
|
|
197
197
|
parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
198
198
|
end
|
199
|
+
|
200
|
+
def test?
|
201
|
+
@options[:test] || super
|
202
|
+
end
|
199
203
|
end
|
200
204
|
end
|
201
205
|
end
|
@@ -5,7 +5,7 @@ module ActiveMerchant #:nodoc:
|
|
5
5
|
class WirecardGateway < Gateway
|
6
6
|
# Test server location
|
7
7
|
TEST_URL = 'https://c3-test.wirecard.com/secure/ssl-gateway'
|
8
|
-
|
8
|
+
|
9
9
|
# Live server location
|
10
10
|
LIVE_URL = 'https://c3.wirecard.com/secure/ssl-gateway'
|
11
11
|
|
@@ -13,24 +13,23 @@ module ActiveMerchant #:nodoc:
|
|
13
13
|
# It's just specified here for completeness.
|
14
14
|
ENVELOPE_NAMESPACES = {
|
15
15
|
'xmlns:xsi' => 'http://www.w3.org/1999/XMLSchema-instance',
|
16
|
-
|
17
|
-
|
16
|
+
'xsi:noNamespaceSchemaLocation' => 'wirecard.xsd'
|
17
|
+
}
|
18
18
|
|
19
|
-
|
19
|
+
PERMITTED_TRANSACTIONS = %w[ AUTHORIZATION CAPTURE_AUTHORIZATION PURCHASE ]
|
20
20
|
|
21
21
|
RETURN_CODES = %w[ ACK NOK ]
|
22
22
|
|
23
|
-
# Wirecard only allows phone numbers with a format like this: +xxx(yyy)zzz-zzzz-ppp, where:
|
24
|
-
# xxx = Country code
|
25
|
-
# yyy = Area or city code
|
26
|
-
# zzz-zzzz = Local number
|
27
|
-
# ppp = PBX extension
|
28
|
-
# For example, a typical U.S. or Canadian number would be "+1(202)555-1234-739" indicating PBX extension 739 at phone
|
23
|
+
# Wirecard only allows phone numbers with a format like this: +xxx(yyy)zzz-zzzz-ppp, where:
|
24
|
+
# xxx = Country code
|
25
|
+
# yyy = Area or city code
|
26
|
+
# zzz-zzzz = Local number
|
27
|
+
# ppp = PBX extension
|
28
|
+
# For example, a typical U.S. or Canadian number would be "+1(202)555-1234-739" indicating PBX extension 739 at phone
|
29
29
|
# number 5551234 within area code 202 (country code 1).
|
30
30
|
VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/
|
31
|
-
|
31
|
+
|
32
32
|
# The countries the gateway supports merchants from as 2 digit ISO country codes
|
33
|
-
# TODO: Check supported countries
|
34
33
|
self.supported_countries = ['DE']
|
35
34
|
|
36
35
|
# Wirecard supports all major credit and debit cards:
|
@@ -69,35 +68,28 @@ module ActiveMerchant #:nodoc:
|
|
69
68
|
|
70
69
|
# Authorization
|
71
70
|
def authorize(money, creditcard, options = {})
|
72
|
-
|
73
|
-
|
74
|
-
request = build_request(:authorization, money, @options)
|
75
|
-
commit(request)
|
71
|
+
options[:credit_card] = creditcard
|
72
|
+
commit(:authorization, money, options)
|
76
73
|
end
|
77
74
|
|
78
|
-
|
79
75
|
# Capture Authorization
|
80
76
|
def capture(money, authorization, options = {})
|
81
|
-
|
82
|
-
|
83
|
-
request = build_request(:capture_authorization, money, @options)
|
84
|
-
commit(request)
|
77
|
+
options[:authorization] = authorization
|
78
|
+
commit(:capture_authorization, money, options)
|
85
79
|
end
|
86
80
|
|
87
|
-
|
88
81
|
# Purchase
|
89
82
|
def purchase(money, creditcard, options = {})
|
90
|
-
|
91
|
-
|
92
|
-
request = build_request(:purchase, money, @options)
|
93
|
-
commit(request)
|
83
|
+
options[:credit_card] = creditcard
|
84
|
+
commit(:purchase, money, options)
|
94
85
|
end
|
95
86
|
|
96
|
-
|
87
|
+
private
|
97
88
|
|
98
89
|
def prepare_options_hash(options)
|
99
|
-
@options.
|
100
|
-
setup_address_hash!(
|
90
|
+
result = @options.merge(options)
|
91
|
+
setup_address_hash!(result)
|
92
|
+
result
|
101
93
|
end
|
102
94
|
|
103
95
|
# Create all address hash key value pairs so that
|
@@ -111,15 +103,17 @@ module ActiveMerchant #:nodoc:
|
|
111
103
|
|
112
104
|
# Contact WireCard, make the XML request, and parse the
|
113
105
|
# reply into a Response object
|
114
|
-
def commit(
|
115
|
-
|
116
|
-
|
106
|
+
def commit(action, money, options)
|
107
|
+
request = build_request(action, money, options)
|
108
|
+
|
109
|
+
headers = { 'Content-Type' => 'text/xml',
|
110
|
+
'Authorization' => encoded_credentials }
|
117
111
|
|
118
|
-
|
112
|
+
response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, request, headers))
|
119
113
|
# Pending Status also means Acknowledged (as stated in their specification)
|
120
|
-
|
121
|
-
|
122
|
-
authorization = (success &&
|
114
|
+
success = response[:FunctionResult] == "ACK" || response[:FunctionResult] == "PENDING"
|
115
|
+
message = response[:Message]
|
116
|
+
authorization = (success && action == :authorization) ? response[:GuWID] : nil
|
123
117
|
|
124
118
|
Response.new(success, message, response,
|
125
119
|
:test => test?,
|
@@ -127,52 +121,55 @@ module ActiveMerchant #:nodoc:
|
|
127
121
|
:avs_result => { :code => response[:avsCode] },
|
128
122
|
:cvv_result => response[:cvCode]
|
129
123
|
)
|
124
|
+
rescue ResponseError => e
|
125
|
+
if e.response.code == "401"
|
126
|
+
return Response.new(false, "Invalid Login")
|
127
|
+
else
|
128
|
+
raise
|
129
|
+
end
|
130
130
|
end
|
131
131
|
|
132
132
|
# Generates the complete xml-message, that gets sent to the gateway
|
133
|
-
def build_request(action, money, options
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
133
|
+
def build_request(action, money, options)
|
134
|
+
options = prepare_options_hash(options)
|
135
|
+
options[:action] = action
|
136
|
+
xml = Builder::XmlMarkup.new :indent => 2
|
137
|
+
xml.instruct!
|
138
|
+
xml.tag! 'WIRECARD_BXML' do
|
139
|
+
xml.tag! 'W_REQUEST' do
|
138
140
|
xml.tag! 'W_JOB' do
|
139
|
-
|
140
|
-
xml.tag! 'JobID', 'test dummy data'
|
141
|
+
xml.tag! 'JobID', ''
|
141
142
|
# UserID for this transaction
|
142
143
|
xml.tag! 'BusinessCaseSignature', options[:signature] || options[:login]
|
143
144
|
# Create the whole rest of the message
|
144
|
-
add_transaction_data(xml,
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
145
|
+
add_transaction_data(xml, money, options)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
xml.target!
|
149
150
|
end
|
150
151
|
|
151
152
|
# Includes the whole transaction data (payment, creditcard, address)
|
152
|
-
def add_transaction_data(xml,
|
153
|
-
options[:action] = action
|
154
|
-
# TODO: require order_id instead of auto-generating it if not supplied
|
153
|
+
def add_transaction_data(xml, money, options)
|
155
154
|
options[:order_id] ||= generate_unique_id
|
156
|
-
transaction_type = action.to_s.upcase
|
157
|
-
|
158
|
-
xml.tag! "FNC_CC_#{transaction_type}" do
|
159
|
-
# TODO: OPTIONAL, check which param should be used here
|
160
|
-
xml.tag! 'FunctionID', options[:description] || 'Test dummy FunctionID'
|
161
155
|
|
156
|
+
xml.tag! "FNC_CC_#{options[:action].to_s.upcase}" do
|
157
|
+
xml.tag! 'FunctionID', options[:description]
|
162
158
|
xml.tag! 'CC_TRANSACTION' do
|
163
159
|
xml.tag! 'TransactionID', options[:order_id]
|
164
|
-
|
160
|
+
case options[:action]
|
161
|
+
when :authorization, :purchase
|
165
162
|
add_invoice(xml, money, options)
|
166
163
|
add_creditcard(xml, options[:credit_card])
|
167
164
|
add_address(xml, options[:billing_address])
|
168
|
-
|
169
|
-
xml.tag! 'GuWID', options[:authorization]
|
165
|
+
when :capture_authorization
|
166
|
+
xml.tag! 'GuWID', options[:authorization]
|
170
167
|
end
|
171
168
|
end
|
172
169
|
end
|
173
170
|
end
|
174
171
|
|
175
|
-
|
172
|
+
# Includes the payment (amount, currency, country) to the transaction-xml
|
176
173
|
def add_invoice(xml, money, options)
|
177
174
|
xml.tag! 'Amount', amount(money)
|
178
175
|
xml.tag! 'Currency', options[:currency] || currency(money)
|
@@ -182,8 +179,8 @@ module ActiveMerchant #:nodoc:
|
|
182
179
|
end
|
183
180
|
end
|
184
181
|
|
185
|
-
|
186
|
-
|
182
|
+
# Includes the credit-card data to the transaction-xml
|
183
|
+
def add_creditcard(xml, creditcard)
|
187
184
|
raise "Creditcard must be supplied!" if creditcard.nil?
|
188
185
|
xml.tag! 'CREDIT_CARD_DATA' do
|
189
186
|
xml.tag! 'CreditCardNumber', creditcard.number
|
@@ -194,38 +191,37 @@ module ActiveMerchant #:nodoc:
|
|
194
191
|
end
|
195
192
|
end
|
196
193
|
|
197
|
-
|
194
|
+
# Includes the IP address of the customer to the transaction-xml
|
198
195
|
def add_customer_data(xml, options)
|
199
196
|
return unless options[:ip]
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
197
|
+
xml.tag! 'CONTACT_DATA' do
|
198
|
+
xml.tag! 'IPAddress', options[:ip]
|
199
|
+
end
|
200
|
+
end
|
204
201
|
|
205
202
|
# Includes the address to the transaction-xml
|
206
203
|
def add_address(xml, address)
|
207
204
|
return if address.nil?
|
208
205
|
xml.tag! 'CORPTRUSTCENTER_DATA' do
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
206
|
+
xml.tag! 'ADDRESS' do
|
207
|
+
xml.tag! 'Address1', address[:address1]
|
208
|
+
xml.tag! 'Address2', address[:address2] if address[:address2]
|
209
|
+
xml.tag! 'City', address[:city]
|
210
|
+
xml.tag! 'ZipCode', address[:zip]
|
211
|
+
|
212
|
+
if address[:state] =~ /[A-Za-z]{2}/ && address[:country] =~ /^(us|ca)$/i
|
213
|
+
xml.tag! 'State', address[:state].upcase
|
214
|
+
end
|
215
|
+
|
216
|
+
xml.tag! 'Country', address[:country]
|
220
217
|
xml.tag! 'Phone', address[:phone] if address[:phone] =~ VALID_PHONE_FORMAT
|
221
|
-
|
222
|
-
|
223
|
-
|
218
|
+
xml.tag! 'Email', address[:email]
|
219
|
+
end
|
220
|
+
end
|
224
221
|
end
|
225
222
|
|
226
|
-
|
227
223
|
# Read the XML message from the gateway and check if it was successful,
|
228
|
-
|
224
|
+
# and also extract required return values from the response.
|
229
225
|
def parse(xml)
|
230
226
|
basepath = '/WIRECARD_BXML/W_RESPONSE'
|
231
227
|
response = {}
|
@@ -311,7 +307,6 @@ module ActiveMerchant #:nodoc:
|
|
311
307
|
credentials = [@options[:login], @options[:password]].join(':')
|
312
308
|
"Basic " << Base64.encode64(credentials).strip
|
313
309
|
end
|
314
|
-
|
315
310
|
end
|
316
311
|
end
|
317
312
|
end
|