lib/active_merchant/billing/gateways/adyen.rb in activemerchant-1.91.0 vs lib/active_merchant/billing/gateways/adyen.rb in activemerchant-1.92.0

- old
+ new

@@ -7,11 +7,11 @@ self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/v18' self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/v18' self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US'] self.default_currency = 'USD' - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo] self.money_format = :cents self.homepage_url = 'https://www.adyen.com/' self.display_name = 'Adyen' @@ -36,62 +36,71 @@ if options[:execute_threed] || options[:threed_dynamic] authorize(money, payment, options) else MultiResponse.run do |r| r.process { authorize(money, payment, options) } - r.process { capture(money, r.authorization, options) } + r.process { capture(money, r.authorization, capture_options(options)) } end end end def authorize(money, payment, options={}) requires!(options, :order_id) post = init_post(options) add_invoice(post, money, options) add_payment(post, payment) add_extra_data(post, payment, options) - add_shopper_interaction(post, payment, options) + add_stored_credentials(post, payment, options) add_address(post, options) add_installments(post, options) if options[:installments] add_3ds(post, options) - commit('authorise', post) + commit('authorise', post, options) end def capture(money, authorization, options={}) post = init_post(options) add_invoice_for_modification(post, money, options) add_reference(post, authorization, options) - commit('capture', post) + commit('capture', post, options) end def refund(money, authorization, options={}) post = init_post(options) add_invoice_for_modification(post, money, options) add_original_reference(post, authorization, options) - commit('refund', post) + commit('refund', post, options) end def void(authorization, options={}) post = init_post(options) add_reference(post, authorization, options) - commit('cancel', post) + commit('cancel', post, options) end + def adjust(money, authorization, options={}) + post = init_post(options) + add_invoice_for_modification(post, money, options) + add_reference(post, authorization, options) + commit('adjustAuthorisation', post, options) + end + def store(credit_card, options={}) requires!(options, :order_id) post = init_post(options) add_invoice(post, 0, options) add_payment(post, credit_card) add_extra_data(post, credit_card, options) + add_stored_credentials(post, credit_card, options) add_recurring_contract(post, options) add_address(post, options) - commit('authorise', post) + commit('authorise', post, options) end def verify(credit_card, options={}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(0, credit_card, options) } + options[:idempotency_key] = nil r.process(:ignore_result) { void(r.authorization, options) } end end def supports_scrubbing? @@ -149,42 +158,61 @@ 'android_pay' => 'androidpay', 'google_pay' => 'paywithgoogle' } def add_extra_data(post, payment, options) + post[:telephoneNumber] = options[:billing_address][:phone] if options.dig(:billing_address, :phone) post[:shopperEmail] = options[:shopper_email] if options[:shopper_email] post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip] post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference] + post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement] post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset] post[:selectedBrand] = options[:selected_brand] if options[:selected_brand] post[:selectedBrand] ||= NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard) post[:deliveryDate] = options[:delivery_date] if options[:delivery_date] post[:merchantOrderReference] = options[:merchant_order_reference] if options[:merchant_order_reference] post[:additionalData] ||= {} post[:additionalData][:overwriteBrand] = normalize(options[:overwrite_brand]) if options[:overwrite_brand] post[:additionalData][:customRoutingFlag] = options[:custom_routing_flag] if options[:custom_routing_flag] post[:additionalData]['paymentdatasource.type'] = NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard) + post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint] add_risk_data(post, options) end def add_risk_data(post, options) - risk_data = {} - risk_data.merge!(options[:risk_data]) if options[:risk_data] + if (risk_data = options[:risk_data]) + risk_data = Hash[risk_data.map { |k, v| ["riskdata.#{k}", v] }] + post[:additionalData].merge!(risk_data) + end + end - post[:additionalData][:riskData] = risk_data unless risk_data.empty? + def add_stored_credentials(post, payment, options) + add_shopper_interaction(post, payment, options) + add_recurring_processing_model(post, options) end def add_shopper_interaction(post, payment, options={}) - if (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard) + if options.dig(:stored_credential, :initial_transaction) || (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard) shopper_interaction = 'Ecommerce' else shopper_interaction = 'ContAuth' end post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction end + def add_recurring_processing_model(post, options) + return unless options.dig(:stored_credential, :reason_type) || options[:recurring_processing_model] + if options.dig(:stored_credential, :reason_type) && options[:stored_credential][:reason_type] == 'unscheduled' + recurring_processing_model = 'CardOnFile' + else + recurring_processing_model = 'Subscription' + end + + post[:recurringProcessingModel] = options[:recurring_processing_model] || recurring_processing_model + end + def add_address(post, options) return unless post[:card]&.kind_of?(Hash) if (address = options[:billing_address] || options[:address]) && address[:country] post[:card][:billingAddress] = {} post[:card][:billingAddress][:street] = address[:address1] || 'N/A' @@ -200,11 +228,10 @@ amount = { value: amount(money), currency: options[:currency] || currency(money) } post[:amount] = amount - post[:recurringProcessingModel] = options[:recurring_processing_model] if options[:recurring_processing_model] end def add_invoice_for_modification(post, money, options) amount = { value: amount(money), @@ -237,10 +264,15 @@ card[:holderName] ||= 'Not Provided' if credit_card.is_a?(NetworkTokenizationCreditCard) requires!(card, :expiryMonth, :expiryYear, :holderName, :number) post[:card] = card end + def capture_options(options) + return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key] + options + end + def add_reference(post, authorization, options = {}) _, psp_reference, _ = authorization.split('#') post[:originalReference] = single_reference(authorization) || psp_reference end @@ -284,13 +316,13 @@ def parse(body) return {} if body.blank? JSON.parse(body) end - def commit(action, parameters) + def commit(action, parameters, options) begin - raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers) + raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers(options)) response = parse(raw_response) rescue ResponseError => e raw_response = e.response.body response = parse(raw_response) end @@ -327,21 +359,23 @@ def basic_auth Base64.strict_encode64("#{@username}:#{@password}") end - def request_headers - { + def request_headers(options) + headers = { 'Content-Type' => 'application/json', 'Authorization' => "Basic #{basic_auth}" } + headers['Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key] + headers end def success_from(action, response) case action.to_s when 'authorise', 'authorise3d' ['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode']) - when 'capture', 'refund', 'cancel' + when 'capture', 'refund', 'cancel', 'adjustAuthorisation' response['response'] == "[#{action}-received]" else false end end