activemerchant 1.125.0 → 1.126.0

Sign up to get free protection for your applications and to get access to all the features.
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