activemerchant 1.56.0 → 1.57.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.
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