class Ingenico::Direct::SDK::Communicator

Class responsible for facilitating communication with the Ingenico ePayments platform. It combines the following classes to provide communication functionality:

api_endpoint

The base URI ({URI::HTTP}) to the Ingenico ePayments platform

connection

{Ingenico::Direct::SDK::Connection} used to communicate with the Ingenico ePayments platform

authenticator

{Ingenico::Direct::SDK::Authenticator} used for authenticating messages sent

meta_data_provider

{Ingenico::Direct::SDK::MetaDataProvider} object containing information relevant for sending requests

marshaller

{Ingenico::Direct::SDK::Marshaller} that is used to marshal and unmarshal data to and from JSON format

@attr_reader [Ingenico::Direct::SDK::Marshaller] marshaller A Marshaller instance used by the communicator for serializing/deserializing to/from JSON

Attributes

api_endpoint[R]
authenticator[R]
connection[R]
marshaller[R]
meta_data_provider[R]

Public Class Methods

new(api_endpoint, connection, authenticator, meta_data_provider, marshaller) click to toggle source

Creates a new Communicator based on the given arguments.

@param api_endpoint [String] the base URL to the Ingenico ePayments platform @param connection [Ingenico::Direct::SDK::Connection] used to communicate with the Ingenico ePayments platform @param authenticator [Ingenico::Direct::SDK::Authenticator] used for authenticating messages sent @param meta_data_provider [Ingenico::Direct::SDK::MetaDataProvider] object containing information relevant for sending requests @param marshaller [Ingenico::Direct::SDK::Marshaller] used to marshal and unmarshal data to and from JSON format

# File lib/ingenico/direct/sdk/communicator.rb, line 26
def initialize(api_endpoint, connection, authenticator, meta_data_provider, marshaller)
  raise ArgumentError, 'api_endpoint is required' unless api_endpoint
  raise ArgumentError, 'connection is required' unless connection
  raise ArgumentError, 'authenticator is required' unless authenticator
  raise ArgumentError, 'meta_data_provider is required' unless meta_data_provider
  raise ArgumentError('marshaller is required') unless marshaller

  @api_endpoint = URI(api_endpoint)
  if @api_endpoint.path.length.positive? || @api_endpoint.query || @api_endpoint.fragment
    raise ArgumentError, "Base URL should not contain a path, query or fragment #{@api_endpoint}"
  end
  @connection = connection
  @authenticator = authenticator
  @meta_data_provider = meta_data_provider
  @marshaller = marshaller
end

Public Instance Methods

close() click to toggle source

Frees networking resources by closing the underlying network connections. After calling close, any use of the get, delete, post and put methods will not function and likely result in an error.

# File lib/ingenico/direct/sdk/communicator.rb, line 211
def close
  @connection.close
end
close_expired_connections() click to toggle source

Closes any connections that have expired. Will not have any effect if the connection is not a pooled connection (an instance of {Ingenico::Direct::SDK::PooledConnection}).

# File lib/ingenico/direct/sdk/communicator.rb, line 190
def close_expired_connections
  @connection.close_expired_connections if connection.is_a? PooledConnection
end
close_idle_connections(idle_time) click to toggle source

Closes any connections idle for more than idle_time seconds. Will not have any effect if the connection is not a pooled connection (an instance of {Ingenico::Direct::SDK::PooledConnection}).

# File lib/ingenico/direct/sdk/communicator.rb, line 184
def close_idle_connections(idle_time)
  @connection.close_idle_connections(idle_time) if connection.is_a? PooledConnection
end
delete(relative_path, request_headers, request_parameters, response_type, context) click to toggle source

Performs a DELETE request to the Ingenico ePayments platform and returns the response as the given response type.

@param relative_path [String] Path relative to the API endpoint @param request_headers [Array<Ingenico::Direct::SDK::RequestHeader>, nil] Optional array of request headers @param request_parameters [Ingenico::Direct::SDK::ParamRequest, nil] Optional request parameters @param response_type [Type] The response type. @param context [Ingenico::Direct::SDK::CallContext, nil] Optional call context. @return The response of the DELETE request as the given response type @raise [Ingenico::Direct::SDK::ResponseException] if the request could not be fulfilled successfully.

This occurs for example if the request is not authenticated correctly

@raise [Ingenico::Direct::SDK::NotFoundException] if the requested resource is not found @raise [Ingenico::Direct::SDK::CommunicationException] if there is an error in communicating with the Ingenico ePayments platform.

This occurs for example if a timeout occurs.
# File lib/ingenico/direct/sdk/communicator.rb, line 86
def delete(relative_path, request_headers, request_parameters, response_type, context)
  connection = @connection
  request_parameter_list = request_parameters&.to_request_parameters
  uri = to_absolute_uri(relative_path, request_parameter_list)
  request_headers ||= []
  add_generic_headers('DELETE', uri, request_headers, context)

  response_status_code, response_headers, response_body = nil
  connection.delete(uri, request_headers) do |status_code, headers, content|
    response_status_code = status_code
    response_headers = headers
    response_body = content.read.force_encoding('UTF-8')
  end
  process_response(response_body, response_status_code, response_headers, response_type, relative_path, context)
end
disable_logging() click to toggle source

Disables logging by unregistering any previous logger that might be registered.

# File lib/ingenico/direct/sdk/communicator.rb, line 204
def disable_logging
  @connection.disable_logging
end
enable_logging(communicator_logger) click to toggle source

Enables logging outgoing requests and incoming responses by registering the communicator_logger. Note that only one logger can be registered at a time and calling enable_logging a second time will override the old logger instance with the new one.

@param communicator_logger [Ingenico::Direct::SDK::Logging::CommunicatorLogger] The communicator logger the requests and responses are logged to

# File lib/ingenico/direct/sdk/communicator.rb, line 199
def enable_logging(communicator_logger)
  @connection.enable_logging(communicator_logger)
end
get(relative_path, request_headers, request_parameters, response_type, context) click to toggle source

Performs a GET request to the Ingenico ePayments platform and returns the response as the given response type.

@param relative_path [String] path relative to the API endpoint @param request_headers [Array<Ingenico::Direct::SDK::RequestHeader>, nil] optional array of request headers @param request_parameters [Ingenico::Direct::SDK::ParamRequest, nil] optional request parameters @param response_type [Type] the response type. @param context [Ingenico::Direct::SDK::CallContext, nil] optional call context. @return the response of the GET request as the given response type @raise [Ingenico::Direct::SDK::ResponseException] if the request could not be fulfilled successfully.

This occurs for example if the request is not authenticated correctly

@raise [Ingenico::Direct::SDK::NotFoundException] if the requested resource is not found @raise [Ingenico::Direct::SDK::CommunicationException] if there is an error in communicating with the Ingenico ePayments platform.

This occurs for example if a timeout occurs.
# File lib/ingenico/direct/sdk/communicator.rb, line 56
def get(relative_path, request_headers, request_parameters, response_type, context)
  connection = @connection
  request_parameter_list = request_parameters&.to_request_parameters
  uri = to_absolute_uri(relative_path, request_parameter_list)

  request_headers ||= []
  add_generic_headers('GET', uri, request_headers, context)

  response_status_code, response_headers, response_body = nil
  connection.get(uri, request_headers) do |status_code, headers, content|
    response_status_code = status_code
    response_headers = headers
    response_body = content.read.force_encoding('UTF-8')
  end
  process_response(response_body, response_status_code, response_headers, response_type, relative_path, context)
end
post(relative_path, request_headers, request_parameters, request_body, response_type, context) click to toggle source

Performs a POST request to the Ingenico ePayments platform and returns the response as the given response type.

@param relative_path [String] Path relative to the API endpoint @param request_headers [Array<Ingenico::Direct::SDK::RequestHeader>, nil] Optional array of request headers @param request_parameters [Ingenico::Direct::SDK::ParamRequest, nil] Optional request parameters @param request_body [Ingenico::Direct::SDK::DataObject] The optional request body @param response_type [Type] The response type. @param context [Ingenico::Direct::SDK::CallContext, nil] Optional call context. @return The response of the POST request as the given response type @raise [Ingenico::Direct::SDK::ResponseException] if the request could not be fulfilled successfully.

This occurs for example if the request is not authenticated correctly

@raise [Ingenico::Direct::SDK::NotFoundException] if the requested resource is not found @raise [Ingenico::Direct::SDK::CommunicationException] if there is an error in communicating with the Ingenico ePayments platform.

This occurs for example if a timeout occurs.
# File lib/ingenico/direct/sdk/communicator.rb, line 116
def post(relative_path, request_headers, request_parameters, request_body, response_type, context)
  request_parameter_list = request_parameters&.to_request_parameters
  uri = to_absolute_uri(relative_path, request_parameter_list)
  request_headers ||= []

  body = nil
  if request_body
    request_headers.push(RequestHeader.new('Content-Type', 'application/json'))
    body = @marshaller.marshal(request_body)
  else
    # Set the content-type, even though there is no body, to prevent the httpClient from
    # adding a content-type header after authentication has been generated.
    request_headers.push(RequestHeader.new('Content-Type', 'text/plain'))
  end

  add_generic_headers('POST', uri, request_headers, context)

  response_status_code, response_headers, response_body = nil
  @connection.post(uri, request_headers, body) do |status_code, headers, content|
    response_status_code = status_code
    response_headers = headers
    response_body = content.read.force_encoding('UTF-8')
  end
  process_response(response_body, response_status_code, response_headers, response_type, relative_path, context)
end
put(relative_path, request_headers, request_parameters, request_body, response_type, context) click to toggle source

Performs a PUT request to the Ingenico ePayments platform and returns the response as the given response type.

@param relative_path [String] Path relative to the API endpoint @param request_headers [Array<Ingenico::Direct::SDK::RequestHeader>, nil] Optional array of request headers @param request_parameters [Ingenico::Direct::SDK::ParamRequest, nil] Optional request parameters @param request_body [Ingenico::Direct::SDK::DataObject]

The optional request body

@param response_type [Type] The response type. @param context [Ingenico::Direct::SDK::CallContext, nil] Optional call context. @return The response of the PUT request as the given response type @raise [Ingenico::Direct::SDK::ResponseException] if the request could not be fulfilled successfully.

This occurs for example if the request is not authenticated correctly

@raise [Ingenico::Direct::SDK::NotFoundException] if the requested resource is not found @raise [Ingenico::Direct::SDK::CommunicationException] if there is an error in communicating with the Ingenico ePayments platform.

This occurs for example if a timeout occurs.
# File lib/ingenico/direct/sdk/communicator.rb, line 157
def put(relative_path, request_headers, request_parameters, request_body, response_type, context)
  request_parameter_list = request_parameters&.to_request_parameters
  uri = to_absolute_uri(relative_path, request_parameter_list)
  request_headers ||= []

  body = nil
  if request_body
    request_headers.push(RequestHeader.new('Content-Type', 'application/json'))
    body = @marshaller.marshal(request_body)
  else
    # Set the content-type, even though there is no body, to prevent the httpClient from
    # adding a content-type header after authentication has been generated.
    request_headers.push(RequestHeader.new('Content-Type', 'text/plain'))
  end
  add_generic_headers('PUT', uri, request_headers, context)

  response_status_code, response_headers, response_body = nil
  @connection.put(uri, request_headers, body) do |status_code, headers, content|
    response_status_code = status_code
    response_headers = headers
    response_body = content.read.force_encoding('UTF-8')
  end
  process_response(response_body, response_status_code, response_headers, response_type, relative_path, context)
end

Protected Instance Methods

add_generic_headers(http_method, uri, request_headers, context = nil) click to toggle source

Adds several standard headers to the http headers. This method will add the 'Date' and 'Authorization' header; the 'X-GCS-Idempotence-Key' header will also be added if an idempotence context is given

@param http_method [String] 'GET', 'DELETE', 'POST' or 'PUT' depending on the HTTP method being used @param uri [URI::HTTP] The full URI to the Ingenico ePayments platform,

including the relative path and request parameters.

@param request_headers [Array<Ingenico::Direct::SDK::RequestHeader>] List of request headers in which which new headers will be added @param context [Ingenico::Direct::SDK::CallContext, nil] object that will be used to produce

an Idempotence header to prevent accidental request duplication.
# File lib/ingenico/direct/sdk/communicator.rb, line 256
def add_generic_headers(http_method, uri, request_headers, context = nil)
  request_headers.concat(meta_data_provider.meta_data_headers)
  request_headers.push(RequestHeader.new('Date', get_header_date_string))
  if context&.idempotence_key
    request_headers.push(RequestHeader.new('X-GCS-Idempotence-Key', context.idempotence_key))
  end
  authentication_signature = @authenticator.create_simple_authentication_signature(http_method, uri, request_headers)
  request_headers.push(RequestHeader.new('Authorization', authentication_signature))
end
get_header_date_string() click to toggle source
# File lib/ingenico/direct/sdk/communicator.rb, line 266
def get_header_date_string
  Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
end
process_response(body, status, headers, response_type, request_path, context) click to toggle source
# File lib/ingenico/direct/sdk/communicator.rb, line 270
def process_response(body, status, headers, response_type, request_path, context)
  update_context(headers, context) if context

  throw_exception_if_necessary(body, status, headers, request_path)
  @marshaller.unmarshal(body, response_type)
end
throw_exception_if_necessary(body, status_code, headers, request_path) click to toggle source
# File lib/ingenico/direct/sdk/communicator.rb, line 281
def throw_exception_if_necessary(body, status_code, headers, request_path)
  return if status_code >= 200 && status_code < 300
  raise ResponseException.new status_code, headers, body unless body && !is_json(headers)

  cause = ResponseException.new(status_code, headers, body)
  if status_code == 404
    raise NotFoundException, cause, "The requested resource was not found; invalid path: #{request_path}"
  end

  raise CommunicationException, cause
end
to_absolute_uri(relative_path, request_parameters) click to toggle source

Constructs a full URL using the base URL and the given relative path and request_parameters. The returned URL is a URI object.

@param relative_path [String] The relative path of the URL. @param request_parameters [Array<Ingenico::Direct::SDK::RequestParam>, nil] A list of request parameters that each have name and value

which represent the parameter name and value respectively.
# File lib/ingenico/direct/sdk/communicator.rb, line 230
def to_absolute_uri(relative_path, request_parameters)
  raise RuntimeError('api_endpoint should not contain a path') unless @api_endpoint.path.nil? || @api_endpoint.path.empty?

  if @api_endpoint.userinfo || @api_endpoint.query || @api_endpoint.fragment
    raise RuntimeError, 'api_endpoint should not contain user info, query or fragment'
  end
  absolute_path = relative_path.start_with?('/') ? relative_path : "/#{relative_path}"
  uri = URI::HTTP.new(@api_endpoint.scheme, nil, @api_endpoint.host,
                      @api_endpoint.port, nil, absolute_path, nil, nil, nil)
  request_parameters&.each do |nvp|
    parameters = URI.decode_www_form(uri.query || '') << [nvp.name, nvp.value]
    uri.query = URI.encode_www_form(parameters)
  end
  uri
end
update_context(headers, context) click to toggle source
# File lib/ingenico/direct/sdk/communicator.rb, line 277
def update_context(headers, context)
  context.idempotence_request_timestamp = ResponseHeader.get_header_value(headers, 'X-GCS-Idempotence-Request-Timestamp')
end

Private Instance Methods

is_json(headers) click to toggle source
# File lib/ingenico/direct/sdk/communicator.rb, line 295
def is_json(headers)
  content_type = ResponseHeader.get_header_value(headers, 'Content-Type')
  content_type.nil? || 'application/json'.casecmp(content_type).zero? ||
    content_type.downcase.start_with?('application/json')
end