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)


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

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)


334
335
336
337
338
# File 'lib/stripe/util.rb', line 334

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, klass: 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.



106
107
108
# File 'lib/stripe/util.rb', line 106

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

.convert_to_stripe_object_with_params(data, params, opts = {}, last_response = nil, api_mode: :v1, requestor: nil, v2_deleted_object: false, klass: 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.

  • v2_deleted_object - If true, ignore the object tag for casting purposes

  • klass - The class to use for inner types



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
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/stripe/util.rb', line 127

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

  case data
  when Array
    data.map { |i| convert_to_stripe_object(i, opts, api_mode: api_mode, requestor: requestor, klass: klass) }
  when Hash
    # TODO: This is a terrible hack.
    # Waiting on https://go/j/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 klass
                     klass
                   elsif api_mode == :v2
                     if v2_deleted_object
                       V2::DeletedObject
                     elsif object_name == "v2.core.event" && v2_event_classes.key?(object_type)
                       v2_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?



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
87
88
89
90
# File 'lib/stripe/util.rb', line 60

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. ‘&`).



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

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

.event_notification_classesObject



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

def self.event_notification_classes
  @event_notification_classes ||= Stripe::EventTypes.event_notification_types_to_classes
end

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



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/stripe/util.rb', line 251

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



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/stripe/util.rb', line 270

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



381
382
383
384
385
386
387
# File 'lib/stripe/util.rb', line 381

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

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



201
202
203
204
205
206
207
208
209
# File 'lib/stripe/util.rb', line 201

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



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

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



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

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).



296
297
298
# File 'lib/stripe/util.rb', line 296

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.



351
352
353
354
355
356
357
358
# File 'lib/stripe/util.rb', line 351

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



300
301
302
303
304
305
306
307
308
# File 'lib/stripe/util.rb', line 300

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



312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/stripe/util.rb', line 312

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)


41
42
43
# File 'lib/stripe/util.rb', line 41

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.



363
364
365
366
# File 'lib/stripe/util.rb', line 363

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



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

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



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/stripe/util.rb', line 211

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

.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.



243
244
245
246
247
248
249
# File 'lib/stripe/util.rb', line 243

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_event_classesObject



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

def self.v2_event_classes
  @v2_event_classes ||= Stripe::EventTypes.v2_event_types_to_classes
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)


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

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

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