class ActiveMerchant::Billing::AdyenGateway

Constants

AVS_MAPPING
CVC_MAPPING
NETWORK_TOKENIZATION_CARD_SOURCE
PAYMENT_API_VERSION
RECURRING_API_VERSION
STANDARD_ERROR_CODE_MAPPING

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/adyen.rb, line 37
def initialize(options = {})
  requires!(options, :username, :password, :merchant_account)
  @username, @password, @merchant_account = options.values_at(:username, :password, :merchant_account)
  super
end

Public Instance Methods

adjust(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 130
def adjust(money, authorization, options = {})
  post = init_post(options)
  add_invoice_for_modification(post, money, options)
  add_reference(post, authorization, options)
  add_extra_data(post, nil, options)
  commit('adjustAuthorisation', post, options)
end
authorize(money, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 54
def authorize(money, payment, options = {})
  requires!(options, :order_id)
  post = init_post(options)
  add_invoice(post, money, options)
  add_payment(post, payment, options)
  add_extra_data(post, payment, options)
  add_stored_credentials(post, payment, options)
  add_address(post, options)
  add_installments(post, options) if options[:installments]
  add_3ds(post, options)
  add_3ds_authenticated_data(post, options)
  add_splits(post, options)
  add_recurring_contract(post, options)
  add_network_transaction_reference(post, options)
  add_application_info(post, options)
  add_level_2_data(post, options)
  add_level_3_data(post, options)
  add_data_airline(post, options)
  add_data_lodging(post, options)
  add_metadata(post, options)
  commit('authorise', post, options)
end
capture(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 77
def capture(money, authorization, options = {})
  post = init_post(options)
  add_invoice_for_modification(post, money, options)
  add_reference(post, authorization, options)
  add_splits(post, options)
  add_network_transaction_reference(post, options)
  add_shopper_statement(post, options)
  commit('capture', post, options)
end
credit(money, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 96
def credit(money, payment, options = {})
  action = options[:payout] ? 'payout' : 'refundWithData'
  post = init_post(options)
  add_invoice(post, money, options)
  add_payment(post, payment, options, action)
  add_shopper_reference(post, options)
  add_network_transaction_reference(post, options)

  if action == 'payout'
    add_shopper_interaction(post, payment, options)
    add_fraud_offset(post, options)
    add_fund_source(post, options)
    add_recurring_contract(post, options)
    add_shopper_data(post, payment, options)

    if (address = options[:billing_address] || options[:address]) && address[:country]
      add_billing_address(post, options, address)
    end

    post[:dateOfBirth] = options[:date_of_birth] if options[:date_of_birth]
    post[:nationality] = options[:nationality] if options[:nationality]
  end

  commit(action, post, options)
end
purchase(money, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 43
def purchase(money, payment, options = {})
  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, capture_options(options)) }
    end
  end
end
refund(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 87
def refund(money, authorization, options = {})
  post = init_post(options)
  add_invoice_for_modification(post, money, options)
  add_reference(post, authorization, options)
  add_splits(post, options)
  add_network_transaction_reference(post, options)
  commit('refund', post, options)
end
scrub(transcript) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 189
def scrub(transcript)
  transcript.
    gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
    gsub(%r(("number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("cvc\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("cavv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("bankLocationId\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("iban\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("bankAccountNumber\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
end
store(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 138
def store(credit_card, options = {})
  requires!(options, :order_id)
  post = init_post(options)
  add_invoice(post, 0, options)
  add_payment(post, credit_card, options)
  add_extra_data(post, credit_card, options)
  add_stored_credentials(post, credit_card, options)
  add_address(post, options)
  add_network_transaction_reference(post, options)
  options[:recurring_contract_type] ||= 'RECURRING'
  add_recurring_contract(post, options)

  action = options[:tokenize_only] ? 'storeToken' : 'authorise'

  initial_response = commit(action, post, options)

  if initial_response.success? && card_not_stored?(initial_response)
    unsupported_failure_response(initial_response)
  else
    initial_response
  end
end
supports_network_tokenization?() click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 185
def supports_network_tokenization?
  true
end
supports_scrubbing?() click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 181
def supports_scrubbing?
  true
end
unstore(options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 161
def unstore(options = {})
  requires!(options, :shopper_reference, :recurring_detail_reference)
  post = {}

  add_shopper_reference(post, options)
  add_merchant_account(post, options)
  post[:recurringDetailReference] = options[:recurring_detail_reference]

  commit('disable', post, options)
end
verify(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 172
def verify(credit_card, options = {})
  amount = options[:verify_amount]&.to_i || 0
  MultiResponse.run(:use_first_response) do |r|
    r.process { authorize(amount, credit_card, options) }
    options[:idempotency_key] = nil
    r.process(:ignore_result) { void(r.authorization, options) }
  end
end
void(authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 122
def void(authorization, options = {})
  post = init_post(options)
  endpoint = options[:cancel_or_refund] ? 'cancelOrRefund' : 'cancel'
  add_reference(post, authorization, options)
  add_network_transaction_reference(post, options)
  commit(endpoint, post, options)
end

Private Instance Methods

add_3ds(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 673
def add_3ds(post, options)
  if three_ds_2_options = options[:three_ds_2]
    device_channel = three_ds_2_options[:channel]
    if device_channel == 'app'
      post[:threeDS2RequestData] = { deviceChannel: device_channel }
    else
      add_browser_info(three_ds_2_options[:browser_info], post)
      post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] }
    end

    if options.has_key?(:execute_threed)
      post[:additionalData][:executeThreeD] = options[:execute_threed]
      post[:additionalData][:scaExemption] = options[:sca_exemption] if options[:sca_exemption]
    end
  else
    return unless !options[:execute_threed].nil? || !options[:threed_dynamic].nil?

    post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] } if options[:execute_threed] || options[:threed_dynamic]
    post[:additionalData] ||= {}
    post[:additionalData][:executeThreeD] = options[:execute_threed] if !options[:execute_threed].nil?
  end
end
add_3ds1_authenticated_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 704
def add_3ds1_authenticated_data(post, options)
  three_d_secure_options = options[:three_d_secure]
  post[:mpiData] = {
    cavv: three_d_secure_options[:cavv],
    cavvAlgorithm: three_d_secure_options[:cavv_algorithm],
    eci: three_d_secure_options[:eci],
    xid: three_d_secure_options[:xid],
    directoryResponse: three_d_secure_options[:enrolled],
    authenticationResponse: three_d_secure_options[:authentication_response_status]
  }
end
add_3ds2_authenticated_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 716
def add_3ds2_authenticated_data(post, options)
  three_d_secure_options = options[:three_d_secure]
  # If the transaction was authenticated in a frictionless flow, send the transStatus from the ARes.
  if three_d_secure_options[:authentication_response_status].nil?
    authentication_response = three_d_secure_options[:directory_response_status]
  else
    authentication_response = three_d_secure_options[:authentication_response_status]
  end
  post[:mpiData] = {
    threeDSVersion: three_d_secure_options[:version],
    eci: three_d_secure_options[:eci],
    cavv: three_d_secure_options[:cavv],
    dsTransID: three_d_secure_options[:ds_transaction_id],
    directoryResponse: three_d_secure_options[:directory_response_status],
    authenticationResponse: authentication_response
  }
end
add_3ds_authenticated_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 696
def add_3ds_authenticated_data(post, options)
  if options[:three_d_secure] && options[:three_d_secure][:eci] && options[:three_d_secure][:xid]
    add_3ds1_authenticated_data(post, options)
  elsif options[:three_d_secure]
    add_3ds2_authenticated_data(post, options)
  end
end
add_additional_data(post, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 273
def add_additional_data(post, payment, options)
  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[:additionalData][:authorisationType] = options[:authorisation_type] if options[:authorisation_type]
  post[:additionalData][:adjustAuthorisationData] = options[:adjust_authorisation_data] if options[:adjust_authorisation_data]
  post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage]
  post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test?
  post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
end
add_address(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 495
def add_address(post, options)
  if address = options[:shipping_address]
    post[:deliveryAddress] = {}
    post[:deliveryAddress][:street] = options[:address_override] == true ? address[:address2] : address[:address1] || 'NA'
    post[:deliveryAddress][:houseNumberOrName] = options[:address_override] == true ? address[:address1] : address[:address2] || 'NA'
    post[:deliveryAddress][:postalCode] = address[:zip] if address[:zip]
    post[:deliveryAddress][:city] = address[:city] || 'NA'
    post[:deliveryAddress][:stateOrProvince] = get_state(address)
    post[:deliveryAddress][:country] = get_country(address)
  end
  return unless post[:bankAccount]&.kind_of?(Hash) || post[:card]&.kind_of?(Hash)

  if (address = options[:billing_address] || options[:address]) && address[:country]
    add_billing_address(post, options, address)
  end
end
add_application_info(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 641
def add_application_info(post, options)
  post[:applicationInfo] ||= {}
  add_external_platform(post, options)
  add_merchant_application(post, options)
end
add_bank_account(post, bank_account, options, action) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 563
def add_bank_account(post, bank_account, options, action)
  bank = {
    bankAccountNumber: bank_account.account_number,
    ownerName: bank_account.name,
    countryCode: options[:billing_address].try(:[], :country)
  }

  action == 'refundWithData' ? bank[:iban] = bank_account.routing_number : bank[:bankLocationId] = bank_account.routing_number

  requires!(bank, :bankAccountNumber, :ownerName, :countryCode)
  post[:bankAccount] = bank
end
add_billing_address(post, options, address) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 512
def add_billing_address(post, options, address)
  post[:billingAddress] = {}
  post[:billingAddress][:street] = options[:address_override] == true ? address[:address2] : address[:address1] || 'NA'
  post[:billingAddress][:houseNumberOrName] = options[:address_override] == true ? address[:address1] : address[:address2] || 'NA'
  post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
  post[:billingAddress][:city] = address[:city] || 'NA'
  post[:billingAddress][:stateOrProvince] = get_state(address)
  post[:billingAddress][:country] = get_country(address)
  post[:telephoneNumber] = address[:phone_number] || address[:phone] || ''
end
add_browser_info(browser_info, post) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 940
def add_browser_info(browser_info, post)
  return unless browser_info

  post[:browserInfo] = {
    acceptHeader: browser_info[:accept_header],
    colorDepth: browser_info[:depth],
    javaEnabled: browser_info[:java],
    language: browser_info[:language],
    screenHeight: browser_info[:height],
    screenWidth: browser_info[:width],
    timeZoneOffset: browser_info[:timezone],
    userAgent: browser_info[:user_agent]
  }
end
add_card(post, credit_card) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 576
def add_card(post, credit_card)
  card = {
    expiryMonth: credit_card.month,
    expiryYear: credit_card.year,
    holderName: credit_card.name,
    number: credit_card.number,
    cvc: credit_card.verification_value
  }

  card.delete_if { |_k, v| v.blank? }
  card[:holderName] ||= 'Not Provided'
  requires!(card, :expiryMonth, :expiryYear, :holderName, :number)
  post[:card] = card
end
add_data_airline(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 327
def add_data_airline(post, options)
  return unless options[:additional_data_airline]

  mapper = %w[
    agency_invoice_number
    agency_plan_name
    airline_code
    airline_designator_code
    boarding_fee
    computerized_reservation_system
    customer_reference_number
    document_type
    flight_date
    ticket_issue_address
    ticket_number
    travel_agency_code
    travel_agency_name
    passenger_name
  ].each_with_object({}) { |value, hash| hash["airline.#{value}"] = value }

  post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_airline]))

  if options[:additional_data_airline][:leg].present?
    leg_data = %w[
      carrier_code
      class_of_travel
      date_of_travel
      depart_airport
      depart_tax
      destination_code
      fare_base_code
      flight_number
      stop_over_code
    ].each_with_object({}) { |value, hash| hash["airline.leg.#{value}"] = value }

    post[:additionalData].merge!(extract_and_transform(leg_data, options[:additional_data_airline][:leg]))
  end

  if options[:additional_data_airline][:passenger].present?
    passenger_data = %w[
      date_of_birth
      first_name
      last_name
      telephone_number
      traveller_type
    ].each_with_object({}) { |value, hash| hash["airline.passenger.#{value}"] = value }

    post[:additionalData].merge!(extract_and_transform(passenger_data, options[:additional_data_airline][:passenger]))
  end
  post[:additionalData].compact!
end
add_data_lodging(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 379
def add_data_lodging(post, options)
  return unless options[:additional_data_lodging]

  mapper = {
    'lodging.checkInDate': 'check_in_date',
    'lodging.checkOutDate': 'check_out_date',
    'lodging.customerServiceTollFreeNumber': 'customer_service_toll_free_number',
    'lodging.fireSafetyActIndicator': 'fire_safety_act_indicator',
    'lodging.folioCashAdvances': 'folio_cash_advances',
    'lodging.folioNumber': 'folio_number',
    'lodging.foodBeverageCharges': 'food_beverage_charges',
    'lodging.noShowIndicator': 'no_show_indicator',
    'lodging.prepaidExpenses': 'prepaid_expenses',
    'lodging.propertyPhoneNumber': 'property_phone_number',
    'lodging.room1.numberOfNights': 'number_of_nights',
    'lodging.room1.rate': 'rate',
    'lodging.totalRoomTax': 'total_room_tax',
    'lodging.totalTax': 'totalTax',
    'travelEntertainmentAuthData.duration': 'duration',
    'travelEntertainmentAuthData.market': 'market'
  }

  post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_lodging]))
  post[:additionalData].compact!
end
add_external_platform(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 647
def add_external_platform(post, options)
  options.update(externalPlatform: application_id) if application_id

  return unless options[:externalPlatform]

  post[:applicationInfo][:externalPlatform] = {
    name: options[:externalPlatform][:name],
    version: options[:externalPlatform][:version]
  }
end
add_extra_data(post, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 248
def add_extra_data(post, payment, options)
  post[:telephoneNumber] = (options[:billing_address][:phone_number] if options.dig(:billing_address, :phone_number)) || (options[:billing_address][:phone] if options.dig(:billing_address, :phone)) || ''
  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[:captureDelayHours] = options[:capture_delay_hours] if options[:capture_delay_hours]
  post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
  post[:shopperIP] = options[:shopper_ip] || options[:ip] if options[:shopper_ip] || options[:ip]
  post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
  post[:store] = options[:store] if options[:store]
  post[:mcc] = options[:mcc] if options[:mcc]

  add_shopper_data(post, payment, options)
  add_additional_data(post, payment, options)
  add_risk_data(post, options)
  add_shopper_reference(post, options)
  add_merchant_data(post, options)
  add_fraud_offset(post, options)
end
add_fraud_offset(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 269
def add_fraud_offset(post, options)
  post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
end
add_fund_source(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 734
def add_fund_source(post, options)
  return unless fund_source = options[:fund_source]

  post[:fundSource] = {}
  post[:fundSource][:additionalData] = fund_source[:additional_data] if fund_source[:additional_data]

  if fund_source[:first_name] && fund_source[:last_name]
    post[:fundSource][:shopperName] = {}
    post[:fundSource][:shopperName][:firstName] = fund_source[:first_name]
    post[:fundSource][:shopperName][:lastName] = fund_source[:last_name]
  end

  if (address = fund_source[:billing_address])
    add_billing_address(post[:fundSource], options, address)
  end
end
add_header_fields(response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 758
def add_header_fields(response)
  return unless @response_headers.present?

  headers = {}
  headers['response_headers'] = {}
  headers['response_headers']['transient_error'] = @response_headers['transient-error'] if @response_headers['transient-error']

  response.merge!(headers)
end
add_installments(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 667
def add_installments(post, options)
  post[:installments] = {
    value: options[:installments]
  }
end
add_invoice(post, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 531
def add_invoice(post, money, options)
  currency = options[:currency] || currency(money)
  amount = {
    value: localized_amount(money, currency),
    currency: currency
  }

  post[:amount] = amount
end
add_invoice_for_modification(post, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 541
def add_invoice_for_modification(post, money, options)
  currency = options[:currency] || currency(money)
  amount = {
    value: localized_amount(money, currency),
    currency: currency
  }
  post[:modificationAmount] = amount
end
add_level_2_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 292
def add_level_2_data(post, options)
  return unless options[:level_2_data].present?

  mapper = {
    "enhancedSchemeData.totalTaxAmount": 'total_tax_amount',
    "enhancedSchemeData.customerReference": 'customer_reference'
  }
  post[:additionalData].merge!(extract_and_transform(mapper, options[:level_2_data]))
end
add_level_3_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 302
def add_level_3_data(post, options)
  return unless options[:level_3_data].present?

  mapper = { "enhancedSchemeData.freightAmount": 'freight_amount',
    "enhancedSchemeData.destinationStateProvinceCode": 'destination_state_province_code',
    "enhancedSchemeData.shipFromPostalCode": 'ship_from_postal_code',
    "enhancedSchemeData.orderDate": 'order_date',
    "enhancedSchemeData.destinationPostalCode": 'destination_postal_code',
    "enhancedSchemeData.destinationCountryCode": 'destination_country_code',
    "enhancedSchemeData.dutyAmount": 'duty_amount' }

  post[:additionalData].merge!(extract_and_transform(mapper, options[:level_3_data]))

  item_detail_keys = %w[description product_code quantity unit_of_measure unit_price discount_amount total_amount commodity_code]
  if options[:level_3_data][:items].present?
    options[:level_3_data][:items].last(9).each.with_index(1) do |item, index|
      mapper = item_detail_keys.each_with_object({}) do |key, hsh|
        hsh["enhancedSchemeData.itemDetailLine#{index}.#{key.camelize(:lower)}"] = key
      end
      post[:additionalData].merge!(extract_and_transform(mapper, item))
    end
  end
  post[:additionalData].compact!
end
add_merchant_account(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 459
def add_merchant_account(post, options)
  post[:merchantAccount] = options[:merchant_account] || @merchant_account
end
add_merchant_application(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 658
def add_merchant_application(post, options)
  return unless options[:merchantApplication]

  post[:applicationInfo][:merchantApplication] = {
    name: options[:merchantApplication][:name],
    version: options[:merchantApplication][:version]
  }
end
add_merchant_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 413
def add_merchant_data(post, options)
  post[:additionalData][:subMerchantID] = options[:sub_merchant_id] if options[:sub_merchant_id]
  post[:additionalData][:subMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name]
  post[:additionalData][:subMerchantStreet] = options[:sub_merchant_street] if options[:sub_merchant_street]
  post[:additionalData][:subMerchantCity] = options[:sub_merchant_city] if options[:sub_merchant_city]
  post[:additionalData][:subMerchantState] = options[:sub_merchant_state] if options[:sub_merchant_state]
  post[:additionalData][:subMerchantPostalCode] = options[:sub_merchant_postal_code] if options[:sub_merchant_postal_code]
  post[:additionalData][:subMerchantCountry] = options[:sub_merchant_country] if options[:sub_merchant_country]
  post[:additionalData][:subMerchantTaxId] = options[:sub_merchant_tax_id] if options[:sub_merchant_tax_id]
  post[:additionalData][:subMerchantMCC] = options[:sub_merchant_mcc] if options[:sub_merchant_mcc]
  post[:additionalData] = post[:additionalData].merge(options[:sub_merchant_data]) if options[:sub_merchant_data]
end
add_metadata(post, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 751
def add_metadata(post, options = {})
  return unless options[:metadata]

  post[:metadata] ||= {}
  post[:metadata].merge!(options[:metadata]) if options[:metadata]
end
add_mpi_data_for_network_tokenization_card(post, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 620
def add_mpi_data_for_network_tokenization_card(post, payment, options)
  return if options[:skip_mpi_data] == 'Y'

  post[:mpiData] = {}
  post[:mpiData][:authenticationResponse] = 'Y'
  post[:mpiData][:cavv] = payment.payment_cryptogram
  post[:mpiData][:directoryResponse] = 'Y'
  post[:mpiData][:eci] = payment.eci || '07'
end
add_network_transaction_reference(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 608
def add_network_transaction_reference(post, options)
  return unless ntid = options[:network_transaction_id] || options.dig(:stored_credential, :network_transaction_id)

  post[:additionalData] = {} unless post[:additionalData]
  post[:additionalData][:networkTxReference] = ntid
end
add_payment(post, payment, options, action = nil) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 550
def add_payment(post, payment, options, action = nil)
  if payment.is_a?(String)
    _, _, recurring_detail_reference = payment.split('#')
    post[:selectedRecurringDetailReference] = recurring_detail_reference
    options[:recurring_contract_type] ||= 'RECURRING'
  elsif payment.is_a?(Check)
    add_bank_account(post, payment, options, action)
  else
    add_mpi_data_for_network_tokenization_card(post, payment, options) if payment.is_a?(NetworkTokenizationCreditCard)
    add_card(post, payment)
  end
end
add_recurring_contract(post, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 630
def add_recurring_contract(post, options = {})
  return unless options[:recurring_contract_type]

  post[:recurring] = {}
  post[:recurring][:contract] = options[:recurring_contract_type]
  post[:recurring][:recurringDetailName] = options[:recurring_detail_name] if options[:recurring_detail_name]
  post[:recurring][:recurringExpiry] = options[:recurring_expiry] if options[:recurring_expiry]
  post[:recurring][:recurringFrequency] = options[:recurring_frequency] if options[:recurring_frequency]
  post[:recurring][:tokenService] = options[:token_service] if options[:token_service]
end
add_recurring_processing_model(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 479
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) == 'unscheduled'
    if options.dig(:stored_credential, :initiator) == 'merchant'
      recurring_processing_model = 'UnscheduledCardOnFile'
    else
      recurring_processing_model = 'CardOnFile'
    end
  else
    recurring_processing_model = 'Subscription'
  end

  post[:recurringProcessingModel] = options[:recurring_processing_model] || recurring_processing_model
end
add_reference(post, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 615
def add_reference(post, authorization, options = {})
  original_reference = authorization.split('#').reject(&:empty?).first
  post[:originalReference] = original_reference
end
add_risk_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 426
def add_risk_data(post, options)
  if (risk_data = options[:risk_data])
    risk_data = risk_data.map { |k, v| ["riskdata.#{k}", v] }.to_h
    post[:additionalData].merge!(risk_data)
  end
end
add_shopper_data(post, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 591
def add_shopper_data(post, payment, options)
  if payment && !payment.is_a?(String)
    post[:shopperName] = {}
    post[:shopperName][:firstName] = payment.first_name
    post[:shopperName][:lastName] = payment.last_name
  end

  post[:shopperEmail] = options[:email] if options[:email]
  post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
end
add_shopper_interaction(post, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 467
def add_shopper_interaction(post, payment, options = {})
  if  (options.dig(:stored_credential, :initial_transaction) && options.dig(:stored_credential, :initiator) == 'cardholder') ||
      (payment.respond_to?(:verification_value) && payment.verification_value && options.dig(:stored_credential, :initial_transaction).nil?) ||
      payment.is_a?(NetworkTokenizationCreditCard)
    shopper_interaction = 'Ecommerce'
  else
    shopper_interaction = 'ContAuth'
  end

  post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
end
add_shopper_reference(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 463
def add_shopper_reference(post, options)
  post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
end
add_shopper_statement(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 405
def add_shopper_statement(post, options)
  return unless options[:shopper_statement]

  post[:additionalData] = {
    shopperStatement: options[:shopper_statement]
  }
end
add_splits(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 433
def add_splits(post, options)
  return unless split_data = options[:splits]

  splits = []
  split_data.each do |split|
    amount = {
      value: split['amount']['value']
    }
    amount[:currency] = split['amount']['currency'] if split['amount']['currency']

    split_hash = {
      amount: amount,
      type: split['type'],
      reference: split['reference']
    }
    split_hash['account'] = split['account'] if split['account']
    splits.push(split_hash)
  end
  post[:splits] = splits
end
add_stored_credentials(post, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 454
def add_stored_credentials(post, payment, options)
  add_shopper_interaction(post, payment, options)
  add_recurring_processing_model(post, options)
end
authorization_from(action, parameters, response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 909
def authorization_from(action, parameters, response)
  return nil if response['pspReference'].nil?

  recurring = response['additionalData']['recurring.recurringDetailReference'] if response['additionalData']
  recurring = response['recurringDetailReference'] if action == 'storeToken'

  "#{parameters[:originalReference]}##{response['pspReference']}##{recurring}"
end
authorize_message_from(response, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 891
def authorize_message_from(response, options = {})
  return raw_authorize_error_message(response) if options[:raw_error_message]

  if response['refusalReason'] && response['additionalData'] && (response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw'])
    "#{response['refusalReason']} | #{response['additionalData']['merchantAdviceCode'] || response['additionalData']['refusalReasonRaw']}"
  else
    response['refusalReason'] || response['resultCode'] || response['message'] || response['result']
  end
end
avs_code_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 811
def avs_code_from(response)
  AVS_MAPPING[response['additionalData']['avsResult'][0..1].strip] if response.dig('additionalData', 'avsResult')
end
basic_auth() click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 840
def basic_auth
  Base64.strict_encode64("#{@username}:#{@password}")
end
capture_options(options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 602
def capture_options(options)
  return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key]

  options
end
card_not_stored?(response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 968
def card_not_stored?(response)
  response.authorization ? response.authorization.split('#')[2].nil? : true
end
commit(action, parameters, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 788
def commit(action, parameters, options)
  begin
    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

  success = success_from(action, response, options)
  Response.new(
    success,
    message_from(action, response, options),
    response,
    authorization: authorization_from(action, parameters, response),
    test: test?,
    error_code: success ? nil : error_code_from(response),
    network_transaction_id: network_transaction_id_from(response),
    avs_result: AVSResult.new(code: avs_code_from(response)),
    cvv_result: CVVResult.new(cvv_result_from(response))
  )
end
cvv_result_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 815
def cvv_result_from(response)
  CVC_MAPPING[response['additionalData']['cvcResult'][0]] if response.dig('additionalData', 'cvcResult')
end
endpoint(action) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 819
def endpoint(action)
  case action
  when 'disable', 'storeToken'
    "Recurring/#{RECURRING_API_VERSION}/#{action}"
  when 'payout'
    "Payout/#{PAYMENT_API_VERSION}/#{action}"
  else
    "Payment/#{PAYMENT_API_VERSION}/#{action}"
  end
end
error_code_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 929
def error_code_from(response)
  response.dig('additionalData', 'refusalReasonRaw').try(:match, /^([a-zA-Z0-9 ]{1,5})(?=:)/).try(:[], 1).try(:strip) ||
    STANDARD_ERROR_CODE_MAPPING[response['errorCode']] ||
    response['errorCode'] ||
    response['refusalReason']
end
extract_and_transform(mapper, from) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 285
def extract_and_transform(mapper, from)
  mapper.each_with_object({}) do |key_map, hsh|
    key, item_key = key_map[0], key_map[1]
    hsh[key] = from[item_key.to_sym]
  end
end
get_country(address) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 527
def get_country(address)
  address[:country].present? ? address[:country] : 'ZZ'
end
get_state(address) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 523
def get_state(address)
  address[:state] && !address[:state].blank? ? address[:state] : 'NA'
end
handle_response(response) click to toggle source

Override the regular handle response so we can access the headers set header fields and values so we can add them to the response body

# File lib/active_merchant/billing/gateways/adyen.rb, line 778
def handle_response(response)
  @response_headers = response.each_header.to_h if response.respond_to?(:header)
  case response.code.to_i
  when 200...300
    response.body
  else
    raise ResponseError.new(response)
  end
end
init_post(options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 918
def init_post(options = {})
  post = {}
  add_merchant_account(post, options)
  post[:reference] = options[:order_id][0..79] if options[:order_id]
  post
end
message_from(action, response, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 880
def message_from(action, response, options = {})
  case action.to_s
  when 'authorise', 'authorise3d', 'authorise3ds2'
    authorize_message_from(response, options)
  when 'payout'
    response['refusalReason'] || response['resultCode'] || response['message']
  else
    response['response'] || response['message'] || response['result'] || response['resultCode']
  end
end
network_transaction_id_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 936
def network_transaction_id_from(response)
  response.dig('additionalData', 'networkTxReference')
end
parse(body) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 768
def parse(body)
  return {} if body.blank?

  response = JSON.parse(body)
  add_header_fields(response)
  response
end
post_data(action, parameters = {}) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 925
def post_data(action, parameters = {})
  JSON.generate(parameters)
end
raw_authorize_error_message(response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 901
def raw_authorize_error_message(response)
  if response['refusalReason'] && response['additionalData'] && response['additionalData']['refusalReasonRaw']
    "#{response['refusalReason']} | #{response['additionalData']['refusalReasonRaw']}"
  else
    response['refusalReason'] || response['resultCode'] || response['message'] || response['result']
  end
end
request_headers(options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 844
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
success_from(action, response, options) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 853
def success_from(action, response, options)
  if %w[RedirectShopper ChallengeShopper].include?(response.dig('resultCode')) && !options[:execute_threed] && (!options[:threed_dynamic] || options[:ignore_threed_dynamic])
    response['refusalReason'] = 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.'
    return false
  end
  case action.to_s
  when 'authorise', 'authorise3d'
    %w[Authorised Received RedirectShopper].include?(response['resultCode'])
  when 'capture', 'refund', 'cancel', 'cancelOrRefund'
    response['response'] == "[#{action}-received]"
  when 'adjustAuthorisation'
    response['response'] == 'Authorised' || response['response'] == '[adjustAuthorisation-received]'
  when 'storeToken'
    response['result'] == 'Success'
  when 'disable'
    response['response'] == '[detail-successfully-disabled]'
  when 'refundWithData'
    response['resultCode'] == 'Received'
  when 'payout'
    return false unless response['resultCode'] && response['authCode']

    %[AuthenticationFinished Authorised Received].include?(response['resultCode'])
  else
    false
  end
end
unsupported_failure_response(initial_response) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 955
def unsupported_failure_response(initial_response)
  Response.new(
    false,
    'Recurring transactions are not supported for this card type.',
    initial_response.params,
    authorization: initial_response.authorization,
    test: initial_response.test,
    error_code: initial_response.error_code,
    avs_result: initial_response.avs_result,
    cvv_result: initial_response.cvv_result[:code]
  )
end
url(action) click to toggle source
# File lib/active_merchant/billing/gateways/adyen.rb, line 830
def url(action)
  if test?
    "#{test_url}#{endpoint(action)}"
  elsif @options[:subdomain]
    "https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/#{endpoint(action)}"
  else
    "#{live_url}#{endpoint(action)}"
  end
end