Module: Stripe::Util

Defined in:
lib/stripe/util.rb

Constant Summary collapse

/[a-zA-Z_]/.freeze
/[a-zA-Z0-9_]/.freeze

Class Method Summary collapse

Class Method Details

.check_api_key!(key) ⇒ Object

Raises:

  • (TypeError)


329
330
331
332
333
# File 'lib/stripe/util.rb', line 329

def self.check_api_key!(key)
  raise TypeError, "api_key must be a string" unless key.is_a?(String)

  key
end

.check_string_argument!(key) ⇒ Object

Raises:

  • (TypeError)


323
324
325
326
327
# File 'lib/stripe/util.rb', line 323

def self.check_string_argument!(key)
  raise TypeError, "argument must be a string" unless key.is_a?(String)

  key
end

.convert_to_stripe_object(data, opts = {}, api_mode: :v1, requestor: nil) ⇒ Object

Converts a hash of fields or an array of hashes into a StripeObject or array of StripeObjects. These new objects will be created as a concrete type as dictated by their ‘object` field (e.g. an `object` value of `charge` would create an instance of Charge), but if `object` is not present or of an unknown type, the newly created instance will fall back to being a StripeObject.

Attributes

  • data - Hash of fields and values to be converted into a StripeObject.

  • params - Params for StripeObject like filters used in search that will be reused on subsequent API calls.

  • opts - Options for StripeObject like an API key that will be reused on subsequent API calls.



102
103
104
# File 'lib/stripe/util.rb', line 102

def self.convert_to_stripe_object(data, opts = {}, api_mode: :v1, requestor: nil)
  convert_to_stripe_object_with_params(data, {}, opts, api_mode: api_mode, requestor: requestor)
end

.convert_to_stripe_object_with_params(data, params, opts = {}, last_response = nil, api_mode: :v1, requestor: nil) ⇒ Object

Converts a hash of fields or an array of hashes into a StripeObject or array of StripeObjects. These new objects will be created as a concrete type as dictated by their ‘object` field (e.g. an `object` value of `charge` would create an instance of Charge), but if `object` is not present or of an unknown type, the newly created instance will fall back to being a StripeObject.

Attributes

  • data - Hash of fields and values to be converted into a StripeObject.

  • opts - Options for StripeObject like an API key that will be reused on subsequent API calls.

  • last_response - The raw response associated with the object.

  • api_mode - The API mode to use when converting the object, either :v1 or :v2.

  • requestor - The requestor to use when constructing the object.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/stripe/util.rb', line 121

def self.convert_to_stripe_object_with_params(
  data,
  params,
  opts = {},
  last_response = nil,
  api_mode: :v1,
  requestor: nil
)
  opts = normalize_opts(opts)

  case data
  when Array
    data.map { |i| convert_to_stripe_object(i, opts, api_mode: api_mode, requestor: requestor) }
  when Hash
    # TODO: This is a terrible hack.
    # Waiting on https://jira.corp.stripe.com/browse/API_SERVICES-3167 to add
    # an object in v2 lists
    if api_mode == :v2 && data.include?(:data) && data.include?(:next_page_url)
      return V2::ListObject.construct_from(data, opts, last_response, api_mode, requestor)
    end

    # Try converting to a known object class.  If none available, fall back
    # to generic StripeObject
    object_type = data[:type] || data["type"]
    object_name = data[:object] || data["object"]
    object_class = if api_mode == :v2
                     if object_name == "v2.core.event" && thin_event_classes.key?(object_type)
                       thin_event_classes.fetch(object_type)
                     else
                       v2_object_classes.fetch(
                         object_name, StripeObject
                       )
                     end
                   else
                     object_classes.fetch(object_name, StripeObject)
                   end

    obj = object_class.construct_from(data, opts, last_response, api_mode, requestor)

    # set filters so that we can fetch the same limit, expansions, and
    # predicates when accessing the next and previous pages
    obj.filters = params.dup if obj && (obj.is_a?(SearchResultObject) || obj.is_a?(ListObject))

    obj
  else
    data
  end
end

.custom_method(resource, target, name, http_verb, http_path) ⇒ Object

Adds a custom method to a resource class. This is used to add support for non-CRUDL API requests, e.g. capturing charges. custom_method takes the following parameters:

  • name: the name of the custom method to create (as a symbol)

  • http_verb: the HTTP verb for the API request (:get, :post, or :delete)

  • http_path: the path to append to the resource’s URL. If not provided,

    the name is used as the path
    
  • resource: the resource implementation class

  • target: the class that custom static method will be added to

For example, this call:

custom_method :capture, http_verb: post

adds a ‘capture` class method to the resource class that, when called, will send a POST request to `/v1/<object_name>/capture`. TODO: are we comfortable with deleting this?



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/stripe/util.rb', line 56

def self.custom_method(resource, target, name, http_verb, http_path)
  unless %i[get post delete].include?(http_verb)
    raise ArgumentError,
          "Invalid http_verb value: #{http_verb.inspect}. Should be one " \
          "of :get, :post or :delete."
  end
  unless target.respond_to?(:resource_url)
    raise ArgumentError,
          "Invalid target value: #{target}. Target class should have a " \
          "`resource_url` method."
  end
  http_path ||= name.to_s
  target.define_singleton_method(name) do |id, params = {}, opts = {}|
    unless id.is_a?(String)
      raise ArgumentError,
            "id should be a string representing the ID of an API resource"
    end

    url = "#{target.resource_url}/" \
          "#{CGI.escape(id)}/" \
          "#{CGI.escape(http_path)}"

    resource.execute_resource_request(
      http_verb,
      url,
      :api,
      params,
      opts
    )
  end
end

.encode_parameters(params, api_mode) ⇒ Object

Encodes a hash of parameters in a way that’s suitable for use as query parameters in a URI or as form parameters in a request body. This mainly involves escaping special characters from parameter keys and values (e.g. ‘&`).



224
225
226
227
# File 'lib/stripe/util.rb', line 224

def self.encode_parameters(params, api_mode)
  Util.flatten_params(params, api_mode)
      .map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join("&")
end

.flatten_params(params, api_mode, parent_key = nil) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/stripe/util.rb', line 240

def self.flatten_params(params, api_mode, parent_key = nil)
  result = []

  # do not sort the final output because arrays (and arrays of hashes
  # especially) can be order sensitive, but do sort incoming parameters
  params.each do |key, value|
    calculated_key = parent_key ? "#{parent_key}[#{key}]" : key.to_s
    if value.is_a?(Hash)
      result += flatten_params(value, api_mode, calculated_key)
    elsif value.is_a?(Array)
      result += flatten_params_array(value, api_mode, calculated_key)
    else
      result << [calculated_key, value]
    end
  end

  result
end

.flatten_params_array(value, api_mode, calculated_key) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/stripe/util.rb', line 259

def self.flatten_params_array(value, api_mode, calculated_key)
  result = []
  value.each_with_index do |elem, i|
    if elem.is_a?(Hash)
      result += flatten_params(elem, api_mode, "#{calculated_key}[#{i}]")
    elsif elem.is_a?(Array)
      result += flatten_params_array(elem, api_mode, calculated_key)
    else
      result << if api_mode == :v2
                  [calculated_key, elem]
                else
                  ["#{calculated_key}[#{i}]", elem]
                end
    end
  end
  result
end

.get_api_mode(path) ⇒ Object

Returns either v1 or v2 as api_mode based on the given path



370
371
372
373
374
375
376
# File 'lib/stripe/util.rb', line 370

def self.get_api_mode(path)
  if path.start_with?("/v2/")
    :v2
  else
    :v1
  end
end

.log_debug(message, data = {}) ⇒ Object



190
191
192
193
194
195
196
197
198
# File 'lib/stripe/util.rb', line 190

def self.log_debug(message, data = {})
  config = data.delete(:config) || Stripe.config
  logger = config.logger || Stripe.logger
  if !logger.nil? ||
     (!config.log_level.nil? && config.log_level <= Stripe::LEVEL_DEBUG)
    log_internal(message, data, color: :blue, level: Stripe::LEVEL_DEBUG,
                                logger: Stripe.logger, out: $stdout)
  end
end

.log_error(message, data = {}) ⇒ Object



170
171
172
173
174
175
176
177
178
# File 'lib/stripe/util.rb', line 170

def self.log_error(message, data = {})
  config = data.delete(:config) || Stripe.config
  logger = config.logger || Stripe.logger
  if !logger.nil? ||
     (!config.log_level.nil? && config.log_level <= Stripe::LEVEL_ERROR)
    log_internal(message, data, color: :cyan, level: Stripe::LEVEL_ERROR,
                                logger: Stripe.logger, out: $stderr)
  end
end

.log_info(message, data = {}) ⇒ Object



180
181
182
183
184
185
186
187
188
# File 'lib/stripe/util.rb', line 180

def self.log_info(message, data = {})
  config = data.delete(:config) || Stripe.config
  logger = config.logger || Stripe.logger
  if !logger.nil? ||
     (!config.log_level.nil? && config.log_level <= Stripe::LEVEL_INFO)
    log_internal(message, data, color: :cyan, level: Stripe::LEVEL_INFO,
                                logger: Stripe.logger, out: $stdout)
  end
end

.monotonic_timeObject

‘Time.now` can be unstable in cases like an administrator manually updating its value or a reconcilation via NTP. For this reason, prefer the use of the system’s monotonic clock especially where comparing times to calculate an elapsed duration.

Shortcut for getting monotonic time, mostly for purposes of line length and test stubbing. Returns time in seconds since the event used for monotonic reference purposes by the platform (e.g. system boot time).



285
286
287
# File 'lib/stripe/util.rb', line 285

def self.monotonic_time
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end

.normalize_headers(headers) ⇒ Object

Normalizes header keys so that they’re all lower case and each hyphen-delimited section starts with a single capitalized letter. For example, ‘request-id` becomes `Request-Id`. This is useful for extracting certain key values when the user could have set them with a variety of diffent naming schemes.



340
341
342
343
344
345
346
347
# File 'lib/stripe/util.rb', line 340

def self.normalize_headers(headers)
  headers.each_with_object({}) do |(k, v), new_headers|
    k = k.to_s.tr("_", "-") if k.is_a?(Symbol)
    k = k.split("-").reject(&:empty?).map(&:capitalize).join("-")

    new_headers[k] = v
  end
end

.normalize_id(id) ⇒ Object



289
290
291
292
293
294
295
296
297
# File 'lib/stripe/util.rb', line 289

def self.normalize_id(id)
  if id.is_a?(Hash) # overloaded id
    params_hash = id.dup
    id = params_hash.delete(:id)
  else
    params_hash = {}
  end
  [id, params_hash]
end

.normalize_opts(opts) ⇒ Object

The secondary opts argument can either be a string or hash Turn this value into an api_key and a set of headers



301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/stripe/util.rb', line 301

def self.normalize_opts(opts)
  case opts
  when String
    { api_key: opts }
  when Hash
    check_api_key!(opts.fetch(:api_key)) if opts.key?(:api_key)
    # Explicitly use dup here instead of clone to avoid preserving freeze
    # state on input params.
    opts.dup
  else
    raise TypeError, "normalize_opts expects a string or a hash"
  end
end

.object_classesObject



25
26
27
# File 'lib/stripe/util.rb', line 25

def self.object_classes
  @object_classes ||= Stripe::ObjectTypes.object_names_to_classes
end

.object_name_matches_class?(object_name, klass) ⇒ Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/stripe/util.rb', line 37

def self.object_name_matches_class?(object_name, klass)
  Util.object_classes[object_name] == klass
end

.objects_to_ids(obj) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/stripe/util.rb', line 10

def self.objects_to_ids(obj)
  case obj
  when APIResource
    obj.id
  when Hash
    res = {}
    obj.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
    res
  when Array
    obj.map { |v| objects_to_ids(v) }
  else
    obj
  end
end

.request_id_dashboard_url(request_id, api_key) ⇒ Object

Generates a Dashboard link to inspect a request ID based off of a request ID value and an API key, which is used to attempt to extract whether the environment is livemode or testmode.



352
353
354
355
# File 'lib/stripe/util.rb', line 352

def self.request_id_dashboard_url(request_id, api_key)
  env = !api_key.nil? && api_key.start_with?("sk_live") ? "live" : "test"
  "https://dashboard.stripe.com/#{env}/logs/#{request_id}"
end

.secure_compare(str_a, str_b) ⇒ Object

Constant time string comparison to prevent timing attacks Code borrowed from ActiveSupport



359
360
361
362
363
364
365
366
367
# File 'lib/stripe/util.rb', line 359

def self.secure_compare(str_a, str_b)
  return false unless str_a.bytesize == str_b.bytesize

  l = str_a.unpack "C#{str_a.bytesize}"

  res = 0
  str_b.each_byte { |byte| res |= byte ^ l.shift }
  res.zero?
end

.symbolize_names(object) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/stripe/util.rb', line 200

def self.symbolize_names(object)
  case object
  when Hash
    new_hash = {}
    object.each do |key, value|
      key = (begin
        key.to_sym
      rescue StandardError
        key
      end) || key
      new_hash[key] = symbolize_names(value)
    end
    new_hash
  when Array
    object.map { |value| symbolize_names(value) }
  else
    object
  end
end

.thin_event_classesObject



33
34
35
# File 'lib/stripe/util.rb', line 33

def self.thin_event_classes
  @thin_event_classes ||= Stripe::EventTypes.thin_event_names_to_classes
end

.url_encode(key) ⇒ Object

Encodes a string in a way that makes it suitable for use in a set of query parameters in a URI or in a set of form parameters in a request body.



232
233
234
235
236
237
238
# File 'lib/stripe/util.rb', line 232

def self.url_encode(key)
  CGI.escape(key.to_s).
    # Don't use strict form encoding by changing the square bracket control
    # characters back to their literals. This is fine by the server, and
    # makes these parameter strings easier to read.
    gsub("%5B", "[").gsub("%5D", "]")
end

.v2_object_classesObject



29
30
31
# File 'lib/stripe/util.rb', line 29

def self.v2_object_classes
  @v2_object_classes ||= Stripe::ObjectTypes.v2_object_names_to_classes
end

.valid_variable_name?(key) ⇒ Boolean

Return false for strings that are invalid variable names Does NOT expect there to be a preceding ‘@’ for instance variables

Returns:

  • (Boolean)


317
318
319
320
321
# File 'lib/stripe/util.rb', line 317

def self.valid_variable_name?(key)
  return false if key.empty? || key[0] !~ LEGAL_FIRST_CHARACTER

  key[1..-1].chars.all? { |char| char =~ LEGAL_VARIABLE_CHARACTER }
end