activemerchant 1.121.0 → 1.123.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 (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',