activemerchant 1.125.0 → 1.126.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +75 -0
  3. data/lib/active_merchant/billing/credit_card_methods.rb +12 -0
  4. data/lib/active_merchant/billing/gateway.rb +2 -1
  5. data/lib/active_merchant/billing/gateways/adyen.rb +7 -4
  6. data/lib/active_merchant/billing/gateways/airwallex.rb +341 -0
  7. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +2 -1
  8. data/lib/active_merchant/billing/gateways/blue_pay.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/blue_snap.rb +31 -21
  10. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +6 -1
  11. data/lib/active_merchant/billing/gateways/braintree/token_nonce.rb +113 -0
  12. data/lib/active_merchant/billing/gateways/braintree_blue.rb +87 -15
  13. data/lib/active_merchant/billing/gateways/card_connect.rb +1 -1
  14. data/lib/active_merchant/billing/gateways/checkout_v2.rb +1 -1
  15. data/lib/active_merchant/billing/gateways/credorax.rb +10 -0
  16. data/lib/active_merchant/billing/gateways/cyber_source.rb +13 -33
  17. data/lib/active_merchant/billing/gateways/d_local.rb +49 -0
  18. data/lib/active_merchant/billing/gateways/decidir.rb +17 -1
  19. data/lib/active_merchant/billing/gateways/decidir_plus.rb +185 -14
  20. data/lib/active_merchant/billing/gateways/ebanx.rb +3 -2
  21. data/lib/active_merchant/billing/gateways/global_collect.rb +26 -16
  22. data/lib/active_merchant/billing/gateways/ipg.rb +1 -2
  23. data/lib/active_merchant/billing/gateways/litle.rb +93 -1
  24. data/lib/active_merchant/billing/gateways/moneris.rb +35 -8
  25. data/lib/active_merchant/billing/gateways/nmi.rb +12 -7
  26. data/lib/active_merchant/billing/gateways/orbital.rb +349 -327
  27. data/lib/active_merchant/billing/gateways/payflow.rb +62 -0
  28. data/lib/active_merchant/billing/gateways/paymentez.rb +26 -7
  29. data/lib/active_merchant/billing/gateways/paysafe.rb +15 -15
  30. data/lib/active_merchant/billing/gateways/payu_latam.rb +25 -15
  31. data/lib/active_merchant/billing/gateways/priority.rb +158 -136
  32. data/lib/active_merchant/billing/gateways/rapyd.rb +258 -0
  33. data/lib/active_merchant/billing/gateways/safe_charge.rb +1 -4
  34. data/lib/active_merchant/billing/gateways/simetrik.rb +362 -0
  35. data/lib/active_merchant/billing/gateways/stripe.rb +4 -2
  36. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +93 -48
  37. data/lib/active_merchant/billing/gateways/visanet_peru.rb +6 -2
  38. data/lib/active_merchant/version.rb +1 -1
  39. metadata +6 -2
@@ -78,7 +78,7 @@ module ActiveMerchant
78
78
  def purchase(money, payment_method, options = {})
79
79
  payment_method_details = PaymentMethodDetails.new(payment_method)
80
80
 
81
- commit(:purchase, :post, payment_method_details) do |doc|
81
+ commit(:purchase, options, :post, payment_method_details) do |doc|
82
82
  if payment_method_details.alt_transaction?
83
83
  add_alt_transaction_purchase(doc, money, payment_method_details, options)
84
84
  else
@@ -88,13 +88,13 @@ module ActiveMerchant
88
88
  end
89
89
 
90
90
  def authorize(money, payment_method, options = {})
91
- commit(:authorize) do |doc|
91
+ commit(:authorize, options) do |doc|
92
92
  add_auth_purchase(doc, money, payment_method, options)
93
93
  end
94
94
  end
95
95
 
96
96
  def capture(money, authorization, options = {})
97
- commit(:capture, :put) do |doc|
97
+ commit(:capture, options, :put) do |doc|
98
98
  add_authorization(doc, authorization)
99
99
  add_order(doc, options)
100
100
  add_amount(doc, money, options) if options[:include_capture_amount] == true
@@ -102,15 +102,16 @@ module ActiveMerchant
102
102
  end
103
103
 
104
104
  def refund(money, authorization, options = {})
105
- commit(:refund, :put) do |doc|
106
- add_authorization(doc, authorization)
107
- add_amount(doc, money, options)
108
- add_order(doc, options)
105
+ options[:endpoint] = options[:merchant_transaction_id] ? "/refund/merchant/#{options[:merchant_transaction_id]}" : "/refund/#{authorization}"
106
+ commit(:refund, options, :post) do |doc|
107
+ add_amount(doc, money, options) if money
108
+ %i[reason cancel_subscription tax_amount].each { |field| send_when_present(doc, field, options) }
109
+ add_metadata(doc, options)
109
110
  end
110
111
  end
111
112
 
112
113
  def void(authorization, options = {})
113
- commit(:void, :put) do |doc|
114
+ commit(:void, options, :put) do |doc|
114
115
  add_authorization(doc, authorization)
115
116
  add_order(doc, options)
116
117
  end
@@ -123,7 +124,7 @@ module ActiveMerchant
123
124
  def store(payment_method, options = {})
124
125
  payment_method_details = PaymentMethodDetails.new(payment_method)
125
126
 
126
- commit(:store, :post, payment_method_details) do |doc|
127
+ commit(:store, options, :post, payment_method_details) do |doc|
127
128
  add_personal_info(doc, payment_method, options)
128
129
  add_echeck_company(doc, payment_method) if payment_method_details.check?
129
130
  doc.send('payment-sources') do
@@ -149,7 +150,7 @@ module ActiveMerchant
149
150
 
150
151
  def verify_credentials
151
152
  begin
152
- ssl_get(url.to_s, headers)
153
+ ssl_get(url.to_s, headers(options))
153
154
  rescue ResponseError => e
154
155
  return false if e.response.code.to_i == 401
155
156
  end
@@ -234,6 +235,7 @@ module ActiveMerchant
234
235
  doc.send('meta-key', truncate(entry[:meta_key], 40))
235
236
  doc.send('meta-value', truncate(entry[:meta_value], 500))
236
237
  doc.send('meta-description', truncate(entry[:meta_description], 40))
238
+ doc.send('is-visible', truncate(entry[:meta_is_visible], 5))
237
239
  end
238
240
  end
239
241
  end
@@ -386,13 +388,12 @@ module ActiveMerchant
386
388
 
387
389
  def parse(response)
388
390
  return bad_authentication_response if response.code.to_i == 401
389
- return generic_error_response(response.body) if [403, 429].include?(response.code.to_i)
391
+ return generic_error_response(response.body) if [403, 405, 429].include?(response.code.to_i)
390
392
 
391
393
  parsed = {}
392
394
  doc = Nokogiri::XML(response.body)
393
395
  doc.root.xpath('*').each do |node|
394
396
  name = node.name.downcase
395
-
396
397
  if node.elements.empty?
397
398
  parsed[name] = node.text
398
399
  elsif name == 'transaction-meta-data'
@@ -433,15 +434,15 @@ module ActiveMerchant
433
434
  end
434
435
  end
435
436
 
436
- def api_request(action, request, verb, payment_method_details)
437
- ssl_request(verb, url(action, payment_method_details), request, headers)
437
+ def api_request(action, request, verb, payment_method_details, options)
438
+ ssl_request(verb, url(action, options, payment_method_details), request, headers(options))
438
439
  rescue ResponseError => e
439
440
  e.response
440
441
  end
441
442
 
442
- def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new())
443
+ def commit(action, options, verb = :post, payment_method_details = PaymentMethodDetails.new())
443
444
  request = build_xml_request(action, payment_method_details) { |doc| yield(doc) }
444
- response = api_request(action, request, verb, payment_method_details)
445
+ response = api_request(action, request, verb, payment_method_details, options)
445
446
  parsed = parse(response)
446
447
 
447
448
  succeeded = success_from(action, response)
@@ -457,9 +458,10 @@ module ActiveMerchant
457
458
  )
458
459
  end
459
460
 
460
- def url(action = nil, payment_method_details = PaymentMethodDetails.new())
461
+ def url(action = nil, options = {}, payment_method_details = PaymentMethodDetails.new())
461
462
  base = test? ? test_url : live_url
462
463
  resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url
464
+ resource += options[:endpoint] if action == :refund
463
465
  "#{base}/#{resource}"
464
466
  end
465
467
 
@@ -532,20 +534,28 @@ module ActiveMerchant
532
534
  end
533
535
 
534
536
  def root_element(action, payment_method_details)
535
- action == :store ? 'vaulted-shopper' : payment_method_details.root_element
537
+ return 'refund' if action == :refund
538
+ return 'vaulted-shopper' if action == :store
539
+
540
+ payment_method_details.root_element
536
541
  end
537
542
 
538
- def headers
539
- {
543
+ def headers(options)
544
+ idempotency_key = options[:idempotency_key] if options[:idempotency_key]
545
+
546
+ headers = {
540
547
  'Content-Type' => 'application/xml',
541
548
  'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip)
542
549
  }
550
+
551
+ headers['Idempotency-Key'] = idempotency_key if idempotency_key
552
+ headers
543
553
  end
544
554
 
545
555
  def build_xml_request(action, payment_method_details)
546
556
  builder = Nokogiri::XML::Builder.new
547
557
  builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc|
548
- doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction?
558
+ doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction? && action != :refund
549
559
  yield(doc)
550
560
  end
551
561
  builder.doc.root.to_xml
@@ -18,6 +18,11 @@ module BraintreeCommon
18
18
  transcript.
19
19
  gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
20
20
  gsub(%r((&?ccnumber=)\d*(&?)), '\1[FILTERED]\2').
21
- gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2')
21
+ gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2').
22
+ gsub(%r((<account-number>)\d+(</account-number>)), '\1[FILTERED]\2').
23
+ gsub(%r((<payment-method-nonce>)[^<]+(</payment-method-nonce>)), '\1[FILTERED]\2').
24
+ gsub(%r((<payment-method-token>)[^<]+(</payment-method-token>)), '\1[FILTERED]\2').
25
+ gsub(%r((<value>)[^<]{100,}(</value>)), '\1[FILTERED]\2').
26
+ gsub(%r((<token>)[^<]+(</token>)), '\1[FILTERED]\2')
22
27
  end
23
28
  end
@@ -0,0 +1,113 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class TokenNonce #:nodoc:
4
+ include PostsData
5
+ # This class emulates the behavior of the front-end js library to
6
+ # create token nonce for a bank account base on the docs:
7
+ # https://developer.paypal.com/braintree/docs/guides/ach/client-side
8
+
9
+ attr_reader :braintree_gateway, :options
10
+
11
+ def initialize(gateway, options = {})
12
+ @braintree_gateway = gateway
13
+ @options = options
14
+ end
15
+
16
+ def url
17
+ sandbox = @braintree_gateway.config.environment == :sandbox
18
+ "https://payments#{'.sandbox' if sandbox}.braintree-api.com/graphql"
19
+ end
20
+
21
+ def create_token_nonce_for_payment_method(payment_method)
22
+ headers = {
23
+ 'Accept' => 'application/json',
24
+ 'Authorization' => "Bearer #{client_token}",
25
+ 'Content-Type' => 'application/json',
26
+ 'Braintree-Version' => '2018-05-10'
27
+ }
28
+ resp = ssl_post(url, build_nonce_request(payment_method), headers)
29
+ json_response = JSON.parse(resp)
30
+
31
+ message = json_response['errors'].map { |err| err['message'] }.join("\n") if json_response['errors'].present?
32
+ token = json_response.dig('data', 'tokenizeUsBankAccount', 'paymentMethod', 'id')
33
+
34
+ return token, message
35
+ end
36
+
37
+ def client_token
38
+ base64_token = @braintree_gateway.client_token.generate
39
+ JSON.parse(Base64.decode64(base64_token))['authorizationFingerprint']
40
+ end
41
+
42
+ private
43
+
44
+ def graphql_query
45
+ <<-GRAPHQL
46
+ mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
47
+ tokenizeUsBankAccount(input: $input) {
48
+ paymentMethod {
49
+ id
50
+ details {
51
+ ... on UsBankAccountDetails {
52
+ last4
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ GRAPHQL
59
+ end
60
+
61
+ def billing_address_from_options
62
+ return nil if options[:billing_address].blank?
63
+
64
+ address = options[:billing_address]
65
+
66
+ {
67
+ streetAddress: address[:address1],
68
+ extendedAddress: address[:address2],
69
+ city: address[:city],
70
+ state: address[:state],
71
+ zipCode: address[:zip]
72
+ }.compact
73
+ end
74
+
75
+ def build_nonce_request(payment_method)
76
+ input = {
77
+ usBankAccount: {
78
+ achMandate: options[:ach_mandate],
79
+ routingNumber: payment_method.routing_number,
80
+ accountNumber: payment_method.account_number,
81
+ accountType: payment_method.account_type.upcase,
82
+ billingAddress: billing_address_from_options
83
+ }
84
+ }
85
+
86
+ if payment_method.account_holder_type == 'personal'
87
+ input[:usBankAccount][:individualOwner] = {
88
+ firstName: payment_method.first_name,
89
+ lastName: payment_method.last_name
90
+ }
91
+ else
92
+ input[:usBankAccount][:businessOwner] = {
93
+ businessName: payment_method.name
94
+ }
95
+ end
96
+
97
+ {
98
+ clientSdkMetadata: {
99
+ platform: 'web',
100
+ source: 'client',
101
+ integration: 'custom',
102
+ sessionId: SecureRandom.uuid,
103
+ version: '3.83.0'
104
+ },
105
+ query: graphql_query,
106
+ variables: {
107
+ input: input
108
+ }
109
+ }.to_json
110
+ end
111
+ end
112
+ end
113
+ end
@@ -1,4 +1,5 @@
1
1
  require 'active_merchant/billing/gateways/braintree/braintree_common'
2
+ require 'active_merchant/billing/gateways/braintree/token_nonce'
2
3
  require 'active_support/core_ext/array/extract_options'
3
4
 
4
5
  begin
@@ -46,6 +47,8 @@ module ActiveMerchant #:nodoc:
46
47
  cannot_refund_if_unsettled: 91506
47
48
  }
48
49
 
50
+ DIRECT_BANK_ERROR = 'Direct bank account transactions are not supported. Bank accounts must be successfully stored before use.'.freeze
51
+
49
52
  def initialize(options = {})
50
53
  requires!(options, :merchant_id, :public_key, :private_key)
51
54
  @merchant_account_id = options[:merchant_account_id]
@@ -73,6 +76,8 @@ module ActiveMerchant #:nodoc:
73
76
  end
74
77
 
75
78
  def authorize(money, credit_card_or_vault_id, options = {})
79
+ return Response.new(false, DIRECT_BANK_ERROR) if credit_card_or_vault_id.is_a? Check
80
+
76
81
  create_transaction(:sale, money, credit_card_or_vault_id, options)
77
82
  end
78
83
 
@@ -148,21 +153,13 @@ module ActiveMerchant #:nodoc:
148
153
  end
149
154
  end
150
155
 
151
- def store(creditcard, options = {})
152
- if options[:customer].present?
153
- MultiResponse.new.tap do |r|
154
- customer_exists_response = nil
155
- r.process { customer_exists_response = check_customer_exists(options[:customer]) }
156
- r.process do
157
- if customer_exists_response.params['exists']
158
- add_credit_card_to_customer(creditcard, options)
159
- else
160
- add_customer_with_credit_card(creditcard, options)
161
- end
162
- end
163
- end
164
- else
165
- add_customer_with_credit_card(creditcard, options)
156
+ def store(payment_method, options = {})
157
+ return Response.new(false, bank_account_errors(payment_method, options)) if payment_method.is_a?(Check) && bank_account_errors(payment_method, options).present?
158
+
159
+ MultiResponse.run do |r|
160
+ r.process { check_customer_exists(options[:customer]) }
161
+ process_by = payment_method.is_a?(Check) ? :store_bank_account : :store_credit_card
162
+ send process_by, payment_method, options, r
166
163
  end
167
164
  end
168
165
 
@@ -227,6 +224,8 @@ module ActiveMerchant #:nodoc:
227
224
  private
228
225
 
229
226
  def check_customer_exists(customer_vault_id)
227
+ return Response.new true, 'Customer not found', { exists: false } if customer_vault_id.blank?
228
+
230
229
  commit do
231
230
  @braintree_gateway.customer.find(customer_vault_id)
232
231
  ActiveMerchant::Billing::Response.new(true, 'Customer found', { exists: true }, authorization: customer_vault_id)
@@ -827,6 +826,79 @@ module ActiveMerchant #:nodoc:
827
826
  end
828
827
  end
829
828
  end
829
+
830
+ def bank_account_errors(payment_method, options)
831
+ if payment_method.validate.present?
832
+ payment_method.validate
833
+ elsif options[:billing_address].blank?
834
+ 'billing_address is required parameter to store and verify Bank accounts.'
835
+ elsif options[:ach_mandate].blank?
836
+ 'ach_mandate is a required parameter to process bank acccount transactions see (https://developer.paypal.com/braintree/docs/guides/ach/client-side#show-required-authorization-language)'
837
+ end
838
+ end
839
+
840
+ def add_bank_account_to_customer(payment_method, options)
841
+ bank_account_nonce, error_message = TokenNonce.new(@braintree_gateway, options).create_token_nonce_for_payment_method payment_method
842
+ return Response.new(false, error_message) unless bank_account_nonce.present?
843
+
844
+ result = @braintree_gateway.payment_method.create(
845
+ customer_id: options[:customer],
846
+ payment_method_nonce: bank_account_nonce,
847
+ options: {
848
+ us_bank_account_verification_method: 'network_check'
849
+ }
850
+ )
851
+
852
+ verified = result.success? && result.payment_method&.verified
853
+ message = message_from_result(result)
854
+ message = not_verified_reason(result.payment_method) unless verified
855
+
856
+ Response.new(verified, message,
857
+ {
858
+ customer_vault_id: options[:customer],
859
+ bank_account_token: result.payment_method&.token,
860
+ verified: verified
861
+ },
862
+ authorization: result.payment_method&.token)
863
+ end
864
+
865
+ def not_verified_reason(bank_account)
866
+ return unless bank_account.verifications.present?
867
+
868
+ verification = bank_account.verifications.first
869
+ "verification_status: [#{verification.status}], processor_response: [#{verification.processor_response_code}-#{verification.processor_response_text}]"
870
+ end
871
+
872
+ def store_bank_account(payment_method, options, multi_response)
873
+ multi_response.process { create_customer_from_bank_account payment_method, options } unless multi_response.params['exists']
874
+ multi_response.process { add_bank_account_to_customer payment_method, options }
875
+ end
876
+
877
+ def store_credit_card(payment_method, options, multi_response)
878
+ process_by = multi_response.params['exists'] ? :add_credit_card_to_customer : :add_customer_with_credit_card
879
+ multi_response.process { send process_by, payment_method, options }
880
+ end
881
+
882
+ def create_customer_from_bank_account(payment_method, options)
883
+ parameters = {
884
+ id: options[:customer],
885
+ first_name: payment_method.first_name,
886
+ last_name: payment_method.last_name,
887
+ email: scrub_email(options[:email]),
888
+ phone: options[:phone] || options.dig(:billing_address, :phone),
889
+ device_data: options[:device_data]
890
+ }.compact
891
+
892
+ result = @braintree_gateway.customer.create(parameters)
893
+ customer_id = result.customer.id if result.success?
894
+ options[:customer] = customer_id
895
+
896
+ Response.new(
897
+ result.success?,
898
+ message_from_result(result),
899
+ { customer_vault_id: customer_id, 'exists': true }
900
+ )
901
+ end
830
902
  end
831
903
  end
832
904
  end
@@ -169,7 +169,7 @@ module ActiveMerchant #:nodoc:
169
169
  def add_address(post, options)
170
170
  if address = options[:billing_address] || options[:address]
171
171
  post[:address] = address[:address1] if address[:address1]
172
- post[:address].concat(" #{address[:address2]}") if address[:address2]
172
+ post[:address2] = address[:address2] if address[:address2]
173
173
  post[:city] = address[:city] if address[:city]
174
174
  post[:region] = address[:state] if address[:state]
175
175
  post[:country] = address[:country] if address[:country]
@@ -169,7 +169,7 @@ module ActiveMerchant #:nodoc:
169
169
  end
170
170
 
171
171
  case options[:stored_credential][:reason_type]
172
- when 'recurring' || 'installment'
172
+ when 'recurring', 'installment'
173
173
  post[:payment_type] = 'Recurring'
174
174
  when 'unscheduled'
175
175
  return
@@ -193,6 +193,7 @@ module ActiveMerchant #:nodoc:
193
193
  add_submerchant_id(post, options)
194
194
  add_processor(post, options)
195
195
  add_email(post, options)
196
+ add_recipient(post, options)
196
197
 
197
198
  if options[:referral_cft]
198
199
  add_customer_name(post, options)
@@ -320,6 +321,15 @@ module ActiveMerchant #:nodoc:
320
321
  post[:c3] = options[:email] || 'unspecified@example.com'
321
322
  end
322
323
 
324
+ def add_recipient(post, options)
325
+ return unless options[:recipient_street_address] || options[:recipient_city] || options[:recipient_province_code] || options[:recipient_country_code]
326
+
327
+ post[:j6] = options[:recipient_street_address] if options[:recipient_street_address]
328
+ post[:j7] = options[:recipient_city] if options[:recipient_city]
329
+ post[:j8] = options[:recipient_province_code] if options[:recipient_province_code]
330
+ post[:j9] = options[:recipient_country_code] if options[:recipient_country_code]
331
+ end
332
+
323
333
  def add_customer_name(post, options)
324
334
  post[:j5] = options[:first_name] if options[:first_name]
325
335
  post[:j13] = options[:last_name] if options[:last_name]
@@ -15,9 +15,6 @@ module ActiveMerchant #:nodoc:
15
15
  # CyberSource what kind of item you are selling. It is used when
16
16
  # calculating tax/VAT.
17
17
  # * All transactions use dollar values.
18
- # * To process pinless debit cards through the pinless debit card
19
- # network, your Cybersource merchant account must accept pinless
20
- # debit card payments.
21
18
  # * The order of the XML elements does matter, make sure to follow the order in
22
19
  # the documentation exactly.
23
20
  class CyberSourceGateway < Gateway
@@ -139,7 +136,6 @@ module ActiveMerchant #:nodoc:
139
136
  commit(build_capture_request(money, authorization, options), :capture, money, options)
140
137
  end
141
138
 
142
- # options[:pinless_debit_card] => true # attempts to process as pinless debit card
143
139
  def purchase(money, payment_method_or_reference, options = {})
144
140
  setup_address_hash(options)
145
141
  commit(build_purchase_request(money, payment_method_or_reference, options), :purchase, money, options)
@@ -158,9 +154,10 @@ module ActiveMerchant #:nodoc:
158
154
  end
159
155
 
160
156
  def verify(payment, options = {})
157
+ amount = eligible_for_zero_auth?(payment, options) ? 0 : 100
161
158
  MultiResponse.run(:use_first_response) do |r|
162
- r.process { authorize(100, payment, options) }
163
- r.process(:ignore_result) { void(r.authorization, options) }
159
+ r.process { authorize(amount, payment, options) }
160
+ r.process(:ignore_result) { void(r.authorization, options) } unless amount == 0
164
161
  end
165
162
  end
166
163
 
@@ -229,12 +226,6 @@ module ActiveMerchant #:nodoc:
229
226
  commit(build_tax_calculation_request(creditcard, options), :calculate_tax, nil, options)
230
227
  end
231
228
 
232
- # Determines if a card can be used for Pinless Debit Card transactions
233
- def validate_pinless_debit_card(creditcard, options = {})
234
- requires!(options, :order_id)
235
- commit(build_validate_pinless_debit_request(creditcard, options), :validate_pinless_debit_card, nil, options)
236
- end
237
-
238
229
  def supports_scrubbing?
239
230
  true
240
231
  end
@@ -291,6 +282,7 @@ module ActiveMerchant #:nodoc:
291
282
  xml = Builder::XmlMarkup.new indent: 2
292
283
  add_customer_id(xml, options)
293
284
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
285
+ add_other_tax(xml, options)
294
286
  add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
295
287
  add_decision_manager_fields(xml, options)
296
288
  add_mdd_fields(xml, options)
@@ -349,6 +341,7 @@ module ActiveMerchant #:nodoc:
349
341
  xml = Builder::XmlMarkup.new indent: 2
350
342
  add_customer_id(xml, options)
351
343
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
344
+ add_other_tax(xml, options)
352
345
  add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
353
346
  add_decision_manager_fields(xml, options)
354
347
  add_mdd_fields(xml, options)
@@ -362,7 +355,7 @@ module ActiveMerchant #:nodoc:
362
355
  add_purchase_service(xml, payment_method_or_reference, options)
363
356
  add_threeds_services(xml, options)
364
357
  add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
365
- add_business_rules_data(xml, payment_method_or_reference, options) unless options[:pinless_debit_card]
358
+ add_business_rules_data(xml, payment_method_or_reference, options)
366
359
  add_stored_credential_subsequent_auth(xml, options)
367
360
  add_issuer_additional_data(xml, options)
368
361
  add_partner_solution_id(xml)
@@ -474,13 +467,6 @@ module ActiveMerchant #:nodoc:
474
467
  xml.target!
475
468
  end
476
469
 
477
- def build_validate_pinless_debit_request(creditcard, options)
478
- xml = Builder::XmlMarkup.new indent: 2
479
- add_creditcard(xml, creditcard)
480
- add_validate_pinless_debit_service(xml)
481
- xml.target!
482
- end
483
-
484
470
  def add_business_rules_data(xml, payment_method, options)
485
471
  prioritized_options = [options, @options]
486
472
 
@@ -794,15 +780,9 @@ module ActiveMerchant #:nodoc:
794
780
  end
795
781
 
796
782
  def add_purchase_service(xml, payment_method, options)
797
- if options[:pinless_debit_card]
798
- xml.tag! 'pinlessDebitService', { 'run' => 'true' } do
799
- xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
800
- end
801
- else
802
- add_auth_service(xml, payment_method, options)
803
- xml.tag! 'ccCaptureService', { 'run' => 'true' } do
804
- xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
805
- end
783
+ add_auth_service(xml, payment_method, options)
784
+ xml.tag! 'ccCaptureService', { 'run' => 'true' } do
785
+ xml.tag!('reconciliationID', options[:reconciliation_id]) if options[:reconciliation_id]
806
786
  end
807
787
  end
808
788
 
@@ -911,10 +891,6 @@ module ActiveMerchant #:nodoc:
911
891
  end
912
892
  end
913
893
 
914
- def add_validate_pinless_debit_service(xml)
915
- xml.tag! 'pinlessDebitValidateService', { 'run' => 'true' }
916
- end
917
-
918
894
  def add_threeds_services(xml, options)
919
895
  xml.tag! 'payerAuthEnrollService', { 'run' => 'true' } if options[:payer_auth_enroll_service]
920
896
  if options[:payer_auth_validate_service]
@@ -1077,6 +1053,10 @@ module ActiveMerchant #:nodoc:
1077
1053
  response[:message]
1078
1054
  end
1079
1055
  end
1056
+
1057
+ def eligible_for_zero_auth?(payment_method, options = {})
1058
+ payment_method.is_a?(CreditCard) && options[:zero_amount_auth]
1059
+ end
1080
1060
  end
1081
1061
  end
1082
1062
  end
@@ -19,6 +19,7 @@ module ActiveMerchant #:nodoc:
19
19
  def purchase(money, payment, options = {})
20
20
  post = {}
21
21
  add_auth_purchase_params(post, money, payment, 'purchase', options)
22
+ add_three_ds(post, options)
22
23
 
23
24
  commit('purchase', post, options)
24
25
  end
@@ -26,6 +27,7 @@ module ActiveMerchant #:nodoc:
26
27
  def authorize(money, payment, options = {})
27
28
  post = {}
28
29
  add_auth_purchase_params(post, money, payment, 'authorize', options)
30
+ add_three_ds(post, options)
29
31
  post[:card][:verify] = true if options[:verify].to_s == 'true'
30
32
 
31
33
  commit('authorize', post, options)
@@ -154,6 +156,7 @@ module ActiveMerchant #:nodoc:
154
156
  post[:card][:capture] = (action == 'purchase')
155
157
  post[:card][:installments] = options[:installments] if options[:installments]
156
158
  post[:card][:installments_id] = options[:installments_id] if options[:installments_id]
159
+ post[:card][:force_type] = options[:force_type].to_s.upcase if options[:force_type]
157
160
  end
158
161
 
159
162
  def parse(body)
@@ -161,6 +164,9 @@ module ActiveMerchant #:nodoc:
161
164
  end
162
165
 
163
166
  def commit(action, parameters, options = {})
167
+ three_ds_errors = validate_three_ds_params(parameters[:three_dsecure]) if parameters[:three_dsecure].present?
168
+ return three_ds_errors if three_ds_errors
169
+
164
170
  url = url(action, parameters, options)
165
171
  post = post_data(action, parameters)
166
172
  begin
@@ -249,6 +255,49 @@ module ActiveMerchant #:nodoc:
249
255
  def post_data(action, parameters = {})
250
256
  parameters.to_json
251
257
  end
258
+
259
+ def xid_or_ds_trans_id(three_d_secure)
260
+ if three_d_secure[:version].to_f >= 2
261
+ { ds_transaction_id: three_d_secure[:ds_transaction_id] }
262
+ else
263
+ { xid: three_d_secure[:xid] }
264
+ end
265
+ end
266
+
267
+ def add_three_ds(post, options)
268
+ return unless three_d_secure = options[:three_d_secure]
269
+
270
+ post[:three_dsecure] = {
271
+ mpi: true,
272
+ three_dsecure_version: three_d_secure[:version],
273
+ cavv: three_d_secure[:cavv],
274
+ eci: three_d_secure[:eci],
275
+ enrollment_response: formatted_enrollment(three_d_secure[:enrolled]),
276
+ authentication_response: three_d_secure[:authentication_response_status]
277
+ }.merge(xid_or_ds_trans_id(three_d_secure))
278
+ end
279
+
280
+ def validate_three_ds_params(three_ds)
281
+ errors = {}
282
+ supported_version = %w{1.0 2.0 2.1.0 2.2.0}.include?(three_ds[:three_dsecure_version])
283
+ supported_enrollment = ['Y', 'N', 'U', nil].include?(three_ds[:enrollment_response])
284
+ supported_auth_response = ['Y', 'N', 'U', nil].include?(three_ds[:authentication_response])
285
+
286
+ errors[:three_ds_version] = 'ThreeDs version not supported' unless supported_version
287
+ errors[:enrollment] = 'Enrollment value not supported' unless supported_enrollment
288
+ errors[:auth_response] = 'Authentication response value not supported' unless supported_auth_response
289
+ errors.compact!
290
+
291
+ errors.present? ? Response.new(false, 'ThreeDs data is invalid', errors) : nil
292
+ end
293
+
294
+ def formatted_enrollment(val)
295
+ case val
296
+ when 'Y', 'N', 'U' then val
297
+ when true, 'true' then 'Y'
298
+ when false, 'false' then 'N'
299
+ end
300
+ end
252
301
  end
253
302
  end
254
303
  end