stripe 5.28.0 → 5.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Gemfile +0 -1
  4. data/README.md +5 -1
  5. data/VERSION +1 -1
  6. data/lib/stripe/api_operations/request.rb +35 -2
  7. data/lib/stripe/api_resource.rb +8 -0
  8. data/lib/stripe/connection_manager.rb +27 -10
  9. data/lib/stripe/error_object.rb +2 -3
  10. data/lib/stripe/instrumentation.rb +3 -1
  11. data/lib/stripe/oauth.rb +4 -3
  12. data/lib/stripe/object_types.rb +7 -1
  13. data/lib/stripe/resources/account.rb +3 -8
  14. data/lib/stripe/resources/application_fee_refund.rb +2 -1
  15. data/lib/stripe/resources/billing_portal/configuration.rb +14 -0
  16. data/lib/stripe/resources/bitcoin_transaction.rb +3 -2
  17. data/lib/stripe/resources/capability.rb +2 -1
  18. data/lib/stripe/resources/credit_note_line_item.rb +1 -0
  19. data/lib/stripe/resources/customer.rb +6 -1
  20. data/lib/stripe/resources/customer_balance_transaction.rb +3 -2
  21. data/lib/stripe/resources/file.rb +2 -1
  22. data/lib/stripe/resources/identity/verification_report.rb +12 -0
  23. data/lib/stripe/resources/identity/verification_session.rb +35 -0
  24. data/lib/stripe/resources/invoice_line_item.rb +1 -0
  25. data/lib/stripe/resources/line_item.rb +1 -0
  26. data/lib/stripe/resources/login_link.rb +1 -0
  27. data/lib/stripe/resources/person.rb +1 -0
  28. data/lib/stripe/resources/quote.rb +105 -0
  29. data/lib/stripe/resources/reversal.rb +3 -2
  30. data/lib/stripe/resources/tax_code.rb +10 -0
  31. data/lib/stripe/resources/tax_id.rb +1 -0
  32. data/lib/stripe/resources/usage_record.rb +1 -0
  33. data/lib/stripe/resources/usage_record_summary.rb +1 -0
  34. data/lib/stripe/resources.rb +6 -0
  35. data/lib/stripe/stripe_client.rb +242 -111
  36. data/lib/stripe/stripe_configuration.rb +25 -9
  37. data/lib/stripe/stripe_object.rb +23 -0
  38. data/lib/stripe/stripe_response.rb +80 -52
  39. data/lib/stripe/util.rb +15 -7
  40. data/lib/stripe/version.rb +1 -1
  41. data/lib/stripe.rb +23 -22
  42. data/stripe.gemspec +9 -2
  43. metadata +8 -192
  44. data/.editorconfig +0 -10
  45. data/.gitattributes +0 -4
  46. data/.github/ISSUE_TEMPLATE.md +0 -5
  47. data/.gitignore +0 -8
  48. data/.rubocop.yml +0 -80
  49. data/.rubocop_todo.yml +0 -33
  50. data/.travis.yml +0 -38
  51. data/.vscode/extensions.json +0 -7
  52. data/.vscode/settings.json +0 -8
  53. data/test/openapi/README.md +0 -9
  54. data/test/stripe/account_link_test.rb +0 -18
  55. data/test/stripe/account_test.rb +0 -412
  56. data/test/stripe/alipay_account_test.rb +0 -37
  57. data/test/stripe/api_operations_test.rb +0 -80
  58. data/test/stripe/api_resource_test.rb +0 -646
  59. data/test/stripe/apple_pay_domain_test.rb +0 -46
  60. data/test/stripe/application_fee_refund_test.rb +0 -37
  61. data/test/stripe/application_fee_test.rb +0 -58
  62. data/test/stripe/balance_test.rb +0 -13
  63. data/test/stripe/balance_transaction_test.rb +0 -20
  64. data/test/stripe/bank_account_test.rb +0 -36
  65. data/test/stripe/billing_portal/session_test.rb +0 -18
  66. data/test/stripe/capability_test.rb +0 -45
  67. data/test/stripe/charge_test.rb +0 -64
  68. data/test/stripe/checkout/session_test.rb +0 -53
  69. data/test/stripe/connection_manager_test.rb +0 -167
  70. data/test/stripe/country_spec_test.rb +0 -20
  71. data/test/stripe/coupon_test.rb +0 -61
  72. data/test/stripe/credit_note_test.rb +0 -90
  73. data/test/stripe/customer_balance_transaction_test.rb +0 -37
  74. data/test/stripe/customer_card_test.rb +0 -48
  75. data/test/stripe/customer_test.rb +0 -226
  76. data/test/stripe/dispute_test.rb +0 -51
  77. data/test/stripe/ephemeral_key_test.rb +0 -93
  78. data/test/stripe/errors_test.rb +0 -53
  79. data/test/stripe/exchange_rate_test.rb +0 -20
  80. data/test/stripe/file_link_test.rb +0 -41
  81. data/test/stripe/file_test.rb +0 -87
  82. data/test/stripe/instrumentation_test.rb +0 -74
  83. data/test/stripe/invoice_item_test.rb +0 -66
  84. data/test/stripe/invoice_line_item_test.rb +0 -8
  85. data/test/stripe/invoice_test.rb +0 -229
  86. data/test/stripe/issuing/authorization_test.rb +0 -72
  87. data/test/stripe/issuing/card_test.rb +0 -74
  88. data/test/stripe/issuing/cardholder_test.rb +0 -53
  89. data/test/stripe/issuing/dispute_test.rb +0 -54
  90. data/test/stripe/issuing/transaction_test.rb +0 -48
  91. data/test/stripe/list_object_test.rb +0 -202
  92. data/test/stripe/login_link_test.rb +0 -37
  93. data/test/stripe/mandate_test.rb +0 -14
  94. data/test/stripe/multipart_encoder_test.rb +0 -130
  95. data/test/stripe/oauth_test.rb +0 -104
  96. data/test/stripe/order_return_test.rb +0 -21
  97. data/test/stripe/order_test.rb +0 -82
  98. data/test/stripe/payment_intent_test.rb +0 -107
  99. data/test/stripe/payment_method_test.rb +0 -84
  100. data/test/stripe/payout_test.rb +0 -72
  101. data/test/stripe/person_test.rb +0 -46
  102. data/test/stripe/plan_test.rb +0 -98
  103. data/test/stripe/price_test.rb +0 -48
  104. data/test/stripe/product_test.rb +0 -58
  105. data/test/stripe/promotion_code_test.rb +0 -42
  106. data/test/stripe/radar/early_fraud_warning_test.rb +0 -22
  107. data/test/stripe/radar/value_list_item_test.rb +0 -48
  108. data/test/stripe/radar/value_list_test.rb +0 -61
  109. data/test/stripe/recipient_test.rb +0 -62
  110. data/test/stripe/refund_test.rb +0 -39
  111. data/test/stripe/reporting/report_run_test.rb +0 -33
  112. data/test/stripe/reporting/report_type_test.rb +0 -22
  113. data/test/stripe/reversal_test.rb +0 -43
  114. data/test/stripe/review_test.rb +0 -27
  115. data/test/stripe/setup_attempt_test.rb +0 -16
  116. data/test/stripe/setup_intent_test.rb +0 -84
  117. data/test/stripe/sigma/scheduled_query_run_test.rb +0 -22
  118. data/test/stripe/sku_test.rb +0 -60
  119. data/test/stripe/source_test.rb +0 -119
  120. data/test/stripe/stripe_client_test.rb +0 -1291
  121. data/test/stripe/stripe_configuration_test.rb +0 -131
  122. data/test/stripe/stripe_object_test.rb +0 -500
  123. data/test/stripe/stripe_response_test.rb +0 -95
  124. data/test/stripe/subscription_item_test.rb +0 -84
  125. data/test/stripe/subscription_schedule_test.rb +0 -82
  126. data/test/stripe/subscription_test.rb +0 -80
  127. data/test/stripe/tax_id_test.rb +0 -31
  128. data/test/stripe/tax_rate_test.rb +0 -43
  129. data/test/stripe/terminal/connection_token_test.rb +0 -16
  130. data/test/stripe/terminal/location_test.rb +0 -68
  131. data/test/stripe/terminal/reader_test.rb +0 -62
  132. data/test/stripe/three_d_secure_test.rb +0 -23
  133. data/test/stripe/topup_test.rb +0 -62
  134. data/test/stripe/transfer_test.rb +0 -88
  135. data/test/stripe/usage_record_summary_test.rb +0 -29
  136. data/test/stripe/util_test.rb +0 -402
  137. data/test/stripe/webhook_endpoint_test.rb +0 -59
  138. data/test/stripe/webhook_test.rb +0 -135
  139. data/test/stripe_mock.rb +0 -78
  140. data/test/stripe_test.rb +0 -132
  141. data/test/test_data.rb +0 -61
  142. data/test/test_helper.rb +0 -77
@@ -9,19 +9,36 @@ module Stripe
9
9
  class StripeClient
10
10
  # A set of all known thread contexts across all threads and a mutex to
11
11
  # synchronize global access to them.
12
- @thread_contexts_with_connection_managers = []
12
+ @thread_contexts_with_connection_managers = Set.new
13
13
  @thread_contexts_with_connection_managers_mutex = Mutex.new
14
14
  @last_connection_manager_gc = Util.monotonic_time
15
15
 
16
- # Initializes a new `StripeClient`.
17
- #
18
- # Takes a connection manager object for backwards compatibility only, and
19
- # that use is DEPRECATED.
20
- def initialize(_connection_manager = nil)
16
+ # Initializes a new StripeClient
17
+ def initialize(config_arg = {})
21
18
  @system_profiler = SystemProfiler.new
22
19
  @last_request_metrics = nil
20
+
21
+ @config = case config_arg
22
+ when Hash
23
+ Stripe.config.reverse_duplicate_merge(config_arg)
24
+ when Stripe::ConnectionManager
25
+ # Supports accepting a connection manager object for backwards
26
+ # compatibility only, and that use is DEPRECATED.
27
+ Stripe.config.dup
28
+ when Stripe::StripeConfiguration
29
+ config_arg
30
+ when String
31
+ Stripe.config.reverse_duplicate_merge(
32
+ { api_key: config_arg }
33
+ )
34
+ else
35
+ raise ArgumentError, "Can't handle argument: #{config_arg}"
36
+ end
23
37
  end
24
38
 
39
+ attr_reader :config
40
+ attr_reader :options
41
+
25
42
  # Gets a currently active `StripeClient`. Set for the current thread when
26
43
  # `StripeClient#request` is being run so that API operations being executed
27
44
  # inside of that block can find the currently active client. It's reset to
@@ -37,36 +54,51 @@ module Stripe
37
54
  # clears them from internal tracking in all connection managers across all
38
55
  # threads.
39
56
  #
57
+ # If passed a `config` object, only clear connection managers for that
58
+ # particular configuration.
59
+ #
40
60
  # For internal use only. Does not provide a stable API and may be broken
41
61
  # with future non-major changes.
42
- def self.clear_all_connection_managers
62
+ def self.clear_all_connection_managers(config: nil)
43
63
  # Just a quick path for when configuration is being set for the first
44
64
  # time before any connections have been opened. There is technically some
45
65
  # potential for thread raciness here, but not in a practical sense.
46
66
  return if @thread_contexts_with_connection_managers.empty?
47
67
 
48
68
  @thread_contexts_with_connection_managers_mutex.synchronize do
69
+ pruned_contexts = Set.new
70
+
49
71
  @thread_contexts_with_connection_managers.each do |thread_context|
50
72
  # Note that the thread context itself is not destroyed, but we clear
51
73
  # its connection manager and remove our reference to it. If it ever
52
74
  # makes a new request we'll give it a new connection manager and
53
75
  # it'll go back into `@thread_contexts_with_connection_managers`.
54
- thread_context.default_connection_manager.clear
55
- thread_context.default_connection_manager = nil
76
+ thread_context.default_connection_managers.reject! do |cm_config, cm|
77
+ if config.nil? || config.key == cm_config
78
+ cm.clear
79
+ true
80
+ end
81
+ end
82
+
83
+ if thread_context.default_connection_managers.empty?
84
+ pruned_contexts << thread_context
85
+ end
56
86
  end
57
- @thread_contexts_with_connection_managers.clear
87
+
88
+ @thread_contexts_with_connection_managers.subtract(pruned_contexts)
58
89
  end
59
90
  end
60
91
 
61
92
  # A default client for the current thread.
62
93
  def self.default_client
63
- current_thread_context.default_client ||= StripeClient.new
94
+ current_thread_context.default_client ||= StripeClient.new(Stripe.config)
64
95
  end
65
96
 
66
- # A default connection manager for the current thread.
67
- def self.default_connection_manager
68
- current_thread_context.default_connection_manager ||= begin
69
- connection_manager = ConnectionManager.new
97
+ # A default connection manager for the current thread scoped to the
98
+ # configuration object that may be provided.
99
+ def self.default_connection_manager(config = Stripe.config)
100
+ current_thread_context.default_connection_managers[config.key] ||= begin
101
+ connection_manager = ConnectionManager.new(config)
70
102
 
71
103
  @thread_contexts_with_connection_managers_mutex.synchronize do
72
104
  maybe_gc_connection_managers
@@ -80,8 +112,9 @@ module Stripe
80
112
  # Checks if an error is a problem that we should retry on. This includes
81
113
  # both socket errors that may represent an intermittent problem and some
82
114
  # special HTTP statuses.
83
- def self.should_retry?(error, method:, num_retries:)
84
- return false if num_retries >= Stripe.max_network_retries
115
+ def self.should_retry?(error,
116
+ method:, num_retries:, config: Stripe.config)
117
+ return false if num_retries >= config.max_network_retries
85
118
 
86
119
  case error
87
120
  when Net::OpenTimeout, Net::ReadTimeout
@@ -127,13 +160,13 @@ module Stripe
127
160
  end
128
161
  end
129
162
 
130
- def self.sleep_time(num_retries)
163
+ def self.sleep_time(num_retries, config: Stripe.config)
131
164
  # Apply exponential backoff with initial_network_retry_delay on the
132
165
  # number of num_retries so far as inputs. Do not allow the number to
133
166
  # exceed max_network_retry_delay.
134
167
  sleep_seconds = [
135
- Stripe.initial_network_retry_delay * (2**(num_retries - 1)),
136
- Stripe.max_network_retry_delay,
168
+ config.initial_network_retry_delay * (2**(num_retries - 1)),
169
+ config.max_network_retry_delay,
137
170
  ].min
138
171
 
139
172
  # Apply some jitter by randomizing the value in the range of
@@ -141,9 +174,7 @@ module Stripe
141
174
  sleep_seconds *= (0.5 * (1 + rand))
142
175
 
143
176
  # But never sleep less than the base sleep seconds.
144
- sleep_seconds = [Stripe.initial_network_retry_delay, sleep_seconds].max
145
-
146
- sleep_seconds
177
+ [config.initial_network_retry_delay, sleep_seconds].max
147
178
  end
148
179
 
149
180
  # Gets the connection manager in use for the current `StripeClient`.
@@ -182,60 +213,9 @@ module Stripe
182
213
 
183
214
  def execute_request(method, path,
184
215
  api_base: nil, api_key: nil, headers: {}, params: {})
185
- raise ArgumentError, "method should be a symbol" \
186
- unless method.is_a?(Symbol)
187
- raise ArgumentError, "path should be a string" \
188
- unless path.is_a?(String)
189
-
190
- api_base ||= Stripe.api_base
191
- api_key ||= Stripe.api_key
192
- params = Util.objects_to_ids(params)
193
-
194
- check_api_key!(api_key)
195
-
196
- body_params = nil
197
- query_params = nil
198
- case method
199
- when :get, :head, :delete
200
- query_params = params
201
- else
202
- body_params = params
203
- end
204
-
205
- query_params, path = merge_query_params(query_params, path)
206
-
207
- headers = request_headers(api_key, method)
208
- .update(Util.normalize_headers(headers))
209
- url = api_url(path, api_base)
210
-
211
- # Merge given query parameters with any already encoded in the path.
212
- query = query_params ? Util.encode_parameters(query_params) : nil
213
-
214
- # Encoding body parameters is a little more complex because we may have
215
- # to send a multipart-encoded body. `body_log` is produced separately as
216
- # a log-friendly variant of the encoded form. File objects are displayed
217
- # as such instead of as their file contents.
218
- body, body_log =
219
- body_params ? encode_body(body_params, headers) : [nil, nil]
220
-
221
- # stores information on the request we're about to make so that we don't
222
- # have to pass as many parameters around for logging.
223
- context = RequestLogContext.new
224
- context.account = headers["Stripe-Account"]
225
- context.api_key = api_key
226
- context.api_version = headers["Stripe-Version"]
227
- context.body = body_log
228
- context.idempotency_key = headers["Idempotency-Key"]
229
- context.method = method
230
- context.path = path
231
- context.query = query
232
-
233
- http_resp = execute_request_with_rescues(method, api_base, context) do
234
- self.class.default_connection_manager.execute_request(method, url,
235
- body: body,
236
- headers: headers,
237
- query: query)
238
- end
216
+ http_resp, api_key = execute_request_internal(
217
+ method, path, api_base, api_key, headers, params
218
+ )
239
219
 
240
220
  begin
241
221
  resp = StripeResponse.from_net_http(http_resp)
@@ -246,13 +226,53 @@ module Stripe
246
226
  # If being called from `StripeClient#request`, put the last response in
247
227
  # thread-local memory so that it can be returned to the user. Don't store
248
228
  # anything otherwise so that we don't leak memory.
249
- if self.class.current_thread_context.last_responses&.key?(object_id)
250
- self.class.current_thread_context.last_responses[object_id] = resp
229
+ store_last_response(object_id, resp)
230
+
231
+ [resp, api_key]
232
+ end
233
+
234
+ # Executes a request and returns the body as a stream instead of converting
235
+ # it to a StripeObject. This should be used for any request where we expect
236
+ # an arbitrary binary response.
237
+ #
238
+ # A `read_body_chunk` block can be passed, which will be called repeatedly
239
+ # with the body chunks read from the socket.
240
+ #
241
+ # If a block is passed, a StripeHeadersOnlyResponse is returned as the
242
+ # block is expected to do all the necessary body processing. If no block is
243
+ # passed, then a StripeStreamResponse is returned containing an IO stream
244
+ # with the response body.
245
+ def execute_request_stream(method, path,
246
+ api_base: nil, api_key: nil,
247
+ headers: {}, params: {},
248
+ &read_body_chunk_block)
249
+ unless block_given?
250
+ raise ArgumentError,
251
+ "execute_request_stream requires a read_body_chunk_block"
251
252
  end
252
253
 
254
+ http_resp, api_key = execute_request_internal(
255
+ method, path, api_base, api_key, headers, params, &read_body_chunk_block
256
+ )
257
+
258
+ # When the read_body_chunk_block is given, we no longer have access to the
259
+ # response body at this point and so return a response object containing
260
+ # only the headers. This is because the body was consumed by the block.
261
+ resp = StripeHeadersOnlyResponse.from_net_http(http_resp)
262
+
253
263
  [resp, api_key]
254
264
  end
255
265
 
266
+ def store_last_response(object_id, resp)
267
+ return unless last_response_has_key?(object_id)
268
+
269
+ self.class.current_thread_context.last_responses[object_id] = resp
270
+ end
271
+
272
+ def last_response_has_key?(object_id)
273
+ self.class.current_thread_context.last_responses&.key?(object_id)
274
+ end
275
+
256
276
  #
257
277
  # private
258
278
  #
@@ -328,11 +348,6 @@ module Stripe
328
348
  # the user hasn't specified their own.
329
349
  attr_accessor :default_client
330
350
 
331
- # A default `ConnectionManager` for the thread. Normally shared between
332
- # all `StripeClient` objects on a particular thread, and created so as to
333
- # minimize the number of open connections that an application needs.
334
- attr_accessor :default_connection_manager
335
-
336
351
  # A temporary map of object IDs to responses from last executed API
337
352
  # calls. Used to return a responses from calls to `StripeClient#request`.
338
353
  #
@@ -345,6 +360,17 @@ module Stripe
345
360
  # because that's wrapped in an `ensure` block, they should never leave
346
361
  # garbage in `Thread.current`.
347
362
  attr_accessor :last_responses
363
+
364
+ # A map of connection mangers for the thread. Normally shared between
365
+ # all `StripeClient` objects on a particular thread, and created so as to
366
+ # minimize the number of open connections that an application needs.
367
+ def default_connection_managers
368
+ @default_connection_managers ||= {}
369
+ end
370
+
371
+ def reset_connection_managers
372
+ @default_connection_managers = {}
373
+ end
348
374
  end
349
375
 
350
376
  # Access data stored for `StripeClient` within the thread's current
@@ -380,24 +406,109 @@ module Stripe
380
406
  last_used_threshold =
381
407
  Util.monotonic_time - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY
382
408
 
383
- pruned_thread_contexts = []
409
+ pruned_contexts = []
384
410
  @thread_contexts_with_connection_managers.each do |thread_context|
385
- connection_manager = thread_context.default_connection_manager
386
- next if connection_manager.last_used > last_used_threshold
411
+ thread_context
412
+ .default_connection_managers
413
+ .each do |config_key, connection_manager|
414
+ next if connection_manager.last_used > last_used_threshold
387
415
 
388
- connection_manager.clear
389
- thread_context.default_connection_manager = nil
390
- pruned_thread_contexts << thread_context
416
+ connection_manager.clear
417
+ thread_context.default_connection_managers.delete(config_key)
418
+ end
391
419
  end
392
420
 
393
- @thread_contexts_with_connection_managers -= pruned_thread_contexts
421
+ @thread_contexts_with_connection_managers.each do |thread_context|
422
+ next unless thread_context.default_connection_managers.empty?
423
+
424
+ pruned_contexts << thread_context
425
+ end
426
+
427
+ @thread_contexts_with_connection_managers -= pruned_contexts
394
428
  @last_connection_manager_gc = Util.monotonic_time
395
429
 
396
- pruned_thread_contexts.count
430
+ pruned_contexts.count
431
+ end
432
+
433
+ private def execute_request_internal(method, path,
434
+ api_base, api_key, headers, params,
435
+ &read_body_chunk_block)
436
+ raise ArgumentError, "method should be a symbol" \
437
+ unless method.is_a?(Symbol)
438
+ raise ArgumentError, "path should be a string" \
439
+ unless path.is_a?(String)
440
+
441
+ api_base ||= config.api_base
442
+ api_key ||= config.api_key
443
+ params = Util.objects_to_ids(params)
444
+
445
+ check_api_key!(api_key)
446
+
447
+ body_params = nil
448
+ query_params = nil
449
+ case method
450
+ when :get, :head, :delete
451
+ query_params = params
452
+ else
453
+ body_params = params
454
+ end
455
+
456
+ query_params, path = merge_query_params(query_params, path)
457
+
458
+ headers = request_headers(api_key, method)
459
+ .update(Util.normalize_headers(headers))
460
+ url = api_url(path, api_base)
461
+
462
+ # Merge given query parameters with any already encoded in the path.
463
+ query = query_params ? Util.encode_parameters(query_params) : nil
464
+
465
+ # Encoding body parameters is a little more complex because we may have
466
+ # to send a multipart-encoded body. `body_log` is produced separately as
467
+ # a log-friendly variant of the encoded form. File objects are displayed
468
+ # as such instead of as their file contents.
469
+ body, body_log =
470
+ body_params ? encode_body(body_params, headers) : [nil, nil]
471
+
472
+ # stores information on the request we're about to make so that we don't
473
+ # have to pass as many parameters around for logging.
474
+ context = RequestLogContext.new
475
+ context.account = headers["Stripe-Account"]
476
+ context.api_key = api_key
477
+ context.api_version = headers["Stripe-Version"]
478
+ context.body = body_log
479
+ context.idempotency_key = headers["Idempotency-Key"]
480
+ context.method = method
481
+ context.path = path
482
+ context.query = query
483
+
484
+ # A block can be passed in to read the content directly from the response.
485
+ # We want to execute this block only when the response was actually
486
+ # successful. When it wasn't, we defer to the standard error handling as
487
+ # we have to read the body and parse the error JSON.
488
+ response_block =
489
+ if block_given?
490
+ lambda do |response|
491
+ unless should_handle_as_error(response.code.to_i)
492
+ response.read_body(&read_body_chunk_block)
493
+ end
494
+ end
495
+ end
496
+
497
+ http_resp = execute_request_with_rescues(method, api_base, context) do
498
+ self.class
499
+ .default_connection_manager(config)
500
+ .execute_request(method, url,
501
+ body: body,
502
+ headers: headers,
503
+ query: query,
504
+ &response_block)
505
+ end
506
+
507
+ [http_resp, api_key]
397
508
  end
398
509
 
399
510
  private def api_url(url = "", api_base = nil)
400
- (api_base || Stripe.api_base) + url
511
+ (api_base || config.api_base) + url
401
512
  end
402
513
 
403
514
  private def check_api_key!(api_key)
@@ -435,6 +546,7 @@ module Stripe
435
546
  # that's more condusive to logging.
436
547
  flattened_params =
437
548
  flattened_params.map { |k, v| [k, v.is_a?(String) ? v : v.to_s] }.to_h
549
+
438
550
  else
439
551
  body = Util.encode_parameters(body_params)
440
552
  end
@@ -448,6 +560,10 @@ module Stripe
448
560
  [body, body_log]
449
561
  end
450
562
 
563
+ private def should_handle_as_error(http_status)
564
+ http_status >= 400
565
+ end
566
+
451
567
  private def execute_request_with_rescues(method, api_base, context)
452
568
  num_retries = 0
453
569
 
@@ -465,13 +581,15 @@ module Stripe
465
581
  http_status = resp.code.to_i
466
582
  context = context.dup_from_response_headers(resp)
467
583
 
468
- handle_error_response(resp, context) if http_status >= 400
584
+ if should_handle_as_error(http_status)
585
+ handle_error_response(resp, context)
586
+ end
469
587
 
470
588
  log_response(context, request_start, http_status, resp.body)
471
589
  notify_request_end(context, request_duration, http_status,
472
590
  num_retries, user_data)
473
591
 
474
- if Stripe.enable_telemetry? && context.request_id
592
+ if config.enable_telemetry? && context.request_id
475
593
  request_duration_ms = (request_duration * 1000).to_i
476
594
  @last_request_metrics =
477
595
  StripeRequestMetrics.new(context.request_id, request_duration_ms)
@@ -498,9 +616,12 @@ module Stripe
498
616
  notify_request_end(context, request_duration, http_status, num_retries,
499
617
  user_data)
500
618
 
501
- if self.class.should_retry?(e, method: method, num_retries: num_retries)
619
+ if self.class.should_retry?(e,
620
+ method: method,
621
+ num_retries: num_retries,
622
+ config: config)
502
623
  num_retries += 1
503
- sleep self.class.sleep_time(num_retries)
624
+ sleep self.class.sleep_time(num_retries, config: config)
504
625
  retry
505
626
  end
506
627
 
@@ -546,6 +667,7 @@ module Stripe
546
667
  method: context.method,
547
668
  num_retries: num_retries,
548
669
  path: context.path,
670
+ request_id: context.request_id,
549
671
  user_data: user_data || {}
550
672
  )
551
673
  Stripe::Instrumentation.notify(:request_end, event)
@@ -622,7 +744,8 @@ module Stripe
622
744
  error_param: error_data[:param],
623
745
  error_type: error_data[:type],
624
746
  idempotency_key: context.idempotency_key,
625
- request_id: context.request_id)
747
+ request_id: context.request_id,
748
+ config: config)
626
749
 
627
750
  # The standard set of arguments that can be used to initialize most of
628
751
  # the exceptions.
@@ -671,7 +794,8 @@ module Stripe
671
794
  error_code: error_code,
672
795
  error_description: description,
673
796
  idempotency_key: context.idempotency_key,
674
- request_id: context.request_id)
797
+ request_id: context.request_id,
798
+ config: config)
675
799
 
676
800
  args = {
677
801
  http_status: resp.http_status, http_body: resp.http_body,
@@ -703,7 +827,8 @@ module Stripe
703
827
  Util.log_error("Stripe network error",
704
828
  error_message: error.message,
705
829
  idempotency_key: context.idempotency_key,
706
- request_id: context.request_id)
830
+ request_id: context.request_id,
831
+ config: config)
707
832
 
708
833
  errors, message = NETWORK_ERROR_MESSAGES_MAP.detect do |(e, _)|
709
834
  error.is_a?(e)
@@ -714,7 +839,7 @@ module Stripe
714
839
  "with Stripe. Please let us know at support@stripe.com."
715
840
  end
716
841
 
717
- api_base ||= Stripe.api_base
842
+ api_base ||= config.api_base
718
843
  message = message % api_base
719
844
 
720
845
  message += " Request was retried #{num_retries} times." if num_retries > 0
@@ -735,7 +860,7 @@ module Stripe
735
860
  "Content-Type" => "application/x-www-form-urlencoded",
736
861
  }
737
862
 
738
- if Stripe.enable_telemetry? && !@last_request_metrics.nil?
863
+ if config.enable_telemetry? && !@last_request_metrics.nil?
739
864
  headers["X-Stripe-Client-Telemetry"] = JSON.generate(
740
865
  last_request_metrics: @last_request_metrics.payload
741
866
  )
@@ -743,12 +868,12 @@ module Stripe
743
868
 
744
869
  # It is only safe to retry network failures on post and delete
745
870
  # requests if we add an Idempotency-Key header
746
- if %i[post delete].include?(method) && Stripe.max_network_retries > 0
871
+ if %i[post delete].include?(method) && config.max_network_retries > 0
747
872
  headers["Idempotency-Key"] ||= SecureRandom.uuid
748
873
  end
749
874
 
750
- headers["Stripe-Version"] = Stripe.api_version if Stripe.api_version
751
- headers["Stripe-Account"] = Stripe.stripe_account if Stripe.stripe_account
875
+ headers["Stripe-Version"] = config.api_version if config.api_version
876
+ headers["Stripe-Account"] = config.stripe_account if config.stripe_account
752
877
 
753
878
  user_agent = @system_profiler.user_agent
754
879
  begin
@@ -772,11 +897,13 @@ module Stripe
772
897
  idempotency_key: context.idempotency_key,
773
898
  method: context.method,
774
899
  num_retries: num_retries,
775
- path: context.path)
900
+ path: context.path,
901
+ config: config)
776
902
  Util.log_debug("Request details",
777
903
  body: context.body,
778
904
  idempotency_key: context.idempotency_key,
779
- query: context.query)
905
+ query: context.query,
906
+ config: config)
780
907
  end
781
908
 
782
909
  private def log_response(context, request_start, status, body)
@@ -788,11 +915,13 @@ module Stripe
788
915
  method: context.method,
789
916
  path: context.path,
790
917
  request_id: context.request_id,
791
- status: status)
918
+ status: status,
919
+ config: config)
792
920
  Util.log_debug("Response details",
793
921
  body: body,
794
922
  idempotency_key: context.idempotency_key,
795
- request_id: context.request_id)
923
+ request_id: context.request_id,
924
+ config: config)
796
925
 
797
926
  return unless context.request_id
798
927
 
@@ -800,7 +929,8 @@ module Stripe
800
929
  idempotency_key: context.idempotency_key,
801
930
  request_id: context.request_id,
802
931
  url: Util.request_id_dashboard_url(context.request_id,
803
- context.api_key))
932
+ context.api_key),
933
+ config: config)
804
934
  end
805
935
 
806
936
  private def log_response_error(context, request_start, error)
@@ -810,7 +940,8 @@ module Stripe
810
940
  error_message: error.message,
811
941
  idempotency_key: context.idempotency_key,
812
942
  method: context.method,
813
- path: context.path)
943
+ path: context.path,
944
+ config: config)
814
945
  end
815
946
 
816
947
  # RequestLogContext stores information about a request that's begin made so
@@ -101,14 +101,22 @@ module Stripe
101
101
  @max_network_retries = val.to_i
102
102
  end
103
103
 
104
+ def max_network_retry_delay=(val)
105
+ @max_network_retry_delay = val.to_i
106
+ end
107
+
108
+ def initial_network_retry_delay=(val)
109
+ @initial_network_retry_delay = val.to_i
110
+ end
111
+
104
112
  def open_timeout=(open_timeout)
105
113
  @open_timeout = open_timeout
106
- StripeClient.clear_all_connection_managers
114
+ StripeClient.clear_all_connection_managers(config: self)
107
115
  end
108
116
 
109
117
  def read_timeout=(read_timeout)
110
118
  @read_timeout = read_timeout
111
- StripeClient.clear_all_connection_managers
119
+ StripeClient.clear_all_connection_managers(config: self)
112
120
  end
113
121
 
114
122
  def write_timeout=(write_timeout)
@@ -117,32 +125,32 @@ module Stripe
117
125
  end
118
126
 
119
127
  @write_timeout = write_timeout
120
- StripeClient.clear_all_connection_managers
128
+ StripeClient.clear_all_connection_managers(config: self)
121
129
  end
122
130
 
123
131
  def proxy=(proxy)
124
132
  @proxy = proxy
125
- StripeClient.clear_all_connection_managers
133
+ StripeClient.clear_all_connection_managers(config: self)
126
134
  end
127
135
 
128
136
  def verify_ssl_certs=(verify_ssl_certs)
129
137
  @verify_ssl_certs = verify_ssl_certs
130
- StripeClient.clear_all_connection_managers
138
+ StripeClient.clear_all_connection_managers(config: self)
131
139
  end
132
140
 
133
141
  def uploads_base=(uploads_base)
134
142
  @uploads_base = uploads_base
135
- StripeClient.clear_all_connection_managers
143
+ StripeClient.clear_all_connection_managers(config: self)
136
144
  end
137
145
 
138
146
  def connect_base=(connect_base)
139
147
  @connect_base = connect_base
140
- StripeClient.clear_all_connection_managers
148
+ StripeClient.clear_all_connection_managers(config: self)
141
149
  end
142
150
 
143
151
  def api_base=(api_base)
144
152
  @api_base = api_base
145
- StripeClient.clear_all_connection_managers
153
+ StripeClient.clear_all_connection_managers(config: self)
146
154
  end
147
155
 
148
156
  def ca_bundle_path=(path)
@@ -151,7 +159,7 @@ module Stripe
151
159
  # empty this field so a new store is initialized
152
160
  @ca_store = nil
153
161
 
154
- StripeClient.clear_all_connection_managers
162
+ StripeClient.clear_all_connection_managers(config: self)
155
163
  end
156
164
 
157
165
  # A certificate store initialized from the the bundle in #ca_bundle_path and
@@ -174,5 +182,13 @@ module Stripe
174
182
  def enable_telemetry?
175
183
  enable_telemetry
176
184
  end
185
+
186
+ # Generates a deterministic key to identify configuration objects with
187
+ # identical configuration values.
188
+ def key
189
+ instance_variables
190
+ .collect { |variable| instance_variable_get(variable) }
191
+ .join
192
+ end
177
193
  end
178
194
  end