stripe 5.22.0 → 5.55.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (223) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +142 -0
  3. data/Gemfile +0 -1
  4. data/Makefile +7 -0
  5. data/README.md +32 -27
  6. data/VERSION +1 -1
  7. data/lib/stripe/api_operations/create.rb +1 -1
  8. data/lib/stripe/api_operations/delete.rb +7 -3
  9. data/lib/stripe/api_operations/list.rb +1 -1
  10. data/lib/stripe/api_operations/nested_resource.rb +6 -5
  11. data/lib/stripe/api_operations/request.rb +54 -5
  12. data/lib/stripe/api_operations/save.rb +7 -4
  13. data/lib/stripe/api_operations/search.rb +19 -0
  14. data/lib/stripe/api_resource.rb +13 -19
  15. data/lib/stripe/api_resource_test_helpers.rb +47 -0
  16. data/lib/stripe/connection_manager.rb +51 -10
  17. data/lib/stripe/error_object.rb +2 -3
  18. data/lib/stripe/instrumentation.rb +3 -1
  19. data/lib/stripe/list_object.rb +2 -2
  20. data/lib/stripe/oauth.rb +7 -6
  21. data/lib/stripe/object_types.rb +21 -1
  22. data/lib/stripe/resources/account.rb +5 -9
  23. data/lib/stripe/resources/account_link.rb +1 -0
  24. data/lib/stripe/resources/apple_pay_domain.rb +1 -0
  25. data/lib/stripe/resources/application_fee.rb +1 -0
  26. data/lib/stripe/resources/application_fee_refund.rb +2 -1
  27. data/lib/stripe/resources/balance.rb +1 -0
  28. data/lib/stripe/resources/balance_transaction.rb +1 -0
  29. data/lib/stripe/resources/bank_account.rb +2 -1
  30. data/lib/stripe/resources/billing_portal/configuration.rb +14 -0
  31. data/lib/stripe/resources/billing_portal/session.rb +1 -0
  32. data/lib/stripe/resources/bitcoin_receiver.rb +1 -0
  33. data/lib/stripe/resources/bitcoin_transaction.rb +3 -2
  34. data/lib/stripe/resources/capability.rb +2 -1
  35. data/lib/stripe/resources/card.rb +1 -0
  36. data/lib/stripe/resources/cash_balance.rb +22 -0
  37. data/lib/stripe/resources/charge.rb +10 -0
  38. data/lib/stripe/resources/checkout/session.rb +12 -0
  39. data/lib/stripe/resources/country_spec.rb +1 -0
  40. data/lib/stripe/resources/coupon.rb +1 -0
  41. data/lib/stripe/resources/credit_note.rb +3 -2
  42. data/lib/stripe/resources/credit_note_line_item.rb +1 -0
  43. data/lib/stripe/resources/customer.rb +41 -2
  44. data/lib/stripe/resources/customer_balance_transaction.rb +3 -2
  45. data/lib/stripe/resources/discount.rb +1 -0
  46. data/lib/stripe/resources/dispute.rb +1 -0
  47. data/lib/stripe/resources/ephemeral_key.rb +1 -0
  48. data/lib/stripe/resources/event.rb +1 -0
  49. data/lib/stripe/resources/exchange_rate.rb +1 -0
  50. data/lib/stripe/resources/file.rb +3 -1
  51. data/lib/stripe/resources/file_link.rb +1 -0
  52. data/lib/stripe/resources/financial_connections/account.rb +31 -0
  53. data/lib/stripe/resources/financial_connections/account_owner.rb +10 -0
  54. data/lib/stripe/resources/financial_connections/account_ownership.rb +10 -0
  55. data/lib/stripe/resources/financial_connections/session.rb +12 -0
  56. data/lib/stripe/resources/funding_instructions.rb +16 -0
  57. data/lib/stripe/resources/identity/verification_report.rb +12 -0
  58. data/lib/stripe/resources/identity/verification_session.rb +35 -0
  59. data/lib/stripe/resources/invoice.rb +12 -2
  60. data/lib/stripe/resources/invoice_item.rb +1 -0
  61. data/lib/stripe/resources/invoice_line_item.rb +1 -0
  62. data/lib/stripe/resources/issuing/authorization.rb +1 -0
  63. data/lib/stripe/resources/issuing/card.rb +1 -0
  64. data/lib/stripe/resources/issuing/card_details.rb +2 -1
  65. data/lib/stripe/resources/issuing/cardholder.rb +1 -0
  66. data/lib/stripe/resources/issuing/dispute.rb +12 -0
  67. data/lib/stripe/resources/issuing/transaction.rb +1 -0
  68. data/lib/stripe/resources/line_item.rb +1 -0
  69. data/lib/stripe/resources/login_link.rb +1 -0
  70. data/lib/stripe/resources/mandate.rb +1 -0
  71. data/lib/stripe/resources/order.rb +1 -0
  72. data/lib/stripe/resources/order_return.rb +1 -0
  73. data/lib/stripe/resources/payment_intent.rb +40 -0
  74. data/lib/stripe/resources/payment_link.rb +23 -0
  75. data/lib/stripe/resources/payment_method.rb +1 -0
  76. data/lib/stripe/resources/payout.rb +11 -0
  77. data/lib/stripe/resources/person.rb +1 -0
  78. data/lib/stripe/resources/plan.rb +1 -0
  79. data/lib/stripe/resources/price.rb +10 -0
  80. data/lib/stripe/resources/product.rb +10 -0
  81. data/lib/stripe/resources/promotion_code.rb +12 -0
  82. data/lib/stripe/resources/quote.rb +105 -0
  83. data/lib/stripe/resources/radar/early_fraud_warning.rb +1 -0
  84. data/lib/stripe/resources/radar/value_list.rb +1 -0
  85. data/lib/stripe/resources/radar/value_list_item.rb +1 -0
  86. data/lib/stripe/resources/recipient.rb +1 -0
  87. data/lib/stripe/resources/refund.rb +31 -0
  88. data/lib/stripe/resources/reporting/report_run.rb +1 -0
  89. data/lib/stripe/resources/reporting/report_type.rb +1 -0
  90. data/lib/stripe/resources/reversal.rb +3 -2
  91. data/lib/stripe/resources/review.rb +1 -0
  92. data/lib/stripe/resources/setup_attempt.rb +10 -0
  93. data/lib/stripe/resources/setup_intent.rb +11 -0
  94. data/lib/stripe/resources/shipping_rate.rb +12 -0
  95. data/lib/stripe/resources/sigma/scheduled_query_run.rb +1 -0
  96. data/lib/stripe/resources/sku.rb +1 -0
  97. data/lib/stripe/resources/source.rb +4 -3
  98. data/lib/stripe/resources/source_transaction.rb +1 -0
  99. data/lib/stripe/resources/subscription.rb +10 -0
  100. data/lib/stripe/resources/subscription_item.rb +2 -1
  101. data/lib/stripe/resources/subscription_schedule.rb +1 -0
  102. data/lib/stripe/resources/tax_code.rb +10 -0
  103. data/lib/stripe/resources/tax_id.rb +1 -0
  104. data/lib/stripe/resources/tax_rate.rb +1 -0
  105. data/lib/stripe/resources/terminal/configuration.rb +15 -0
  106. data/lib/stripe/resources/terminal/connection_token.rb +1 -0
  107. data/lib/stripe/resources/terminal/location.rb +1 -0
  108. data/lib/stripe/resources/terminal/reader.rb +61 -0
  109. data/lib/stripe/resources/test_helpers/test_clock.rb +25 -0
  110. data/lib/stripe/resources/three_d_secure.rb +1 -0
  111. data/lib/stripe/resources/token.rb +1 -0
  112. data/lib/stripe/resources/topup.rb +1 -0
  113. data/lib/stripe/resources/transfer.rb +1 -0
  114. data/lib/stripe/resources/usage_record.rb +1 -0
  115. data/lib/stripe/resources/usage_record_summary.rb +1 -0
  116. data/lib/stripe/resources/webhook_endpoint.rb +1 -0
  117. data/lib/stripe/resources.rb +18 -0
  118. data/lib/stripe/search_result_object.rb +86 -0
  119. data/lib/stripe/stripe_client.rb +252 -114
  120. data/lib/stripe/stripe_configuration.rb +194 -0
  121. data/lib/stripe/stripe_object.rb +24 -1
  122. data/lib/stripe/stripe_response.rb +80 -52
  123. data/lib/stripe/util.rb +62 -7
  124. data/lib/stripe/version.rb +1 -1
  125. data/lib/stripe.rb +39 -168
  126. data/stripe.gemspec +12 -5
  127. metadata +29 -190
  128. data/.editorconfig +0 -10
  129. data/.gitattributes +0 -4
  130. data/.github/ISSUE_TEMPLATE.md +0 -5
  131. data/.gitignore +0 -8
  132. data/.rubocop.yml +0 -80
  133. data/.rubocop_todo.yml +0 -33
  134. data/.travis.yml +0 -40
  135. data/.vscode/extensions.json +0 -7
  136. data/.vscode/settings.json +0 -8
  137. data/test/openapi/README.md +0 -9
  138. data/test/stripe/account_link_test.rb +0 -18
  139. data/test/stripe/account_test.rb +0 -412
  140. data/test/stripe/alipay_account_test.rb +0 -37
  141. data/test/stripe/api_operations_test.rb +0 -80
  142. data/test/stripe/api_resource_test.rb +0 -646
  143. data/test/stripe/apple_pay_domain_test.rb +0 -46
  144. data/test/stripe/application_fee_refund_test.rb +0 -37
  145. data/test/stripe/application_fee_test.rb +0 -58
  146. data/test/stripe/balance_test.rb +0 -13
  147. data/test/stripe/balance_transaction_test.rb +0 -20
  148. data/test/stripe/bank_account_test.rb +0 -36
  149. data/test/stripe/billing_portal/session_test.rb +0 -18
  150. data/test/stripe/capability_test.rb +0 -45
  151. data/test/stripe/charge_test.rb +0 -64
  152. data/test/stripe/checkout/session_test.rb +0 -53
  153. data/test/stripe/connection_manager_test.rb +0 -163
  154. data/test/stripe/country_spec_test.rb +0 -20
  155. data/test/stripe/coupon_test.rb +0 -61
  156. data/test/stripe/credit_note_test.rb +0 -90
  157. data/test/stripe/customer_balance_transaction_test.rb +0 -37
  158. data/test/stripe/customer_card_test.rb +0 -42
  159. data/test/stripe/customer_test.rb +0 -226
  160. data/test/stripe/dispute_test.rb +0 -51
  161. data/test/stripe/ephemeral_key_test.rb +0 -93
  162. data/test/stripe/errors_test.rb +0 -53
  163. data/test/stripe/exchange_rate_test.rb +0 -20
  164. data/test/stripe/file_link_test.rb +0 -41
  165. data/test/stripe/file_test.rb +0 -87
  166. data/test/stripe/instrumentation_test.rb +0 -74
  167. data/test/stripe/invoice_item_test.rb +0 -66
  168. data/test/stripe/invoice_line_item_test.rb +0 -8
  169. data/test/stripe/invoice_test.rb +0 -229
  170. data/test/stripe/issuing/authorization_test.rb +0 -72
  171. data/test/stripe/issuing/card_test.rb +0 -74
  172. data/test/stripe/issuing/cardholder_test.rb +0 -53
  173. data/test/stripe/issuing/dispute_test.rb +0 -35
  174. data/test/stripe/issuing/transaction_test.rb +0 -48
  175. data/test/stripe/list_object_test.rb +0 -202
  176. data/test/stripe/login_link_test.rb +0 -37
  177. data/test/stripe/mandate_test.rb +0 -14
  178. data/test/stripe/multipart_encoder_test.rb +0 -130
  179. data/test/stripe/oauth_test.rb +0 -104
  180. data/test/stripe/order_return_test.rb +0 -21
  181. data/test/stripe/order_test.rb +0 -82
  182. data/test/stripe/payment_intent_test.rb +0 -107
  183. data/test/stripe/payment_method_test.rb +0 -84
  184. data/test/stripe/payout_test.rb +0 -57
  185. data/test/stripe/person_test.rb +0 -46
  186. data/test/stripe/plan_test.rb +0 -98
  187. data/test/stripe/price_test.rb +0 -48
  188. data/test/stripe/product_test.rb +0 -59
  189. data/test/stripe/radar/early_fraud_warning_test.rb +0 -22
  190. data/test/stripe/radar/value_list_item_test.rb +0 -48
  191. data/test/stripe/radar/value_list_test.rb +0 -61
  192. data/test/stripe/recipient_test.rb +0 -62
  193. data/test/stripe/refund_test.rb +0 -39
  194. data/test/stripe/reporting/report_run_test.rb +0 -33
  195. data/test/stripe/reporting/report_type_test.rb +0 -22
  196. data/test/stripe/reversal_test.rb +0 -43
  197. data/test/stripe/review_test.rb +0 -27
  198. data/test/stripe/setup_intent_test.rb +0 -84
  199. data/test/stripe/sigma/scheduled_query_run_test.rb +0 -22
  200. data/test/stripe/sku_test.rb +0 -60
  201. data/test/stripe/source_test.rb +0 -119
  202. data/test/stripe/stripe_client_test.rb +0 -1291
  203. data/test/stripe/stripe_object_test.rb +0 -500
  204. data/test/stripe/stripe_response_test.rb +0 -95
  205. data/test/stripe/subscription_item_test.rb +0 -84
  206. data/test/stripe/subscription_schedule_test.rb +0 -82
  207. data/test/stripe/subscription_test.rb +0 -80
  208. data/test/stripe/tax_id_test.rb +0 -31
  209. data/test/stripe/tax_rate_test.rb +0 -43
  210. data/test/stripe/terminal/connection_token_test.rb +0 -16
  211. data/test/stripe/terminal/location_test.rb +0 -68
  212. data/test/stripe/terminal/reader_test.rb +0 -62
  213. data/test/stripe/three_d_secure_test.rb +0 -23
  214. data/test/stripe/topup_test.rb +0 -62
  215. data/test/stripe/transfer_test.rb +0 -88
  216. data/test/stripe/usage_record_summary_test.rb +0 -29
  217. data/test/stripe/util_test.rb +0 -402
  218. data/test/stripe/webhook_endpoint_test.rb +0 -59
  219. data/test/stripe/webhook_test.rb +0 -135
  220. data/test/stripe_mock.rb +0 -78
  221. data/test/stripe_test.rb +0 -50
  222. data/test/test_data.rb +0 -61
  223. data/test/test_helper.rb +0 -75
@@ -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
- log_response(context, request_start, http_status, resp.body)
588
+ log_response(context, request_start, http_status, resp.body, resp)
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)
@@ -491,16 +609,19 @@ module Stripe
491
609
  error_context = context.dup_from_response_headers(e.http_headers)
492
610
  http_status = resp.code.to_i
493
611
  log_response(error_context, request_start,
494
- e.http_status, e.http_body)
612
+ e.http_status, e.http_body, resp)
495
613
  else
496
614
  log_response_error(error_context, request_start, e)
497
615
  end
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,14 +897,19 @@ 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,
907
+ process_id: Process.pid,
908
+ thread_object_id: Thread.current.object_id,
909
+ log_timestamp: Util.monotonic_time)
780
910
  end
781
911
 
782
- private def log_response(context, request_start, status, body)
912
+ private def log_response(context, request_start, status, body, resp)
783
913
  Util.log_info("Response from Stripe API",
784
914
  account: context.account,
785
915
  api_version: context.api_version,
@@ -788,11 +918,17 @@ module Stripe
788
918
  method: context.method,
789
919
  path: context.path,
790
920
  request_id: context.request_id,
791
- status: status)
921
+ status: status,
922
+ config: config)
792
923
  Util.log_debug("Response details",
793
924
  body: body,
794
925
  idempotency_key: context.idempotency_key,
795
- request_id: context.request_id)
926
+ request_id: context.request_id,
927
+ config: config,
928
+ process_id: Process.pid,
929
+ thread_object_id: Thread.current.object_id,
930
+ response_object_id: resp.object_id,
931
+ log_timestamp: Util.monotonic_time)
796
932
 
797
933
  return unless context.request_id
798
934
 
@@ -800,7 +936,8 @@ module Stripe
800
936
  idempotency_key: context.idempotency_key,
801
937
  request_id: context.request_id,
802
938
  url: Util.request_id_dashboard_url(context.request_id,
803
- context.api_key))
939
+ context.api_key),
940
+ config: config)
804
941
  end
805
942
 
806
943
  private def log_response_error(context, request_start, error)
@@ -810,7 +947,8 @@ module Stripe
810
947
  error_message: error.message,
811
948
  idempotency_key: context.idempotency_key,
812
949
  method: context.method,
813
- path: context.path)
950
+ path: context.path,
951
+ config: config)
814
952
  end
815
953
 
816
954
  # RequestLogContext stores information about a request that's begin made so