activemerchant 1.121.0 → 1.123.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +86 -0
  3. data/README.md +1 -1
  4. data/lib/active_merchant/billing/check.rb +13 -16
  5. data/lib/active_merchant/billing/credit_card.rb +3 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +21 -12
  8. data/lib/active_merchant/billing/gateways/adyen.rb +15 -19
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
  10. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
  12. data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
  13. data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
  14. data/lib/active_merchant/billing/gateways/cyber_source.rb +30 -3
  15. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  16. data/lib/active_merchant/billing/gateways/elavon.rb +60 -28
  17. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  18. data/lib/active_merchant/billing/gateways/global_collect.rb +19 -10
  19. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  20. data/lib/active_merchant/billing/gateways/mercado_pago.rb +3 -2
  21. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  22. data/lib/active_merchant/billing/gateways/moka.rb +277 -0
  23. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  24. data/lib/active_merchant/billing/gateways/mundipagg.rb +14 -5
  25. data/lib/active_merchant/billing/gateways/nmi.rb +14 -9
  26. data/lib/active_merchant/billing/gateways/orbital.rb +28 -6
  27. data/lib/active_merchant/billing/gateways/pay_arc.rb +390 -0
  28. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  29. data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
  30. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  31. data/lib/active_merchant/billing/gateways/payflow.rb +9 -0
  32. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  33. data/lib/active_merchant/billing/gateways/paymentez.rb +5 -0
  34. data/lib/active_merchant/billing/gateways/paysafe.rb +291 -0
  35. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  36. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  37. data/lib/active_merchant/billing/gateways/safe_charge.rb +2 -0
  38. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  39. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +19 -1
  40. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
  42. data/lib/active_merchant/billing/gateways/worldpay.rb +39 -7
  43. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  44. data/lib/active_merchant/billing.rb +1 -0
  45. data/lib/active_merchant/version.rb +1 -1
  46. metadata +8 -3
@@ -386,7 +386,7 @@ module ActiveMerchant
386
386
 
387
387
  def parse(response)
388
388
  return bad_authentication_response if response.code.to_i == 401
389
- return forbidden_response(response.body) if response.code.to_i == 403
389
+ return generic_error_response(response.body) if [403, 429].include?(response.code.to_i)
390
390
 
391
391
  parsed = {}
392
392
  doc = Nokogiri::XML(response.body)
@@ -564,7 +564,7 @@ module ActiveMerchant
564
564
  { 'description' => 'Unable to authenticate. Please check your credentials.' }
565
565
  end
566
566
 
567
- def forbidden_response(body)
567
+ def generic_error_response(body)
568
568
  { 'description' => body }
569
569
  end
570
570
  end
@@ -135,8 +135,8 @@ module ActiveMerchant #:nodoc:
135
135
  result = @braintree_gateway.verification.create(payload)
136
136
  response = Response.new(result.success?, message_from_transaction_result(result), response_options(result))
137
137
  response.cvv_result['message'] = ''
138
- response.cvv_result['code'] = response.params['cvv_result']
139
- response.avs_result['code'] = response.params['avs_result'][:code]
138
+ response.cvv_result['code'] = response.params['cvv_result'] if response.params['cvv_result']
139
+ response.avs_result['code'] = response.params['avs_result'][:code] if response.params.dig('avs_result', :code)
140
140
  response
141
141
  end
142
142
 
@@ -588,7 +588,8 @@ module ActiveMerchant #:nodoc:
588
588
  'merchant_account_id' => transaction.merchant_account_id,
589
589
  'risk_data' => risk_data,
590
590
  'network_transaction_id' => transaction.network_transaction_id || nil,
591
- 'processor_response_code' => response_code_from_result(result)
591
+ 'processor_response_code' => response_code_from_result(result),
592
+ 'recurring' => transaction.recurring
592
593
  }
593
594
  end
594
595
 
@@ -770,6 +771,8 @@ module ActiveMerchant #:nodoc:
770
771
  else
771
772
  parameters[:transaction_source] = stored_credential[:reason_type]
772
773
  end
774
+ elsif %w(recurring_first moto).include?(stored_credential[:reason_type])
775
+ parameters[:transaction_source] = stored_credential[:reason_type]
773
776
  else
774
777
  parameters[:transaction_source] = ''
775
778
  end
@@ -20,8 +20,9 @@ module ActiveMerchant #:nodoc:
20
20
  self.live_url = 'https://assigned-subdomain.credorax.net/crax_gate/service/gateway'
21
21
 
22
22
  self.supported_countries = %w(AD AT BE BG HR CY CZ DK EE FR DE GI GR GG HU IS IE IM IT JE LV LI LT LU MT MC NO PL PT RO SM SK ES SE CH GB)
23
+
23
24
  self.default_currency = 'EUR'
24
- self.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW PYG RWF VND VUV XAF XOF XPF)
25
+ self.currencies_without_fractions = %w(BIF CLP DJF GNF ISK JPY KMF KRW PYG RWF VND VUV XAF XOF XPF)
25
26
  self.currencies_with_three_decimal_places = %w(BHD IQD JOD KWD LYD OMR TND)
26
27
 
27
28
  self.money_format = :cents
@@ -153,6 +153,10 @@ module ActiveMerchant #:nodoc:
153
153
  commit(build_refund_request(money, identification, options), :refund, money, options)
154
154
  end
155
155
 
156
+ def adjust(money, authorization, options = {})
157
+ commit(build_adjust_request(money, authorization, options), :adjust, money, options)
158
+ end
159
+
156
160
  def verify(payment, options = {})
157
161
  MultiResponse.run(:use_first_response) do |r|
158
162
  r.process { authorize(100, payment, options) }
@@ -285,6 +289,7 @@ module ActiveMerchant #:nodoc:
285
289
 
286
290
  def build_auth_request(money, creditcard_or_reference, options)
287
291
  xml = Builder::XmlMarkup.new indent: 2
292
+ add_customer_id(xml, options)
288
293
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
289
294
  add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
290
295
  add_decision_manager_fields(xml, options)
@@ -300,7 +305,15 @@ module ActiveMerchant #:nodoc:
300
305
  add_merchant_description(xml, options)
301
306
  add_sales_slip_number(xml, options)
302
307
  add_airline_data(xml, options)
308
+ xml.target!
309
+ end
303
310
 
311
+ def build_adjust_request(money, authorization, options)
312
+ _, request_id = authorization.split(';')
313
+
314
+ xml = Builder::XmlMarkup.new indent: 2
315
+ add_purchase_data(xml, money, true, options)
316
+ add_incremental_auth_service(xml, request_id, options)
304
317
  xml.target!
305
318
  end
306
319
 
@@ -334,6 +347,7 @@ module ActiveMerchant #:nodoc:
334
347
 
335
348
  def build_purchase_request(money, payment_method_or_reference, options)
336
349
  xml = Builder::XmlMarkup.new indent: 2
350
+ add_customer_id(xml, options)
337
351
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
338
352
  add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
339
353
  add_decision_manager_fields(xml, options)
@@ -472,8 +486,8 @@ module ActiveMerchant #:nodoc:
472
486
 
473
487
  unless network_tokenization?(payment_method)
474
488
  xml.tag! 'businessRules' do
475
- xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
476
- xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv)
489
+ xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs).to_s == 'true'
490
+ xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv).to_s == 'true'
477
491
  end
478
492
  end
479
493
  end
@@ -515,6 +529,12 @@ module ActiveMerchant #:nodoc:
515
529
  end
516
530
  end
517
531
 
532
+ def add_customer_id(xml, options)
533
+ return unless options[:customer_id]
534
+
535
+ xml.tag! 'customerID', options[:customer_id]
536
+ end
537
+
518
538
  def add_merchant_description(xml, options)
519
539
  return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality]
520
540
 
@@ -585,7 +605,7 @@ module ActiveMerchant #:nodoc:
585
605
  xml.tag! 'accountNumber', creditcard.number
586
606
  xml.tag! 'expirationMonth', format(creditcard.month, :two_digits)
587
607
  xml.tag! 'expirationYear', format(creditcard.year, :four_digits)
588
- xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank?
608
+ xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv].to_s == 'true' || creditcard.verification_value.blank?
589
609
  xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym]
590
610
  end
591
611
  end
@@ -658,6 +678,13 @@ module ActiveMerchant #:nodoc:
658
678
  end
659
679
  end
660
680
 
681
+ def add_incremental_auth_service(xml, authorization, options)
682
+ xml.tag! 'ccIncrementalAuthService', { 'run' => 'true' } do
683
+ xml.tag! 'authRequestID', authorization
684
+ end
685
+ xml.tag! 'subsequentAuthReason', options[:auth_reason]
686
+ end
687
+
661
688
  def add_normalized_threeds_2_data(xml, payment_method, options)
662
689
  threeds_2_options = options[:three_d_secure]
663
690
  cc_brand = card_brand(payment_method).to_sym
@@ -282,7 +282,13 @@ module ActiveMerchant #:nodoc:
282
282
  if error = response.dig('status_details', 'error')
283
283
  message = "#{error.dig('reason', 'description')} | #{error['type']}"
284
284
  elsif response['error_type']
285
- message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ') if response['validation_errors']
285
+ if response['validation_errors'].is_a?(Array)
286
+ message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ')
287
+ elsif response['validation_errors'].is_a?(Hash)
288
+ errors = response['validation_errors'].map { |k, v| "#{k}: #{v}" }.join(', ')
289
+ message = "#{response['error_type']} - #{errors}"
290
+ end
291
+
286
292
  message ||= response['error_type']
287
293
  end
288
294
 
@@ -201,8 +201,8 @@ module ActiveMerchant #:nodoc:
201
201
  private
202
202
 
203
203
  def add_invoice(xml, options)
204
- xml.ssl_invoice_number truncate((options[:order_id] || options[:invoice]), 25)
205
- xml.ssl_description truncate(options[:description], 255)
204
+ xml.ssl_invoice_number url_encode_truncate((options[:order_id] || options[:invoice]), 25)
205
+ xml.ssl_description url_encode_truncate(options[:description], 255)
206
206
  end
207
207
 
208
208
  def add_approval_code(xml, authorization)
@@ -219,8 +219,8 @@ module ActiveMerchant #:nodoc:
219
219
 
220
220
  add_verification_value(xml, creditcard) if creditcard.verification_value?
221
221
 
222
- xml.ssl_first_name truncate(creditcard.first_name, 20)
223
- xml.ssl_last_name truncate(creditcard.last_name, 30)
222
+ xml.ssl_first_name url_encode_truncate(creditcard.first_name, 20)
223
+ xml.ssl_last_name url_encode_truncate(creditcard.last_name, 30)
224
224
  end
225
225
 
226
226
  def add_currency(xml, money, options)
@@ -240,7 +240,7 @@ module ActiveMerchant #:nodoc:
240
240
  end
241
241
 
242
242
  def add_customer_email(xml, options)
243
- xml.ssl_email truncate(options[:email], 100) unless empty?(options[:email])
243
+ xml.ssl_email url_encode_truncate(options[:email], 100) unless empty?(options[:email])
244
244
  end
245
245
 
246
246
  def add_salestax(xml, options)
@@ -253,27 +253,27 @@ module ActiveMerchant #:nodoc:
253
253
  billing_address = options[:billing_address] || options[:address]
254
254
 
255
255
  if billing_address
256
- xml.ssl_avs_address truncate(billing_address[:address1], 30)
257
- xml.ssl_address2 truncate(billing_address[:address2], 30)
258
- xml.ssl_avs_zip truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9)
259
- xml.ssl_city truncate(billing_address[:city], 30)
260
- xml.ssl_state truncate(billing_address[:state], 10)
261
- xml.ssl_company truncate(billing_address[:company], 50)
262
- xml.ssl_phone truncate(billing_address[:phone], 20)
263
- xml.ssl_country truncate(billing_address[:country], 50)
256
+ xml.ssl_avs_address url_encode_truncate(billing_address[:address1], 30)
257
+ xml.ssl_address2 url_encode_truncate(billing_address[:address2], 30)
258
+ xml.ssl_avs_zip url_encode_truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9)
259
+ xml.ssl_city url_encode_truncate(billing_address[:city], 30)
260
+ xml.ssl_state url_encode_truncate(billing_address[:state], 10)
261
+ xml.ssl_company url_encode_truncate(billing_address[:company], 50)
262
+ xml.ssl_phone url_encode_truncate(billing_address[:phone], 20)
263
+ xml.ssl_country url_encode_truncate(billing_address[:country], 50)
264
264
  end
265
265
 
266
266
  if shipping_address = options[:shipping_address]
267
- xml.ssl_ship_to_address1 truncate(shipping_address[:address1], 30)
268
- xml.ssl_ship_to_address2 truncate(shipping_address[:address2], 30)
269
- xml.ssl_ship_to_city truncate(shipping_address[:city], 30)
270
- xml.ssl_ship_to_company truncate(shipping_address[:company], 50)
271
- xml.ssl_ship_to_country truncate(shipping_address[:country], 50)
272
- xml.ssl_ship_to_first_name truncate(shipping_address[:first_name], 20)
273
- xml.ssl_ship_to_last_name truncate(shipping_address[:last_name], 30)
274
- xml.ssl_ship_to_phone truncate(shipping_address[:phone], 10)
275
- xml.ssl_ship_to_state truncate(shipping_address[:state], 2)
276
- xml.ssl_ship_to_zip truncate(shipping_address[:zip], 10)
267
+ xml.ssl_ship_to_address1 url_encode_truncate(shipping_address[:address1], 30)
268
+ xml.ssl_ship_to_address2 url_encode_truncate(shipping_address[:address2], 30)
269
+ xml.ssl_ship_to_city url_encode_truncate(shipping_address[:city], 30)
270
+ xml.ssl_ship_to_company url_encode_truncate(shipping_address[:company], 50)
271
+ xml.ssl_ship_to_country url_encode_truncate(shipping_address[:country], 50)
272
+ xml.ssl_ship_to_first_name url_encode_truncate(shipping_address[:first_name], 20)
273
+ xml.ssl_ship_to_last_name url_encode_truncate(shipping_address[:last_name], 30)
274
+ xml.ssl_ship_to_phone url_encode_truncate(shipping_address[:phone], 10)
275
+ xml.ssl_ship_to_state url_encode_truncate(shipping_address[:state], 2)
276
+ xml.ssl_ship_to_zip url_encode_truncate(shipping_address[:zip], 10)
277
277
  end
278
278
  end
279
279
 
@@ -293,9 +293,12 @@ module ActiveMerchant #:nodoc:
293
293
  xml.ssl_cardholder_ip options[:ip] if options.has_key?(:ip)
294
294
  end
295
295
 
296
+ # add_recurring_token is a field that can be sent in to obtain a token from Elavon for use with their tokenization program
296
297
  def add_auth_purchase_params(xml, options)
297
298
  xml.ssl_dynamic_dba options[:dba] if options.has_key?(:dba)
298
299
  xml.ssl_merchant_initiated_unscheduled merchant_initiated_unscheduled(options) if merchant_initiated_unscheduled(options)
300
+ xml.ssl_add_token options[:add_recurring_token] if options.has_key?(:add_recurring_token)
301
+ xml.ssl_token options[:ssl_token] if options.has_key?(:ssl_token)
299
302
  xml.ssl_customer_code options[:customer] if options.has_key?(:customer)
300
303
  xml.ssl_customer_number options[:customer_number] if options.has_key?(:customer_number)
301
304
  xml.ssl_entry_mode entry_mode(options) if entry_mode(options)
@@ -367,7 +370,7 @@ module ActiveMerchant #:nodoc:
367
370
 
368
371
  def merchant_initiated_unscheduled(options)
369
372
  return options[:merchant_initiated_unscheduled] if options[:merchant_initiated_unscheduled]
370
- return 'Y' if options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled'
373
+ return 'Y' if options.dig(:stored_credential, :initiator) == 'merchant' && options.dig(:stored_credential, :reason_type) == 'unscheduled' || options.dig(:stored_credential, :reason_type) == 'recurring'
371
374
  end
372
375
 
373
376
  def entry_mode(options)
@@ -392,6 +395,7 @@ module ActiveMerchant #:nodoc:
392
395
  request = "xmldata=#{request}".delete('&')
393
396
 
394
397
  response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
398
+ response = hash_html_decode(response)
395
399
 
396
400
  Response.new(
397
401
  response[:result] == '0',
@@ -413,7 +417,7 @@ module ActiveMerchant #:nodoc:
413
417
  def headers
414
418
  {
415
419
  'Accept' => 'application/xml',
416
- 'Content-type' => 'application/x-www-form-urlencoded'
420
+ 'Content-type' => 'application/x-www-form-urlencoded;charset=utf8'
417
421
  }
418
422
  end
419
423
 
@@ -428,12 +432,40 @@ module ActiveMerchant #:nodoc:
428
432
  [response[:approval_code], response[:txn_id]].join(';')
429
433
  end
430
434
 
431
- def truncate(value, size)
435
+ def url_encode_truncate(value, size)
432
436
  return nil unless value
433
437
 
434
- difference = value.force_encoding('iso-8859-1').length - value.length
438
+ encoded = url_encode(value)
435
439
 
436
- return value.delete('&"<>').to_s[0, (size - difference)]
440
+ while encoded.length > size
441
+ value.chop!
442
+ encoded = url_encode(value)
443
+ end
444
+ encoded
445
+ end
446
+
447
+ def url_encode(value)
448
+ if value.is_a?(String)
449
+ encoded = CGI.escape(value)
450
+ encoded = encoded.tr('+', ' ') # don't encode spaces
451
+ encoded = encoded.gsub('%26', '%26amp;') # account for Elavon's weird '&' handling
452
+ encoded
453
+ else
454
+ value.to_s
455
+ end
456
+ end
457
+
458
+ def hash_html_decode(hash)
459
+ hash.each do |k, v|
460
+ if v.is_a?(String)
461
+ # decode all string params
462
+ v = v.gsub('&amp;amp;', '&amp;') # account for Elavon's weird '&' handling
463
+ hash[k] = CGI.unescape_html(v)
464
+ elsif v.is_a?(Hash)
465
+ hash_html_decode(v)
466
+ end
467
+ end
468
+ hash
437
469
  end
438
470
  end
439
471
  end
@@ -192,6 +192,8 @@ module ActiveMerchant #:nodoc:
192
192
  xml.PaymentType options[:payment_type] if options[:payment_type]
193
193
  xml.SubmissionType options[:submission_type] if options[:submission_type]
194
194
  xml.DuplicateCheckDisableFlag options[:duplicate_check_disable_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_check_disable_flag].nil?
195
+ xml.DuplicateOverrideFlag options[:duplicate_override_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_override_flag].nil?
196
+ xml.MerchantDescriptor options[:merchant_descriptor] if options[:merchant_descriptor]
195
197
  end
196
198
  end
197
199
 
@@ -33,7 +33,7 @@ module ActiveMerchant #:nodoc:
33
33
  add_creator_info(post, options)
34
34
  add_fraud_fields(post, options)
35
35
  add_external_cardholder_authentication_data(post, options)
36
- commit(:authorize, post)
36
+ commit(:authorize, post, options: options)
37
37
  end
38
38
 
39
39
  def capture(money, authorization, options = {})
@@ -41,7 +41,7 @@ module ActiveMerchant #:nodoc:
41
41
  add_order(post, money, options, capture: true)
42
42
  add_customer_data(post, options)
43
43
  add_creator_info(post, options)
44
- commit(:capture, post, authorization)
44
+ commit(:capture, post, authorization: authorization)
45
45
  end
46
46
 
47
47
  def refund(money, authorization, options = {})
@@ -49,13 +49,13 @@ module ActiveMerchant #:nodoc:
49
49
  add_amount(post, money, options)
50
50
  add_refund_customer_data(post, options)
51
51
  add_creator_info(post, options)
52
- commit(:refund, post, authorization)
52
+ commit(:refund, post, authorization: authorization)
53
53
  end
54
54
 
55
55
  def void(authorization, options = {})
56
56
  post = nestable_hash
57
57
  add_creator_info(post, options)
58
- commit(:void, post, authorization)
58
+ commit(:void, post, authorization: authorization)
59
59
  end
60
60
 
61
61
  def verify(payment, options = {})
@@ -277,9 +277,13 @@ module ActiveMerchant #:nodoc:
277
277
  end
278
278
  end
279
279
 
280
- def commit(action, post, authorization = nil)
280
+ def idempotency_key_for_signature(options)
281
+ "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key]
282
+ end
283
+
284
+ def commit(action, post, authorization: nil, options: {})
281
285
  begin
282
- raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization))
286
+ raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization, options))
283
287
  response = parse(raw_response)
284
288
  rescue ResponseError => e
285
289
  response = parse(e.response.body) if e.response.code.to_i >= 400
@@ -306,21 +310,26 @@ module ActiveMerchant #:nodoc:
306
310
  }
307
311
  end
308
312
 
309
- def headers(action, post, authorization = nil)
310
- {
313
+ def headers(action, post, authorization = nil, options = {})
314
+ headers = {
311
315
  'Content-Type' => content_type,
312
- 'Authorization' => auth_digest(action, post, authorization),
316
+ 'Authorization' => auth_digest(action, post, authorization, options),
313
317
  'Date' => date
314
318
  }
319
+
320
+ headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key]
321
+ headers
315
322
  end
316
323
 
317
- def auth_digest(action, post, authorization = nil)
324
+ def auth_digest(action, post, authorization = nil, options = {})
318
325
  data = <<~REQUEST
319
326
  POST
320
327
  #{content_type}
321
328
  #{date}
329
+ #{idempotency_key_for_signature(options)}
322
330
  #{uri(action, authorization)}
323
331
  REQUEST
332
+ data = data.each_line.reject { |line| line.strip == '' }.join
324
333
  digest = OpenSSL::Digest.new('sha256')
325
334
  key = @options[:secret_api_key]
326
335
  "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))}"
@@ -37,6 +37,7 @@ module ActiveMerchant #:nodoc:
37
37
  post = {}
38
38
  post[:ticketNumber] = authorization
39
39
  add_invoice(action, post, amount, options)
40
+ add_full_response(post, options)
40
41
 
41
42
  commit(action, post)
42
43
  end
@@ -46,6 +47,7 @@ module ActiveMerchant #:nodoc:
46
47
 
47
48
  post = {}
48
49
  post[:ticketNumber] = authorization
50
+ add_full_response(post, options)
49
51
 
50
52
  commit(action, post)
51
53
  end
@@ -55,6 +57,7 @@ module ActiveMerchant #:nodoc:
55
57
 
56
58
  post = {}
57
59
  post[:ticketNumber] = authorization
60
+ add_full_response(post, options)
58
61
 
59
62
  commit(action, post)
60
63
  end
@@ -78,6 +81,7 @@ module ActiveMerchant #:nodoc:
78
81
  post = {}
79
82
  add_invoice(action, post, amount, options)
80
83
  add_payment_method(post, payment_method, options)
84
+ add_full_response(post, options)
81
85
 
82
86
  commit(action, post)
83
87
  end
@@ -88,6 +92,8 @@ module ActiveMerchant #:nodoc:
88
92
  post = {}
89
93
  add_reference(post, authorization, options)
90
94
  add_invoice(action, post, amount, options)
95
+ add_contact_details(post, options[:contact_details]) if options[:contact_details]
96
+ add_full_response(post, options)
91
97
 
92
98
  commit(action, post)
93
99
  end
@@ -98,6 +104,7 @@ module ActiveMerchant #:nodoc:
98
104
  post = {}
99
105
  add_reference(post, authorization, options)
100
106
  add_invoice(action, post, amount, options)
107
+ add_full_response(post, options)
101
108
 
102
109
  commit(action, post)
103
110
  end
@@ -154,6 +161,22 @@ module ActiveMerchant #:nodoc:
154
161
  post[:token] = authorization
155
162
  end
156
163
 
164
+ def add_contact_details(post, contact_details_options)
165
+ contact_details = {}
166
+ contact_details[:documentType] = contact_details_options[:document_type] if contact_details_options[:document_type]
167
+ contact_details[:documentNumber] = contact_details_options[:document_number] if contact_details_options[:document_number]
168
+ contact_details[:email] = contact_details_options[:email] if contact_details_options[:email]
169
+ contact_details[:firstName] = contact_details_options[:first_name] if contact_details_options[:first_name]
170
+ contact_details[:lastName] = contact_details_options[:last_name] if contact_details_options[:last_name]
171
+ contact_details[:secondLastName] = contact_details_options[:second_last_name] if contact_details_options[:second_last_name]
172
+ contact_details[:phoneNumber] = contact_details_options[:phone_number] if contact_details_options[:phone_number]
173
+ post[:contactDetails] = contact_details
174
+ end
175
+
176
+ def add_full_response(post, options)
177
+ post[:fullResponse] = options[:full_response].to_s.casecmp('true').zero? if options[:full_response]
178
+ end
179
+
157
180
  ENDPOINT = {
158
181
  'tokenize' => 'tokens',
159
182
  'charge' => 'charges',