class Stripe::StripeClient

contains information on the HTTP call.
recover both a resource a call returns as well as a response object that
StripeClient executes requests against the Stripe API and allows a user to

def self.active_client

def self.active_client
  Thread.current[:stripe_client] || default_client
end

def self.default_client

def self.default_client
  Thread.current[:stripe_client_default_client] ||=
    StripeClient.new(default_conn)
end

def self.default_conn

connection and wrapping it in a StripeClient object should be preferred.
object should never be mutated, and instead instantiating your own
A default Faraday connection to be used when one isn't configured. This
def self.default_conn
  # We're going to keep connections around so that we can take advantage
  # of connection re-use, so make sure that we have a separate connection
  # object per thread.
  Thread.current[:stripe_client_default_conn] ||= begin
    conn = Faraday.new do |builder|
      builder.use Faraday::Request::Multipart
      builder.use Faraday::Request::UrlEncoded
      builder.use Faraday::Response::RaiseError
      # Net::HTTP::Persistent doesn't seem to do well on Windows or JRuby,
      # so fall back to default there.
      if Gem.win_platform? || RUBY_PLATFORM == "java"
        builder.adapter :net_http
      else
        builder.adapter :net_http_persistent
      end
    end
    conn.proxy = Stripe.proxy if Stripe.proxy
    if Stripe.verify_ssl_certs
      conn.ssl.verify = true
      conn.ssl.cert_store = Stripe.ca_store
    else
      conn.ssl.verify = false
      unless @verify_ssl_warned
        @verify_ssl_warned = true
        warn("WARNING: Running without SSL cert verification. " \
          "You should never do this in production. " \
          "Execute `Stripe.verify_ssl_certs = true` to enable " \
          "verification.")
      end
    end
    conn
  end
end

def self.should_retry?(error, num_retries)

special HTTP statuses.
both socket errors that may represent an intermittent problem and some
Checks if an error is a problem that we should retry on. This includes
def self.should_retry?(error, num_retries)
  return false if num_retries >= Stripe.max_network_retries
  # Retry on timeout-related problems (either on open or read).
  return true if error.is_a?(Faraday::TimeoutError)
  # Destination refused the connection, the connection was reset, or a
  # variety of other connection failures. This could occur from a single
  # saturated server, so retry in case it's intermittent.
  return true if error.is_a?(Faraday::ConnectionFailed)
  if error.is_a?(Faraday::ClientError) && error.response
    # 409 conflict
    return true if error.response[:status] == 409
  end
  false
end

def self.sleep_time(num_retries)

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

def api_url(url = "", api_base = nil)

def api_url(url = "", api_base = nil)
ase || Stripe.api_base) + url

def check_api_key!(api_key)

def check_api_key!(api_key)
 api_key
e AuthenticationError, "No API key provided. " \
et your API key using "Stripe.api_key = <API-KEY>". ' \
ou can generate API keys from the Stripe web interface. " \
ee https://stripe.com/api for details, or email " \
upport@stripe.com if you have any questions."
 unless api_key =~ /\s/
AuthenticationError, "Your API key is invalid, as it contains " \
tespace. (HINT: You can double-check your API key from the " \
ipe web interface. See https://stripe.com/api for details, or " \
il support@stripe.com if you have any questions.)"

def execute_request(method, path,

def execute_request(method, path,
                    api_base: nil, api_key: nil, headers: {}, params: {})
  api_base ||= Stripe.api_base
  api_key ||= Stripe.api_key
  params = Util.objects_to_ids(params)
  check_api_key!(api_key)
  body = nil
  query_params = nil
  case method.to_s.downcase.to_sym
  when :get, :head, :delete
    query_params = params
  else
    body = params
  end
  # This works around an edge case where we end up with both query
  # parameters in `query_params` and query parameters that are appended
  # onto the end of the given path. In this case, Faraday will silently
  # discard the URL's parameters which may break a request.
  #
  # Here we decode any parameters that were added onto the end of a path
  # and add them to `query_params` so that all parameters end up in one
  # place and all of them are correctly included in the final request.
  u = URI.parse(path)
  unless u.query.nil?
    query_params ||= {}
    query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
    # Reset the path minus any query parameters that were specified.
    path = u.path
  end
  headers = request_headers(api_key, method)
            .update(Util.normalize_headers(headers))
  params_encoder = FaradayStripeEncoder.new
  url = api_url(path, api_base)
  # stores information on the request we're about to make so that we don't
  # have to pass as many parameters around for logging.
  context = RequestLogContext.new
  context.account         = headers["Stripe-Account"]
  context.api_key         = api_key
  context.api_version     = headers["Stripe-Version"]
  context.body            = body ? params_encoder.encode(body) : nil
  context.idempotency_key = headers["Idempotency-Key"]
  context.method          = method
  context.path            = path
  context.query_params    = if query_params
                              params_encoder.encode(query_params)
                            end
  # note that both request body and query params will be passed through
  # `FaradayStripeEncoder`
  http_resp = execute_request_with_rescues(api_base, context) do
    conn.run_request(method, url, body, headers) do |req|
      req.options.open_timeout = Stripe.open_timeout
      req.options.params_encoder = params_encoder
      req.options.timeout = Stripe.read_timeout
      req.params = query_params unless query_params.nil?
    end
  end
  begin
    resp = StripeResponse.from_faraday_response(http_resp)
  rescue JSON::ParserError
    raise general_api_error(http_resp.status, http_resp.body)
  end
  # Allows StripeClient#request to return a response object to a caller.
  @last_response = resp
  [resp, api_key]
end

def execute_request_with_rescues(api_base, context)

def execute_request_with_rescues(api_base, context)
tries = 0
est_start = Time.now
request(context, num_retries)
 = yield
ext = context.dup_from_response(resp)
response(context, request_start, resp.status, resp.body)
tripe.enable_telemetry? && context.request_id
quest_duration_ms = ((Time.now - request_start) * 1000).to_int
ast_request_metrics =
StripeRequestMetrics.new(context.request_id, request_duration_ms)
escue all exceptions from a request so that we have an easy spot to
ement our retry logic across the board. We'll re-raise if it's a
 of exception that we didn't expect to handle.
 StandardError => e
 we modify context we copy it into a new variable so as not to
int the original on a retry.
r_context = context
.respond_to?(:response) && e.response
ror_context = context.dup_from_response(e.response)
g_response(error_context, request_start,
           e.response[:status], e.response[:body])

g_response_error(error_context, request_start, e)
elf.class.should_retry?(e, num_retries)
m_retries += 1
eep self.class.sleep_time(num_retries)
try
 e
 Faraday::ClientError
 e.response
handle_error_response(e.response, error_context)
se
handle_network_error(e, error_context, num_retries, api_base)
d
ly handle errors when we know we can do so, and re-raise otherwise.
is should be pretty infrequent.

ise

def format_app_info(info)

other libraries, and shouldn't be changed without universal consensus.
the Dashboard. Note that this formatting has been implemented to match
end of a User-Agent string where it'll be fairly prominent in places like
Formats a plugin "app info" hash into a string that we can tack onto the
def format_app_info(info)
info[:name]
"#{str}/#{info[:version]}" unless info[:version].nil?
"#{str} (#{info[:url]})" unless info[:url].nil?

def general_api_error(status, body)

def general_api_error(status, body)
or.new("Invalid response object from API: #{body.inspect} " \
       "(HTTP response code was #{status})",
       http_status: status, http_body: body)

def handle_error_response(http_resp, context)

def handle_error_response(http_resp, context)
 = StripeResponse.from_faraday_hash(http_resp)
r_data = resp.data[:error]
e StripeError, "Indeterminate error" unless error_data
 JSON::ParserError, StripeError
e general_api_error(http_resp[:status], http_resp[:body])
= if error_data.is_a?(String)
    specific_oauth_error(resp, error_data, context)
  else
    specific_api_error(resp, error_data, context)
  end
response = resp
error)

def handle_network_error(error, context, num_retries,

def handle_network_error(error, context, num_retries,
                         api_base = nil)
og_error("Stripe network error",
         error_message: error.message,
         idempotency_key: context.idempotency_key,
         request_id: context.request_id)
rror
araday::ConnectionFailed
age = "Unexpected error communicating when trying to connect to " \
tripe. You may be seeing this message because your DNS is not" \
orking.  To check, try running `host stripe.com` from the " \
ommand line."
araday::SSLError
age = "Could not establish a secure connection to Stripe, you " \
ay need to upgrade your OpenSSL version. To check, try running " \
openssl s_client -connect api.stripe.com:443` from the command " \
ine."
araday::TimeoutError
base ||= Stripe.api_base
age = "Could not connect to Stripe (#{api_base}). " \
lease check your internet connection and try again. " \
f this problem persists, you should check Stripe's service " \
tatus at https://status.stripe.com, or let us know at " \
upport@stripe.com."
age = "Unexpected error communicating with Stripe. " \
f this problem persists, let us know at support@stripe.com."
e += " Request was retried #{num_retries} times." if num_retries > 0
APIConnectionError,
message + "\n\n(Network error: #{error.message})"

def initialize(conn = nil)

uses a default connection unless one is passed.
Initializes a new StripeClient. Expects a Faraday connection object, and
def initialize(conn = nil)
  self.conn = conn || self.class.default_conn
  @system_profiler = SystemProfiler.new
  @last_request_metrics = nil
end

def log_request(context, num_retries)

def log_request(context, num_retries)
og_info("Request to Stripe API",
        account: context.account,
        api_version: context.api_version,
        idempotency_key: context.idempotency_key,
        method: context.method,
        num_retries: num_retries,
        path: context.path)
og_debug("Request details",
         body: context.body,
         idempotency_key: context.idempotency_key,
         query_params: context.query_params)

def log_response(context, request_start, status, body)

def log_response(context, request_start, status, body)
og_info("Response from Stripe API",
        account: context.account,
        api_version: context.api_version,
        elapsed: Time.now - request_start,
        idempotency_key: context.idempotency_key,
        method: context.method,
        path: context.path,
        request_id: context.request_id,
        status: status)
og_debug("Response details",
         body: body,
         idempotency_key: context.idempotency_key,
         request_id: context.request_id)
 unless context.request_id
og_debug("Dashboard link for request",
         idempotency_key: context.idempotency_key,
         request_id: context.request_id,
         url: Util.request_id_dashboard_url(context.request_id,
                                            context.api_key))

def log_response_error(context, request_start, error)

def log_response_error(context, request_start, error)
og_error("Request error",
         elapsed: Time.now - request_start,
         error_message: error.message,
         idempotency_key: context.idempotency_key,
         method: context.method,
         path: context.path)

def request


charge, resp = client.request { Charge.create }
client = StripeClient.new

Executes the API call within the given block. Usage looks like:
def request
  @last_response = nil
  old_stripe_client = Thread.current[:stripe_client]
  Thread.current[:stripe_client] = self
  begin
    res = yield
    [res, @last_response]
  ensure
    Thread.current[:stripe_client] = old_stripe_client
  end
end

def request_headers(api_key, method)

def request_headers(api_key, method)
gent = "Stripe/v1 RubyBindings/#{Stripe::VERSION}"
 Stripe.app_info.nil?
_agent += " " + format_app_info(Stripe.app_info)
s = {
r-Agent" => user_agent,
horization" => "Bearer #{api_key}",
tent-Type" => "application/x-www-form-urlencoded",
ipe.enable_telemetry? && !@last_request_metrics.nil?
ers["X-Stripe-Client-Telemetry"] = JSON.generate(
st_request_metrics: @last_request_metrics.payload
s only safe to retry network failures on post and delete
ests if we add an Idempotency-Key header
post delete].include?(method) && Stripe.max_network_retries > 0
ers["Idempotency-Key"] ||= SecureRandom.uuid
s["Stripe-Version"] = Stripe.api_version if Stripe.api_version
s["Stripe-Account"] = Stripe.stripe_account if Stripe.stripe_account
gent = @system_profiler.user_agent
ers.update(
-Stripe-Client-User-Agent" => JSON.generate(user_agent)
 StandardError => e
ers.update(
-Stripe-Client-Raw-User-Agent" => user_agent.inspect,
rror => "#{e} (#{e.class})"
s

def specific_api_error(resp, error_data, context)

def specific_api_error(resp, error_data, context)
og_error("Stripe API error",
         status: resp.http_status,
         error_code: error_data[:code],
         error_message: error_data[:message],
         error_param: error_data[:param],
         error_type: error_data[:type],
         idempotency_key: context.idempotency_key,
         request_id: context.request_id)
standard set of arguments that can be used to initialize most of
exceptions.
 {
_body: resp.http_body,
_headers: resp.http_headers,
_status: resp.http_status,
_body: resp.data,
: error_data[:code],
esp.http_status
00, 404
 error_data[:type]
 "idempotency_error"
empotencyError.new(error_data[:message], opts)

validRequestError.new(
error_data[:message], error_data[:param],
opts
01
enticationError.new(error_data[:message], opts)
02
DO: modify CardError constructor to make code a keyword argument
    so we don't have to delete it from opts
.delete(:code)
Error.new(
ror_data[:message], error_data[:param], error_data[:code],
ts
03
issionError.new(error_data[:message], opts)
29
LimitError.new(error_data[:message], opts)
rror.new(error_data[:message], opts)

def specific_oauth_error(resp, error_code, context)

one matches. Will return `nil` if the code isn't recognized.
Attempts to look at a response's error code and return an OAuth error if
def specific_oauth_error(resp, error_code, context)
ption = resp.data[:error_description] || error_code
og_error("Stripe OAuth error",
         status: resp.http_status,
         error_code: error_code,
         error_description: description,
         idempotency_key: context.idempotency_key,
         request_id: context.request_id)
 [error_code, description, {
_status: resp.http_status, http_body: resp.http_body,
_body: resp.data, http_headers: resp.http_headers,
rror_code
invalid_client"
h::InvalidClientError.new(*args)
invalid_grant"
h::InvalidGrantError.new(*args)
invalid_request"
h::InvalidRequestError.new(*args)
invalid_scope"
h::InvalidScopeError.new(*args)
unsupported_grant_type"
h::UnsupportedGrantTypeError.new(*args)
unsupported_response_type"
h::UnsupportedResponseTypeError.new(*args)
'd prefer that all errors are typed, but we create a generic
uthError in case we run into a code that we don't recognize.
h::OAuthError.new(*args)