activemerchant 1.46.0 → 1.47.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fef1eb0d503d1d6aff1334d0a6715ac163f4f6fe
4
- data.tar.gz: 4ae14dcf7fc8883c819ee1b2c36d00c95f63a7cc
3
+ metadata.gz: dbec33f0dbb45aa949b164bf0bc376b5aeee2f51
4
+ data.tar.gz: 6bb77b71ff18e0cdaf18ff62b2fb28e1cace2588
5
5
  SHA512:
6
- metadata.gz: 337e1502bcf24ff67fc58da0128d2526bb449089df5908c38b02645c56024c760b8239eaaf675570aefcd99fcf654e1c87ad7daeeaf7c34511bcc1a089db1671
7
- data.tar.gz: e0d8140445aa478bee95d90dd672df544aec404976b21b5f2583335713518281090509d92e0eeb240dcb618e8741de8d3f287290ea049c06fa61ca3a8bb42a84
6
+ metadata.gz: 9e34c4f81edd376e109de5d0e2ef50463763c7ea2be6884d401417044336982cb048235c029771ba8b7e99c7a985b91e214d63ee248a25d0d9e6038999fe1fe1
7
+ data.tar.gz: a9a128c91e69846177d0e3d9f9b51c1295c741423d9780db449a284902f443255f0f54660e2bfc23d89bad67d26d7956a56e19d17a28ed0a5410bfc8fa9e51a5
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,17 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.47.0 (February 25, 2015)
4
+
5
+ * Authorize.Net: Properly send name in shipping address line, when shipping address is provided [girasquid]
6
+ * Payflow: Add verify support [ntalbott]
7
+ * Capture ConnectionError#triggering_exception [ntalbott]
8
+ * Flo2Cash: Map Reference->:order_id (not :invoice) [ntalbott]
9
+ * Flo2Cash: Fix card brand handling [ntalbott]
10
+ * Flo2Cash: Improve error handling & simplify "Simple" gateway [ntalbott]
11
+ * Remove Vindicia gateway [mutemule]
12
+ * Firstdata E4: Support other mastercard string [jcbantuelle]
13
+ * Checkout: Disallow altering endpoint via options [markabe]
14
+
3
15
  == Version 1.46.0 (January 20, 2015)
4
16
 
5
17
  * CHANGE: drop `offsite_payments` and `active_utils` as dependencies. [wvanbergen]
@@ -24,6 +36,11 @@
24
36
  * Pin: Handle JSON parsing exception in response [duff]
25
37
  * Improve test suite to test against multiple ActiveSupport versions [wvanbergen]
26
38
  * Misc. code cleanup [wvanbergen]
39
+ * Add Flo2Cash gateway [markabe]
40
+ * Litle: Allow order_source override [duff]
41
+ * Quickpay: Add ability to finalize a capture [askehansen]
42
+ * AuthorizeNet: Prevent test mode using live gateway [duff]
43
+ * Add Flo2Cash Simple gateway [markabe]
27
44
 
28
45
  == Version 1.45.0 (December 1, 2014)
29
46
 
data/README.md CHANGED
@@ -116,6 +116,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
116
116
  * [Fat Zebra](https://www.fatzebra.com.au/) - AU
117
117
  * [Federated Canada](http://www.federatedcanada.com/) - CA
118
118
  * [Finansbank WebPOS](https://www.fbwebpos.com/) - US, TR
119
+ * [Flo2Cash](http://www.flo2cash.co.nz/) - NZ
119
120
  * [1stPayGateway.Net](http://1stpaygateway.net/) - US
120
121
  * [FirstData Global Gateway e4](http://www.firstdata.com) - CA, US
121
122
  * [FirstGiving](http://www.firstgiving.com/) - US
@@ -202,7 +203,6 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
202
203
  * [USA ePay](http://www.usaepay.com/) - US
203
204
  * [Verifi](http://www.verifi.com/) - US
204
205
  * [ViaKLIX](http://viaklix.com) - US
205
- * [Vindicia](http://www.vindicia.com/) - US, CA, GB, AU, MX, BR, DE, KR, CN, HK
206
206
  * [WebPay](https://webpay.jp/) - JP
207
207
  * [WePay](https://www.wepay.com/) - US
208
208
  * [Wirecard](http://www.wirecard.com) - AD, CY, GI, IM, MT, RO, CH, AT, DK, GR, IT, MC, SM, TR, BE, EE, HU, LV, NL, SK, GB, BG, FI, IS, LI, NO, SI, VA, FR, IL, LT, PL, ES, CZ, DE, IE, LU, PT, SE
@@ -5,6 +5,7 @@ require 'active_merchant/billing/cvv_result'
5
5
  require 'active_merchant/billing/credit_card_methods'
6
6
  require 'active_merchant/billing/credit_card_formatting'
7
7
  require 'active_merchant/billing/credit_card'
8
+ require 'active_merchant/billing/network_tokenization_credit_card'
8
9
  require 'active_merchant/billing/base'
9
10
  require 'active_merchant/billing/check'
10
11
  require 'active_merchant/billing/payment_token'
@@ -143,16 +143,51 @@ module ActiveMerchant #:nodoc:
143
143
  %w[1 2 3 success failure error].include?(number.to_s)
144
144
  end
145
145
 
146
+ ODD_LUHN_VALUE = {
147
+ 48 => 0,
148
+ 49 => 1,
149
+ 50 => 2,
150
+ 51 => 3,
151
+ 52 => 4,
152
+ 53 => 5,
153
+ 54 => 6,
154
+ 55 => 7,
155
+ 56 => 8,
156
+ 57 => 9,
157
+ nil => 0
158
+ }.freeze
159
+
160
+ EVEN_LUHN_VALUE = {
161
+ 48 => 0, # 0 * 2
162
+ 49 => 2, # 1 * 2
163
+ 50 => 4, # 2 * 2
164
+ 51 => 6, # 3 * 2
165
+ 52 => 8, # 4 * 2
166
+ 53 => 1, # 5 * 2 - 9
167
+ 54 => 3, # 6 * 2 - 9
168
+ 55 => 5, # etc ...
169
+ 56 => 7,
170
+ 57 => 9,
171
+ }.freeze
172
+
146
173
  # Checks the validity of a card number by use of the Luhn Algorithm.
147
174
  # Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details.
148
- def valid_checksum?(number) #:nodoc:
175
+ # This implementation is from the luhn_checksum gem, https://github.com/zendesk/luhn_checksum.
176
+ def valid_checksum?(numbers) #:nodoc:
149
177
  sum = 0
150
- for i in 0..number.length
151
- weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
152
- sum += (weight < 10) ? weight : weight - 9
178
+
179
+ odd = true
180
+ numbers.reverse.bytes.each do |number|
181
+ if odd
182
+ odd = false
183
+ sum += ODD_LUHN_VALUE[number]
184
+ else
185
+ odd = true
186
+ sum += EVEN_LUHN_VALUE[number]
187
+ end
153
188
  end
154
189
 
155
- (number[-1,1].to_i == (10 - sum % 10) % 10)
190
+ sum % 10 == 0
156
191
  end
157
192
  end
158
193
  end
@@ -1,3 +1,5 @@
1
+ require 'pathname'
2
+
1
3
  module ActiveMerchant
2
4
  module Billing
3
5
  load_path = Pathname.new(__FILE__ + '/../../..')
@@ -123,6 +123,16 @@ module ActiveMerchant #:nodoc:
123
123
  end
124
124
  end
125
125
 
126
+ def supports_scrubbing?
127
+ true
128
+ end
129
+
130
+ def scrub(transcript)
131
+ transcript.
132
+ gsub(%r((<cardNumber>).+(</cardNumber>)), '\1[FILTERED]\2').
133
+ gsub(%r((<cardCode>).+(</cardCode>)), '\1[FILTERED]\2')
134
+ end
135
+
126
136
  private
127
137
 
128
138
  def add_payment_source(xml, source)
@@ -267,7 +277,11 @@ module ActiveMerchant #:nodoc:
267
277
 
268
278
  unless shipping_address.blank?
269
279
  xml.shipTo do
270
- first_name, last_name = names_from(payment_source, shipping_address, options)
280
+ (first_name, last_name) = if shipping_address[:name]
281
+ shipping_address[:name].split
282
+ else
283
+ [shipping_address[:first_name], shipping_address[:last_name]]
284
+ end
271
285
  xml.firstName(truncate(first_name, 50)) unless empty?(first_name)
272
286
  xml.lastName(truncate(last_name, 50)) unless empty?(last_name)
273
287
 
@@ -314,16 +328,20 @@ module ActiveMerchant #:nodoc:
314
328
 
315
329
  avs_result = AVSResult.new(code: response[:avs_result_code])
316
330
  cvv_result = CVVResult.new(response[:card_code])
317
- Response.new(
318
- success_from(response),
319
- message_from(response, avs_result, cvv_result),
320
- response,
321
- authorization: authorization_from(response),
322
- test: test?,
323
- avs_result: avs_result,
324
- cvv_result: cvv_result,
325
- fraud_review: fraud_review?(response)
326
- )
331
+ if using_live_gateway_in_test_mode?(response)
332
+ Response.new(false, "Using a live Authorize.net account in Test Mode is not permitted.")
333
+ else
334
+ Response.new(
335
+ success_from(response),
336
+ message_from(response, avs_result, cvv_result),
337
+ response,
338
+ authorization: authorization_from(response),
339
+ test: test?,
340
+ avs_result: avs_result,
341
+ cvv_result: cvv_result,
342
+ fraud_review: fraud_review?(response)
343
+ )
344
+ end
327
345
  end
328
346
 
329
347
  def post_data
@@ -386,6 +404,10 @@ module ActiveMerchant #:nodoc:
386
404
  (empty?(element.content) ? nil : element.content[-4..-1])
387
405
  end
388
406
 
407
+ response[:test_request] = if(element = doc.at_xpath("//testRequest"))
408
+ (empty?(element.content) ? nil : element.content)
409
+ end
410
+
389
411
  response
390
412
  end
391
413
 
@@ -425,6 +447,10 @@ module ActiveMerchant #:nodoc:
425
447
  return nil unless value
426
448
  value.to_s[0, max_size]
427
449
  end
450
+
451
+ def using_live_gateway_in_test_mode?(response)
452
+ !test? && response[:test_request] == "1"
453
+ end
428
454
  end
429
455
  end
430
456
  end
@@ -26,8 +26,6 @@ module ActiveMerchant #:nodoc:
26
26
  }
27
27
 
28
28
  def initialize(options = {})
29
- @url = (options[:gateway_url] || self.live_url)
30
-
31
29
  requires!(options, :merchant_id, :password)
32
30
  super
33
31
  end
@@ -166,7 +164,7 @@ module ActiveMerchant #:nodoc:
166
164
  end
167
165
 
168
166
  def commit(action, amount=nil, options={}, &builder)
169
- response = parse_xml(ssl_post(@url, build_xml(action, &builder)))
167
+ response = parse_xml(ssl_post(live_url, build_xml(action, &builder)))
170
168
  Response.new(
171
169
  (response[:responsecode] == "0"),
172
170
  (response[:result] || response[:error_text] || "Unknown Response"),
@@ -27,11 +27,13 @@ module ActiveMerchant #:nodoc:
27
27
  # * To process pinless debit cards through the pinless debit card
28
28
  # network, your Cybersource merchant account must accept pinless
29
29
  # debit card payments.
30
+ # * The order of the XML elements does matter, make sure to follow the order in
31
+ # the documentation exactly.
30
32
  class CyberSourceGateway < Gateway
31
33
  self.test_url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor'
32
34
  self.live_url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor'
33
35
 
34
- XSD_VERSION = "1.69"
36
+ XSD_VERSION = "1.109"
35
37
 
36
38
  # visa, master, american_express, discover
37
39
  self.supported_cardtypes = [:visa, :master, :american_express, :discover]
@@ -241,8 +243,8 @@ module ActiveMerchant #:nodoc:
241
243
  def build_auth_request(money, creditcard_or_reference, options)
242
244
  xml = Builder::XmlMarkup.new :indent => 2
243
245
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
244
- add_auth_service(xml)
245
- add_business_rules_data(xml, options)
246
+ add_auth_service(xml, creditcard_or_reference, options)
247
+ add_business_rules_data(xml, creditcard_or_reference, options)
246
248
  xml.target!
247
249
  end
248
250
 
@@ -253,7 +255,7 @@ module ActiveMerchant #:nodoc:
253
255
  add_line_item_data(xml, options)
254
256
  add_purchase_data(xml, 0, false, options)
255
257
  add_tax_service(xml)
256
- add_business_rules_data(xml, options)
258
+ add_business_rules_data(xml, creditcard, options)
257
259
  xml.target!
258
260
  end
259
261
 
@@ -264,7 +266,7 @@ module ActiveMerchant #:nodoc:
264
266
  xml = Builder::XmlMarkup.new :indent => 2
265
267
  add_purchase_data(xml, money, true, options)
266
268
  add_capture_service(xml, request_id, request_token)
267
- add_business_rules_data(xml, options)
269
+ add_business_rules_data(xml, authorization, options)
268
270
  xml.target!
269
271
  end
270
272
 
@@ -274,8 +276,8 @@ module ActiveMerchant #:nodoc:
274
276
  if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check'
275
277
  add_check_service(xml)
276
278
  else
277
- add_purchase_service(xml, options)
278
- add_business_rules_data(xml, options) unless options[:pinless_debit_card]
279
+ add_purchase_service(xml, payment_method_or_reference, options)
280
+ add_business_rules_data(xml, payment_method_or_reference, options) unless options[:pinless_debit_card]
279
281
  end
280
282
  xml.target!
281
283
  end
@@ -340,11 +342,11 @@ module ActiveMerchant #:nodoc:
340
342
  if card_brand(payment_method) == 'check'
341
343
  add_check_service(xml, options)
342
344
  else
343
- add_purchase_service(xml, options)
345
+ add_purchase_service(xml, payment_method, options)
344
346
  end
345
347
  end
346
348
  add_subscription_create_service(xml, options)
347
- add_business_rules_data(xml, options)
349
+ add_business_rules_data(xml, payment_method, options)
348
350
  xml.target!
349
351
  end
350
352
 
@@ -356,7 +358,7 @@ module ActiveMerchant #:nodoc:
356
358
  add_creditcard_payment_method(xml) if creditcard
357
359
  add_subscription(xml, options, reference)
358
360
  add_subscription_update_service(xml, options)
359
- add_business_rules_data(xml, options)
361
+ add_business_rules_data(xml, creditcard, options)
360
362
  xml.target!
361
363
  end
362
364
 
@@ -381,12 +383,14 @@ module ActiveMerchant #:nodoc:
381
383
  xml.target!
382
384
  end
383
385
 
384
- def add_business_rules_data(xml, options)
386
+ def add_business_rules_data(xml, payment_method, options)
385
387
  prioritized_options = [options, @options]
386
388
 
387
- xml.tag! 'businessRules' do
388
- xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
389
- xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv)
389
+ unless network_tokenization?(payment_method)
390
+ xml.tag! 'businessRules' do
391
+ xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
392
+ xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv)
393
+ end
390
394
  end
391
395
  end
392
396
 
@@ -468,8 +472,48 @@ module ActiveMerchant #:nodoc:
468
472
  end
469
473
  end
470
474
 
471
- def add_auth_service(xml)
472
- xml.tag! 'ccAuthService', {'run' => 'true'}
475
+ def add_auth_service(xml, payment_method, options)
476
+ if network_tokenization?(payment_method)
477
+ add_network_tokenization(xml, payment_method, options)
478
+ else
479
+ xml.tag! 'ccAuthService', {'run' => 'true'}
480
+ end
481
+ end
482
+
483
+ def network_tokenization?(payment_method)
484
+ payment_method.is_a?(NetworkTokenizationCreditCard)
485
+ end
486
+
487
+ def add_network_tokenization(xml, payment_method, options)
488
+ return unless network_tokenization?(payment_method)
489
+
490
+ case card_brand(payment_method).to_sym
491
+ when :visa
492
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
493
+ xml.tag!("cavv", payment_method.payment_cryptogram)
494
+ xml.tag!("commerceIndicator", "vbv")
495
+ xml.tag!("xid", payment_method.payment_cryptogram)
496
+ end
497
+ when :mastercard
498
+ xml.tag! 'ucaf' do
499
+ xml.tag!("authenticationData", payment_method.payment_cryptogram)
500
+ xml.tag!("collectionIndicator", "2")
501
+ end
502
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
503
+ xml.tag!("commerceIndicator", "spa")
504
+ end
505
+ when :american_express
506
+ cryptogram = Base64.decode64(payment_method.payment_cryptogram)
507
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
508
+ xml.tag!("cavv", Base64.encode64(cryptogram[0...20]))
509
+ xml.tag!("commerceIndicator", "aesk")
510
+ xml.tag!("xid", Base64.encode64(cryptogram[20...40]))
511
+ end
512
+ end
513
+
514
+ xml.tag! 'paymentNetworkToken' do
515
+ xml.tag!('transactionType', "1")
516
+ end
473
517
  end
474
518
 
475
519
  def add_capture_service(xml, request_id, request_token)
@@ -479,11 +523,11 @@ module ActiveMerchant #:nodoc:
479
523
  end
480
524
  end
481
525
 
482
- def add_purchase_service(xml, options)
526
+ def add_purchase_service(xml, payment_method, options)
483
527
  if options[:pinless_debit_card]
484
528
  xml.tag! 'pinlessDebitService', {'run' => 'true'}
485
529
  else
486
- xml.tag! 'ccAuthService', {'run' => 'true'}
530
+ add_auth_service(xml, payment_method, options)
487
531
  xml.tag! 'ccCaptureService', {'run' => 'true'}
488
532
  end
489
533
  end
@@ -32,6 +32,8 @@ module ActiveMerchant #:nodoc:
32
32
  :discover => "Discover"
33
33
  }
34
34
 
35
+ E4_BRANDS = BRANDS.merge({:mastercard => "Mastercard"})
36
+
35
37
  self.supported_cardtypes = BRANDS.keys
36
38
  self.supported_countries = ["CA", "US"]
37
39
  self.default_currency = "USD"
@@ -106,6 +108,16 @@ module ActiveMerchant #:nodoc:
106
108
  commit(:store, build_store_request(credit_card, options), credit_card)
107
109
  end
108
110
 
111
+ def supports_scrubbing?
112
+ true
113
+ end
114
+
115
+ def scrub(transcript)
116
+ transcript.
117
+ gsub(%r((<Card_Number>).+(</Card_Number>)), '\1[FILTERED]\2').
118
+ gsub(%r((<VerificationStr2>).+(</VerificationStr2>)), '\1[FILTERED]\2')
119
+ end
120
+
109
121
  private
110
122
 
111
123
  def build_request(action, body)
@@ -250,7 +262,7 @@ module ActiveMerchant #:nodoc:
250
262
  end
251
263
 
252
264
  def card_type(credit_card_brand)
253
- BRANDS[credit_card_brand.to_sym] if credit_card_brand
265
+ E4_BRANDS[credit_card_brand.to_sym] if credit_card_brand
254
266
  end
255
267
 
256
268
  def commit(action, request, credit_card = nil)
@@ -263,7 +275,7 @@ module ActiveMerchant #:nodoc:
263
275
 
264
276
  Response.new(successful?(response), message_from(response), response,
265
277
  :test => test?,
266
- :authorization => response_authorization(action, response, credit_card),
278
+ :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '',
267
279
  :avs_result => {:code => response[:avs]},
268
280
  :cvv_result => response[:cvv2]
269
281
  )
@@ -0,0 +1,215 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class Flo2cashGateway < Gateway
4
+ self.display_name = 'Flo2Cash'
5
+ self.homepage_url = 'http://www.flo2cash.co.nz/'
6
+
7
+ self.test_url = 'https://demo.flo2cash.co.nz/ws/paymentws.asmx'
8
+ self.live_url = 'https://secure.flo2cash.co.nz/ws/paymentws.asmx'
9
+
10
+ self.supported_countries = ['NZ']
11
+ self.default_currency = 'NZD'
12
+ self.money_format = :dollars
13
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
14
+
15
+ BRAND_MAP = {
16
+ "visa" => "VISA",
17
+ "master" => "MC",
18
+ "american_express" => "AMEX",
19
+ "diners_club" => "DINERS"
20
+ }
21
+
22
+ def initialize(options={})
23
+ requires!(options, :username, :password, :account_id)
24
+ super
25
+ end
26
+
27
+ def purchase(amount, payment_method, options={})
28
+ MultiResponse.run do |r|
29
+ r.process { authorize(amount, payment_method, options) }
30
+ r.process { capture(amount, r.authorization, options) }
31
+ end
32
+ end
33
+
34
+ def authorize(amount, payment_method, options={})
35
+ post = {}
36
+ add_invoice(post, amount, options)
37
+ add_payment_method(post, payment_method)
38
+ add_customer_data(post, options)
39
+
40
+ commit("ProcessAuthorise", post)
41
+ end
42
+
43
+ def capture(amount, authorization, options={})
44
+ post = {}
45
+ add_invoice(post, amount, options)
46
+ add_reference(post, authorization)
47
+ add_customer_data(post, options)
48
+
49
+ commit("ProcessCapture", post)
50
+ end
51
+
52
+ def refund(amount, authorization, options={})
53
+ post = {}
54
+ add_invoice(post, amount, options)
55
+ add_reference(post, authorization)
56
+ add_customer_data(post, options)
57
+
58
+ commit("ProcessRefund", post)
59
+ end
60
+
61
+ def supports_scrubbing?
62
+ true
63
+ end
64
+
65
+ def scrub(transcript)
66
+ transcript.
67
+ gsub(%r((<Password>)[^<]+(<))i, '\1[FILTERED]\2').
68
+ gsub(%r((<CardNumber>)[^<]+(<))i, '\1[FILTERED]\2').
69
+ gsub(%r((<CardCSC>)[^<]+(<))i, '\1[FILTERED]\2')
70
+ end
71
+
72
+ private
73
+
74
+ CURRENCY_CODES = Hash.new{|h,k| raise ArgumentError.new("Unsupported currency: #{k}")}
75
+ CURRENCY_CODES["NZD"] = "554"
76
+
77
+ def add_invoice(post, money, options)
78
+ post[:Amount] = amount(money)
79
+ post[:Reference] = options[:order_id]
80
+ post[:Particular] = options[:description]
81
+ end
82
+
83
+ def add_payment_method(post, payment_method)
84
+ post[:CardNumber] = payment_method.number
85
+ post[:CardType] = BRAND_MAP[payment_method.brand.to_s]
86
+ post[:CardExpiry] = format(payment_method.month, :two_digits) + format(payment_method.year, :two_digits)
87
+ post[:CardHolderName] = payment_method.name
88
+ post[:CardCSC] = payment_method.verification_value
89
+ end
90
+
91
+ def add_customer_data(post, options)
92
+ if(billing_address = (options[:billing_address] || options[:address]))
93
+ post[:Email] = billing_address[:email]
94
+ end
95
+ end
96
+
97
+ def add_reference(post, authorization)
98
+ post[:OriginalTransactionId] = authorization
99
+ end
100
+
101
+ def commit(action, post)
102
+ post[:Username] = @options[:username]
103
+ post[:Password] = @options[:password]
104
+ post[:AccountId] = @options[:account_id]
105
+
106
+ data = build_request(action, post)
107
+ begin
108
+ raw = parse(ssl_post(url, data, headers(action)), action)
109
+ rescue ActiveMerchant::ResponseError => e
110
+ if(e.response.code == "500" && e.response.body.start_with?("<?xml"))
111
+ raw = parse(e.response.body, action)
112
+ else
113
+ raise
114
+ end
115
+ end
116
+
117
+ succeeded = success_from(raw[:status])
118
+ Response.new(
119
+ succeeded,
120
+ message_from(succeeded, raw),
121
+ raw,
122
+ :authorization => authorization_from(action, raw[:transaction_id], post[:OriginalTransactionId]),
123
+ :error_code => error_code_from(succeeded, raw),
124
+ :test => test?
125
+ )
126
+ end
127
+
128
+ def headers(action)
129
+ {
130
+ 'Content-Type' => 'application/soap+xml; charset=utf-8',
131
+ 'SOAPAction' => %{"http://www.flo2cash.co.nz/webservices/paymentwebservice/#{action}"}
132
+ }
133
+ end
134
+
135
+ def build_request(action, post)
136
+ xml = Builder::XmlMarkup.new :indent => 2
137
+ post.each do |field, value|
138
+ xml.tag!(field, value)
139
+ end
140
+ body = xml.target!
141
+ envelope_wrap(action, body)
142
+ end
143
+
144
+ def envelope_wrap(action, body)
145
+ <<-EOS
146
+ <?xml version="1.0" encoding="utf-8"?>
147
+ <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
148
+ <soap12:Body>
149
+ <#{action} xmlns="http://www.flo2cash.co.nz/webservices/paymentwebservice">
150
+ #{body}
151
+ </#{action}>
152
+ </soap12:Body>
153
+ </soap12:Envelope>
154
+ EOS
155
+ end
156
+
157
+ def url
158
+ (test? ? test_url : live_url)
159
+ end
160
+
161
+ def parse(body, action)
162
+ response = {}
163
+ xml = REXML::Document.new(body)
164
+ root = (REXML::XPath.first(xml, "//#{action}Response") || REXML::XPath.first(xml, "//detail"))
165
+
166
+ root.elements.to_a.each do |node|
167
+ parse_element(response, node)
168
+ end if root
169
+
170
+ response
171
+ end
172
+
173
+ def parse_element(response, node)
174
+ if node.has_elements?
175
+ node.elements.each{|element| parse_element(response, element) }
176
+ else
177
+ response[node.name.underscore.to_sym] = node.text
178
+ end
179
+ end
180
+
181
+ def success_from(response)
182
+ response == 'SUCCESSFUL'
183
+ end
184
+
185
+ def message_from(succeeded, response)
186
+ if succeeded
187
+ "Succeeded"
188
+ else
189
+ response[:message] || response[:errormessage] || "Unable to read error message"
190
+ end
191
+ end
192
+
193
+ def authorization_from(action, current, original)
194
+ # Refunds require the authorization from the authorize() of the MultiResponse.
195
+ if action == 'ProcessCapture'
196
+ original
197
+ else
198
+ current
199
+ end
200
+ end
201
+
202
+ STANDARD_ERROR_CODE_MAPPING = {
203
+ 'Transaction Declined - Expired Card' => STANDARD_ERROR_CODE[:expired_card],
204
+ 'Bank Declined Transaction' => STANDARD_ERROR_CODE[:card_declined],
205
+ 'Insufficient Funds' => STANDARD_ERROR_CODE[:card_declined],
206
+ 'Transaction Declined - Bank Error' => STANDARD_ERROR_CODE[:processing_error],
207
+ 'No Reply from Bank' => STANDARD_ERROR_CODE[:processing_error],
208
+ }
209
+
210
+ def error_code_from(succeeded, response)
211
+ succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response[:message]]
212
+ end
213
+ end
214
+ end
215
+ end