# rubocop:disable Metrics/ParameterLists # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/ClassLength require 'faraday' require 'json' require_relative './errors' require_relative './result' module Adyen class Client attr_accessor :ws_user, :ws_password, :api_key, :oauth_token, :client, :adapter attr_reader :env, :connection_options, :adapter_options def initialize(ws_user: nil, ws_password: nil, api_key: nil, oauth_token: nil, env: :live, adapter: nil, mock_port: 3001, live_url_prefix: nil, mock_service_url_base: nil, connection_options: nil, adapter_options: nil) @ws_user = ws_user @ws_password = ws_password @api_key = api_key @oauth_token = oauth_token @env = env @adapter = adapter || Faraday.default_adapter if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new('2.1') # for faraday 2.1 and higher @adapter_options = adapter_options || Faraday.default_adapter_options else # for faraday 1.x and 2.0 @adapter_options = adapter_options || {} end @mock_service_url_base = mock_service_url_base || "http://localhost:#{mock_port}" @live_url_prefix = live_url_prefix @connection_options = connection_options || Faraday::ConnectionOptions.new end # make sure that env can only be :live, :test, or :mock def env=(value) raise ArgumentError, "Invalid value for Client.env: '#{value}'' - must be one of [:live, :test, :mock]" unless %i[ live test mock ].include? value @env = value end # remove 'https' from live_url_prefix if necessary def live_url_prefix=(value) value['https://'] = '' unless value['https://'].nil? @live_url_prefix = value end # base URL for API given service and @env def service_url_base(service) if @env == :mock @mock_service_url_base else case service when 'Checkout' url = "https://checkout-#{@env}.adyen.com" supports_live_url_prefix = true when 'Account', 'Fund', 'Notification', 'Hop' url = "https://cal-#{@env}.adyen.com/cal/services/#{service}" supports_live_url_prefix = false when 'Recurring', 'Payment', 'Payout', 'BinLookup', 'StoredValue', 'BalanceControlService' url = "https://pal-#{@env}.adyen.com/pal/servlet/#{service}" supports_live_url_prefix = true when 'PosTerminalManagement' url = "https://postfmapi-#{@env}.adyen.com/postfmapi/terminal" supports_live_url_prefix = false when 'DataProtectionService', 'DisputesService' url = "https://ca-#{@env}.adyen.com/ca/services/#{service}" supports_live_url_prefix = false when 'LegalEntityManagement' url = "https://kyc-#{@env}.adyen.com/lem" supports_live_url_prefix = false when 'BalancePlatform' url = "https://balanceplatform-api-#{@env}.adyen.com/bcl" supports_live_url_prefix = false when 'Transfers' url = "https://balanceplatform-api-#{@env}.adyen.com/btl" supports_live_url_prefix = false when 'Management' url = "https://management-#{@env}.adyen.com" supports_live_url_prefix = false when 'TerminalCloudAPI' url = "https://terminal-api-#{@env}.adyen.com" supports_live_url_prefix = false else raise ArgumentError, 'Invalid service specified' end if @live_url_prefix.nil? && (@env == :live) && supports_live_url_prefix raise ArgumentError, "Please set Client.live_url_prefix to the portion \ of your merchant-specific URL prior to '-[service]-live.adyenpayments.com'" end if @env == :live && supports_live_url_prefix url.insert(8, "#{@live_url_prefix}-") url['adyen.com'] = 'adyenpayments.com' end url end end # construct full URL from service and endpoint def service_url(service, action, version) if service == "Checkout" && @env == :live return "#{service_url_base(service)}/checkout/v#{version}/#{action}" elsif version == nil return "#{service_url_base(service)}/#{action}" else return "#{service_url_base(service)}/v#{version}/#{action}" end end # send request to adyen API def call_adyen_api(service, action, request_data, headers, version, _with_application_info: false) # get URL for requested endpoint url = service_url(service, action.is_a?(String) ? action : action.fetch(:url), version) auth_type = auth_type(service, request_data) # initialize Faraday connection object conn = Faraday.new(url, @connection_options) do |faraday| faraday.adapter @adapter, **@adapter_options faraday.headers['Content-Type'] = 'application/json' faraday.headers['User-Agent'] = "#{Adyen::NAME}/#{Adyen::VERSION}" # set header based on auth_type and service auth_header(auth_type, faraday) # add optional headers if specified in request # will overwrite default headers if overlapping headers.map do |key, value| faraday.headers[key] = value end # add library headers faraday.headers['adyen-library-name'] = Adyen::NAME faraday.headers['adyen-library-version'] = Adyen::VERSION end # if json string convert to hash # needed to add applicationInfo request_data = JSON.parse(request_data) if request_data.is_a?(String) # convert to json request_data = request_data.to_json if action.is_a?(::Hash) if action.fetch(:method) == 'get' begin response = conn.get rescue Faraday::ConnectionFailed => e raise e, "Connection to #{url} failed" end end if action.fetch(:method) == 'delete' begin response = conn.delete rescue Faraday::ConnectionFailed => e raise e, "Connection to #{url} failed" end end if action.fetch(:method) == 'patch' begin response = conn.patch do |req| req.body = request_data end rescue Faraday::ConnectionFailed => e raise e, "Connection to #{url} failed" end end if action.fetch(:method) == 'post' # post request to Adyen begin response = conn.post do |req| req.body = request_data end rescue Faraday::ConnectionFailed => e raise e, "Connection to #{url} failed" end end else begin response = conn.post do |req| req.body = request_data end rescue Faraday::ConnectionFailed => e raise e, "Connection to #{url} failed" end end # check for API errors case response.status when 401 raise Adyen::AuthenticationError.new( 'Invalid API authentication; https://docs.adyen.com/user-management/how-to-get-the-api-key', request_data ) when 403 raise Adyen::PermissionError.new('Missing user permissions; https://docs.adyen.com/user-management/user-roles', request_data, response.body) end # delete has no response.body (unless it throws an error) if response.body.nil? || response.body === '' AdyenResult.new('{}', response.headers, response.status) # terminal API async call returns always 'ok' elsif response.body === 'ok' AdyenResult.new('{}', response.headers, response.status) else AdyenResult.new(response.body, response.headers, response.status) end end # services def checkout @checkout ||= Adyen::Checkout.new(self) end def payment @payment ||= Adyen::Payment.new(self) end def payout @payout ||= Adyen::Payout.new(self) end def recurring @recurring ||= Adyen::Recurring.new(self) end def marketpay @marketpay ||= Adyen::Marketpay::Marketpay.new(self) end def pos_terminal_management @pos_terminal_management ||= Adyen::PosTerminalManagement.new(self) end def data_protection @data_protection ||= Adyen::DataProtection.new(self) end def disputes @disputes ||= Adyen::Disputes.new(self) end def bin_lookup @bin_lookup ||= Adyen::BinLookup.new(self) end def legal_entity_management @legal_entity_management ||= Adyen::LegalEntityManagement.new(self) end def balance_platform @balance_platform ||= Adyen::BalancePlatform.new(self) end def transfers @transfers ||= Adyen::Transfers.new(self) end def management @management ||= Adyen::Management.new(self) end def stored_value @stored_value ||= Adyen::StoredValue.new(self) end def balance_control_service @balance_control_service ||= Adyen::BalanceControlService.new(self) end def terminal_cloud_api @terminal_cloud_api ||= Adyen::TerminalCloudAPI.new(self) end private def auth_header(auth_type, faraday) case auth_type when "basic" if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new('2.0') # for faraday 2.0 and higher faraday.request :authorization, :basic, @ws_user, @ws_password else # for faraday 1.x faraday.basic_auth(@ws_user, @ws_password) end when "api-key" faraday.headers["x-api-key"] = @api_key when "oauth" faraday.headers["Authorization"] = "Bearer #{@oauth_token}" end end def auth_type(service, request_data) # make sure valid authentication has been provided validate_auth_type(service, request_data) # Will prioritize authentication methods in this order: # api-key, oauth, basic return "api-key" unless @api_key.nil? return "oauth" unless @oauth_token.nil? "basic" end def validate_auth_type(service, request_data) # ensure authentication has been provided if @api_key.nil? && @oauth_token.nil? && (@ws_password.nil? || @ws_user.nil?) raise Adyen::AuthenticationError.new( 'No authentication found - please set api_key, oauth_token, or ws_user and ws_password', request_data ) end if service == "PaymentSetupAndVerification" && @api_key.nil? && @oauth_token.nil? && @ws_password.nil? && @ws_user.nil? raise Adyen::AuthenticationError.new('Checkout service requires API-key or oauth_token', request_data), 'Checkout service requires API-key or oauth_token' end end end end # rubocop:enable all