18F/identity-idp

View on GitHub
app/services/proofing/socure/id_plus/request.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Proofing
  module Socure
    module IdPlus
      class RequestError < StandardError
        def initialize(wrapped)
          @wrapped = wrapped
          super(build_message)
        end

        def reference_id
          return @reference_id if defined?(@reference_id)
          @reference_id = response_body.is_a?(Hash) ?
            response_body['referenceId'] :
            nil
        end

        def response_body
          return @response_body if defined?(@response_body)
          @response_body = wrapped.try(:response_body)
        end

        def response_status
          return @response_status if defined?(@response_status)
          @response_status = wrapped.try(:response_status)
        end

        private

        attr_reader :wrapped

        def build_message
          message = response_body.is_a?(Hash) ? response_body['msg'] : nil
          message ||= wrapped.message
          status = response_status ? " (#{response_status})" : ''
          [message, status].join('')
        end
      end

      class Request
        attr_reader :config, :input

        SERVICE_NAME = 'socure_id_plus'

        # @param [Proofing::Socure::IdPlus::Config] config
        # @param [Proofing::Socure::IdPlus::Input] input
        def initialize(config:, input:)
          @config = config
          @input = input
        end

        def send_request
          conn = Faraday.new do |f|
            f.request :instrumentation, name: 'request_metric.faraday'
            f.response :raise_error
            f.response :json
            f.options.timeout = config.timeout
            f.options.read_timeout = config.timeout
            f.options.open_timeout = config.timeout
            f.options.write_timeout = config.timeout
          end

          Response.new(
            conn.post(url, body, headers) do |req|
              req.options.context = { service_name: SERVICE_NAME }
            end,
          )
        rescue Faraday::BadRequestError,
               Faraday::ConnectionFailed,
               Faraday::ServerError,
               Faraday::SSLError,
               Faraday::TimeoutError,
               Faraday::UnauthorizedError => e

          if timeout_error?(e)
            raise ::Proofing::TimeoutError,
                  'Timed out waiting for verification response'
          end

          raise RequestError, e
        end

        def body
          @body ||= {
            modules: ['kyc'],
            firstName: input.first_name,
            surName: input.last_name,
            country: 'US',

            physicalAddress: input.address1,
            physicalAddress2: input.address2,
            city: input.city,
            state: input.state,
            zip: input.zipcode,

            nationalId: input.ssn,
            dob: input.dob&.to_date&.to_s,

            userConsent: true,
            consentTimestamp: input.consent_given_at&.to_time&.iso8601,

            email: input.email,
            mobileNumber: input.phone,

            # > The country or jurisdiction from where the transaction originates,
            # > specified in ISO-2 country codes format
            countryOfOrigin: 'US',
          }.to_json
        end

        def headers
          @headers ||= {
            'Content-Type' => 'application/json',
            'Authorization' => "SocureApiKey #{config.api_key}",
          }
        end

        def url
          @url ||= URI.join(
            config.base_url,
            '/api/3.0/EmailAuthScore',
          ).to_s
        end

        private

        # @param [Faraday::Error] err
        def faraday_error_message(err)
          message = begin
            err.response[:body].dig('msg')
          rescue
            'HTTP request failed'
          end

          status = begin
            err.response[:status]
          rescue
            'unknown status'
          end

          "#{message} (#{status})"
        end

        def timeout_error?(err)
          err.is_a?(Faraday::TimeoutError) ||
            (err.is_a?(Faraday::ConnectionFailed) && err.wrapped_exception.is_a?(Net::OpenTimeout))
        end
      end
    end
  end
end