module Stripe

def self.api_error(error, resp, error_obj)

def self.api_error(error, resp, error_obj)
  APIError.new(error[:message], resp.code, resp.body, error_obj, resp.headers)
end

def self.api_url(url='', api_base_url=nil)

def self.api_url(url='', api_base_url=nil)
  (api_base_url || @api_base) + url
end

def self.authentication_error(error, resp, error_obj)

def self.authentication_error(error, resp, error_obj)
  AuthenticationError.new(error[:message], resp.code, resp.body, error_obj,
                          resp.headers)
end

def self.ca_bundle_path

Stripe certificates.
the library will use an included bundle that can successfully validate
The location of a file containing a bundle of CA certificates. By default
def self.ca_bundle_path
  @ca_bundle_path
end

def self.ca_bundle_path=(path)

def self.ca_bundle_path=(path)
  @ca_bundle_path = path
  # empty this field so a new store is initialized
  @ca_store = nil
end

def self.ca_store

and is itself not thread safe.
`Stripe.ca_store`) in their initialization code because it marshals lazily
to leverage this pseudo safety should make a call to this method (i.e.
the most likely point of failure (see issue #382). Any program attempting
when initiating many parallel requests marshaling the certificate store is
This was added to the give the gem "pseudo thread safety" in that it seems

which is used to validate TLS on every request.
A certificate store initialized from the the bundle in #ca_bundle_path and
def self.ca_store
  @ca_store ||= begin
    store = OpenSSL::X509::Store.new
    store.add_file(ca_bundle_path)
    store
  end
end

def self.card_error(error, resp, error_obj)

def self.card_error(error, resp, error_obj)
  CardError.new(error[:message], error[:param], error[:code],
                resp.code, resp.body, error_obj, resp.headers)
end

def self.execute_request(opts)

def self.execute_request(opts)
  RestClient::Request.execute(opts)
end

def self.execute_request_with_rescues(request_opts, api_base_url, retry_count = 0)

def self.execute_request_with_rescues(request_opts, api_base_url, retry_count = 0)
  begin
    response = execute_request(request_opts)
  # We rescue all exceptions from a request so that we have an easy spot to
  # implement our retry logic across the board. We'll re-raise if it's a type
  # of exception that we didn't expect to handle.
  rescue => e
    if should_retry?(e, retry_count)
      retry_count = retry_count + 1
      sleep sleep_time(retry_count)
      retry
    end
    case e
    when SocketError
      response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
    when RestClient::ExceptionWithResponse
      if e.response
        handle_api_error(e.response)
      else
        response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
      end
    when RestClient::Exception, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError
      response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
    # Only handle errors when we know we can do so, and re-raise otherwise.
    # This should be pretty infrequent.
    else
      raise
    end
  end
  response
end

def self.general_api_error(rcode, rbody)

def self.general_api_error(rcode, rbody)
  APIError.new("Invalid response object from API: #{rbody.inspect} " +
               "(HTTP response code was #{rcode})", rcode, rbody)
end

def self.get_uname

def self.get_uname
  if File.exist?('/proc/version')
    File.read('/proc/version').strip
  else
    case RbConfig::CONFIG['host_os']
    when /linux|darwin|bsd|sunos|solaris|cygwin/i
      get_uname_from_system
    when /mswin|mingw/i
      get_uname_from_system_ver
    else
      "unknown platform"
    end
  end
end

def self.get_uname_from_system

def self.get_uname_from_system
  (`uname -a 2>/dev/null` || '').strip
rescue Errno::ENOENT
  "uname executable not found"
rescue Errno::ENOMEM # couldn't create subprocess
  "uname lookup failed"
end

def self.get_uname_from_system_ver

def self.get_uname_from_system_ver
  (`ver` || '').strip
rescue Errno::ENOENT
  "ver executable not found"
rescue Errno::ENOMEM # couldn't create subprocess
  "uname lookup failed"
end

def self.handle_api_error(resp)

def self.handle_api_error(resp)
  begin
    error_obj = JSON.parse(resp.body)
    error_obj = Util.symbolize_names(error_obj)
    error = error_obj[:error]
    raise StripeError.new unless error && error.is_a?(Hash)
  rescue JSON::ParserError, StripeError
    raise general_api_error(resp.code, resp.body)
  end
  case resp.code
  when 400, 404
    raise invalid_request_error(error, resp, error_obj)
  when 401
    raise authentication_error(error, resp, error_obj)
  when 402
    raise card_error(error, resp, error_obj)
  when 403
    raise permission_error(error, resp, error_obj)
  when 429
    raise rate_limit_error(error, resp, error_obj)
  else
    raise api_error(error, resp, error_obj)
  end
end

def self.handle_restclient_error(e, request_opts, retry_count, api_base_url=nil)

def self.handle_restclient_error(e, request_opts, retry_count, api_base_url=nil)
  api_base_url = @api_base unless api_base_url
  connection_message = "Please check your internet connection and try again. " \
      "If this problem persists, you should check Stripe's service status at " \
      "https://twitter.com/stripestatus, or let us know at support@stripe.com."
  case e
  when RestClient::RequestTimeout
    message = "Could not connect to Stripe (#{api_base_url}). #{connection_message}"
  when RestClient::ServerBrokeConnection
    message = "The connection to the server (#{api_base_url}) broke before the " \
      "request completed. #{connection_message}"
  when OpenSSL::SSL::SSLError
    message = "Could not establish a secure connection to Stripe, you may " \
              "need to upgrade your OpenSSL version. To check, try running " \
              "'openssl s_client -connect api.stripe.com:443' from the " \
              "command line."
  when RestClient::SSLCertificateNotVerified
    message = "Could not verify Stripe's SSL certificate. " \
      "Please make sure that your network is not intercepting certificates. " \
      "(Try going to https://api.stripe.com/v1 in your browser.) " \
      "If this problem persists, let us know at support@stripe.com."
  when SocketError
    message = "Unexpected error communicating when trying to connect to Stripe. " \
      "You may be seeing this message because your DNS is not working. " \
      "To check, try running 'host stripe.com' from the command line."
  else
    message = "Unexpected error communicating with Stripe. " \
      "If this problem persists, let us know at support@stripe.com."
  end
  if retry_count > 0
    message += " Request was retried #{retry_count} times."
  end
  raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
end

def self.invalid_request_error(error, resp, error_obj)

def self.invalid_request_error(error, resp, error_obj)
  InvalidRequestError.new(error[:message], error[:param], resp.code,
                          resp.body, error_obj, resp.headers)
end

def self.max_network_retries

def self.max_network_retries
  @max_network_retries
end

def self.max_network_retries=(val)

def self.max_network_retries=(val)
  @max_network_retries = val.to_i
end

def self.parse(response)

def self.parse(response)
  begin
    # Would use :symbolize_names => true, but apparently there is
    # some library out there that makes symbolize_names not work.
    response = JSON.parse(response.body)
  rescue JSON::ParserError
    raise general_api_error(response.code, response.body)
  end
  Util.symbolize_names(response)
end

def self.permission_error(error, resp, error_obj)

def self.permission_error(error, resp, error_obj)
  PermissionError.new(error[:message], resp.code, resp.body, error_obj, resp.headers)
end

def self.rate_limit_error(error, resp, error_obj)

def self.rate_limit_error(error, resp, error_obj)
  RateLimitError.new(error[:message], resp.code, resp.body, error_obj,
                     resp.headers)
end

def self.request(method, url, api_key, params={}, headers={}, api_base_url=nil)

def self.request(method, url, api_key, params={}, headers={}, api_base_url=nil)
  api_base_url = api_base_url || @api_base
  unless api_key ||= @api_key
    raise AuthenticationError.new('No API key provided. ' \
      'Set your API key using "Stripe.api_key = <API-KEY>". ' \
      'You can generate API keys from the Stripe web interface. ' \
      'See https://stripe.com/api for details, or email support@stripe.com ' \
      'if you have any questions.')
  end
  if api_key =~ /\s/
    raise AuthenticationError.new('Your API key is invalid, as it contains ' \
      'whitespace. (HINT: You can double-check your API key from the ' \
      'Stripe web interface. See https://stripe.com/api for details, or ' \
      'email support@stripe.com if you have any questions.)')
  end
  if verify_ssl_certs
    request_opts = {:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
                    :ssl_cert_store => ca_store}
  else
    request_opts = {:verify_ssl => false}
    unless @verify_ssl_warned
      @verify_ssl_warned = true
      $stderr.puts("WARNING: Running without SSL cert verification. " \
        "You should never do this in production. " \
        "Execute 'Stripe.verify_ssl_certs = true' to enable verification.")
    end
  end
  params = Util.objects_to_ids(params)
  url = api_url(url, api_base_url)
  case method.to_s.downcase.to_sym
  when :get, :head, :delete
    # Make params into GET parameters
    url += "#{URI.parse(url).query ? '&' : '?'}#{Util.encode_parameters(params)}" if params && params.any?
    payload = nil
  else
    if headers[:content_type] && headers[:content_type] == "multipart/form-data"
      payload = params
    else
      payload = Util.encode_parameters(params)
    end
  end
  request_opts.update(:headers => request_headers(api_key, method).update(headers),
                      :method => method, :open_timeout => open_timeout,
                      :payload => payload, :url => url, :timeout => read_timeout)
  response = execute_request_with_rescues(request_opts, api_base_url)
  [parse(response), api_key]
end

def self.request_headers(api_key, method)

def self.request_headers(api_key, method)
  headers = {
    'User-Agent' => "Stripe/v1 RubyBindings/#{Stripe::VERSION}",
    'Authorization' => "Bearer #{api_key}",
    'Content-Type' => 'application/x-www-form-urlencoded'
  }
  # It is only safe to retry network failures on post and delete
  # requests if we add an Idempotency-Key header
  if [:post, :delete].include?(method) && self.max_network_retries > 0
    headers['Idempotency-Key'] ||= SecureRandom.uuid
  end
  headers['Stripe-Version'] = api_version if api_version
  headers['Stripe-Account'] = stripe_account if stripe_account
  begin
    headers.update('X-Stripe-Client-User-Agent' => JSON.generate(user_agent))
  rescue => e
    headers.update('X-Stripe-Client-Raw-User-Agent' => user_agent.inspect,
                   :error => "#{e} (#{e.class})")
  end
end

def self.should_retry?(e, retry_count)

def self.should_retry?(e, retry_count)
  retry_count < self.max_network_retries &&
    RETRY_EXCEPTIONS.any? { |klass| e.is_a?(klass) }
end

def self.sleep_time(retry_count)

def self.sleep_time(retry_count)
  # Apply exponential backoff with initial_network_retry_delay on the number
  # of attempts so far as inputs. Do not allow the number to exceed
  # max_network_retry_delay.
  sleep_seconds = [initial_network_retry_delay * (2 ** (retry_count - 1)), max_network_retry_delay].min
  # Apply some jitter by randomizing the value in the range of (sleep_seconds
  # / 2) to (sleep_seconds).
  sleep_seconds = sleep_seconds * (0.5 * (1 + rand()))
  # But never sleep less than the base sleep seconds.
  sleep_seconds = [initial_network_retry_delay, sleep_seconds].max
  sleep_seconds
end

def self.uri_encode(params)

DEPRECATED. Use `Util#encode_parameters` instead.
def self.uri_encode(params)
  Util.encode_parameters(params)
end

def self.user_agent

def self.user_agent
  @uname ||= get_uname
  lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
  {
    :bindings_version => Stripe::VERSION,
    :lang => 'ruby',
    :lang_version => lang_version,
    :platform => RUBY_PLATFORM,
    :engine => defined?(RUBY_ENGINE) ? RUBY_ENGINE : '',
    :publisher => 'stripe',
    :uname => @uname,
    :hostname => Socket.gethostname,
  }
end