activemerchant 1.100.0 → 1.101.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +10 -0
  3. data/lib/active_merchant/billing/check.rb +2 -6
  4. data/lib/active_merchant/billing/credit_card.rb +1 -3
  5. data/lib/active_merchant/billing/gateway.rb +4 -7
  6. data/lib/active_merchant/billing/gateways/authorize_net.rb +3 -9
  7. data/lib/active_merchant/billing/gateways/authorize_net_arb.rb +1 -3
  8. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +4 -12
  9. data/lib/active_merchant/billing/gateways/axcessms.rb +1 -3
  10. data/lib/active_merchant/billing/gateways/balanced.rb +12 -11
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +2 -6
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +1 -3
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +7 -21
  14. data/lib/active_merchant/billing/gateways/cecabank.rb +1 -3
  15. data/lib/active_merchant/billing/gateways/checkout.rb +1 -3
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +1 -3
  17. data/lib/active_merchant/billing/gateways/clearhaus.rb +9 -12
  18. data/lib/active_merchant/billing/gateways/credorax.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/culqi.rb +6 -5
  20. data/lib/active_merchant/billing/gateways/cyber_source.rb +1 -0
  21. data/lib/active_merchant/billing/gateways/data_cash.rb +1 -3
  22. data/lib/active_merchant/billing/gateways/decidir.rb +1 -3
  23. data/lib/active_merchant/billing/gateways/ebanx.rb +1 -3
  24. data/lib/active_merchant/billing/gateways/elavon.rb +2 -6
  25. data/lib/active_merchant/billing/gateways/element.rb +1 -3
  26. data/lib/active_merchant/billing/gateways/eway_managed.rb +6 -5
  27. data/lib/active_merchant/billing/gateways/fat_zebra.rb +7 -6
  28. data/lib/active_merchant/billing/gateways/forte.rb +2 -6
  29. data/lib/active_merchant/billing/gateways/garanti.rb +1 -3
  30. data/lib/active_merchant/billing/gateways/global_collect.rb +1 -3
  31. data/lib/active_merchant/billing/gateways/hdfc.rb +1 -3
  32. data/lib/active_merchant/billing/gateways/hps.rb +6 -5
  33. data/lib/active_merchant/billing/gateways/inspire.rb +2 -6
  34. data/lib/active_merchant/billing/gateways/instapay.rb +1 -3
  35. data/lib/active_merchant/billing/gateways/iridium.rb +1 -3
  36. data/lib/active_merchant/billing/gateways/iveri.rb +7 -8
  37. data/lib/active_merchant/billing/gateways/jetpay.rb +3 -9
  38. data/lib/active_merchant/billing/gateways/jetpay_v2.rb +2 -6
  39. data/lib/active_merchant/billing/gateways/kushki.rb +7 -8
  40. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +6 -5
  41. data/lib/active_merchant/billing/gateways/merchant_one.rb +1 -3
  42. data/lib/active_merchant/billing/gateways/merchant_ware.rb +1 -3
  43. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +1 -3
  44. data/lib/active_merchant/billing/gateways/mercury.rb +2 -6
  45. data/lib/active_merchant/billing/gateways/metrics_global.rb +4 -12
  46. data/lib/active_merchant/billing/gateways/migs.rb +1 -3
  47. data/lib/active_merchant/billing/gateways/moneris.rb +1 -3
  48. data/lib/active_merchant/billing/gateways/moneris_us.rb +1 -3
  49. data/lib/active_merchant/billing/gateways/ncr_secure_pay.rb +1 -3
  50. data/lib/active_merchant/billing/gateways/netbanx.rb +24 -9
  51. data/lib/active_merchant/billing/gateways/netbilling.rb +1 -3
  52. data/lib/active_merchant/billing/gateways/network_merchants.rb +1 -3
  53. data/lib/active_merchant/billing/gateways/opp.rb +12 -13
  54. data/lib/active_merchant/billing/gateways/optimal_payment.rb +1 -3
  55. data/lib/active_merchant/billing/gateways/orbital.rb +8 -11
  56. data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +1 -3
  57. data/lib/active_merchant/billing/gateways/pagarme.rb +4 -12
  58. data/lib/active_merchant/billing/gateways/pago_facil.rb +1 -3
  59. data/lib/active_merchant/billing/gateways/pay_conex.rb +1 -3
  60. data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +1 -3
  61. data/lib/active_merchant/billing/gateways/pay_junction_v2.rb +6 -5
  62. data/lib/active_merchant/billing/gateways/payflow.rb +2 -6
  63. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -3
  64. data/lib/active_merchant/billing/gateways/paymentez.rb +1 -3
  65. data/lib/active_merchant/billing/gateways/paymill.rb +1 -3
  66. data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -3
  67. data/lib/active_merchant/billing/gateways/quickbooks.rb +104 -31
  68. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +2 -6
  69. data/lib/active_merchant/billing/gateways/realex.rb +1 -3
  70. data/lib/active_merchant/billing/gateways/redsys.rb +17 -27
  71. data/lib/active_merchant/billing/gateways/safe_charge.rb +1 -3
  72. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -3
  73. data/lib/active_merchant/billing/gateways/secure_net.rb +2 -6
  74. data/lib/active_merchant/billing/gateways/secure_pay.rb +4 -12
  75. data/lib/active_merchant/billing/gateways/smart_ps.rb +2 -6
  76. data/lib/active_merchant/billing/gateways/spreedly_core.rb +2 -6
  77. data/lib/active_merchant/billing/gateways/stripe.rb +3 -9
  78. data/lib/active_merchant/billing/gateways/telr.rb +2 -6
  79. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +6 -5
  80. data/lib/active_merchant/billing/gateways/transact_pro.rb +1 -3
  81. data/lib/active_merchant/billing/gateways/trust_commerce.rb +1 -3
  82. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +4 -12
  83. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +3 -9
  84. data/lib/active_merchant/billing/gateways/viaklix.rb +2 -6
  85. data/lib/active_merchant/billing/gateways/wepay.rb +2 -6
  86. data/lib/active_merchant/billing/gateways/wirecard.rb +2 -6
  87. data/lib/active_merchant/billing/gateways/worldpay.rb +5 -15
  88. data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +2 -6
  89. data/lib/active_merchant/version.rb +1 -1
  90. data/lib/support/ssl_verify.rb +1 -3
  91. metadata +2 -2
@@ -156,9 +156,7 @@ module ActiveMerchant #:nodoc:
156
156
  end
157
157
 
158
158
  def error_code_from(response)
159
- unless success_from(response)
160
- response[:msoft_code] || response[:phard_code]
161
- end
159
+ response[:msoft_code] || response[:phard_code] unless success_from(response)
162
160
  end
163
161
  end
164
162
  end
@@ -129,9 +129,9 @@ module ActiveMerchant #:nodoc:
129
129
  post[:card][:holderName] = credit_card.name
130
130
  post[:card][:cvv] = credit_card.verification_value
131
131
  post[:card][:cardExpiry] = expdate(credit_card)
132
- if options[:billing_address]
133
- post[:card][:billingAddress] = map_address(options[:billing_address])
134
- end
132
+
133
+ post[:authentication] = map_3ds(options[:three_d_secure]) if options[:three_d_secure]
134
+ post[:card][:billingAddress] = map_address(options[:billing_address]) if options[:billing_address]
135
135
  end
136
136
 
137
137
  def add_invoice(post, money, options)
@@ -151,6 +151,7 @@ module ActiveMerchant #:nodoc:
151
151
 
152
152
  post[:currencyCode] = options[:currency] if options[:currency]
153
153
  post[:billingDetails] = map_address(options[:billing_address]) if options[:billing_address]
154
+ post[:authentication] = map_3ds(options[:three_d_secure]) if options[:three_d_secure]
154
155
  end
155
156
 
156
157
  def expdate(credit_card)
@@ -179,18 +180,32 @@ module ActiveMerchant #:nodoc:
179
180
  mapped
180
181
  end
181
182
 
183
+ def map_3ds(three_d_secure_options)
184
+ mapped = {
185
+ :eci => three_d_secure_options[:eci],
186
+ :cavv => three_d_secure_options[:cavv],
187
+ :xid => three_d_secure_options[:xid],
188
+ :threeDResult => three_d_secure_options[:directory_response_status],
189
+ :threeDSecureVersion => three_d_secure_options[:version],
190
+ :directoryServerTransactionId => three_d_secure_options[:ds_transaction_id]
191
+ }
192
+
193
+ mapped
194
+ end
195
+
182
196
  def parse(body)
183
197
  body.blank? ? {} : JSON.parse(body)
184
198
  end
185
199
 
186
200
  def commit(method, uri, parameters)
187
201
  params = parameters.to_json unless parameters.nil?
188
- response = begin
189
- parse(ssl_request(method, get_url(uri), params, headers))
190
- rescue ResponseError => e
191
- return Response.new(false, 'Invalid Login') if(e.response.code == '401')
192
- parse(e.response.body)
193
- end
202
+ response =
203
+ begin
204
+ parse(ssl_request(method, get_url(uri), params, headers))
205
+ rescue ResponseError => e
206
+ return Response.new(false, 'Invalid Login') if(e.response.code == '401')
207
+ parse(e.response.body)
208
+ end
194
209
 
195
210
  success = success_from(response)
196
211
  Response.new(
@@ -166,9 +166,7 @@ module ActiveMerchant #:nodoc:
166
166
  end
167
167
 
168
168
  def add_user_data(post, options)
169
- if options[:order_id]
170
- post[:user_data] = "order_id:#{options[:order_id]}"
171
- end
169
+ post[:user_data] = "order_id:#{options[:order_id]}" if options[:order_id]
172
170
  end
173
171
 
174
172
  def add_transaction_id(post, transaction_id)
@@ -218,9 +218,7 @@ module ActiveMerchant #:nodoc:
218
218
  return nil unless success
219
219
 
220
220
  authorization = response['transactionid']
221
- if(parameters[:customer_vault] && (authorization.nil? || authorization.empty?))
222
- authorization = response['customer_vault_id']
223
- end
221
+ authorization = response['customer_vault_id'] if parameters[:customer_vault] && (authorization.nil? || authorization.empty?)
224
222
 
225
223
  authorization
226
224
  end
@@ -125,9 +125,7 @@ module ActiveMerchant #:nodoc:
125
125
 
126
126
  def purchase(money, payment, options={})
127
127
  # debit
128
- if payment.is_a?(String)
129
- options[:registrationId] = payment
130
- end
128
+ options[:registrationId] = payment if payment.is_a?(String)
131
129
  execute_dbpa(options[:risk_workflow] ? 'PA.CP': 'DB',
132
130
  money, payment, options)
133
131
  end
@@ -311,17 +309,18 @@ module ActiveMerchant #:nodoc:
311
309
  add_authentication(post)
312
310
  post = flatten_hash(post)
313
311
 
314
- response = begin
315
- parse(
316
- ssl_post(
317
- url,
318
- post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&'),
319
- 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
312
+ response =
313
+ begin
314
+ parse(
315
+ ssl_post(
316
+ url,
317
+ post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&'),
318
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
319
+ )
320
320
  )
321
- )
322
- rescue ResponseError => e
323
- parse(e.response.body)
324
- end
321
+ rescue ResponseError => e
322
+ parse(e.response.body)
323
+ end
325
324
 
326
325
  success = success_from(response)
327
326
 
@@ -135,9 +135,7 @@ module ActiveMerchant #:nodoc:
135
135
 
136
136
  def message_from(response)
137
137
  REXML::XPath.each(response, '//detail') do |detail|
138
- if detail.is_a?(REXML::Element) && detail.elements['tag'].text == 'InternalResponseDescription'
139
- return detail.elements['value'].text
140
- end
138
+ return detail.elements['value'].text if detail.is_a?(REXML::Element) && detail.elements['tag'].text == 'InternalResponseDescription'
141
139
  end
142
140
  nil
143
141
  end
@@ -327,9 +327,7 @@ module ActiveMerchant #:nodoc:
327
327
  xml.tag! :CustomerRefNum, options[:customer_ref_num]
328
328
  else
329
329
  if options[:customer_ref_num]
330
- if creditcard
331
- xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM
332
- end
330
+ xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM if creditcard
333
331
  xml.tag! :CustomerRefNum, options[:customer_ref_num]
334
332
  else
335
333
  xml.tag! :CustomerProfileFromOrderInd, AUTO_GENERATE
@@ -457,9 +455,7 @@ module ActiveMerchant #:nodoc:
457
455
  # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
458
456
  unless creditcard.nil?
459
457
  if creditcard.verification_value?
460
- if %w( visa discover ).include?(creditcard.brand)
461
- xml.tag! :CardSecValInd, '1'
462
- end
458
+ xml.tag! :CardSecValInd, '1' if %w( visa discover ).include?(creditcard.brand)
463
459
  xml.tag! :CardSecVal, creditcard.verification_value
464
460
  end
465
461
  end
@@ -613,11 +609,12 @@ module ActiveMerchant #:nodoc:
613
609
  request = ->(url) { parse(ssl_post(url, order, headers)) }
614
610
 
615
611
  # Failover URL will be attempted in the event of a connection error
616
- response = begin
617
- request.call(remote_url)
618
- rescue ConnectionError
619
- request.call(remote_url(:secondary))
620
- end
612
+ response =
613
+ begin
614
+ request.call(remote_url)
615
+ rescue ConnectionError
616
+ request.call(remote_url(:secondary))
617
+ end
621
618
 
622
619
  Response.new(success?(response, message_type), message_from(response), response,
623
620
  {
@@ -30,9 +30,7 @@ module ActiveMerchant #:nodoc:
30
30
  errors << [:merchant_name, 'is required'] if self.merchant_name.blank?
31
31
  errors << [:merchant_name, 'is required to be 25 bytes or less'] if self.merchant_name.bytesize > 25
32
32
 
33
- if(!empty?(self.merchant_phone) && !self.merchant_phone.match(PHONE_FORMAT_1) && !self.merchant_phone.match(PHONE_FORMAT_2))
34
- errors << [:merchant_phone, 'is required to follow "NNN-NNN-NNNN" or "NNN-AAAAAAA" format']
35
- end
33
+ errors << [:merchant_phone, 'is required to follow "NNN-NNN-NNNN" or "NNN-AAAAAAA" format'] if !empty?(self.merchant_phone) && !self.merchant_phone.match(PHONE_FORMAT_1) && !self.merchant_phone.match(PHONE_FORMAT_2)
36
34
 
37
35
  [:merchant_email, :merchant_url].each do |attr|
38
36
  unless self.send(attr).blank?
@@ -44,26 +44,20 @@ module ActiveMerchant #:nodoc:
44
44
  end
45
45
 
46
46
  def capture(money, authorization, options={})
47
- if authorization.nil?
48
- return Response.new(false, 'Não é possível capturar uma transação sem uma prévia autorização.')
49
- end
47
+ return Response.new(false, 'Não é possível capturar uma transação sem uma prévia autorização.') if authorization.nil?
50
48
 
51
49
  post = {}
52
50
  commit(:post, "transactions/#{authorization}/capture", post)
53
51
  end
54
52
 
55
53
  def refund(money, authorization, options={})
56
- if authorization.nil?
57
- return Response.new(false, 'Não é possível estornar uma transação sem uma prévia captura.')
58
- end
54
+ return Response.new(false, 'Não é possível estornar uma transação sem uma prévia captura.') if authorization.nil?
59
55
 
60
56
  void(authorization, options)
61
57
  end
62
58
 
63
59
  def void(authorization, options={})
64
- if authorization.nil?
65
- return Response.new(false, 'Não é possível estornar uma transação autorizada sem uma prévia autorização.')
66
- end
60
+ return Response.new(false, 'Não é possível estornar uma transação autorizada sem uma prévia autorização.') if authorization.nil?
67
61
 
68
62
  post = {}
69
63
  commit(:post, "transactions/#{authorization}/refund", post)
@@ -225,9 +219,7 @@ module ActiveMerchant #:nodoc:
225
219
  end
226
220
 
227
221
  def authorization_from(response)
228
- if success_from(response)
229
- response['id']
230
- end
222
+ response['id'] if success_from(response)
231
223
  end
232
224
 
233
225
  def test?
@@ -53,9 +53,7 @@ module ActiveMerchant #:nodoc:
53
53
 
54
54
  def add_currency(post, money, options)
55
55
  currency = options.fetch(:currency, currency(money))
56
- unless currency == self.class.default_currency
57
- post[:divisa] = currency
58
- end
56
+ post[:divisa] = currency unless currency == self.class.default_currency
59
57
  end
60
58
 
61
59
  def add_payment(post, credit_card)
@@ -51,9 +51,7 @@ module ActiveMerchant #:nodoc:
51
51
  end
52
52
 
53
53
  def credit(money, payment_method, options={})
54
- if payment_method.is_a?(String)
55
- raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.'
56
- end
54
+ raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' if payment_method.is_a?(String)
57
55
 
58
56
  post = {}
59
57
  add_auth_purchase_params(post, money, payment_method, options)
@@ -255,9 +255,7 @@ module ActiveMerchant #:nodoc:
255
255
  response_action = action.gsub(/tx/, 'rx')
256
256
  root = REXML::XPath.first(xml.root, response_action)
257
257
  # we might have gotten an error
258
- if root.nil?
259
- root = REXML::XPath.first(xml.root, 'errorrx')
260
- end
258
+ root = REXML::XPath.first(xml.root, 'errorrx') if root.nil?
261
259
  root.attributes.each do |name, value|
262
260
  hash[name.to_sym] = value
263
261
  end
@@ -111,11 +111,12 @@ module ActiveMerchant #:nodoc:
111
111
  end
112
112
 
113
113
  def commit(action, params)
114
- response = begin
115
- parse(ssl_invoke(action, params))
116
- rescue ResponseError => e
117
- parse(e.response.body)
118
- end
114
+ response =
115
+ begin
116
+ parse(ssl_invoke(action, params))
117
+ rescue ResponseError => e
118
+ parse(e.response.body)
119
+ end
119
120
 
120
121
  success = success_from(response)
121
122
  Response.new(
@@ -312,9 +312,7 @@ module ActiveMerchant #:nodoc:
312
312
  end
313
313
 
314
314
  def build_recurring_request(action, money, options)
315
- unless RECURRING_ACTIONS.include?(action)
316
- raise StandardError, "Invalid Recurring Profile Action: #{action}"
317
- end
315
+ raise StandardError, "Invalid Recurring Profile Action: #{action}" unless RECURRING_ACTIONS.include?(action)
318
316
 
319
317
  xml = Builder::XmlMarkup.new
320
318
  xml.tag! 'RecurringProfiles' do
@@ -354,9 +352,7 @@ module ActiveMerchant #:nodoc:
354
352
  yield xml
355
353
  end
356
354
  end
357
- if action != :add
358
- xml.tag! 'ProfileID', options[:profile_id]
359
- end
355
+ xml.tag! 'ProfileID', options[:profile_id] if action != :add
360
356
  if action == :inquiry
361
357
  xml.tag! 'PaymentHistory', (options[:history] ? 'Y' : 'N')
362
358
  end
@@ -159,9 +159,7 @@ module ActiveMerchant #:nodoc:
159
159
  # REXML::XPath in Ruby 1.8.6 is now unable to match nodes based on their attributes
160
160
  tx_result = root.xpath('.//TransactionResult').first
161
161
 
162
- if tx_result && tx_result.attributes['Duplicate'].to_s == 'true'
163
- response[:duplicate] = true
164
- end
162
+ response[:duplicate] = true if tx_result && tx_result.attributes['Duplicate'].to_s == 'true'
165
163
 
166
164
  root.xpath('.//*').each do |node|
167
165
  parse_element(response, node)
@@ -276,9 +276,7 @@ module ActiveMerchant #:nodoc:
276
276
  return if success_from(response)
277
277
  if response['transaction']
278
278
  detail = response['transaction']['status_detail']
279
- if STANDARD_ERROR_CODE_MAPPING.include?(detail)
280
- return STANDARD_ERROR_CODE[STANDARD_ERROR_CODE_MAPPING[detail]]
281
- end
279
+ return STANDARD_ERROR_CODE[STANDARD_ERROR_CODE_MAPPING[detail]] if STANDARD_ERROR_CODE_MAPPING.include?(detail)
282
280
  elsif response['error']
283
281
  return STANDARD_ERROR_CODE[:config_error]
284
282
  end
@@ -357,9 +357,7 @@ module ActiveMerchant #:nodoc:
357
357
 
358
358
  def handle_response_correct_parsing
359
359
  @message = parsed['transaction']['processing']['return']['message']
360
- if @succeeded = is_ack?
361
- @options[:authorization] = parsed['transaction']['identification']['uniqueId']
362
- end
360
+ @options[:authorization] = parsed['transaction']['identification']['uniqueId'] if @succeeded = is_ack?
363
361
  end
364
362
 
365
363
  def is_ack?
@@ -295,9 +295,7 @@ module ActiveMerchant #:nodoc:
295
295
  end
296
296
 
297
297
  def error_code_from(response)
298
- unless success_from(response)
299
- response[:status]
300
- end
298
+ response[:status] unless success_from(response)
301
299
  end
302
300
 
303
301
  def build_xml_request
@@ -10,13 +10,10 @@ module ActiveMerchant #:nodoc:
10
10
 
11
11
  self.homepage_url = 'http://payments.intuit.com'
12
12
  self.display_name = 'QuickBooks Payments'
13
- ENDPOINT = '/quickbooks/v4/payments/charges'
14
- OAUTH_ENDPOINTS = {
15
- site: 'https://oauth.intuit.com',
16
- request_token_path: '/oauth/v1/get_request_token',
17
- authorize_url: 'https://appcenter.intuit.com/Connect/Begin',
18
- access_token_path: '/oauth/v1/get_access_token'
19
- }
13
+ BASE = '/quickbooks/v4/payments'
14
+ ENDPOINT = "#{BASE}/charges"
15
+ VOID_ENDPOINT = "#{BASE}/txn-requests"
16
+ REFRESH_URI = 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer'
20
17
 
21
18
  # https://developer.intuit.com/docs/0150_payments/0300_developer_guides/error_handling
22
19
 
@@ -51,7 +48,15 @@ module ActiveMerchant #:nodoc:
51
48
  FRAUD_WARNING_CODES = ['PMT-1000', 'PMT-1001', 'PMT-1002', 'PMT-1003']
52
49
 
53
50
  def initialize(options = {})
54
- requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm)
51
+ # Quickbooks is deprecating OAuth 1.0 on December 17, 2019.
52
+ # OAuth 2.0 requires a client_id, client_secret, access_token, and refresh_token
53
+ # To maintain backwards compatibility, check for the presence of a refresh_token (only specified for OAuth 2.0)
54
+ # When present, validate that all OAuth 2.0 options are present
55
+ if options[:refresh_token]
56
+ requires!(options, :client_id, :client_secret, :access_token, :refresh_token)
57
+ else
58
+ requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm)
59
+ end
55
60
  @options = options
56
61
  super
57
62
  end
@@ -62,7 +67,8 @@ module ActiveMerchant #:nodoc:
62
67
  add_charge_data(post, payment, options)
63
68
  post[:capture] = 'true'
64
69
 
65
- commit(ENDPOINT, post)
70
+ response = commit(ENDPOINT, post)
71
+ check_token_response(response, ENDPOINT, post)
66
72
  end
67
73
 
68
74
  def authorize(money, payment, options = {})
@@ -71,24 +77,35 @@ module ActiveMerchant #:nodoc:
71
77
  add_charge_data(post, payment, options)
72
78
  post[:capture] = 'false'
73
79
 
74
- commit(ENDPOINT, post)
80
+ response = commit(ENDPOINT, post)
81
+ check_token_response(response, ENDPOINT, post)
75
82
  end
76
83
 
77
84
  def capture(money, authorization, options = {})
78
85
  post = {}
79
- capture_uri = "#{ENDPOINT}/#{CGI.escape(authorization)}/capture"
86
+ authorization, _ = split_authorization(authorization)
80
87
  post[:amount] = localized_amount(money, currency(money))
81
88
  add_context(post, options)
82
89
 
83
- commit(capture_uri, post)
90
+ response = commit(capture_uri(authorization), post)
91
+ check_token_response(response, capture_uri(authorization), post)
84
92
  end
85
93
 
86
94
  def refund(money, authorization, options = {})
87
95
  post = {}
88
96
  post[:amount] = localized_amount(money, currency(money))
89
97
  add_context(post, options)
98
+ authorization, _ = split_authorization(authorization)
90
99
 
91
- commit(refund_uri(authorization), post)
100
+ response = commit(refund_uri(authorization), post)
101
+ check_token_response(response, refund_uri(authorization), post)
102
+ end
103
+
104
+ def void(authorization, options = {})
105
+ _, request_id = split_authorization(authorization)
106
+
107
+ response = commit(void_uri(request_id))
108
+ check_token_response(response, void_uri(request_id))
92
109
  end
93
110
 
94
111
  def verify(credit_card, options = {})
@@ -107,7 +124,12 @@ module ActiveMerchant #:nodoc:
107
124
  gsub(%r((oauth_signature=\")[a-zA-Z%0-9]+), '\1[FILTERED]').
108
125
  gsub(%r((oauth_token=\")\w+), '\1[FILTERED]').
109
126
  gsub(%r((number\D+)\d{16}), '\1[FILTERED]').
110
- gsub(%r((cvc\D+)\d{3}), '\1[FILTERED]')
127
+ gsub(%r((cvc\D+)\d{3}), '\1[FILTERED]').
128
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
129
+ gsub(%r((access_token\\?":\\?")[\w\-\.]+)i, '\1[FILTERED]').
130
+ gsub(%r((refresh_token\\?":\\?")\w+), '\1[FILTERED]').
131
+ gsub(%r((refresh_token=)\w+), '\1[FILTERED]').
132
+ gsub(%r((Authorization: Bearer )[\w\-\.]+)i, '\1[FILTERED]\2')
111
133
  end
112
134
 
113
135
  private
@@ -170,30 +192,30 @@ module ActiveMerchant #:nodoc:
170
192
  # The QuickBooks API returns HTTP 4xx on failed transactions, which causes a
171
193
  # ResponseError raise, so we have to inspect the response and discern between
172
194
  # a legitimate HTTP error and an actual gateway transactional error.
173
- response = begin
174
- case method
175
- when :post
176
- ssl_post(endpoint, post_data(body), headers(:post, endpoint))
177
- when :get
178
- ssl_request(:get, endpoint, nil, headers(:get, endpoint))
179
- else
180
- raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get"
195
+ headers = {}
196
+ response =
197
+ begin
198
+ headers = headers(method, endpoint)
199
+ method == :post ? ssl_post(endpoint, post_data(body), headers) : ssl_request(:get, endpoint, nil, headers)
200
+ rescue ResponseError => e
201
+ extract_response_body_or_raise(e)
181
202
  end
182
- rescue ResponseError => e
183
- extract_response_body_or_raise(e)
184
- end
185
203
 
186
- response_object(response)
204
+ response_object(response, headers)
187
205
  end
188
206
 
189
- def response_object(raw_response)
207
+ def response_object(raw_response, headers = {})
190
208
  parsed_response = parse(raw_response)
191
209
 
210
+ # Include access_token and refresh_token in params for OAuth 2.0
211
+ parsed_response['access_token'] = @options[:access_token] if @options[:refresh_token]
212
+ parsed_response['refresh_token'] = @options[:refresh_token] if @options[:refresh_token]
213
+
192
214
  Response.new(
193
215
  success?(parsed_response),
194
216
  message_from(parsed_response),
195
217
  parsed_response,
196
- authorization: authorization_from(parsed_response),
218
+ authorization: authorization_from(parsed_response, headers),
197
219
  test: test?,
198
220
  cvv_result: cvv_code_from(parsed_response),
199
221
  error_code: errors_from(parsed_response),
@@ -210,6 +232,8 @@ module ActiveMerchant #:nodoc:
210
232
  end
211
233
 
212
234
  def headers(method, uri)
235
+ return oauth_v2_headers if @options[:refresh_token]
236
+
213
237
  raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" unless [:post, :get].include?(method)
214
238
  request_uri = URI.parse(uri)
215
239
 
@@ -243,6 +267,42 @@ module ActiveMerchant #:nodoc:
243
267
  }
244
268
  end
245
269
 
270
+ def oauth_v2_headers
271
+ {
272
+ 'Content-Type' => 'application/json',
273
+ 'Request-Id' => generate_unique_id,
274
+ 'Accept' => 'application/json',
275
+ 'Authorization' => "Bearer #{@options[:access_token]}"
276
+ }
277
+ end
278
+
279
+ def check_token_response(response, endpoint, body = {})
280
+ return response unless @options[:refresh_token]
281
+ return response unless response.params['code'] == 'AuthenticationFailed'
282
+ refresh_access_token
283
+ commit(endpoint, body)
284
+ end
285
+
286
+ def refresh_access_token
287
+ post = {}
288
+ post[:grant_type] = 'refresh_token'
289
+ post[:refresh_token] = @options[:refresh_token]
290
+ data = post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
291
+
292
+ basic_auth = Base64.strict_encode64("#{@options[:client_id]}:#{@options[:client_secret]}")
293
+ headers = {
294
+ 'Content-Type' => 'application/x-www-form-urlencoded',
295
+ 'Accept' => 'application/json',
296
+ 'Authorization' => "Basic #{basic_auth}"
297
+ }
298
+
299
+ response = ssl_post(REFRESH_URI, data, headers)
300
+ response = JSON.parse(response)
301
+
302
+ @options[:access_token] = response['access_token'] if response['access_token']
303
+ @options[:refresh_token] = response['refresh_token'] if response['refresh_token']
304
+ end
305
+
246
306
  def cvv_code_from(response)
247
307
  if response['errors'].present?
248
308
  FRAUD_WARNING_CODES.include?(response['errors'].first['code']) ? 'I' : ''
@@ -265,8 +325,13 @@ module ActiveMerchant #:nodoc:
265
325
  response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response['errors'].first['code']] : ''
266
326
  end
267
327
 
268
- def authorization_from(response)
269
- response['id']
328
+ def authorization_from(response, headers = {})
329
+ [response['id'], headers['Request-Id']].join('|')
330
+ end
331
+
332
+ def split_authorization(authorization)
333
+ authorization, request_id = authorization.split('|')
334
+ [authorization, request_id]
270
335
  end
271
336
 
272
337
  def fraud_review_status_from(response)
@@ -283,7 +348,15 @@ module ActiveMerchant #:nodoc:
283
348
  end
284
349
 
285
350
  def refund_uri(authorization)
286
- "#{ENDPOINT}/#{CGI.escape(authorization)}/refunds"
351
+ "#{ENDPOINT}/#{CGI.escape(authorization.to_s)}/refunds"
352
+ end
353
+
354
+ def capture_uri(authorization)
355
+ "#{ENDPOINT}/#{CGI.escape(authorization.to_s)}/capture"
356
+ end
357
+
358
+ def void_uri(request_id)
359
+ "#{VOID_ENDPOINT}/#{CGI.escape(request_id.to_s)}/void"
287
360
  end
288
361
  end
289
362
  end