module MultiSafePay class Client LIVE_API_ENDPOINT = 'https://api.multisafepay.com/v1/json'.freeze TEST_API_ENDPOINT = 'https://testapi.multisafepay.com/v1/json'.freeze MODE_TEST = :test MODE_LIVE = :live class << self attr_accessor :configuration end class Configuration attr_accessor :api_key, :environment, :open_timeout, :read_timeout, :debug def initialize @api_key = '' @debug = false @environment = MODE_LIVE @open_timeout = 60 @read_timeout = 60 end end def self.configure self.configuration ||= Configuration.new yield(configuration) end # @return [MultiSafePay::Client] def self.instance Thread.current['MULTISAFEPAY_CLIENT'] ||= begin self.configuration ||= Configuration.new new(configuration.api_key, configuration.environment) end end def self.with_api_key(api_key, environment = MODE_LIVE) Thread.current['MULTISAFEPAY_API_KEY'] = instance.api_key Thread.current['MULTISAFEPAY_ENVIRONMENT'] = instance.environment instance.api_key = api_key instance.environment = environment yield ensure instance.api_key = Thread.current['MULTISAFEPAY_API_KEY'] instance.environment = Thread.current['MULTISAFEPAY_ENVIRONMENT'] Thread.current['MULTISAFEPAY_API_KEY'] = nil Thread.current['MULTISAFEPAY_ENVIRONMENT'] = nil end attr_accessor :api_key, :environment def initialize(api_key = nil, environment = MODE_LIVE) @api_key = api_key @environment = environment @version_strings = [] add_version_string 'MultiSafePay/' << VERSION add_version_string 'Ruby/' << RUBY_VERSION add_version_string OpenSSL::OPENSSL_VERSION.split(' ').slice(0, 2).join '/' end def live? @environment == MODE_LIVE end def test? !live? end def api_endpoint @api_endpoint || (live? ? LIVE_API_ENDPOINT : TEST_API_ENDPOINT) end def api_endpoint=(api_endpoint) @api_endpoint = api_endpoint.chomp('/') end def add_version_string(version_string) @version_strings << version_string.gsub(/\s+/, '-') end def get(api_method, id = nil, query = {}) perform_http_call('GET', api_method, id, {}, query) end def post(api_method, id = nil, http_body = {}, query = {}) perform_http_call('POST', api_method, id, http_body, query) end def patch(api_method, id = nil, http_body = {}, query = {}) perform_http_call('PATCH', api_method, id, http_body, query) end def delete(api_method, id = nil, http_body = {}, query = {}) perform_http_call('DELETE', api_method, id, http_body, query) end def perform_http_call(http_method, api_method, id = nil, http_body = {}, query = {}) path = if api_method.start_with?(self.api_endpoint) URI.parse(api_method).path else "#{URI.parse(self.api_endpoint).path}/#{api_method}/#{id}".chomp('/') end environment = http_body.delete(:environment) || query.delete(:environment) || @environment api_endpoint = http_body.delete(:api_endpoint) || query.delete(:api_endpoint) || (environment == MODE_LIVE ? LIVE_API_ENDPOINT : TEST_API_ENDPOINT) api_key = http_body.delete(:api_key) || query.delete(:api_key) || @api_key unless query.empty? path += "?#{build_nested_query(query)}" end uri = URI.parse(api_endpoint) client = Net::HTTP.new(uri.host, uri.port) client.use_ssl = true client.verify_mode = OpenSSL::SSL::VERIFY_PEER client.ca_file = (File.expand_path '../cacert.pem', File.dirname(__FILE__)) client.read_timeout = self.class.configuration.read_timeout client.open_timeout = self.class.configuration.open_timeout if self.class.configuration.debug puts " -> api_token: #{api_key}" puts " -> api_endpoint: #{api_endpoint}" puts " -> path: #{path}" puts " -> http_body: #{http_body}" end case http_method when 'GET' request = Net::HTTP::Get.new(path) when 'POST' http_body.delete_if { |_k, v| v.nil? } request = Net::HTTP::Post.new(path) request.body = http_body.to_json when 'PATCH' http_body.delete_if { |_k, v| v.nil? } request = Net::HTTP::Patch.new(path) request.body = http_body.to_json when 'DELETE' http_body.delete_if { |_k, v| v.nil? } request = Net::HTTP::Delete.new(path) request.body = http_body.to_json else raise MultiSafePay::Exception, "Invalid HTTP Method: #{http_method}" end request['Accept'] = 'application/json' request['Content-Type'] = 'application/json' request['User-Agent'] = @version_strings.join(' ') request['api-key'] = api_key begin response = client.request(request) rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e raise MultiSafePay::Exception, e.message end if self.class.configuration.debug puts " => response code: #{response.code}" puts " => response body: #{response.body}" end http_code = response.code.to_i case http_code when 200, 201 JSON.parse(response.body) when 204 {} # No Content when 404 json = JSON.parse(response.body) exception = ResourceNotFoundError.new(json, response) raise exception else json = JSON.parse(response.body) exception = MultiSafePay::RequestError.new(json, response) raise exception end end private def build_nested_query(value, prefix = nil) case value when Array value.map do |v| build_nested_query(v, "#{prefix}[]") end.join('&') when Hash value.map do |k, v| build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) end.reject(&:empty?).join('&') when nil prefix else raise ArgumentError, 'value must be a Hash' if prefix.nil? "#{prefix}=#{escape(value)}" end end def escape(s) URI.encode_www_form_component(s) end end end