Module: Stripe::Util

Defined in:
lib/stripe/util.rb

Class Method Summary collapse

Class Method Details

.check_api_key!(key) ⇒ Object

Raises:

  • (TypeError)


338
339
340
341
342
# File 'lib/stripe/util.rb', line 338

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)


332
333
334
335
336
# File 'lib/stripe/util.rb', line 332

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.



109
110
111
# File 'lib/stripe/util.rb', line 109

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



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
180
181
182
# File 'lib/stripe/util.rb', line 130

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?



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
91
92
93
# File 'lib/stripe/util.rb', line 63

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



238
239
240
241
# File 'lib/stripe/util.rb', line 238

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



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

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



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

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



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

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
      # Always use indexed format for arrays
      result << ["#{calculated_key}[#{i}]", elem]
    end
  end
  result
end

.get_api_mode(path) ⇒ Object

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



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

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

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



204
205
206
207
208
209
210
211
212
# File 'lib/stripe/util.rb', line 204

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



184
185
186
187
188
189
190
191
192
# File 'lib/stripe/util.rb', line 184

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



194
195
196
197
198
199
200
201
202
# File 'lib/stripe/util.rb', line 194

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.



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

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



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

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

.object_name_matches_class?(object_name, klass) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/stripe/util.rb', line 44

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

.objects_to_ids(obj, serialize_empty: false) ⇒ Object



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

def self.objects_to_ids(obj, serialize_empty: false)
  case obj
  when APIResource
    obj.id
  when Hash
    res = {}
    obj.each do |k, v|
      if !v.nil?
        res[k] = objects_to_ids(v, serialize_empty: serialize_empty)
      elsif serialize_empty
        res[k] = nil
      end
    end
    res
  when Array
    obj.map { |v| objects_to_ids(v, serialize_empty: serialize_empty) }
  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.



361
362
363
364
# File 'lib/stripe/util.rb', line 361

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



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

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



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

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.



246
247
248
249
250
251
252
# File 'lib/stripe/util.rb', line 246

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



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

def self.v2_event_classes
  @v2_event_classes ||= Stripe::EventTypes.v2_event_types_to_classes
end

.v2_object_classesObject



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

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
# File 'lib/stripe/util.rb', line 328

def self.valid_variable_name?(key)
  key.match?(/\A[a-zA-Z_]\w*\z/)
end