stripe 4.9.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +10 -0
  3. data/.rubocop.yml +28 -4
  4. data/.rubocop_todo.yml +11 -22
  5. data/.travis.yml +3 -6
  6. data/.vscode/extensions.json +7 -0
  7. data/.vscode/settings.json +8 -0
  8. data/CHANGELOG.md +102 -2
  9. data/Gemfile +2 -10
  10. data/README.md +96 -40
  11. data/Rakefile +8 -7
  12. data/VERSION +1 -1
  13. data/lib/stripe.rb +64 -85
  14. data/lib/stripe/api_operations/delete.rb +23 -1
  15. data/lib/stripe/api_operations/list.rb +0 -6
  16. data/lib/stripe/api_operations/nested_resource.rb +14 -7
  17. data/lib/stripe/api_operations/request.rb +3 -7
  18. data/lib/stripe/api_operations/save.rb +1 -3
  19. data/lib/stripe/api_resource.rb +50 -2
  20. data/lib/stripe/connection_manager.rb +141 -0
  21. data/lib/stripe/error_object.rb +94 -0
  22. data/lib/stripe/errors.rb +22 -9
  23. data/lib/stripe/list_object.rb +11 -5
  24. data/lib/stripe/multipart_encoder.rb +131 -0
  25. data/lib/stripe/object_types.rb +94 -0
  26. data/lib/stripe/resources.rb +77 -0
  27. data/lib/stripe/{account.rb → resources/account.rb} +49 -27
  28. data/lib/stripe/{account_link.rb → resources/account_link.rb} +1 -1
  29. data/lib/stripe/resources/alipay_account.rb +34 -0
  30. data/lib/stripe/{apple_pay_domain.rb → resources/apple_pay_domain.rb} +1 -1
  31. data/lib/stripe/resources/application_fee.rb +13 -0
  32. data/lib/stripe/resources/application_fee_refund.rb +30 -0
  33. data/lib/stripe/{balance.rb → resources/balance.rb} +1 -1
  34. data/lib/stripe/{balance_transaction.rb → resources/balance_transaction.rb} +1 -5
  35. data/lib/stripe/{bank_account.rb → resources/bank_account.rb} +14 -4
  36. data/lib/stripe/{bitcoin_receiver.rb → resources/bitcoin_receiver.rb} +3 -3
  37. data/lib/stripe/{bitcoin_transaction.rb → resources/bitcoin_transaction.rb} +1 -1
  38. data/lib/stripe/resources/capability.rb +33 -0
  39. data/lib/stripe/{card.rb → resources/card.rb} +12 -4
  40. data/lib/stripe/resources/charge.rb +22 -0
  41. data/lib/stripe/{checkout → resources/checkout}/session.rb +2 -2
  42. data/lib/stripe/{country_spec.rb → resources/country_spec.rb} +1 -1
  43. data/lib/stripe/{coupon.rb → resources/coupon.rb} +2 -2
  44. data/lib/stripe/resources/credit_note.rb +22 -0
  45. data/lib/stripe/resources/customer.rb +35 -0
  46. data/lib/stripe/resources/customer_balance_transaction.rb +30 -0
  47. data/lib/stripe/resources/discount.rb +7 -0
  48. data/lib/stripe/{dispute.rb → resources/dispute.rb} +9 -7
  49. data/lib/stripe/{ephemeral_key.rb → resources/ephemeral_key.rb} +5 -2
  50. data/lib/stripe/{event.rb → resources/event.rb} +1 -1
  51. data/lib/stripe/{exchange_rate.rb → resources/exchange_rate.rb} +1 -1
  52. data/lib/stripe/{file.rb → resources/file.rb} +8 -11
  53. data/lib/stripe/{file_link.rb → resources/file_link.rb} +2 -2
  54. data/lib/stripe/resources/invoice.rb +73 -0
  55. data/lib/stripe/{invoice_item.rb → resources/invoice_item.rb} +2 -2
  56. data/lib/stripe/{invoice_line_item.rb → resources/invoice_line_item.rb} +1 -1
  57. data/lib/stripe/resources/issuing/authorization.rb +33 -0
  58. data/lib/stripe/resources/issuing/card.rb +24 -0
  59. data/lib/stripe/{issuing → resources/issuing}/card_details.rb +1 -1
  60. data/lib/stripe/{issuing → resources/issuing}/cardholder.rb +2 -2
  61. data/lib/stripe/{issuing → resources/issuing}/dispute.rb +2 -2
  62. data/lib/stripe/{issuing → resources/issuing}/transaction.rb +2 -2
  63. data/lib/stripe/resources/login_link.rb +14 -0
  64. data/lib/stripe/resources/order.rb +32 -0
  65. data/lib/stripe/{order_return.rb → resources/order_return.rb} +1 -1
  66. data/lib/stripe/resources/payment_intent.rb +42 -0
  67. data/lib/stripe/resources/payment_method.rb +32 -0
  68. data/lib/stripe/resources/payout.rb +22 -0
  69. data/lib/stripe/{person.rb → resources/person.rb} +8 -3
  70. data/lib/stripe/{plan.rb → resources/plan.rb} +1 -1
  71. data/lib/stripe/{product.rb → resources/product.rb} +3 -3
  72. data/lib/stripe/resources/radar/early_fraud_warning.rb +11 -0
  73. data/lib/stripe/{radar → resources/radar}/value_list.rb +2 -2
  74. data/lib/stripe/{radar → resources/radar}/value_list_item.rb +2 -2
  75. data/lib/stripe/{recipient.rb → resources/recipient.rb} +2 -6
  76. data/lib/stripe/{recipient_transfer.rb → resources/recipient_transfer.rb} +1 -1
  77. data/lib/stripe/{refund.rb → resources/refund.rb} +1 -1
  78. data/lib/stripe/{reporting → resources/reporting}/report_run.rb +2 -2
  79. data/lib/stripe/{reporting → resources/reporting}/report_type.rb +2 -2
  80. data/lib/stripe/resources/reversal.rb +29 -0
  81. data/lib/stripe/resources/review.rb +20 -0
  82. data/lib/stripe/resources/setup_intent.rb +32 -0
  83. data/lib/stripe/{sigma → resources/sigma}/scheduled_query_run.rb +2 -2
  84. data/lib/stripe/{sku.rb → resources/sku.rb} +3 -3
  85. data/lib/stripe/{source.rb → resources/source.rb} +17 -15
  86. data/lib/stripe/{source_transaction.rb → resources/source_transaction.rb} +1 -1
  87. data/lib/stripe/resources/subscription.rb +25 -0
  88. data/lib/stripe/{subscription_item.rb → resources/subscription_item.rb} +5 -2
  89. data/lib/stripe/resources/subscription_schedule.rb +32 -0
  90. data/lib/stripe/resources/tax_id.rb +26 -0
  91. data/lib/stripe/resources/tax_rate.rb +11 -0
  92. data/lib/stripe/{terminal → resources/terminal}/connection_token.rb +2 -2
  93. data/lib/stripe/{terminal → resources/terminal}/location.rb +3 -2
  94. data/lib/stripe/{terminal → resources/terminal}/reader.rb +3 -2
  95. data/lib/stripe/{three_d_secure.rb → resources/three_d_secure.rb} +1 -1
  96. data/lib/stripe/{token.rb → resources/token.rb} +1 -1
  97. data/lib/stripe/resources/topup.rb +22 -0
  98. data/lib/stripe/resources/transfer.rb +26 -0
  99. data/lib/stripe/resources/usage_record.rb +7 -0
  100. data/lib/stripe/{usage_record_summary.rb → resources/usage_record_summary.rb} +1 -1
  101. data/lib/stripe/{webhook_endpoint.rb → resources/webhook_endpoint.rb} +2 -2
  102. data/lib/stripe/singleton_api_resource.rb +3 -1
  103. data/lib/stripe/stripe_client.rb +347 -218
  104. data/lib/stripe/stripe_object.rb +72 -59
  105. data/lib/stripe/stripe_response.rb +53 -21
  106. data/lib/stripe/util.rb +54 -109
  107. data/lib/stripe/version.rb +1 -1
  108. data/lib/stripe/webhook.rb +5 -3
  109. data/stripe.gemspec +14 -5
  110. data/test/stripe/account_link_test.rb +1 -1
  111. data/test/stripe/account_test.rb +193 -32
  112. data/test/stripe/alipay_account_test.rb +1 -1
  113. data/test/stripe/api_operations_test.rb +3 -4
  114. data/test/stripe/api_resource_test.rb +119 -30
  115. data/test/stripe/apple_pay_domain_test.rb +18 -5
  116. data/test/stripe/application_fee_refund_test.rb +1 -1
  117. data/test/stripe/application_fee_test.rb +45 -1
  118. data/test/stripe/balance_test.rb +1 -1
  119. data/test/stripe/balance_transaction_test.rb +20 -0
  120. data/test/stripe/bank_account_test.rb +1 -1
  121. data/test/stripe/capability_test.rb +45 -0
  122. data/test/stripe/charge_test.rb +13 -8
  123. data/test/stripe/checkout/session_test.rb +7 -1
  124. data/test/stripe/connection_manager_test.rb +138 -0
  125. data/test/stripe/country_spec_test.rb +1 -1
  126. data/test/stripe/coupon_test.rb +16 -6
  127. data/test/stripe/credit_note_test.rb +61 -0
  128. data/test/stripe/customer_balance_transaction_test.rb +37 -0
  129. data/test/stripe/customer_card_test.rb +1 -1
  130. data/test/stripe/customer_test.rb +151 -40
  131. data/test/stripe/dispute_test.rb +10 -1
  132. data/test/stripe/ephemeral_key_test.rb +8 -1
  133. data/test/stripe/errors_test.rb +30 -9
  134. data/test/stripe/exchange_rate_test.rb +1 -1
  135. data/test/stripe/file_link_test.rb +1 -1
  136. data/test/stripe/file_test.rb +19 -5
  137. data/test/stripe/invoice_item_test.rb +18 -7
  138. data/test/stripe/invoice_line_item_test.rb +1 -1
  139. data/test/stripe/invoice_test.rb +77 -9
  140. data/test/stripe/issuing/authorization_test.rb +33 -11
  141. data/test/stripe/issuing/card_test.rb +15 -6
  142. data/test/stripe/issuing/cardholder_test.rb +1 -1
  143. data/test/stripe/issuing/dispute_test.rb +1 -1
  144. data/test/stripe/issuing/transaction_test.rb +1 -1
  145. data/test/stripe/list_object_test.rb +1 -17
  146. data/test/stripe/login_link_test.rb +2 -2
  147. data/test/stripe/multipart_encoder_test.rb +130 -0
  148. data/test/stripe/oauth_test.rb +1 -1
  149. data/test/stripe/order_return_test.rb +1 -1
  150. data/test/stripe/order_test.rb +28 -3
  151. data/test/stripe/payment_intent_test.rb +31 -4
  152. data/test/stripe/payment_method_test.rb +84 -0
  153. data/test/stripe/payout_test.rb +8 -1
  154. data/test/stripe/person_test.rb +1 -1
  155. data/test/stripe/plan_test.rb +26 -20
  156. data/test/stripe/product_test.rb +16 -6
  157. data/test/stripe/radar/early_fraud_warning_test.rb +22 -0
  158. data/test/stripe/radar/value_list_item_test.rb +16 -6
  159. data/test/stripe/radar/value_list_test.rb +16 -6
  160. data/test/stripe/recipient_test.rb +18 -5
  161. data/test/stripe/refund_test.rb +1 -1
  162. data/test/stripe/reporting/report_run_test.rb +1 -1
  163. data/test/stripe/reporting/report_type_test.rb +1 -1
  164. data/test/stripe/reversal_test.rb +1 -1
  165. data/test/stripe/review_test.rb +1 -1
  166. data/test/stripe/setup_intent_test.rb +84 -0
  167. data/test/stripe/sigma/scheduled_query_run_test.rb +1 -1
  168. data/test/stripe/sku_test.rb +16 -6
  169. data/test/stripe/source_test.rb +14 -19
  170. data/test/stripe/source_transaction_test.rb +1 -1
  171. data/test/stripe/stripe_client_test.rb +242 -26
  172. data/test/stripe/stripe_object_test.rb +8 -36
  173. data/test/stripe/stripe_response_test.rb +71 -25
  174. data/test/stripe/subscription_item_test.rb +28 -6
  175. data/test/stripe/subscription_schedule_test.rb +19 -1
  176. data/test/stripe/subscription_test.rb +29 -9
  177. data/test/stripe/tax_id_test.rb +31 -0
  178. data/test/stripe/tax_rate_test.rb +43 -0
  179. data/test/stripe/terminal/connection_token_test.rb +1 -1
  180. data/test/stripe/terminal/location_test.rb +18 -1
  181. data/test/stripe/terminal/reader_test.rb +18 -1
  182. data/test/stripe/three_d_secure_test.rb +1 -1
  183. data/test/stripe/topup_test.rb +9 -1
  184. data/test/stripe/transfer_test.rb +46 -1
  185. data/test/stripe/usage_record_summary_test.rb +1 -1
  186. data/test/stripe/util_test.rb +1 -1
  187. data/test/stripe/webhook_endpoint_test.rb +18 -1
  188. data/test/stripe/webhook_test.rb +4 -4
  189. data/test/stripe_mock.rb +4 -3
  190. data/test/stripe_test.rb +1 -14
  191. data/test/test_helper.rb +14 -11
  192. metadata +117 -125
  193. data/lib/stripe/alipay_account.rb +0 -27
  194. data/lib/stripe/application_fee.rb +0 -23
  195. data/lib/stripe/application_fee_refund.rb +0 -22
  196. data/lib/stripe/charge.rb +0 -84
  197. data/lib/stripe/customer.rb +0 -90
  198. data/lib/stripe/invoice.rb +0 -48
  199. data/lib/stripe/issuer_fraud_record.rb +0 -9
  200. data/lib/stripe/issuing/authorization.rb +0 -22
  201. data/lib/stripe/issuing/card.rb +0 -18
  202. data/lib/stripe/login_link.rb +0 -11
  203. data/lib/stripe/order.rb +0 -31
  204. data/lib/stripe/payment_intent.rb +0 -26
  205. data/lib/stripe/payout.rb +0 -20
  206. data/lib/stripe/reversal.rb +0 -22
  207. data/lib/stripe/review.rb +0 -14
  208. data/lib/stripe/subscription.rb +0 -25
  209. data/lib/stripe/subscription_schedule.rb +0 -32
  210. data/lib/stripe/subscription_schedule_revision.rb +0 -25
  211. data/lib/stripe/topup.rb +0 -16
  212. data/lib/stripe/transfer.rb +0 -23
  213. data/lib/stripe/usage_record.rb +0 -14
  214. data/test/stripe/account_external_accounts_operations_test.rb +0 -69
  215. data/test/stripe/account_login_links_operations_test.rb +0 -21
  216. data/test/stripe/account_persons_operations_test.rb +0 -70
  217. data/test/stripe/application_fee_refunds_operations_test.rb +0 -56
  218. data/test/stripe/customer_sources_operations_test.rb +0 -64
  219. data/test/stripe/file_upload_test.rb +0 -76
  220. data/test/stripe/issuer_fraud_record_test.rb +0 -20
  221. data/test/stripe/subscription_schedule_revision_test.rb +0 -37
  222. data/test/stripe/subscription_schedule_revisions_operations_test.rb +0 -35
  223. data/test/stripe/transfer_reversals_operations_test.rb +0 -57
  224. data/test/stripe/usage_record_test.rb +0 -28
@@ -4,7 +4,7 @@ module Stripe
4
4
  class StripeObject
5
5
  include Enumerable
6
6
 
7
- @@permanent_attributes = Set.new([:id])
7
+ @@permanent_attributes = Set.new([:id]) # rubocop:disable Style/ClassVars
8
8
 
9
9
  # The default :id method is deprecated and isn't useful to us
10
10
  undef :id if method_defined?(:id)
@@ -93,10 +93,12 @@ module Stripe
93
93
  # considered to be equal if they have the same set of values and each one
94
94
  # of those values is the same.
95
95
  def ==(other)
96
- other.is_a?(StripeObject) && @values == other.instance_variable_get(:@values)
96
+ other.is_a?(StripeObject) &&
97
+ @values == other.instance_variable_get(:@values)
97
98
  end
98
99
 
99
- # Hash equality. As with `#==`, we consider two equivalent Stripe objects equal.
100
+ # Hash equality. As with `#==`, we consider two equivalent Stripe objects
101
+ # equal.
100
102
  def eql?(other)
101
103
  # Defer to the implementation on `#==`.
102
104
  self == other
@@ -121,21 +123,10 @@ module Stripe
121
123
 
122
124
  def inspect
123
125
  id_string = respond_to?(:id) && !id.nil? ? " id=#{id}" : ""
124
- "#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values)
126
+ "#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " +
127
+ JSON.pretty_generate(@values)
125
128
  end
126
129
 
127
- # Re-initializes the object based on a hash of values (usually one that's
128
- # come back from an API call). Adds or removes value accessors as necessary
129
- # and updates the state of internal data.
130
- #
131
- # Please don't use this method. If you're trying to do mass assignment, try
132
- # #initialize_from instead.
133
- def refresh_from(values, opts, partial = false)
134
- initialize_from(values, opts, partial)
135
- end
136
- extend Gem::Deprecate
137
- deprecate :refresh_from, "#update_attributes", 2016, 1
138
-
139
130
  # Mass assigns attributes on the model.
140
131
  #
141
132
  # This is a version of +update_attributes+ that takes some extra options
@@ -162,12 +153,12 @@ module Stripe
162
153
  end
163
154
  end
164
155
 
165
- def [](k)
166
- @values[k.to_sym]
156
+ def [](key)
157
+ @values[key.to_sym]
167
158
  end
168
159
 
169
- def []=(k, v)
170
- send(:"#{k}=", v)
160
+ def []=(key, value)
161
+ send(:"#{key}=", value)
171
162
  end
172
163
 
173
164
  def keys
@@ -178,17 +169,20 @@ module Stripe
178
169
  @values.values
179
170
  end
180
171
 
181
- def to_json(*_a)
172
+ def to_json(*_opts)
173
+ # TODO: pass opts to JSON.generate?
182
174
  JSON.generate(@values)
183
175
  end
184
176
 
185
- def as_json(*a)
186
- @values.as_json(*a)
177
+ def as_json(*opts)
178
+ @values.as_json(*opts)
187
179
  end
188
180
 
189
181
  def to_hash
190
182
  maybe_to_hash = lambda do |value|
191
- value && value.respond_to?(:to_hash) ? value.to_hash : value
183
+ return nil if value.nil?
184
+
185
+ value.respond_to?(:to_hash) ? value.to_hash : value
192
186
  end
193
187
 
194
188
  @values.each_with_object({}) do |(key, value), acc|
@@ -251,10 +245,11 @@ module Stripe
251
245
  # values within in that its parent StripeObject doesn't know about.
252
246
  #
253
247
  unsaved = @unsaved_values.include?(k)
254
- if options[:force] || unsaved || v.is_a?(StripeObject)
255
- update_hash[k.to_sym] =
256
- serialize_params_value(@values[k], @original_values[k], unsaved, options[:force], key: k)
257
- end
248
+ next unless options[:force] || unsaved || v.is_a?(StripeObject)
249
+
250
+ update_hash[k.to_sym] = serialize_params_value(
251
+ @values[k], @original_values[k], unsaved, options[:force], key: k
252
+ )
258
253
  end
259
254
 
260
255
  # a `nil` that makes it out of `#serialize_params_value` signals an empty
@@ -264,16 +259,6 @@ module Stripe
264
259
  update_hash
265
260
  end
266
261
 
267
- class << self
268
- # This class method has been deprecated in favor of the instance method
269
- # of the same name.
270
- def serialize_params(obj, options = {})
271
- obj.serialize_params(options)
272
- end
273
- extend Gem::Deprecate
274
- deprecate :serialize_params, "#serialize_params", 2016, 9
275
- end
276
-
277
262
  # A protected field is one that doesn't get an accessor assigned to it
278
263
  # (i.e. `obj.public = ...`) and one which is not allowed to be updated via
279
264
  # the class level `Model.update(id, { ... })`.
@@ -281,13 +266,11 @@ module Stripe
281
266
  []
282
267
  end
283
268
 
284
- protected
285
-
286
- def metaclass
269
+ protected def metaclass
287
270
  class << self; self; end
288
271
  end
289
272
 
290
- def remove_accessors(keys)
273
+ protected def remove_accessors(keys)
291
274
  # not available in the #instance_eval below
292
275
  protected_fields = self.class.protected_fields
293
276
 
@@ -298,13 +281,31 @@ module Stripe
298
281
 
299
282
  # Remove methods for the accessor's reader and writer.
300
283
  [k, :"#{k}=", :"#{k}?"].each do |method_name|
301
- remove_method(method_name) if method_defined?(method_name)
284
+ next unless method_defined?(method_name)
285
+
286
+ begin
287
+ remove_method(method_name)
288
+ rescue NameError
289
+ # In some cases there can be a method that's detected with
290
+ # `method_defined?`, but which cannot be removed with
291
+ # `remove_method`, even though it's on the same class. The only
292
+ # case so far that we've noticed this is when a class is
293
+ # reopened for monkey patching:
294
+ #
295
+ # https://github.com/stripe/stripe-ruby/issues/749
296
+ #
297
+ # Here we swallow that error and issue a warning so at least
298
+ # the program doesn't crash.
299
+ warn("WARNING: Unable to remove method `#{method_name}`; " \
300
+ "if custom, please consider renaming to a name that doesn't " \
301
+ "collide with an API property name.")
302
+ end
302
303
  end
303
304
  end
304
305
  end
305
306
  end
306
307
 
307
- def add_accessors(keys, values)
308
+ protected def add_accessors(keys, values)
308
309
  # not available in the #instance_eval below
309
310
  protected_fields = self.class.protected_fields
310
311
 
@@ -341,7 +342,11 @@ module Stripe
341
342
  end
342
343
  end
343
344
 
344
- def method_missing(name, *args)
345
+ # Disabling the cop because it's confused by the fact that the methods are
346
+ # protected, but we do define `#respond_to_missing?` just below. Hopefully
347
+ # this is fixed in more recent Rubocop versions.
348
+ # rubocop:disable Style/MissingRespondToMissing
349
+ protected def method_missing(name, *args)
345
350
  # TODO: only allow setting in updateable classes.
346
351
  if name.to_s.end_with?("=")
347
352
  attr = name.to_s[0...-1].to_sym
@@ -357,7 +362,9 @@ module Stripe
357
362
  begin
358
363
  mth = method(name)
359
364
  rescue NameError
360
- raise NoMethodError, "Cannot set #{attr} on this object. HINT: you can't set: #{@@permanent_attributes.to_a.join(', ')}"
365
+ raise NoMethodError,
366
+ "Cannot set #{attr} on this object. HINT: you can't set: " \
367
+ "#{@@permanent_attributes.to_a.join(', ')}"
361
368
  end
362
369
  return mth.call(args[0])
363
370
  elsif @values.key?(name)
@@ -372,11 +379,17 @@ module Stripe
372
379
  # raise right away.
373
380
  raise unless @transient_values.include?(name)
374
381
 
375
- raise NoMethodError, e.message + ". HINT: The '#{name}' attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Stripe's API, probably as a result of a save(). The attributes currently available on this object are: #{@values.keys.join(', ')}"
382
+ raise NoMethodError,
383
+ e.message + ". HINT: The '#{name}' attribute was set in the " \
384
+ "past, however. It was then wiped when refreshing the object " \
385
+ "with the result returned by Stripe's API, probably as a " \
386
+ "result of a save(). The attributes currently available on " \
387
+ "this object are: #{@values.keys.join(', ')}"
376
388
  end
377
389
  end
390
+ # rubocop:enable Style/MissingRespondToMissing
378
391
 
379
- def respond_to_missing?(symbol, include_private = false)
392
+ protected def respond_to_missing?(symbol, include_private = false)
380
393
  @values && @values.key?(symbol) || super
381
394
  end
382
395
 
@@ -392,7 +405,7 @@ module Stripe
392
405
  # * +:opts:+ Options for StripeObject like an API key.
393
406
  # * +:partial:+ Indicates that the re-initialization should not attempt to
394
407
  # remove accessors.
395
- def initialize_from(values, opts, partial = false)
408
+ protected def initialize_from(values, opts, partial = false)
396
409
  @opts = Util.normalize_opts(opts)
397
410
 
398
411
  # the `#send` is here so that we can keep this method private
@@ -402,8 +415,8 @@ module Stripe
402
415
  added = Set.new(values.keys - @values.keys)
403
416
 
404
417
  # Wipe old state before setting new. This is useful for e.g. updating a
405
- # customer, where there is no persistent card parameter. Mark those values
406
- # which don't persist as transient
418
+ # customer, where there is no persistent card parameter. Mark those
419
+ # values which don't persist as transient
407
420
 
408
421
  remove_accessors(removed)
409
422
  add_accessors(added, values)
@@ -423,7 +436,8 @@ module Stripe
423
436
  self
424
437
  end
425
438
 
426
- def serialize_params_value(value, original, unsaved, force, key: nil)
439
+ protected def serialize_params_value(value, original, unsaved, force,
440
+ key: nil)
427
441
  if value.nil?
428
442
  ""
429
443
 
@@ -498,11 +512,9 @@ module Stripe
498
512
  end
499
513
  end
500
514
 
501
- private
502
-
503
515
  # Produces a deep copy of the given object including support for arrays,
504
516
  # hashes, and StripeObjects.
505
- def self.deep_copy(obj)
517
+ private_class_method def self.deep_copy(obj)
506
518
  case obj
507
519
  when Array
508
520
  obj.map { |e| deep_copy(e) }
@@ -522,9 +534,8 @@ module Stripe
522
534
  obj
523
535
  end
524
536
  end
525
- private_class_method :deep_copy
526
537
 
527
- def dirty_value!(value)
538
+ private def dirty_value!(value)
528
539
  case value
529
540
  when Array
530
541
  value.map { |v| dirty_value!(v) }
@@ -535,12 +546,14 @@ module Stripe
535
546
 
536
547
  # Returns a hash of empty values for all the values that are in the given
537
548
  # StripeObject.
538
- def empty_values(obj)
549
+ private def empty_values(obj)
539
550
  values = case obj
540
551
  when Hash then obj
541
552
  when StripeObject then obj.instance_variable_get(:@values)
542
553
  else
543
- raise ArgumentError, "#empty_values got unexpected object type: #{obj.class.name}"
554
+ raise ArgumentError,
555
+ "#empty_values got unexpected object type: " \
556
+ "#{obj.class.name}"
544
557
  end
545
558
 
546
559
  values.each_with_object({}) do |(k, _), update|
@@ -4,6 +4,53 @@ module Stripe
4
4
  # StripeResponse encapsulates some vitals of a response that came back from
5
5
  # the Stripe API.
6
6
  class StripeResponse
7
+ # Headers provides an access wrapper to an API response's header data. It
8
+ # mainly exists so that we don't need to expose the entire
9
+ # `Net::HTTPResponse` object while still getting some of its benefits like
10
+ # case-insensitive access to header names and flattening of header values.
11
+ class Headers
12
+ # Initializes a Headers object from a Net::HTTP::HTTPResponse object.
13
+ def self.from_net_http(resp)
14
+ new(resp.to_hash)
15
+ end
16
+
17
+ # `hash` is expected to be a hash mapping header names to arrays of
18
+ # header values. This is the default format generated by calling
19
+ # `#to_hash` on a `Net::HTTPResponse` object because headers can be
20
+ # repeated multiple times. Using `#[]` will collapse values down to just
21
+ # the first.
22
+ def initialize(hash)
23
+ if !hash.is_a?(Hash) ||
24
+ !hash.keys.all? { |n| n.is_a?(String) } ||
25
+ !hash.values.all? { |a| a.is_a?(Array) } ||
26
+ !hash.values.all? { |a| a.all? { |v| v.is_a?(String) } }
27
+ raise ArgumentError,
28
+ "expect hash to be a map of string header names to arrays of " \
29
+ "header values"
30
+ end
31
+
32
+ @hash = {}
33
+
34
+ # This shouldn't be strictly necessary because `Net::HTTPResponse` will
35
+ # produce a hash with all headers downcased, but do it anyway just in
36
+ # case an object of this class was constructed manually.
37
+ #
38
+ # Also has the effect of duplicating the hash, which is desirable for a
39
+ # little extra object safety.
40
+ hash.each do |k, v|
41
+ @hash[k.downcase] = v
42
+ end
43
+ end
44
+
45
+ def [](name)
46
+ values = @hash[name.downcase]
47
+ if values && values.count > 1
48
+ warn("Duplicate header values for `#{name}`; returning only first")
49
+ end
50
+ values ? values.first : nil
51
+ end
52
+ end
53
+
7
54
  # The data contained by the HTTP body of the response deserialized from
8
55
  # JSON.
9
56
  attr_accessor :data
@@ -20,30 +67,15 @@ module Stripe
20
67
  # The Stripe request ID of the response.
21
68
  attr_accessor :request_id
22
69
 
23
- # Initializes a StripeResponse object from a Hash like the kind returned as
24
- # part of a Faraday exception.
25
- #
26
- # This may throw JSON::ParserError if the response body is not valid JSON.
27
- def self.from_faraday_hash(http_resp)
28
- resp = StripeResponse.new
29
- resp.data = JSON.parse(http_resp[:body], symbolize_names: true)
30
- resp.http_body = http_resp[:body]
31
- resp.http_headers = http_resp[:headers]
32
- resp.http_status = http_resp[:status]
33
- resp.request_id = http_resp[:headers]["Request-Id"]
34
- resp
35
- end
36
-
37
- # Initializes a StripeResponse object from a Faraday HTTP response object.
38
- #
39
- # This may throw JSON::ParserError if the response body is not valid JSON.
40
- def self.from_faraday_response(http_resp)
70
+ # Initializes a StripeResponse object from a Net::HTTP::HTTPResponse
71
+ # object.
72
+ def self.from_net_http(http_resp)
41
73
  resp = StripeResponse.new
42
74
  resp.data = JSON.parse(http_resp.body, symbolize_names: true)
43
75
  resp.http_body = http_resp.body
44
- resp.http_headers = http_resp.headers
45
- resp.http_status = http_resp.status
46
- resp.request_id = http_resp.headers["Request-Id"]
76
+ resp.http_headers = Headers.from_net_http(http_resp)
77
+ resp.http_status = http_resp.code.to_i
78
+ resp.request_id = http_resp["request-id"]
47
79
  resp
48
80
  end
49
81
  end
@@ -24,97 +24,27 @@ module Stripe
24
24
  OPTS_USER_SPECIFIED + Set[:client] - Set[:idempotency_key]
25
25
  ).freeze
26
26
 
27
- def self.objects_to_ids(h)
28
- case h
27
+ def self.objects_to_ids(obj)
28
+ case obj
29
29
  when APIResource
30
- h.id
30
+ obj.id
31
31
  when Hash
32
32
  res = {}
33
- h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
33
+ obj.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
34
34
  res
35
35
  when Array
36
- h.map { |v| objects_to_ids(v) }
36
+ obj.map { |v| objects_to_ids(v) }
37
37
  else
38
- h
38
+ obj
39
39
  end
40
40
  end
41
41
 
42
- def self.object_classes # rubocop:disable Metrics/MethodLength
43
- @object_classes ||= {
44
- # data structures
45
- ListObject::OBJECT_NAME => ListObject,
46
-
47
- # business objects
48
- Account::OBJECT_NAME => Account,
49
- AccountLink::OBJECT_NAME => AccountLink,
50
- AlipayAccount::OBJECT_NAME => AlipayAccount,
51
- ApplePayDomain::OBJECT_NAME => ApplePayDomain,
52
- ApplicationFee::OBJECT_NAME => ApplicationFee,
53
- ApplicationFeeRefund::OBJECT_NAME => ApplicationFeeRefund,
54
- Balance::OBJECT_NAME => Balance,
55
- BalanceTransaction::OBJECT_NAME => BalanceTransaction,
56
- BankAccount::OBJECT_NAME => BankAccount,
57
- BitcoinReceiver::OBJECT_NAME => BitcoinReceiver,
58
- BitcoinTransaction::OBJECT_NAME => BitcoinTransaction,
59
- Card::OBJECT_NAME => Card,
60
- Charge::OBJECT_NAME => Charge,
61
- Checkout::Session::OBJECT_NAME => Checkout::Session,
62
- CountrySpec::OBJECT_NAME => CountrySpec,
63
- Coupon::OBJECT_NAME => Coupon,
64
- Customer::OBJECT_NAME => Customer,
65
- Dispute::OBJECT_NAME => Dispute,
66
- EphemeralKey::OBJECT_NAME => EphemeralKey,
67
- Event::OBJECT_NAME => Event,
68
- ExchangeRate::OBJECT_NAME => ExchangeRate,
69
- File::OBJECT_NAME => File,
70
- File::OBJECT_NAME_ALT => File,
71
- FileLink::OBJECT_NAME => FileLink,
72
- Invoice::OBJECT_NAME => Invoice,
73
- InvoiceItem::OBJECT_NAME => InvoiceItem,
74
- InvoiceLineItem::OBJECT_NAME => InvoiceLineItem,
75
- IssuerFraudRecord::OBJECT_NAME => IssuerFraudRecord,
76
- Issuing::Authorization::OBJECT_NAME => Issuing::Authorization,
77
- Issuing::Card::OBJECT_NAME => Issuing::Card,
78
- Issuing::CardDetails::OBJECT_NAME => Issuing::CardDetails,
79
- Issuing::Cardholder::OBJECT_NAME => Issuing::Cardholder,
80
- Issuing::Dispute::OBJECT_NAME => Issuing::Dispute,
81
- Issuing::Transaction::OBJECT_NAME => Issuing::Transaction,
82
- LoginLink::OBJECT_NAME => LoginLink,
83
- Order::OBJECT_NAME => Order,
84
- OrderReturn::OBJECT_NAME => OrderReturn,
85
- PaymentIntent::OBJECT_NAME => PaymentIntent,
86
- Payout::OBJECT_NAME => Payout,
87
- Person::OBJECT_NAME => Person,
88
- Plan::OBJECT_NAME => Plan,
89
- Product::OBJECT_NAME => Product,
90
- Radar::ValueList::OBJECT_NAME => Radar::ValueList,
91
- Radar::ValueListItem::OBJECT_NAME => Radar::ValueListItem,
92
- Recipient::OBJECT_NAME => Recipient,
93
- RecipientTransfer::OBJECT_NAME => RecipientTransfer,
94
- Refund::OBJECT_NAME => Refund,
95
- Reporting::ReportRun::OBJECT_NAME => Reporting::ReportRun,
96
- Reporting::ReportType::OBJECT_NAME => Reporting::ReportType,
97
- Reversal::OBJECT_NAME => Reversal,
98
- Review::OBJECT_NAME => Review,
99
- SKU::OBJECT_NAME => SKU,
100
- Sigma::ScheduledQueryRun::OBJECT_NAME => Sigma::ScheduledQueryRun,
101
- Source::OBJECT_NAME => Source,
102
- SourceTransaction::OBJECT_NAME => SourceTransaction,
103
- Subscription::OBJECT_NAME => Subscription,
104
- SubscriptionItem::OBJECT_NAME => SubscriptionItem,
105
- SubscriptionSchedule::OBJECT_NAME => SubscriptionSchedule,
106
- SubscriptionScheduleRevision::OBJECT_NAME => SubscriptionScheduleRevision,
107
- Terminal::ConnectionToken::OBJECT_NAME => Terminal::ConnectionToken,
108
- Terminal::Location::OBJECT_NAME => Terminal::Location,
109
- Terminal::Reader::OBJECT_NAME => Terminal::Reader,
110
- ThreeDSecure::OBJECT_NAME => ThreeDSecure,
111
- Token::OBJECT_NAME => Token,
112
- Topup::OBJECT_NAME => Topup,
113
- Transfer::OBJECT_NAME => Transfer,
114
- UsageRecord::OBJECT_NAME => UsageRecord,
115
- UsageRecordSummary::OBJECT_NAME => UsageRecordSummary,
116
- WebhookEndpoint::OBJECT_NAME => WebhookEndpoint,
117
- }
42
+ def self.object_classes
43
+ @object_classes ||= Stripe::ObjectTypes.object_names_to_classes
44
+ end
45
+
46
+ def self.object_name_matches_class?(object_name, klass)
47
+ Util.object_classes[object_name] == klass
118
48
  end
119
49
 
120
50
  # Converts a hash of fields or an array of hashes into a +StripeObject+ or
@@ -130,12 +60,16 @@ module Stripe
130
60
  # * +opts+ - Options for +StripeObject+ like an API key that will be reused
131
61
  # on subsequent API calls.
132
62
  def self.convert_to_stripe_object(data, opts = {})
63
+ opts = normalize_opts(opts)
64
+
133
65
  case data
134
66
  when Array
135
67
  data.map { |i| convert_to_stripe_object(i, opts) }
136
68
  when Hash
137
- # Try converting to a known object class. If none available, fall back to generic StripeObject
138
- object_classes.fetch(data[:object], StripeObject).construct_from(data, opts)
69
+ # Try converting to a known object class. If none available, fall back
70
+ # to generic StripeObject
71
+ object_classes.fetch(data[:object], StripeObject)
72
+ .construct_from(data, opts)
139
73
  else
140
74
  data
141
75
  end
@@ -144,24 +78,24 @@ module Stripe
144
78
  def self.log_error(message, data = {})
145
79
  if !Stripe.logger.nil? ||
146
80
  !Stripe.log_level.nil? && Stripe.log_level <= Stripe::LEVEL_ERROR
147
- log_internal(message, data, color: :cyan,
148
- level: Stripe::LEVEL_ERROR, logger: Stripe.logger, out: $stderr)
81
+ log_internal(message, data, color: :cyan, level: Stripe::LEVEL_ERROR,
82
+ logger: Stripe.logger, out: $stderr)
149
83
  end
150
84
  end
151
85
 
152
86
  def self.log_info(message, data = {})
153
87
  if !Stripe.logger.nil? ||
154
88
  !Stripe.log_level.nil? && Stripe.log_level <= Stripe::LEVEL_INFO
155
- log_internal(message, data, color: :cyan,
156
- level: Stripe::LEVEL_INFO, logger: Stripe.logger, out: $stdout)
89
+ log_internal(message, data, color: :cyan, level: Stripe::LEVEL_INFO,
90
+ logger: Stripe.logger, out: $stdout)
157
91
  end
158
92
  end
159
93
 
160
94
  def self.log_debug(message, data = {})
161
95
  if !Stripe.logger.nil? ||
162
96
  !Stripe.log_level.nil? && Stripe.log_level <= Stripe::LEVEL_DEBUG
163
- log_internal(message, data, color: :blue,
164
- level: Stripe::LEVEL_DEBUG, logger: Stripe.logger, out: $stdout)
97
+ log_internal(message, data, color: :blue, level: Stripe::LEVEL_DEBUG,
98
+ logger: Stripe.logger, out: $stdout)
165
99
  end
166
100
  end
167
101
 
@@ -264,11 +198,13 @@ module Stripe
264
198
 
265
199
  def self.check_string_argument!(key)
266
200
  raise TypeError, "argument must be a string" unless key.is_a?(String)
201
+
267
202
  key
268
203
  end
269
204
 
270
205
  def self.check_api_key!(key)
271
206
  raise TypeError, "api_key must be a string" unless key.is_a?(String)
207
+
272
208
  key
273
209
  end
274
210
 
@@ -296,13 +232,13 @@ module Stripe
296
232
 
297
233
  # Constant time string comparison to prevent timing attacks
298
234
  # Code borrowed from ActiveSupport
299
- def self.secure_compare(a, b)
300
- return false unless a.bytesize == b.bytesize
235
+ def self.secure_compare(str_a, str_b)
236
+ return false unless str_a.bytesize == str_b.bytesize
301
237
 
302
- l = a.unpack "C#{a.bytesize}"
238
+ l = str_a.unpack "C#{str_a.bytesize}"
303
239
 
304
240
  res = 0
305
- b.each_byte { |byte| res |= byte ^ l.shift }
241
+ str_b.each_byte { |byte| res |= byte ^ l.shift }
306
242
  res.zero?
307
243
  end
308
244
 
@@ -311,14 +247,14 @@ module Stripe
311
247
  #
312
248
 
313
249
  COLOR_CODES = {
314
- black: 0, light_black: 60,
315
- red: 1, light_red: 61,
316
- green: 2, light_green: 62,
317
- yellow: 3, light_yellow: 63,
318
- blue: 4, light_blue: 64,
250
+ black: 0, light_black: 60,
251
+ red: 1, light_red: 61,
252
+ green: 2, light_green: 62,
253
+ yellow: 3, light_yellow: 63,
254
+ blue: 4, light_blue: 64,
319
255
  magenta: 5, light_magenta: 65,
320
- cyan: 6, light_cyan: 66,
321
- white: 7, light_white: 67,
256
+ cyan: 6, light_cyan: 66,
257
+ white: 7, light_white: 67,
322
258
  default: 9,
323
259
  }.freeze
324
260
  private_constant :COLOR_CODES
@@ -347,23 +283,32 @@ module Stripe
347
283
  end
348
284
  private_class_method :level_name
349
285
 
350
- # TODO: Make these named required arguments when we drop support for Ruby
351
- # 2.0.
352
- def self.log_internal(message, data = {}, color: nil, level: nil, logger: nil, out: nil)
286
+ def self.log_internal(message, data = {}, color:, level:, logger:, out:)
353
287
  data_str = data.reject { |_k, v| v.nil? }
354
288
  .map do |(k, v)|
355
- format("%s=%s", colorize(k, color, logger.nil? && !out.nil? && out.isatty), wrap_logfmt_value(v))
289
+ format("%<key>s=%<value>s",
290
+ key: colorize(k, color, logger.nil? && !out.nil? && out.isatty),
291
+ value: wrap_logfmt_value(v))
356
292
  end.join(" ")
357
293
 
358
294
  if !logger.nil?
359
295
  # the library's log levels are mapped to the same values as the
360
296
  # standard library's logger
361
297
  logger.log(level,
362
- format("message=%s %s", wrap_logfmt_value(message), data_str))
298
+ format("message=%<message>s %<data_str>s",
299
+ message: wrap_logfmt_value(message),
300
+ data_str: data_str))
363
301
  elsif out.isatty
364
- out.puts format("%s %s %s", colorize(level_name(level)[0, 4].upcase, color, out.isatty), message, data_str)
302
+ out.puts format("%<level>s %<message>s %<data_str>s",
303
+ level: colorize(level_name(level)[0, 4].upcase,
304
+ color, out.isatty),
305
+ message: message,
306
+ data_str: data_str)
365
307
  else
366
- out.puts format("message=%s level=%s %s", wrap_logfmt_value(message), level_name(level), data_str)
308
+ out.puts format("message=%<message>s level=%<level>s %<data_str>s",
309
+ message: wrap_logfmt_value(message),
310
+ level: level_name(level),
311
+ data_str: data_str)
367
312
  end
368
313
  end
369
314
  private_class_method :log_internal
@@ -381,7 +326,7 @@ module Stripe
381
326
  if %r{[^\w\-/]} =~ val
382
327
  # If the string contains any special characters, escape any double
383
328
  # quotes it has, remove newlines, and wrap the whole thing in quotes.
384
- format(%("%s"), val.gsub('"', '\"').delete("\n"))
329
+ format(%("%<value>s"), value: val.gsub('"', '\"').delete("\n"))
385
330
  else
386
331
  # Otherwise use the basic value if it looks like a standard set of
387
332
  # characters (and allow a few special characters like hyphens, and