class PaymentHighway::Api

Public Class Methods

new(config:) click to toggle source
# File lib/payment_highway/api.rb, line 3
def initialize(config:)
  @config = config
end

Public Instance Methods

get_batch(date:) click to toggle source
# File lib/payment_highway/api.rb, line 7
def get_batch(date:)
  get "/report/batch/#{date.strftime('%Y%m%d')}"
end

Private Instance Methods

get(resource) click to toggle source
# File lib/payment_highway/api.rb, line 13
def get(resource)
  response = Faraday.new(url: @config.service).get do |req|

    req.headers['Content-Type'] = 'application/json'

    # Add Payment Highway specific headers
    signed_headers(
      method: 'GET',
      uri: resource,
      config: @config,
      values: [],
    ).each do |header, value|
      req.headers[header] = value
    end

    req.url resource
  end

  validate(response)

  [response, JSON.parse(response.body)]
end
signed_headers(method:, uri:, config:, values:, body: "", timestamp: Time.now.utc.strftime('%FT%TZ'), request_id: SecureRandom.uuid) click to toggle source
# File lib/payment_highway/api.rb, line 36
def signed_headers(method:, uri:, config:, values:, body: "", timestamp: Time.now.utc.strftime('%FT%TZ'), request_id: SecureRandom.uuid)
  headers = {
    'sph-account': config.account,
    'sph-merchant': config.merchant,
    'sph-timestamp': timestamp,
    'sph-request-id': request_id,
    'sph-api-version': config.version,
  }.compact # Removes optional nil parameters
  headers.merge({ signature: Signer.signature(config: config, method: method, uri: uri, headers: headers, body: body) })
end
validate(response) click to toggle source
# File lib/payment_highway/api.rb, line 47
def validate(response)
  # If server did not respond with 200 output the error message from the body
  unless response.status == 200
    raise Error, response.body
  end

  missing_mandatory_headers = [
    'sph-timestamp',
    'sph-request-id',
    'sph-response-id',
    'signature'
  ] - response.env[:response_headers].keys
  unless missing_mandatory_headers.empty?
    raise Error, "Mandatory headers #{missing_mandatory_headers} are missing from response"
  end
  # SPEC: the timestamp must be checked and it must not differ more than five (5) minutes from the correct global UTC time
  if (Time.now - Time.parse(response.env[:response_headers]['sph-timestamp'])) > 5 * 60
    raise Error, "Latency between server and client was over 5 minutes. Results should not be trusted!"
  end
  # SPEC: Check that request and response headers were the same
  if response.env[:response_headers]['sph-request-id'] != response.env[:request_headers]['sph-request-id']
    raise Error, "sph-request-id mismatch! request: (#{response.env[:request_headers]['sph-request-id']}) was not response: (#{response.env[:response_headers]['sph-request-id']})"
  end

  # Calculate signature again here and we should end up to the same result
  calculated_signature = Signer.signature(
    config: @config,
    method: response.env.method.to_s.upcase,
    uri: response.env.url.path.to_s,
    headers: response.env.response_headers.select {|h,v| h.start_with? 'sph-'}, # Only sph- headers are needed for signature
    body: response.env.body
  )
  if response.env[:response_headers]['signature'] != calculated_signature
    raise Error, "signature mismatch! sph-request-id: #{response.env[:response_headers]['sph-request-id']}"
  end
end