activemerchant 1.46.0 → 1.47.0

Sign up to get free protection for your applications and to get access to all the features.
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