activemerchant 1.56.0 → 1.57.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +42 -0
  3. data/lib/active_merchant/billing/gateways/authorize_net.rb +52 -21
  4. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +1 -0
  5. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +243 -0
  6. data/lib/active_merchant/billing/gateways/bpoint.rb +1 -1
  7. data/lib/active_merchant/billing/gateways/braintree_blue.rb +11 -11
  8. data/lib/active_merchant/billing/gateways/bridge_pay.rb +37 -8
  9. data/lib/active_merchant/billing/gateways/card_stream.rb +36 -11
  10. data/lib/active_merchant/billing/gateways/checkout_v2.rb +3 -0
  11. data/lib/active_merchant/billing/gateways/clearhaus.rb +2 -2
  12. data/lib/active_merchant/billing/gateways/creditcall.rb +1 -1
  13. data/lib/active_merchant/billing/gateways/cyber_source.rb +12 -1
  14. data/lib/active_merchant/billing/gateways/element.rb +335 -0
  15. data/lib/active_merchant/billing/gateways/forte.rb +8 -0
  16. data/lib/active_merchant/billing/gateways/in_context_paypal_express.rb +15 -0
  17. data/lib/active_merchant/billing/gateways/litle.rb +1 -1
  18. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +2 -1
  19. data/lib/active_merchant/billing/gateways/ncr_secure_pay.rb +165 -0
  20. data/lib/active_merchant/billing/gateways/payeezy.rb +54 -12
  21. data/lib/active_merchant/billing/gateways/sage.rb +379 -128
  22. data/lib/active_merchant/billing/gateways/stripe.rb +13 -3
  23. data/lib/active_merchant/billing/gateways/trans_first.rb +26 -6
  24. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +573 -0
  25. data/lib/active_merchant/billing/gateways/worldpay.rb +7 -0
  26. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +11 -0
  27. data/lib/active_merchant/version.rb +1 -1
  28. metadata +7 -6
  29. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +0 -89
  30. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +0 -115
  31. data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +0 -149
  32. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +0 -97
@@ -168,8 +168,10 @@ module ActiveMerchant #:nodoc:
168
168
  post[:validate] = options[:validate] unless options[:validate].nil?
169
169
  post[:description] = options[:description] if options[:description]
170
170
  post[:email] = options[:email] if options[:email]
171
-
172
- if options[:customer]
171
+ if options[:account]
172
+ add_external_account(post, card_params, payment)
173
+ commit(:post, "accounts/#{CGI.escape(options[:account])}/external_accounts", post, options)
174
+ elsif options[:customer]
173
175
  MultiResponse.run(:first) do |r|
174
176
  # The /cards endpoint does not update other customer parameters.
175
177
  r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", card_params, options) }
@@ -290,6 +292,13 @@ module ActiveMerchant #:nodoc:
290
292
  post[:expand].concat(Array.wrap(options[:expand]).map(&:to_sym)).uniq!
291
293
  end
292
294
 
295
+ def add_external_account(post, card_params, payment)
296
+ external_account = {}
297
+ external_account[:object] ="card"
298
+ external_account[:currency] = (options[:currency] || currency(payment)).downcase
299
+ post[:external_account] = external_account.merge(card_params[:card])
300
+ end
301
+
293
302
  def add_customer_data(post, options)
294
303
  metadata_options = [:description, :ip, :user_agent, :referrer]
295
304
  post.update(options.slice(*metadata_options))
@@ -333,7 +342,7 @@ module ActiveMerchant #:nodoc:
333
342
  end
334
343
  post[:card] = card
335
344
 
336
- if creditcard.is_a?(NetworkTokenizationCreditCard)
345
+ if creditcard.is_a?(NetworkTokenizationCreditCard) && creditcard.source == :apple_pay
337
346
  post[:three_d_secure] = {
338
347
  apple_pay: true,
339
348
  cryptogram: creditcard.payment_cryptogram
@@ -423,6 +432,7 @@ module ActiveMerchant #:nodoc:
423
432
  "X-Stripe-Client-User-Metadata" => {:ip => options[:ip]}.to_json
424
433
  }
425
434
  headers.merge!("Idempotency-Key" => idempotency_key) if idempotency_key
435
+ headers.merge!("Stripe-Account" => options[:stripe_account]) if options[:stripe_account]
426
436
  headers
427
437
  end
428
438
 
@@ -9,7 +9,7 @@ module ActiveMerchant #:nodoc:
9
9
  self.homepage_url = 'http://www.transfirst.com/'
10
10
  self.display_name = 'TransFirst'
11
11
 
12
- UNUSED_FIELDS = %w(ECIValue UserId CAVVData TrackData POSInd EComInd MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSEntryMode POSConditionCode AuthCharInd CardCertData)
12
+ UNUSED_CREDIT_CARD_FIELDS = %w(UserId TrackData MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSInd POSEntryMode POSConditionCode EComInd AuthCharInd CardCertData CAVVData)
13
13
 
14
14
  DECLINED = 'The transaction was declined'
15
15
 
@@ -62,6 +62,19 @@ module ActiveMerchant #:nodoc:
62
62
  commit(:void, post)
63
63
  end
64
64
 
65
+ def supports_scrubbing?
66
+ true
67
+ end
68
+
69
+ def scrub(transcript)
70
+ transcript.
71
+ gsub(%r((&?RegKey=)\w*(&?)), '\1[FILTERED]\2').
72
+ gsub(%r((&?CardNumber=)\d*(&?)), '\1[FILTERED]\2').
73
+ gsub(%r((&?CVV2=)\d*(&?)), '\1[FILTERED]\2').
74
+ gsub(%r((&?TransRoute=)\d*(&?)), '\1[FILTERED]\2').
75
+ gsub(%r((&?BankAccountNo=)\d*(&?)), '\1[FILTERED]\2')
76
+ end
77
+
65
78
  private
66
79
 
67
80
  def add_amount(post, money)
@@ -82,8 +95,8 @@ module ActiveMerchant #:nodoc:
82
95
  add_pair(post, :SECCCode, options[:invoice], required: true)
83
96
  add_pair(post, :PONumber, options[:invoice], required: true)
84
97
  add_pair(post, :SaleTaxAmount, amount(options[:tax] || 0))
85
- add_pair(post, :PaymentDesc, options[:description], required: true)
86
98
  add_pair(post, :TaxIndicator, 0)
99
+ add_pair(post, :PaymentDesc, options[:description] || "", required: true)
87
100
  add_pair(post, :CompanyName, options[:company_name] || "", required: true)
88
101
  end
89
102
 
@@ -99,21 +112,28 @@ module ActiveMerchant #:nodoc:
99
112
  add_pair(post, :CardHolderName, payment.name, required: true)
100
113
  add_pair(post, :CardNumber, payment.number, required: true)
101
114
  add_pair(post, :Expiration, expdate(payment), required: true)
102
- add_pair(post, :CVV2, payment.verification_value)
115
+ add_pair(post, :CVV2, payment.verification_value, required: true)
103
116
  end
104
117
 
105
118
  def add_echeck(post, payment)
106
119
  add_pair(post, :TransRoute, payment.routing_number, required: true)
107
120
  add_pair(post, :BankAccountNo, payment.account_number, required: true)
108
- add_pair(post, :BankAccountType, payment.account_type.capitalize, required: true)
109
- add_pair(post, :CheckType, payment.account_holder_type.capitalize, required: true)
121
+ add_pair(post, :BankAccountType, add_or_use_default(payment.account_type, "Checking"), required: true)
122
+ add_pair(post, :CheckType, add_or_use_default(payment.account_holder_type, "Personal"), required: true)
110
123
  add_pair(post, :Name, payment.name, required: true)
111
124
  add_pair(post, :ProcessDate, Time.now.strftime("%m%d%y"), required: true)
112
125
  add_pair(post, :Description, "", required: true)
113
126
  end
114
127
 
128
+ def add_or_use_default(payment_data, default_value)
129
+ return payment_data.capitalize if payment_data
130
+ return default_value
131
+ end
132
+
115
133
  def add_unused_fields(post)
116
- UNUSED_FIELDS.each do |f|
134
+ return if post[:TransRoute]
135
+
136
+ UNUSED_CREDIT_CARD_FIELDS.each do |f|
117
137
  post[f] = ""
118
138
  end
119
139
  end
@@ -0,0 +1,573 @@
1
+ require "nokogiri"
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class TransFirstTransactionExpressGateway < Gateway
6
+ self.display_name = "TransFirst Transaction Express"
7
+ self.homepage_url = "http://transactionexpress.com/"
8
+
9
+ self.test_url = "https://ws.cert.transactionexpress.com/portal/merchantframework/MerchantWebServices-v1?wsdl"
10
+ self.live_url = "https://ws.transactionexpress.com/portal/merchantframework/MerchantWebServices-v1?wsdl"
11
+
12
+ self.supported_countries = ["US"]
13
+ self.default_currency = "USD"
14
+ self.money_format = :cents
15
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
16
+
17
+ V1_NAMESPACE = "http://postilion/realtime/merchantframework/xsd/v1/"
18
+ SOAPENV_NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/"
19
+ AUTHORIZATION_FIELD_SEPARATOR = "|"
20
+
21
+ APPROVAL_CODES = %w(00 10)
22
+
23
+ RESPONSE_MESSAGES = {
24
+ "00" => "Approved",
25
+ "01" => "Refer to card issuer",
26
+ "02" => "Refer to card issuer, special condition",
27
+ "03" => "Invalid merchant",
28
+ "04" => "Pick-up card",
29
+ "05" => "Do not honor",
30
+ "06" => "Error",
31
+ "07" => "Pick-up card, special condition",
32
+ "08" => "Honor with identification",
33
+ "09" => "Request in progress",
34
+ "10" => "Approved, partial authorization",
35
+ "11" => "VIP Approval",
36
+ "12" => "Invalid transaction",
37
+ "13" => "Invalid amount",
38
+ "14" => "Invalid card number",
39
+ "15" => "No such issuer",
40
+ "16" => "Approved, update track 3",
41
+ "17" => "Customer cancellation",
42
+ "18" => "Customer dispute",
43
+ "19" => "Re-enter transaction",
44
+ "20" => "Invalid response",
45
+ "21" => "No action taken",
46
+ "22" => "Suspected malfunction",
47
+ "23" => "Unacceptable transaction fee",
48
+ "24" => "File update not supported",
49
+ "25" => "Unable to locate record",
50
+ "26" => "Duplicate record",
51
+ "27" => "File update field edit error",
52
+ "28" => "File update file locked",
53
+ "29" => "File update failed",
54
+ "30" => "Format error",
55
+ "31" => "Bank not supported",
56
+ "33" => "Expired card, pick-up",
57
+ "34" => "Suspected fraud, pick-up",
58
+ "35" => "Contact acquirer, pick-up",
59
+ "36" => "Restricted card, pick-up",
60
+ "37" => "Call acquirer security, pick-up",
61
+ "38" => "PIN tries exceeded, pick-up",
62
+ "39" => "No credit account",
63
+ "40" => "Function not supported",
64
+ "41" => "Lost card, pick-up",
65
+ "42" => "No universal account",
66
+ "43" => "Stolen card, pick-up",
67
+ "44" => "No investment account",
68
+ "45" => "Account closed",
69
+ "46" => "Identification required",
70
+ "47" => "Identification cross-check required",
71
+ "48" => "No customer record",
72
+ "49" => "Reserved for future Realtime use",
73
+ "50" => "Reserved for future Realtime use",
74
+ "51" => "Not sufficient funds",
75
+ "52" => "No checking account",
76
+ "53" => "No savings account",
77
+ "54" => "Expired card",
78
+ "55" => "Incorrect PIN",
79
+ "56" => "No card record",
80
+ "57" => "Transaction not permitted to cardholder",
81
+ "58" => "Transaction not permitted on terminal",
82
+ "59" => "Suspected fraud",
83
+ "60" => "Contact acquirer",
84
+ "61" => "Exceeds withdrawal limit",
85
+ "62" => "Restricted card",
86
+ "63" => "Security violation",
87
+ "64" => "Original amount incorrect",
88
+ "65" => "Exceeds withdrawal frequency",
89
+ "66" => "Call acquirer security",
90
+ "67" => "Hard capture",
91
+ "68" => "Response received too late",
92
+ "69" => "Advice received too late (the response from a request was received too late )",
93
+ "70" => "Reserved for future use",
94
+ "71" => "Reserved for future Realtime use",
95
+ "72" => "Reserved for future Realtime use",
96
+ "73" => "Reserved for future Realtime use",
97
+ "74" => "Reserved for future Realtime use",
98
+ "75" => "PIN tries exceeded",
99
+ "76" => "Reversal: Unable to locate previous message (no match on Retrieval Reference Number)/ Reserved for future Realtime use",
100
+ "77" => "Previous message located for a repeat or reversal, but repeat or reversal data is inconsistent with original message/ Intervene, bank approval required",
101
+ "78" => "Invalid/non-existent account – Decline (MasterCard specific)/ Intervene, bank approval required for partial amount",
102
+ "79" => "Already reversed (by Switch)/ Reserved for client-specific use (declined)",
103
+ "80" => "No financial Impact (Reserved for declined debit)/ Reserved for client-specific use (declined)",
104
+ "81" => "PIN cryptographic error found by the Visa security module during PIN decryption/ Reserved for client-specific use (declined)",
105
+ "82" => "Incorrect CVV/ Reserved for client-specific use (declined)",
106
+ "83" => "Unable to verify PIN/ Reserved for client-specific use (declined)",
107
+ "84" => "Invalid Authorization Life Cycle – Decline (MasterCard) or Duplicate Transaction Detected (Visa)/ Reserved for client-specific use (declined)",
108
+ "85" => "No reason to decline a request for Account Number Verification or Address Verification/ Reserved for client-specific use (declined)",
109
+ "86" => "Cannot verify PIN/ Reserved for client-specific use (declined)",
110
+ "87" => "Reserved for client-specific use (declined)",
111
+ "88" => "Reserved for client-specific use (declined)",
112
+ "89" => "Reserved for client-specific use (declined)",
113
+ "90" => "Cut-off in progress",
114
+ "91" => "Issuer or switch inoperative",
115
+ "92" => "Routing error",
116
+ "93" => "Violation of law",
117
+ "94" => "Duplicate Transmission (Integrated Debit and MasterCard)",
118
+ "95" => "Reconcile error",
119
+ "96" => "System malfunction",
120
+ "97" => "Reserved for future Realtime use",
121
+ "98" => "Exceeds cash limit",
122
+ "99" => "Reserved for future Realtime use",
123
+ "1106" => "Reserved for future Realtime use",
124
+ "0A" => "Reserved for future Realtime use",
125
+ "A0" => "Reserved for future Realtime use",
126
+ "A1" => "ATC not incremented",
127
+ "A2" => "ATC limit exceeded",
128
+ "A3" => "ATC configuration error",
129
+ "A4" => "CVR check failure",
130
+ "A5" => "CVR configuration error",
131
+ "A6" => "TVR check failure",
132
+ "A7" => "TVR configuration error",
133
+ "A8" => "Reserved for future Realtime use",
134
+ "B1" => "Surcharge amount not permitted on Visa cards or EBT Food Stamps/ Reserved for future Realtime use",
135
+ "B2" => "Surcharge amount not supported by debit network issuer/ Reserved for future Realtime use",
136
+ "C1" => "Unacceptable PIN",
137
+ "C2" => "PIN Change failed",
138
+ "C3" => "PIN Unblock failed",
139
+ "D1" => "MAC Error",
140
+ "E1" => "Prepay error",
141
+ "N1" => "Network Error within the TXP platform",
142
+ "N0" => "Force STIP/ Reserved for client-specific use (declined)",
143
+ "N3" => "Cash service not available/ Reserved for client-specific use (declined)",
144
+ "N4" => "Cash request exceeds Issuer limit/ Reserved for client-specific use (declined)",
145
+ "N5" => "Ineligible for re-submission/ Reserved for client-specific use (declined)",
146
+ "N7" => "Decline for CVV2 failure/ Reserved for client-specific use (declined)",
147
+ "N8" => "Transaction amount exceeds preauthorized approval amount/ Reserved for client-specific use (declined)",
148
+ "P0" => "Approved; PVID code is missing, invalid, or has expired",
149
+ "P1" => "Declined; PVID code is missing, invalid, or has expired/ Reserved for client-specific use (declined)",
150
+ "P2" => "Invalid biller Information/ Reserved for client-specific use (declined)/ Reserved for client-specific use (declined)",
151
+ "R0" => "The transaction was declined or returned, because the cardholder requested that payment of a specific recurring or installment payment transaction be stopped/ Reserved for client-specific use (declined)",
152
+ "R1" => "The transaction was declined or returned, because the cardholder requested that payment of all recurring or installment payment transactions for a specific merchant account be stopped/ Reserved for client-specific use (declined)",
153
+ "Q1" => "Card Authentication failed/ Reserved for client-specific use (declined)",
154
+ "XA" => "Forward to Issuer/ Reserved for client-specific use (declined)",
155
+ "XD" => "Forward to Issuer/ Reserved for client-specific use (declined)",
156
+ }
157
+
158
+ EXTENDED_RESPONSE_MESSAGES = {
159
+ "B40K" => "Declined Post – Credit linked to unextracted settle transaction"
160
+ }
161
+
162
+ TRANSACTION_CODES = {
163
+ authorize: 0,
164
+ void_authorize: 2,
165
+
166
+ purchase: 1,
167
+ capture: 3,
168
+ void_purchase: 6,
169
+ void_capture: 6,
170
+
171
+ refund: 4,
172
+ credit: 5,
173
+ void_refund: 13,
174
+ void_credit: 13,
175
+
176
+ verify: 9,
177
+
178
+ wallet_sale: 14,
179
+ }
180
+
181
+ def initialize(options={})
182
+ requires!(options, :gateway_id, :reg_key)
183
+ super
184
+ end
185
+
186
+ def purchase(amount, payment_method, options={})
187
+ if credit_card?(payment_method)
188
+ action = :purchase
189
+ request = build_xml_transaction_request do |doc|
190
+ add_payment_method(doc, payment_method)
191
+ add_contact(doc, payment_method.name, options)
192
+ add_amount(doc, amount)
193
+ add_order_number(doc, options)
194
+ end
195
+ else
196
+ action = :wallet_sale
197
+ wallet_id = split_authorization(payment_method).last
198
+ request = build_xml_transaction_request do |doc|
199
+ add_amount(doc, amount)
200
+ add_wallet_id(doc, wallet_id)
201
+ end
202
+ end
203
+
204
+ commit(action, request)
205
+ end
206
+
207
+ def authorize(amount, payment_method, options={})
208
+ if credit_card?(payment_method)
209
+ request = build_xml_transaction_request do |doc|
210
+ add_payment_method(doc, payment_method)
211
+ add_contact(doc, payment_method.name, options)
212
+ add_amount(doc, amount)
213
+ end
214
+ else
215
+ wallet_id = split_authorization(payment_method).last
216
+ request = build_xml_transaction_request do |doc|
217
+ add_amount(doc, amount)
218
+ add_wallet_id(doc, wallet_id)
219
+ end
220
+ end
221
+
222
+ commit(:authorize, request)
223
+ end
224
+
225
+ def capture(amount, authorization, options={})
226
+ transaction_id = split_authorization(authorization)[1]
227
+ request = build_xml_transaction_request do |doc|
228
+ add_amount(doc, amount)
229
+ add_original_transaction_data(doc, transaction_id)
230
+ end
231
+
232
+ commit(:capture, request)
233
+ end
234
+
235
+ def void(authorization, options={})
236
+ action, transaction_id = split_authorization(authorization)
237
+
238
+ request = build_xml_transaction_request do |doc|
239
+ add_original_transaction_data(doc, transaction_id)
240
+ end
241
+
242
+ commit(void_type(action), request)
243
+ end
244
+
245
+ def refund(amount, authorization, options={})
246
+ transaction_id = split_authorization(authorization)[1]
247
+
248
+ request = build_xml_transaction_request do |doc|
249
+ add_amount(doc, amount)
250
+ add_original_transaction_data(doc, transaction_id)
251
+ add_order_number(doc, options)
252
+ end
253
+
254
+ commit(:refund, request)
255
+ end
256
+
257
+ def credit(amount, payment_method, options={})
258
+ request = build_xml_transaction_request do |doc|
259
+ add_pan(doc, payment_method)
260
+ add_amount(doc, amount)
261
+ end
262
+
263
+ commit(:credit, request)
264
+ end
265
+
266
+ def verify(credit_card, options={})
267
+ request = build_xml_transaction_request do |doc|
268
+ add_payment_method(doc, credit_card)
269
+ add_contact(doc, credit_card.name, options)
270
+ end
271
+
272
+ commit(:verify, request)
273
+ end
274
+
275
+ def store(payment_method, options={})
276
+ store_customer_request = build_xml_payment_storage_request do |doc|
277
+ store_customer_details(doc, payment_method.name, options)
278
+ end
279
+
280
+ MultiResponse.run do |r|
281
+ r.process { commit(:store, store_customer_request) }
282
+ return r unless r.success? && r.params["custId"]
283
+ customer_id = r.params["custId"]
284
+
285
+ store_payment_method_request = build_xml_payment_storage_request do |doc|
286
+ doc["v1"].cust do
287
+ add_customer_id(doc, customer_id)
288
+ doc["v1"].pmt do
289
+ doc["v1"].type 0 # add
290
+ add_payment_method(doc, payment_method)
291
+ end
292
+ end
293
+ end
294
+
295
+ r.process { commit(:store, store_payment_method_request) }
296
+ end
297
+ end
298
+
299
+ def supports_scrubbing?
300
+ true
301
+ end
302
+
303
+ def scrub(transcript)
304
+ transcript.
305
+ gsub(%r((<[^>]+pan>)[^<]+(<))i, '\1[FILTERED]\2').
306
+ gsub(%r((<[^>]+sec>)[^<]+(<))i, '\1[FILTERED]\2').
307
+ gsub(%r((<[^>]+id>)[^<]+(<))i, '\1[FILTERED]\2').
308
+ gsub(%r((<[^>]+regKey>)[^<]+(<))i, '\1[FILTERED]\2')
309
+ end
310
+
311
+ private
312
+
313
+ CURRENCY_CODES = Hash.new{|h,k| raise ArgumentError.new("Unsupported currency: #{k}")}
314
+ CURRENCY_CODES["USD"] = "840"
315
+
316
+ def headers
317
+ {
318
+ "Content-Type" => "text/xml"
319
+ }
320
+ end
321
+
322
+ def commit(action, request)
323
+ request = add_transaction_code_to_request(request, action)
324
+
325
+ raw_response = begin
326
+ ssl_post(url, request, headers)
327
+ rescue ActiveMerchant::ResponseError => e
328
+ e.response.body
329
+ end
330
+
331
+ response = parse(raw_response)
332
+
333
+ succeeded = success_from(response)
334
+
335
+ Response.new(
336
+ succeeded,
337
+ message_from(succeeded, response),
338
+ response,
339
+ error_code: error_code_from(succeeded, response),
340
+ authorization: authorization_from(action, response),
341
+ avs_result: AVSResult.new(code: response["AVSCode"]),
342
+ cvv_result: CVVResult.new(response["CVV2Response"]),
343
+ test: test?
344
+ )
345
+ end
346
+
347
+ def url
348
+ test? ? test_url : live_url
349
+ end
350
+
351
+ def parse(xml)
352
+ response = {}
353
+ doc = Nokogiri::XML(xml).remove_namespaces!
354
+
355
+ doc.css("Envelope Body *").each do |node|
356
+ # node.name is more readable, but uniq_name is occasionally necessary
357
+ uniq_name = [node.parent.name, node.name].join('_')
358
+ response[uniq_name] = node.text
359
+ response[node.name] = node.text
360
+ end
361
+
362
+ response
363
+ end
364
+
365
+ def success_from(response)
366
+ fault = response["Fault"]
367
+ approved_transaction = APPROVAL_CODES.include?(response["rspCode"])
368
+ found_contact = response["FndRecurrProfResponse"]
369
+
370
+ return !fault && (approved_transaction || found_contact)
371
+ end
372
+
373
+ def error_code_from(succeeded, response)
374
+ return if succeeded
375
+ response["errorCode"] || response["rspCode"]
376
+ end
377
+
378
+ def message_from(succeeded, response)
379
+ return "Succeeded" if succeeded
380
+
381
+ if response["rspCode"]
382
+ code = response["rspCode"]
383
+ extended_code = response["extRspCode"]
384
+
385
+ message = RESPONSE_MESSAGES[code]
386
+ extended = EXTENDED_RESPONSE_MESSAGES[extended_code]
387
+
388
+ [message, extended].compact.join('. ')
389
+ else
390
+ response["faultstring"]
391
+ end
392
+ end
393
+
394
+ def authorization_from(action, response)
395
+ authorization = response["tranNr"] || response["pmtId"]
396
+
397
+ # guard so we don't return something like "purchase|"
398
+ return unless authorization
399
+
400
+ [action, authorization].join(AUTHORIZATION_FIELD_SEPARATOR)
401
+ end
402
+
403
+ # -- helper methods ----------------------------------------------------
404
+ def credit_card?(payment_method)
405
+ payment_method.respond_to?(:number)
406
+ end
407
+
408
+ def split_authorization(authorization)
409
+ authorization.split(AUTHORIZATION_FIELD_SEPARATOR)
410
+ end
411
+
412
+ def void_type(action)
413
+ :"void_#{action}"
414
+ end
415
+
416
+ # -- request methods ---------------------------------------------------
417
+ def build_xml_transaction_request
418
+ build_xml_request("SendTranRequest") do |doc|
419
+ yield doc
420
+ end
421
+ end
422
+
423
+ def build_xml_payment_storage_request
424
+ build_xml_request("UpdtRecurrProfRequest") do |doc|
425
+ yield doc
426
+ end
427
+ end
428
+
429
+ def build_xml_payment_update_request
430
+ merchant_product_type = 5 # credit card
431
+ build_xml_request("UpdtRecurrProfRequest", merchant_product_type) do |doc|
432
+ yield doc
433
+ end
434
+ end
435
+
436
+ def build_xml_payment_search_request
437
+ build_xml_request("FndRecurrProfRequest") do |doc|
438
+ yield doc
439
+ end
440
+ end
441
+
442
+ def build_xml_request(wrapper, merchant_product_type=nil)
443
+ Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
444
+ xml["soapenv"].Envelope("xmlns:soapenv" => SOAPENV_NAMESPACE) do
445
+ xml["soapenv"].Body do
446
+ xml["v1"].send(wrapper, "xmlns:v1" => V1_NAMESPACE) do
447
+ add_merchant(xml)
448
+ yield(xml)
449
+ end
450
+ end
451
+ end
452
+ end.doc.root.to_xml
453
+ end
454
+
455
+ def add_transaction_code_to_request(request, action)
456
+ # store requests don't get a transaction code
457
+ return request if action == :store
458
+
459
+ doc = Nokogiri::XML::Document.parse(request)
460
+ merc_nodeset = doc.xpath('//v1:merc', 'v1' => V1_NAMESPACE)
461
+ merc_nodeset.after "<tranCode>#{TRANSACTION_CODES[action]}</tranCode>"
462
+ doc.root.to_xml
463
+ end
464
+
465
+ def add_merchant(doc, product_type=nil)
466
+ doc["v1"].merc do
467
+ doc["v1"].id @options[:gateway_id]
468
+ doc["v1"].regKey @options[:reg_key]
469
+ doc["v1"].inType "1"
470
+ doc["v1"].prodType product_type if product_type
471
+ end
472
+ end
473
+
474
+ def add_amount(doc, money)
475
+ doc["v1"].reqAmt amount(money)
476
+ end
477
+
478
+ def add_order_number(doc, options)
479
+ return unless options[:order_id]
480
+
481
+ doc["v1"].authReq {
482
+ doc["v1"].ordNr options[:order_id]
483
+ }
484
+ end
485
+
486
+ def add_payment_method(doc, payment_method)
487
+ doc["v1"].card {
488
+ doc["v1"].pan payment_method.number
489
+ doc["v1"].sec payment_method.verification_value
490
+ doc["v1"].xprDt expiration_date(payment_method)
491
+ }
492
+ end
493
+
494
+ def expiration_date(payment_method)
495
+ yy = format(payment_method.year, :two_digits)
496
+ mm = format(payment_method.month, :two_digits)
497
+ yy + mm
498
+ end
499
+
500
+ def add_pan(doc, payment_method)
501
+ doc["v1"].card do
502
+ doc["v1"].pan payment_method.number
503
+ end
504
+ end
505
+
506
+ def add_contact(doc, fullname, options)
507
+ doc["v1"].contact do
508
+ doc["v1"].fullName fullname
509
+ doc["v1"].coName options[:company_name] if options[:company_name]
510
+ doc["v1"].title options[:title] if options[:title]
511
+
512
+ if (billing_address = options[:billing_address])
513
+ doc["v1"].phone do
514
+ doc["v1"].type (options[:phone_number_type] || "4")
515
+ doc["v1"].nr billing_address[:phone].gsub(/\D/, '')
516
+ end
517
+ doc["v1"].addrLn1 billing_address[:address1]
518
+ doc["v1"].addrLn2 billing_address[:address2]
519
+ doc["v1"].city billing_address[:city]
520
+ doc["v1"].state billing_address[:state]
521
+ doc["v1"].zipCode billing_address[:zip]
522
+ doc["v1"].ctry "US"
523
+ end
524
+
525
+ doc["v1"].email options[:email] if options[:email]
526
+ doc["v1"].type options[:contact_type] if options[:contact_type]
527
+ doc["v1"].stat options[:contact_stat] if options[:contact_stat]
528
+
529
+ if (shipping_address = options[:shipping_address])
530
+ doc["v1"].ship do
531
+ doc["v1"].fullName fullname
532
+ doc["v1"].addrLn1 shipping_address[:address1]
533
+ doc["v1"].addrLn2 shipping_address[:address2] if shipping_address[:address2]
534
+ doc["v1"].city shipping_address[:city]
535
+ doc["v1"].state shipping_address[:state]
536
+ doc["v1"].zipCode shipping_address[:zip]
537
+ doc["v1"].phone shipping_address[:phone].gsub(/\D/, '') if shipping_address[:phone]
538
+ doc["v1"].email shipping_address[:email] if shipping_address[:email]
539
+ end
540
+ end
541
+ end
542
+ end
543
+
544
+ def add_original_transaction_data(doc, authorization)
545
+ doc["v1"].origTranData do
546
+ doc["v1"].tranNr authorization
547
+ end
548
+ end
549
+
550
+ def store_customer_details(doc, fullname, options)
551
+ options[:contact_type] = 1 # recurring
552
+ options[:contact_stat] = 1 # active
553
+
554
+ doc["v1"].cust do
555
+ doc["v1"].type 0 # add
556
+ add_contact(doc, fullname, options)
557
+ end
558
+ end
559
+
560
+ def add_customer_id(doc, customer_id)
561
+ doc["v1"].contact do
562
+ doc["v1"].id customer_id
563
+ end
564
+ end
565
+
566
+ def add_wallet_id(doc, wallet_id)
567
+ doc["v1"].recurMan do
568
+ doc["v1"].id wallet_id
569
+ end
570
+ end
571
+ end
572
+ end
573
+ end