stripe 12.7.0.pre.beta.2 → 13.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +720 -1360
  3. data/OPENAPI_VERSION +1 -1
  4. data/README.md +4 -3
  5. data/VERSION +1 -1
  6. data/examples/README.md +11 -0
  7. data/examples/meter_event_stream.rb +47 -0
  8. data/examples/new_example.rb +24 -0
  9. data/examples/stripe_webhook_handler.rb +28 -0
  10. data/lib/stripe/api_operations/nested_resource.rb +1 -21
  11. data/lib/stripe/api_operations/request.rb +19 -70
  12. data/lib/stripe/api_operations/save.rb +4 -3
  13. data/lib/stripe/api_operations/singleton_save.rb +5 -2
  14. data/lib/stripe/api_requestor.rb +1131 -0
  15. data/lib/stripe/api_resource.rb +22 -14
  16. data/lib/stripe/api_version.rb +1 -2
  17. data/lib/stripe/connection_manager.rb +1 -1
  18. data/lib/stripe/errors.rb +8 -2
  19. data/lib/stripe/event_types.rb +14 -0
  20. data/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb +23 -0
  21. data/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb +13 -0
  22. data/lib/stripe/list_object.rb +2 -3
  23. data/lib/stripe/oauth.rb +8 -15
  24. data/lib/stripe/object_types.rb +16 -20
  25. data/lib/stripe/request_options.rb +128 -0
  26. data/lib/stripe/resources/billing/credit_balance_summary.rb +14 -0
  27. data/lib/stripe/resources/billing/credit_balance_transaction.rb +26 -0
  28. data/lib/stripe/resources/billing/credit_grant.rb +88 -0
  29. data/lib/stripe/resources/customer.rb +2 -3
  30. data/lib/stripe/resources/file.rb +7 -5
  31. data/lib/stripe/resources/financial_connections/account.rb +0 -3
  32. data/lib/stripe/resources/invoice.rb +0 -81
  33. data/lib/stripe/resources/payment_intent.rb +0 -50
  34. data/lib/stripe/resources/quote.rb +4 -108
  35. data/lib/stripe/resources/source.rb +3 -2
  36. data/lib/stripe/resources/subscription.rb +6 -6
  37. data/lib/stripe/resources/subscription_schedule.rb +0 -20
  38. data/lib/stripe/resources/terminal/reader.rb +0 -60
  39. data/lib/stripe/resources/v2/billing/meter_event.rb +16 -0
  40. data/lib/stripe/resources/v2/billing/meter_event_adjustment.rb +15 -0
  41. data/lib/stripe/resources/v2/billing/meter_event_session.rb +15 -0
  42. data/lib/stripe/resources/v2/event.rb +13 -0
  43. data/lib/stripe/resources.rb +9 -18
  44. data/lib/stripe/search_result_object.rb +1 -1
  45. data/lib/stripe/services/account_capability_service.rb +39 -0
  46. data/lib/stripe/services/account_external_account_service.rb +68 -0
  47. data/lib/stripe/services/account_link_service.rb +17 -0
  48. data/lib/stripe/services/account_login_link_service.rb +19 -0
  49. data/lib/stripe/services/account_person_service.rb +61 -0
  50. data/lib/stripe/services/account_service.rb +100 -0
  51. data/lib/stripe/services/account_session_service.rb +17 -0
  52. data/lib/stripe/services/apple_pay_domain_service.rb +50 -0
  53. data/lib/stripe/services/application_fee_refund_service.rb +60 -0
  54. data/lib/stripe/services/application_fee_service.rb +35 -0
  55. data/lib/stripe/services/apps/secret_service.rb +52 -0
  56. data/lib/stripe/services/apps_service.rb +13 -0
  57. data/lib/stripe/services/balance_service.rb +12 -0
  58. data/lib/stripe/services/balance_transaction_service.rb +32 -0
  59. data/lib/stripe/services/billing/alert_service.rb +74 -0
  60. data/lib/stripe/services/billing/credit_balance_summary_service.rb +19 -0
  61. data/lib/stripe/services/billing/credit_balance_transaction_service.rb +30 -0
  62. data/lib/stripe/services/billing/credit_grant_service.rb +74 -0
  63. data/lib/stripe/services/billing/meter_event_adjustment_service.rb +19 -0
  64. data/lib/stripe/services/billing/meter_event_service.rb +19 -0
  65. data/lib/stripe/services/billing/meter_event_summary_service.rb +19 -0
  66. data/lib/stripe/services/billing/meter_service.rb +81 -0
  67. data/lib/stripe/services/billing_portal/configuration_service.rb +52 -0
  68. data/lib/stripe/services/billing_portal/session_service.rb +19 -0
  69. data/lib/stripe/services/billing_portal_service.rb +14 -0
  70. data/lib/stripe/services/billing_service.rb +20 -0
  71. data/lib/stripe/services/charge_service.rb +69 -0
  72. data/lib/stripe/services/checkout/session_line_item_service.rb +19 -0
  73. data/lib/stripe/services/checkout/session_service.rb +72 -0
  74. data/lib/stripe/services/checkout_service.rb +13 -0
  75. data/lib/stripe/services/climate/order_service.rb +68 -0
  76. data/lib/stripe/services/climate/product_service.rb +30 -0
  77. data/lib/stripe/services/climate/supplier_service.rb +30 -0
  78. data/lib/stripe/services/climate_service.rb +15 -0
  79. data/lib/stripe/services/confirmation_token_service.rb +17 -0
  80. data/lib/stripe/services/country_spec_service.rb +28 -0
  81. data/lib/stripe/services/coupon_service.rb +51 -0
  82. data/lib/stripe/services/credit_note_line_item_service.rb +17 -0
  83. data/lib/stripe/services/credit_note_preview_lines_service.rb +17 -0
  84. data/lib/stripe/services/credit_note_service.rb +93 -0
  85. data/lib/stripe/services/customer_balance_transaction_service.rb +50 -0
  86. data/lib/stripe/services/customer_cash_balance_service.rb +28 -0
  87. data/lib/stripe/services/customer_cash_balance_transaction_service.rb +28 -0
  88. data/lib/stripe/services/customer_funding_instructions_service.rb +19 -0
  89. data/lib/stripe/services/customer_payment_method_service.rb +28 -0
  90. data/lib/stripe/services/customer_payment_source_service.rb +76 -0
  91. data/lib/stripe/services/customer_service.rb +89 -0
  92. data/lib/stripe/services/customer_session_service.rb +17 -0
  93. data/lib/stripe/services/customer_tax_id_service.rb +50 -0
  94. data/lib/stripe/services/dispute_service.rb +48 -0
  95. data/lib/stripe/services/entitlements/active_entitlement_service.rb +30 -0
  96. data/lib/stripe/services/entitlements/feature_service.rb +52 -0
  97. data/lib/stripe/services/entitlements_service.rb +14 -0
  98. data/lib/stripe/services/ephemeral_key_service.rb +28 -0
  99. data/lib/stripe/services/event_service.rb +22 -0
  100. data/lib/stripe/services/exchange_rate_service.rb +28 -0
  101. data/lib/stripe/services/file_link_service.rb +38 -0
  102. data/lib/stripe/services/file_service.rb +35 -0
  103. data/lib/stripe/services/financial_connections/account_owner_service.rb +19 -0
  104. data/lib/stripe/services/financial_connections/account_service.rb +81 -0
  105. data/lib/stripe/services/financial_connections/session_service.rb +30 -0
  106. data/lib/stripe/services/financial_connections/transaction_service.rb +30 -0
  107. data/lib/stripe/services/financial_connections_service.rb +15 -0
  108. data/lib/stripe/services/forwarding/request_service.rb +41 -0
  109. data/lib/stripe/services/forwarding_service.rb +13 -0
  110. data/lib/stripe/services/identity/verification_report_service.rb +30 -0
  111. data/lib/stripe/services/identity/verification_session_service.rb +106 -0
  112. data/lib/stripe/services/identity_service.rb +14 -0
  113. data/lib/stripe/services/invoice_item_service.rb +61 -0
  114. data/lib/stripe/services/invoice_line_item_service.rb +31 -0
  115. data/lib/stripe/services/invoice_rendering_template_service.rb +50 -0
  116. data/lib/stripe/services/invoice_service.rb +202 -0
  117. data/lib/stripe/services/invoice_upcoming_lines_service.rb +17 -0
  118. data/lib/stripe/services/issuing/authorization_service.rb +65 -0
  119. data/lib/stripe/services/issuing/card_service.rb +52 -0
  120. data/lib/stripe/services/issuing/cardholder_service.rb +52 -0
  121. data/lib/stripe/services/issuing/dispute_service.rb +63 -0
  122. data/lib/stripe/services/issuing/personalization_design_service.rb +52 -0
  123. data/lib/stripe/services/issuing/physical_bundle_service.rb +30 -0
  124. data/lib/stripe/services/issuing/token_service.rb +41 -0
  125. data/lib/stripe/services/issuing/transaction_service.rb +41 -0
  126. data/lib/stripe/services/issuing_service.rb +20 -0
  127. data/lib/stripe/services/mandate_service.rb +17 -0
  128. data/lib/stripe/services/oauth_service.rb +63 -0
  129. data/lib/stripe/services/payment_intent_service.rb +204 -0
  130. data/lib/stripe/services/payment_link_line_item_service.rb +17 -0
  131. data/lib/stripe/services/payment_link_service.rb +57 -0
  132. data/lib/stripe/services/payment_method_configuration_service.rb +50 -0
  133. data/lib/stripe/services/payment_method_domain_service.rb +66 -0
  134. data/lib/stripe/services/payment_method_service.rb +86 -0
  135. data/lib/stripe/services/payout_service.rb +66 -0
  136. data/lib/stripe/services/plan_service.rb +49 -0
  137. data/lib/stripe/services/price_service.rb +52 -0
  138. data/lib/stripe/services/product_feature_service.rb +50 -0
  139. data/lib/stripe/services/product_service.rb +70 -0
  140. data/lib/stripe/services/promotion_code_service.rb +50 -0
  141. data/lib/stripe/services/quote_computed_upfront_line_items_service.rb +17 -0
  142. data/lib/stripe/services/quote_line_item_service.rb +17 -0
  143. data/lib/stripe/services/quote_service.rb +92 -0
  144. data/lib/stripe/services/radar/early_fraud_warning_service.rb +32 -0
  145. data/lib/stripe/services/radar/value_list_item_service.rb +52 -0
  146. data/lib/stripe/services/radar/value_list_service.rb +63 -0
  147. data/lib/stripe/services/radar_service.rb +15 -0
  148. data/lib/stripe/services/refund_service.rb +63 -0
  149. data/lib/stripe/services/reporting/report_run_service.rb +41 -0
  150. data/lib/stripe/services/reporting/report_type_service.rb +30 -0
  151. data/lib/stripe/services/reporting_service.rb +14 -0
  152. data/lib/stripe/services/review_service.rb +33 -0
  153. data/lib/stripe/services/setup_attempt_service.rb +17 -0
  154. data/lib/stripe/services/setup_intent_service.rb +105 -0
  155. data/lib/stripe/services/shipping_rate_service.rb +50 -0
  156. data/lib/stripe/services/sigma/scheduled_query_run_service.rb +30 -0
  157. data/lib/stripe/services/sigma_service.rb +13 -0
  158. data/lib/stripe/services/source_service.rb +64 -0
  159. data/lib/stripe/services/source_transaction_service.rb +17 -0
  160. data/lib/stripe/services/subscription_item_service.rb +69 -0
  161. data/lib/stripe/services/subscription_item_usage_record_service.rb +23 -0
  162. data/lib/stripe/services/subscription_item_usage_record_summary_service.rb +19 -0
  163. data/lib/stripe/services/subscription_schedule_service.rb +72 -0
  164. data/lib/stripe/services/subscription_service.rb +127 -0
  165. data/lib/stripe/services/tax/calculation_line_item_service.rb +19 -0
  166. data/lib/stripe/services/tax/calculation_service.rb +37 -0
  167. data/lib/stripe/services/tax/registration_service.rb +54 -0
  168. data/lib/stripe/services/tax/settings_service.rb +30 -0
  169. data/lib/stripe/services/tax/transaction_line_item_service.rb +19 -0
  170. data/lib/stripe/services/tax/transaction_service.rb +48 -0
  171. data/lib/stripe/services/tax_code_service.rb +22 -0
  172. data/lib/stripe/services/tax_id_service.rb +38 -0
  173. data/lib/stripe/services/tax_rate_service.rb +38 -0
  174. data/lib/stripe/services/tax_service.rb +16 -0
  175. data/lib/stripe/services/terminal/configuration_service.rb +63 -0
  176. data/lib/stripe/services/terminal/connection_token_service.rb +19 -0
  177. data/lib/stripe/services/terminal/location_service.rb +64 -0
  178. data/lib/stripe/services/terminal/reader_service.rb +118 -0
  179. data/lib/stripe/services/terminal_service.rb +16 -0
  180. data/lib/stripe/services/test_helpers/confirmation_token_service.rb +19 -0
  181. data/lib/stripe/services/test_helpers/customer_service.rb +19 -0
  182. data/lib/stripe/services/test_helpers/issuing/authorization_service.rb +76 -0
  183. data/lib/stripe/services/test_helpers/issuing/card_service.rb +54 -0
  184. data/lib/stripe/services/test_helpers/issuing/personalization_design_service.rb +43 -0
  185. data/lib/stripe/services/test_helpers/issuing/transaction_service.rb +43 -0
  186. data/lib/stripe/services/test_helpers/issuing_service.rb +19 -0
  187. data/lib/stripe/services/test_helpers/refund_service.rb +19 -0
  188. data/lib/stripe/services/test_helpers/terminal/reader_service.rb +21 -0
  189. data/lib/stripe/services/test_helpers/terminal_service.rb +15 -0
  190. data/lib/stripe/services/test_helpers/test_clock_service.rb +63 -0
  191. data/lib/stripe/services/test_helpers/treasury/inbound_transfer_service.rb +43 -0
  192. data/lib/stripe/services/test_helpers/treasury/outbound_payment_service.rb +54 -0
  193. data/lib/stripe/services/test_helpers/treasury/outbound_transfer_service.rb +54 -0
  194. data/lib/stripe/services/test_helpers/treasury/received_credit_service.rb +21 -0
  195. data/lib/stripe/services/test_helpers/treasury/received_debit_service.rb +21 -0
  196. data/lib/stripe/services/test_helpers/treasury_service.rb +19 -0
  197. data/lib/stripe/services/test_helpers_service.rb +19 -0
  198. data/lib/stripe/services/token_service.rb +23 -0
  199. data/lib/stripe/services/topup_service.rb +49 -0
  200. data/lib/stripe/services/transfer_reversal_service.rb +56 -0
  201. data/lib/stripe/services/transfer_service.rb +47 -0
  202. data/lib/stripe/services/treasury/credit_reversal_service.rb +41 -0
  203. data/lib/stripe/services/treasury/debit_reversal_service.rb +41 -0
  204. data/lib/stripe/services/treasury/financial_account_features_service.rb +30 -0
  205. data/lib/stripe/services/treasury/financial_account_service.rb +59 -0
  206. data/lib/stripe/services/treasury/inbound_transfer_service.rb +52 -0
  207. data/lib/stripe/services/treasury/outbound_payment_service.rb +52 -0
  208. data/lib/stripe/services/treasury/outbound_transfer_service.rb +52 -0
  209. data/lib/stripe/services/treasury/received_credit_service.rb +30 -0
  210. data/lib/stripe/services/treasury/received_debit_service.rb +30 -0
  211. data/lib/stripe/services/treasury/transaction_entry_service.rb +30 -0
  212. data/lib/stripe/services/treasury/transaction_service.rb +30 -0
  213. data/lib/stripe/services/treasury_service.rb +22 -0
  214. data/lib/stripe/services/v1_services.rb +89 -0
  215. data/lib/stripe/services/v2/billing/meter_event_adjustment_service.rb +21 -0
  216. data/lib/stripe/services/v2/billing/meter_event_service.rb +21 -0
  217. data/lib/stripe/services/v2/billing/meter_event_session_service.rb +21 -0
  218. data/lib/stripe/services/v2/billing/meter_event_stream_service.rb +23 -0
  219. data/lib/stripe/services/v2/billing_service.rb +18 -0
  220. data/lib/stripe/services/v2/core/event_service.rb +32 -0
  221. data/lib/stripe/services/v2/core_service.rb +15 -0
  222. data/lib/stripe/services/v2_services.rb +14 -0
  223. data/lib/stripe/services/webhook_endpoint_service.rb +61 -0
  224. data/lib/stripe/services.rb +181 -0
  225. data/lib/stripe/singleton_api_resource.rb +1 -18
  226. data/lib/stripe/stripe_client.rb +51 -1067
  227. data/lib/stripe/stripe_configuration.rb +32 -20
  228. data/lib/stripe/stripe_object.rb +37 -18
  229. data/lib/stripe/stripe_service.rb +32 -0
  230. data/lib/stripe/thin_event.rb +17 -0
  231. data/lib/stripe/util.rb +69 -46
  232. data/lib/stripe/v2_list_object.rb +84 -0
  233. data/lib/stripe/version.rb +1 -1
  234. data/lib/stripe/webhook.rb +1 -1
  235. data/lib/stripe.rb +15 -54
  236. metadata +203 -23
  237. data/lib/stripe/request_signing_authenticator.rb +0 -79
  238. data/lib/stripe/resources/account_notice.rb +0 -32
  239. data/lib/stripe/resources/capital/financing_offer.rb +0 -49
  240. data/lib/stripe/resources/capital/financing_summary.rb +0 -15
  241. data/lib/stripe/resources/capital/financing_transaction.rb +0 -27
  242. data/lib/stripe/resources/financial_connections/account_inferred_balance.rb +0 -14
  243. data/lib/stripe/resources/financial_connections/institution.rb +0 -26
  244. data/lib/stripe/resources/gift_cards/card.rb +0 -59
  245. data/lib/stripe/resources/gift_cards/transaction.rb +0 -93
  246. data/lib/stripe/resources/invoice_payment.rb +0 -12
  247. data/lib/stripe/resources/issuing/credit_underwriting_record.rb +0 -88
  248. data/lib/stripe/resources/issuing/dispute_settlement_detail.rb +0 -26
  249. data/lib/stripe/resources/margin.rb +0 -37
  250. data/lib/stripe/resources/order.rb +0 -120
  251. data/lib/stripe/resources/quote_preview_invoice.rb +0 -43
  252. data/lib/stripe/resources/quote_preview_subscription_schedule.rb +0 -11
  253. data/lib/stripe/resources/tax/association.rb +0 -24
  254. data/lib/stripe/resources/tax/form.rb +0 -49
  255. data/lib/stripe/resources/terminal/reader_collected_data.rb +0 -14
@@ -0,0 +1,1131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stripe/instrumentation"
4
+
5
+ module Stripe
6
+ # APIRequestor executes requests against the Stripe API and allows a user to
7
+ # recover both a resource a call returns as well as a response object that
8
+ # contains information on the HTTP call.
9
+ class APIRequestor
10
+ # A set of all known thread contexts across all threads and a mutex to
11
+ # synchronize global access to them.
12
+ @thread_contexts_with_connection_managers = Set.new
13
+ @thread_contexts_with_connection_managers_mutex = Mutex.new
14
+ @last_connection_manager_gc = Util.monotonic_time
15
+
16
+ # Initializes a new APIRequestor
17
+ def initialize(config_arg = {})
18
+ @system_profiler = SystemProfiler.new
19
+ @last_request_metrics = Queue.new
20
+
21
+ @config = case config_arg
22
+ when Hash
23
+ StripeConfiguration.new.reverse_duplicate_merge(config_arg)
24
+ when Stripe::StripeConfiguration
25
+ config_arg
26
+ when String
27
+ StripeConfiguration.new.reverse_duplicate_merge(
28
+ { api_key: config_arg }
29
+ )
30
+ else
31
+ raise ArgumentError, "Can't handle argument: #{config_arg}"
32
+ end
33
+ end
34
+
35
+ attr_reader :config, :options
36
+
37
+ # Gets a currently active `APIRequestor`. Set for the current thread when
38
+ # `APIRequestor#request` is being run so that API operations being executed
39
+ # inside of that block can find the currently active requestor. It's reset to
40
+ # the original value (hopefully `nil`) after the block ends.
41
+ #
42
+ # For internal use only. Does not provide a stable API and may be broken
43
+ # with future non-major changes.
44
+ def self.active_requestor
45
+ current_thread_context.active_requestor || default_requestor
46
+ end
47
+
48
+ # Finishes any active connections by closing their TCP connection and
49
+ # clears them from internal tracking in all connection managers across all
50
+ # threads.
51
+ #
52
+ # If passed a `config` object, only clear connection managers for that
53
+ # particular configuration.
54
+ #
55
+ # For internal use only. Does not provide a stable API and may be broken
56
+ # with future non-major changes.
57
+ def self.clear_all_connection_managers(config: nil)
58
+ # Just a quick path for when configuration is being set for the first
59
+ # time before any connections have been opened. There is technically some
60
+ # potential for thread raciness here, but not in a practical sense.
61
+ return if @thread_contexts_with_connection_managers.empty?
62
+
63
+ @thread_contexts_with_connection_managers_mutex.synchronize do
64
+ pruned_contexts = Set.new
65
+
66
+ @thread_contexts_with_connection_managers.each do |thread_context|
67
+ # Note that the thread context itself is not destroyed, but we clear
68
+ # its connection manager and remove our reference to it. If it ever
69
+ # makes a new request we'll give it a new connection manager and
70
+ # it'll go back into `@thread_contexts_with_connection_managers`.
71
+ thread_context.default_connection_managers.reject! do |cm_config, cm|
72
+ if config.nil? || config.key == cm_config
73
+ cm.clear
74
+ true
75
+ end
76
+ end
77
+
78
+ pruned_contexts << thread_context if thread_context.default_connection_managers.empty?
79
+ end
80
+
81
+ @thread_contexts_with_connection_managers.subtract(pruned_contexts)
82
+ end
83
+ end
84
+
85
+ # A default requestor for the current thread.
86
+ def self.default_requestor
87
+ current_thread_context.default_requestor ||= APIRequestor.new(Stripe.config)
88
+ end
89
+
90
+ # A default connection manager for the current thread scoped to the
91
+ # configuration object that may be provided.
92
+ def self.default_connection_manager(config = Stripe.config)
93
+ current_thread_context.default_connection_managers[config.key] ||= begin
94
+ connection_manager = ConnectionManager.new(config)
95
+
96
+ @thread_contexts_with_connection_managers_mutex.synchronize do
97
+ maybe_gc_connection_managers
98
+ @thread_contexts_with_connection_managers << current_thread_context
99
+ end
100
+
101
+ connection_manager
102
+ end
103
+ end
104
+
105
+ # Checks if an error is a problem that we should retry on. This includes
106
+ # both socket errors that may represent an intermittent problem and some
107
+ # special HTTP statuses.
108
+ def self.should_retry?(error,
109
+ num_retries:, config: Stripe.config)
110
+ return false if num_retries >= config.max_network_retries
111
+
112
+ case error
113
+ when Net::OpenTimeout, Net::ReadTimeout
114
+ # Retry on timeout-related problems (either on open or read).
115
+ true
116
+ when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET, # rubocop:todo Lint/DuplicateBranch
117
+ Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError
118
+ # Destination refused the connection, the connection was reset, or a
119
+ # variety of other connection failures. This could occur from a single
120
+ # saturated server, so retry in case it's intermittent.
121
+ true
122
+ when Stripe::StripeError
123
+ # The API may ask us not to retry (e.g. if doing so would be a no-op),
124
+ # or advise us to retry (e.g. in cases of lock timeouts). Defer to
125
+ # those instructions if given.
126
+ return false if error.http_headers["stripe-should-retry"] == "false"
127
+ return true if error.http_headers["stripe-should-retry"] == "true"
128
+
129
+ # 409 Conflict
130
+ return true if error.http_status == 409
131
+
132
+ # 429 Too Many Requests
133
+ #
134
+ # There are a few different problems that can lead to a 429. The most
135
+ # common is rate limiting, on which we *don't* want to retry because
136
+ # that'd likely contribute to more contention problems. However, some
137
+ # 429s are lock timeouts, which is when a request conflicted with
138
+ # another request or an internal process on some particular object.
139
+ # These 429s are safe to retry.
140
+ return true if error.http_status == 429 && error.code == "lock_timeout"
141
+
142
+ # Retry on 500, 503, and other internal errors.
143
+ #
144
+ # Note that we expect the stripe-should-retry header to be false
145
+ # in most cases when a 500 is returned, since our idempotency framework
146
+ # would typically replay it anyway.
147
+ true if error.http_status >= 500
148
+ else
149
+ false
150
+ end
151
+ end
152
+
153
+ def self.sleep_time(num_retries, config: Stripe.config)
154
+ # Apply exponential backoff with initial_network_retry_delay on the
155
+ # number of num_retries so far as inputs. Do not allow the number to
156
+ # exceed max_network_retry_delay.
157
+ sleep_seconds = [
158
+ config.initial_network_retry_delay * (2**(num_retries - 1)),
159
+ config.max_network_retry_delay,
160
+ ].min
161
+
162
+ # Apply some jitter by randomizing the value in the range of
163
+ # (sleep_seconds / 2) to (sleep_seconds).
164
+ sleep_seconds *= (0.5 * (1 + rand))
165
+
166
+ # But never sleep less than the base sleep seconds.
167
+ [config.initial_network_retry_delay, sleep_seconds].max
168
+ end
169
+
170
+ # Executes the API call within the given block. Usage looks like:
171
+ #
172
+ # client = APIRequestor.new
173
+ # charge, resp = client.request { Charge.create }
174
+ #
175
+ def request
176
+ old_api_requestor = self.class.current_thread_context.active_requestor
177
+ self.class.current_thread_context.active_requestor = self
178
+
179
+ if self.class.current_thread_context.last_responses&.key?(object_id)
180
+ raise "calls to APIRequestor#request cannot be nested within a thread"
181
+ end
182
+
183
+ self.class.current_thread_context.last_responses ||= {}
184
+ self.class.current_thread_context.last_responses[object_id] = nil
185
+
186
+ begin
187
+ res = yield
188
+ [res, self.class.current_thread_context.last_responses[object_id]]
189
+ ensure
190
+ self.class.current_thread_context.active_requestor = old_api_requestor
191
+ self.class.current_thread_context.last_responses.delete(object_id)
192
+ end
193
+ end
194
+ extend Gem::Deprecate
195
+ deprecate :request, "StripeClient#raw_request", 2024, 9
196
+
197
+ def execute_request(method, path, base_address,
198
+ params: {}, opts: {}, usage: [])
199
+ http_resp, req_opts = execute_request_internal(
200
+ method, path, base_address, params, opts, usage
201
+ )
202
+ req_opts = RequestOptions.extract_opts_from_hash(req_opts)
203
+
204
+ resp = interpret_response(http_resp)
205
+
206
+ # If being called from `APIRequestor#request`, put the last response in
207
+ # thread-local memory so that it can be returned to the user. Don't store
208
+ # anything otherwise so that we don't leak memory.
209
+ store_last_response(object_id, resp)
210
+
211
+ api_mode = Util.get_api_mode(path)
212
+ Util.convert_to_stripe_object_with_params(resp.data, params, RequestOptions.persistable(req_opts), resp,
213
+ api_mode: api_mode, requestor: self)
214
+ end
215
+
216
+ # Execute request without instantiating a new object if the relevant object's name matches the class
217
+ #
218
+ # For internal use only. Does not provide a stable API and may be broken
219
+ # with future non-major changes.
220
+ def execute_request_initialize_from(method, path, base_address, object,
221
+ params: {}, opts: {}, usage: [])
222
+ opts = RequestOptions.combine_opts(object.instance_variable_get(:@opts), opts)
223
+ opts = Util.normalize_opts(opts)
224
+ http_resp, req_opts = execute_request_internal(
225
+ method, path, base_address, params, opts, usage
226
+ )
227
+ req_opts = RequestOptions.extract_opts_from_hash(req_opts)
228
+
229
+ resp = interpret_response(http_resp)
230
+
231
+ # If being called from `APIRequestor#request`, put the last response in
232
+ # thread-local memory so that it can be returned to the user. Don't store
233
+ # anything otherwise so that we don't leak memory.
234
+ store_last_response(object_id, resp)
235
+
236
+ if Util.object_name_matches_class?(resp.data[:object], object.class)
237
+ object.send(:initialize_from,
238
+ resp.data, RequestOptions.persistable(req_opts), resp,
239
+ api_mode: :v1, requestor: self)
240
+ else
241
+ Util.convert_to_stripe_object_with_params(resp.data, params,
242
+ RequestOptions.persistable(req_opts),
243
+ resp, api_mode: :v1, requestor: self)
244
+ end
245
+ end
246
+
247
+ def interpret_response(http_resp)
248
+ StripeResponse.from_net_http(http_resp)
249
+ rescue JSON::ParserError
250
+ raise general_api_error(http_resp.code.to_i, http_resp.body)
251
+ end
252
+
253
+ # Executes a request and returns the body as a stream instead of converting
254
+ # it to a StripeObject. This should be used for any request where we expect
255
+ # an arbitrary binary response.
256
+ #
257
+ # A `read_body_chunk` block can be passed, which will be called repeatedly
258
+ # with the body chunks read from the socket.
259
+ #
260
+ # If a block is passed, a StripeHeadersOnlyResponse is returned as the
261
+ # block is expected to do all the necessary body processing. If no block is
262
+ # passed, then a StripeStreamResponse is returned containing an IO stream
263
+ # with the response body.
264
+ def execute_request_stream(method, path,
265
+ base_address,
266
+ params: {}, opts: {}, usage: [],
267
+ &read_body_chunk_block)
268
+ unless block_given?
269
+ raise ArgumentError,
270
+ "execute_request_stream requires a read_body_chunk_block"
271
+ end
272
+
273
+ http_resp, api_key = execute_request_internal(
274
+ method, path, base_address, params, opts, usage, &read_body_chunk_block
275
+ )
276
+
277
+ # When the read_body_chunk_block is given, we no longer have access to the
278
+ # response body at this point and so return a response object containing
279
+ # only the headers. This is because the body was consumed by the block.
280
+ resp = StripeHeadersOnlyResponse.from_net_http(http_resp)
281
+
282
+ [resp, api_key]
283
+ end
284
+
285
+ def store_last_response(object_id, resp)
286
+ return unless last_response_has_key?(object_id)
287
+
288
+ self.class.current_thread_context.last_responses[object_id] = resp
289
+ end
290
+
291
+ def last_response_has_key?(object_id)
292
+ self.class.current_thread_context.last_responses&.key?(object_id)
293
+ end
294
+
295
+ #
296
+ # private
297
+ #
298
+
299
+ # Time (in seconds) that a connection manager has not been used before it's
300
+ # eligible for garbage collection.
301
+ CONNECTION_MANAGER_GC_LAST_USED_EXPIRY = 120
302
+
303
+ # How often to check (in seconds) for connection managers that haven't been
304
+ # used in a long time and which should be garbage collected.
305
+ CONNECTION_MANAGER_GC_PERIOD = 60
306
+
307
+ ERROR_MESSAGE_CONNECTION =
308
+ "Unexpected error communicating when trying to connect to " \
309
+ "Stripe (%s). You may be seeing this message because your DNS is not " \
310
+ "working or you don't have an internet connection. To check, try " \
311
+ "running `host stripe.com` from the command line."
312
+ ERROR_MESSAGE_SSL =
313
+ "Could not establish a secure connection to Stripe (%s), you " \
314
+ "may need to upgrade your OpenSSL version. To check, try running " \
315
+ "`openssl s_client -connect api.stripe.com:443` from the command " \
316
+ "line."
317
+
318
+ # Common error suffix sared by both connect and read timeout messages.
319
+ ERROR_MESSAGE_TIMEOUT_SUFFIX =
320
+ "Please check your internet connection and try again. " \
321
+ "If this problem persists, you should check Stripe's service " \
322
+ "status at https://status.stripe.com, or let us know at " \
323
+ "support@stripe.com."
324
+
325
+ ERROR_MESSAGE_TIMEOUT_CONNECT = (
326
+ "Timed out connecting to Stripe (%s). " +
327
+ ERROR_MESSAGE_TIMEOUT_SUFFIX
328
+ ).freeze
329
+
330
+ ERROR_MESSAGE_TIMEOUT_READ = (
331
+ "Timed out communicating with Stripe (%s). " +
332
+ ERROR_MESSAGE_TIMEOUT_SUFFIX
333
+ ).freeze
334
+
335
+ # Maps types of exceptions that we're likely to see during a network
336
+ # request to more user-friendly messages that we put in front of people.
337
+ # The original error message is also appended onto the final exception for
338
+ # full transparency.
339
+ NETWORK_ERROR_MESSAGES_MAP = {
340
+ EOFError => ERROR_MESSAGE_CONNECTION,
341
+ Errno::ECONNREFUSED => ERROR_MESSAGE_CONNECTION,
342
+ Errno::ECONNRESET => ERROR_MESSAGE_CONNECTION,
343
+ Errno::EHOSTUNREACH => ERROR_MESSAGE_CONNECTION,
344
+ Errno::ETIMEDOUT => ERROR_MESSAGE_TIMEOUT_CONNECT,
345
+ SocketError => ERROR_MESSAGE_CONNECTION,
346
+
347
+ Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT,
348
+ Net::ReadTimeout => ERROR_MESSAGE_TIMEOUT_READ,
349
+
350
+ OpenSSL::SSL::SSLError => ERROR_MESSAGE_SSL,
351
+ }.freeze
352
+ private_constant :NETWORK_ERROR_MESSAGES_MAP
353
+
354
+ # A record representing any data that `APIRequestor` puts into
355
+ # `Thread.current`. Making it a class likes this gives us a little extra
356
+ # type safety and lets us document what each field does.
357
+ #
358
+ # For internal use only. Does not provide a stable API and may be broken
359
+ # with future non-major changes.
360
+ class ThreadContext
361
+ # A `APIRequestor` that's been flagged as currently active within a
362
+ # thread by `APIRequestor#request`. A requestor stays active until the
363
+ # completion of the request block.
364
+ attr_accessor :active_requestor
365
+
366
+ # A default `APIRequestor` object for the thread. Used in all cases where
367
+ # the user hasn't specified their own.
368
+ attr_accessor :default_requestor
369
+
370
+ # A temporary map of object IDs to responses from last executed API
371
+ # calls. Used to return a responses from calls to `APIRequestor#request`.
372
+ #
373
+ # Stored in the thread data to make the use of a single `APIRequestor`
374
+ # object safe across multiple threads. Stored as a map so that multiple
375
+ # `APIRequestor` objects can run concurrently on the same thread.
376
+ #
377
+ # Responses are only left in as long as they're needed, which means
378
+ # they're removed as soon as a call leaves `APIRequestor#request`, and
379
+ # because that's wrapped in an `ensure` block, they should never leave
380
+ # garbage in `Thread.current`.
381
+ attr_accessor :last_responses
382
+
383
+ # A map of connection mangers for the thread. Normally shared between
384
+ # all `APIRequestor` objects on a particular thread, and created so as to
385
+ # minimize the number of open connections that an application needs.
386
+ def default_connection_managers
387
+ @default_connection_managers ||= {}
388
+ end
389
+
390
+ def reset_connection_managers
391
+ @default_connection_managers = {}
392
+ end
393
+ end
394
+
395
+ # Access data stored for `APIRequestor` within the thread's current
396
+ # context. Returns `ThreadContext`.
397
+ #
398
+ # For internal use only. Does not provide a stable API and may be broken
399
+ # with future non-major changes.
400
+ def self.current_thread_context
401
+ Thread.current[:api_requestor__internal_use_only] ||= ThreadContext.new
402
+ end
403
+
404
+ # Garbage collects connection managers that haven't been used in some time,
405
+ # with the idea being that we want to remove old connection managers that
406
+ # belong to dead threads and the like.
407
+ #
408
+ # Prefixed with `maybe_` because garbage collection will only run
409
+ # periodically so that we're not constantly engaged in busy work. If
410
+ # connection managers live a little passed their useful age it's not
411
+ # harmful, so it's not necessary to get them right away.
412
+ #
413
+ # For testability, returns `nil` if it didn't run and the number of
414
+ # connection managers that were garbage collected otherwise.
415
+ #
416
+ # IMPORTANT: This method is not thread-safe and expects to be called inside
417
+ # a lock on `@thread_contexts_with_connection_managers_mutex`.
418
+ #
419
+ # For internal use only. Does not provide a stable API and may be broken
420
+ # with future non-major changes.
421
+ def self.maybe_gc_connection_managers
422
+ next_gc_time = @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD
423
+ return nil if next_gc_time > Util.monotonic_time
424
+
425
+ last_used_threshold =
426
+ Util.monotonic_time - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY
427
+
428
+ pruned_contexts = []
429
+ @thread_contexts_with_connection_managers.each do |thread_context|
430
+ thread_context
431
+ .default_connection_managers
432
+ .each do |config_key, connection_manager|
433
+ next if connection_manager.last_used > last_used_threshold
434
+
435
+ connection_manager.clear
436
+ thread_context.default_connection_managers.delete(config_key)
437
+ end
438
+ end
439
+
440
+ @thread_contexts_with_connection_managers.each do |thread_context|
441
+ next unless thread_context.default_connection_managers.empty?
442
+
443
+ pruned_contexts << thread_context
444
+ end
445
+
446
+ @thread_contexts_with_connection_managers -= pruned_contexts
447
+ @last_connection_manager_gc = Util.monotonic_time
448
+
449
+ pruned_contexts.count
450
+ end
451
+
452
+ private def execute_request_internal(method, path,
453
+ base_address, params, opts, usage,
454
+ &read_body_chunk_block)
455
+ api_mode = Util.get_api_mode(path)
456
+ opts = RequestOptions.merge_config_and_opts(config, opts)
457
+
458
+ raise ArgumentError, "method should be a symbol" \
459
+ unless method.is_a?(Symbol)
460
+ raise ArgumentError, "path should be a string" \
461
+ unless path.is_a?(String)
462
+
463
+ base_url ||= config.base_addresses[base_address]
464
+
465
+ raise ArgumentError, "api_base cannot be empty" if base_url.nil? || base_url.empty?
466
+
467
+ api_key ||= opts[:api_key]
468
+ params = Util.objects_to_ids(params)
469
+
470
+ check_api_key!(api_key)
471
+
472
+ body_params = nil
473
+ query_params = nil
474
+ case method
475
+ when :get, :head, :delete
476
+ query_params = params
477
+ else
478
+ body_params = params
479
+ end
480
+
481
+ query_params, path = merge_query_params(query_params, path)
482
+
483
+ headers = request_headers(method, api_mode, opts)
484
+ url = api_url(path, base_url)
485
+
486
+ # Merge given query parameters with any already encoded in the path.
487
+ query = query_params ? Util.encode_parameters(query_params, api_mode) : nil
488
+
489
+ # Encoding body parameters is a little more complex because we may have
490
+ # to send a multipart-encoded body. `body_log` is produced separately as
491
+ # a log-friendly variant of the encoded form. File objects are displayed
492
+ # as such instead of as their file contents.
493
+ body, body_log =
494
+ body_params ? encode_body(body_params, headers, api_mode) : [nil, nil]
495
+
496
+ # stores information on the request we're about to make so that we don't
497
+ # have to pass as many parameters around for logging.
498
+ context = RequestLogContext.new
499
+ context.account = headers["Stripe-Account"]
500
+ context.api_key = api_key
501
+ context.api_version = headers["Stripe-Version"]
502
+ context.body = body_log
503
+ context.idempotency_key = headers["Idempotency-Key"]
504
+ context.method = method
505
+ context.path = path
506
+ context.query = query
507
+
508
+ # A block can be passed in to read the content directly from the response.
509
+ # We want to execute this block only when the response was actually
510
+ # successful. When it wasn't, we defer to the standard error handling as
511
+ # we have to read the body and parse the error JSON.
512
+ response_block =
513
+ if block_given?
514
+ lambda do |response|
515
+ response.read_body(&read_body_chunk_block) unless should_handle_as_error(response.code.to_i)
516
+ end
517
+ end
518
+
519
+ http_resp =
520
+ execute_request_with_rescues(base_url, headers, api_mode, usage, context) do
521
+ self.class
522
+ .default_connection_manager(config)
523
+ .execute_request(method, url,
524
+ body: body,
525
+ headers: headers,
526
+ query: query,
527
+ &response_block)
528
+ end
529
+
530
+ [http_resp, opts]
531
+ end
532
+
533
+ private def api_url(url, base_url)
534
+ base_url + url
535
+ end
536
+
537
+ private def check_api_key!(api_key)
538
+ unless api_key
539
+ raise AuthenticationError, "No API key provided. " \
540
+ 'Set your API key using "Stripe.api_key = <API-KEY>". ' \
541
+ "You can generate API keys from the Stripe web interface. " \
542
+ "See https://stripe.com/api for details, or email " \
543
+ "support@stripe.com if you have any questions."
544
+ end
545
+
546
+ return unless api_key =~ /\s/
547
+
548
+ raise AuthenticationError, "Your API key is invalid, as it contains " \
549
+ "whitespace. (HINT: You can double-check your API key from the " \
550
+ "Stripe web interface. See https://stripe.com/api for details, or " \
551
+ "email support@stripe.com if you have any questions.)"
552
+ end
553
+
554
+ # Encodes a set of body parameters using multipart if `Content-Type` is set
555
+ # for that, or standard form-encoding otherwise. Returns the encoded body
556
+ # and a version of the encoded body that's safe to be logged.
557
+ private def encode_body(body_params, headers, api_mode)
558
+ body = nil
559
+ flattened_params = Util.flatten_params(body_params, api_mode)
560
+
561
+ if headers["Content-Type"] == MultipartEncoder::MULTIPART_FORM_DATA
562
+ body, content_type = MultipartEncoder.encode(flattened_params)
563
+
564
+ # Set a new content type that also includes the multipart boundary.
565
+ # See `MultipartEncoder` for details.
566
+ headers["Content-Type"] = content_type
567
+
568
+ # `#to_s` any complex objects like files and the like to build output
569
+ # that's more condusive to logging.
570
+ flattened_params =
571
+ flattened_params.map { |k, v| [k, v.is_a?(String) ? v : v.to_s] }.to_h
572
+
573
+ elsif api_mode == :v2
574
+ body = JSON.generate(body_params)
575
+ headers["Content-Type"] = "application/json"
576
+ else
577
+ body = Util.encode_parameters(body_params, api_mode)
578
+ headers["Content-Type"] = "application/x-www-form-urlencoded"
579
+ end
580
+
581
+ body_log = if api_mode == :v2
582
+ body
583
+ else
584
+ # We don't use `Util.encode_parameters` partly as an optimization (to
585
+ # not redo work we've already done), and partly because the encoded
586
+ # forms of certain characters introduce a lot of visual noise and it's
587
+ # nice to have a clearer format for logs.
588
+ flattened_params.map { |k, v| "#{k}=#{v}" }.join("&")
589
+ end
590
+
591
+ [body, body_log]
592
+ end
593
+
594
+ private def should_handle_as_error(http_status)
595
+ http_status >= 400
596
+ end
597
+
598
+ private def execute_request_with_rescues(base_url, headers, api_mode, usage, context)
599
+ num_retries = 0
600
+
601
+ begin
602
+ request_start = nil
603
+ user_data = nil
604
+
605
+ log_request(context, num_retries)
606
+ user_data = notify_request_begin(context)
607
+
608
+ request_start = Util.monotonic_time
609
+ resp = yield
610
+ request_duration = Util.monotonic_time - request_start
611
+
612
+ http_status = resp.code.to_i
613
+ context = context.dup_from_response_headers(resp)
614
+
615
+ handle_error_response(resp, context, api_mode) if should_handle_as_error(http_status)
616
+
617
+ log_response(context, request_start, http_status, resp.body, resp)
618
+ notify_request_end(context, request_duration, http_status,
619
+ num_retries, user_data, resp, headers)
620
+
621
+ if config.enable_telemetry? && context.request_id
622
+ request_duration_ms = (request_duration * 1000).to_i
623
+ @last_request_metrics << StripeRequestMetrics.new(context.request_id, request_duration_ms, usage: usage)
624
+ end
625
+
626
+ # We rescue all exceptions from a request so that we have an easy spot to
627
+ # implement our retry logic across the board. We'll re-raise if it's a
628
+ # type of exception that we didn't expect to handle.
629
+ rescue StandardError => e
630
+ # If we modify context we copy it into a new variable so as not to
631
+ # taint the original on a retry.
632
+ error_context = context
633
+ http_status = nil
634
+ request_duration = Util.monotonic_time - request_start if request_start
635
+
636
+ if e.is_a?(Stripe::StripeError)
637
+ error_context = context.dup_from_response_headers(e.http_headers)
638
+ http_status = resp.code.to_i
639
+ log_response(error_context, request_start,
640
+ e.http_status, e.http_body, resp)
641
+ else
642
+ log_response_error(error_context, request_start, e)
643
+ end
644
+ notify_request_end(context, request_duration, http_status, num_retries,
645
+ user_data, resp, headers)
646
+
647
+ if self.class.should_retry?(e,
648
+ num_retries: num_retries,
649
+ config: config)
650
+ num_retries += 1
651
+ sleep self.class.sleep_time(num_retries, config: config)
652
+ retry
653
+ end
654
+
655
+ case e
656
+ when Stripe::StripeError
657
+ raise
658
+ when *NETWORK_ERROR_MESSAGES_MAP.keys
659
+ handle_network_error(e, error_context, num_retries, base_url)
660
+
661
+ # Only handle errors when we know we can do so, and re-raise otherwise.
662
+ # This should be pretty infrequent.
663
+ else # rubocop:todo Lint/DuplicateBranch
664
+ raise
665
+ end
666
+ end
667
+
668
+ resp
669
+ end
670
+
671
+ private def notify_request_begin(context)
672
+ return unless Instrumentation.any_subscribers?(:request_begin)
673
+
674
+ event = Instrumentation::RequestBeginEvent.new(
675
+ method: context.method,
676
+ path: context.path,
677
+ user_data: {}
678
+ )
679
+ Stripe::Instrumentation.notify(:request_begin, event)
680
+
681
+ # This field may be set in the `request_begin` callback. If so, we'll
682
+ # forward it onto `request_end`.
683
+ event.user_data
684
+ end
685
+
686
+ private def notify_request_end(context, duration, http_status, num_retries,
687
+ user_data, resp, headers)
688
+ return if !Instrumentation.any_subscribers?(:request_end) &&
689
+ !Instrumentation.any_subscribers?(:request)
690
+
691
+ request_context = Stripe::Instrumentation::RequestContext.new(
692
+ duration: duration,
693
+ context: context,
694
+ header: headers
695
+ )
696
+ response_context = Stripe::Instrumentation::ResponseContext.new(
697
+ http_status: http_status,
698
+ response: resp
699
+ )
700
+
701
+ event = Instrumentation::RequestEndEvent.new(
702
+ request_context: request_context,
703
+ response_context: response_context,
704
+ num_retries: num_retries,
705
+ user_data: user_data || {}
706
+ )
707
+ Stripe::Instrumentation.notify(:request_end, event)
708
+
709
+ # The name before `request_begin` was also added. Provided for backwards
710
+ # compatibility.
711
+ Stripe::Instrumentation.notify(:request, event)
712
+ end
713
+
714
+ private def general_api_error(status, body)
715
+ APIError.new("Invalid response object from API: #{body.inspect} " \
716
+ "(HTTP response code was #{status})",
717
+ http_status: status, http_body: body)
718
+ end
719
+
720
+ # Formats a plugin "app info" hash into a string that we can tack onto the
721
+ # end of a User-Agent string where it'll be fairly prominent in places like
722
+ # the Dashboard. Note that this formatting has been implemented to match
723
+ # other libraries, and shouldn't be changed without universal consensus.
724
+ private def format_app_info(info)
725
+ str = info[:name]
726
+ str = "#{str}/#{info[:version]}" unless info[:version].nil?
727
+ str = "#{str} (#{info[:url]})" unless info[:url].nil?
728
+ str
729
+ end
730
+
731
+ private def handle_error_response(http_resp, context, api_mode)
732
+ begin
733
+ resp = StripeResponse.from_net_http(http_resp)
734
+ error_data = resp.data[:error]
735
+
736
+ raise StripeError, "Indeterminate error" unless error_data
737
+ rescue JSON::ParserError, StripeError
738
+ raise general_api_error(http_resp.code.to_i, http_resp.body)
739
+ end
740
+
741
+ error = if error_data.is_a?(String)
742
+ specific_oauth_error(resp, error_data, context)
743
+ elsif api_mode == :v2
744
+ specific_v2_api_error(resp, error_data, context)
745
+ else
746
+ specific_api_error(resp, error_data, context)
747
+ end
748
+
749
+ error.response = resp
750
+ raise(error)
751
+ end
752
+
753
+ # Works around an edge case where we end up with both query parameters from
754
+ # parameteers and query parameters that were appended onto the end of the
755
+ # given path.
756
+ #
757
+ # Decode any parameters that were added onto the end of a path and add them
758
+ # to a unified query parameter hash so that all parameters end up in one
759
+ # place and all of them are correctly included in the final request.
760
+ private def merge_query_params(query_params, path)
761
+ u = URI.parse(path)
762
+
763
+ # Return original results if there was nothing to be found.
764
+ return query_params, path if u.query.nil?
765
+
766
+ query_params ||= {}
767
+ query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
768
+
769
+ # Reset the path minus any query parameters that were specified.
770
+ path = u.path
771
+
772
+ [query_params, path]
773
+ end
774
+
775
+ private def specific_api_error(resp, error_data, context)
776
+ Util.log_error("Stripe API error",
777
+ status: resp.http_status,
778
+ error_code: error_data[:code],
779
+ error_message: error_data[:message],
780
+ error_param: error_data[:param],
781
+ error_type: error_data[:type],
782
+ idempotency_key: context.idempotency_key,
783
+ request_id: context.request_id,
784
+ config: config)
785
+
786
+ # The standard set of arguments that can be used to initialize most of
787
+ # the exceptions.
788
+ opts = {
789
+ http_body: resp.http_body,
790
+ http_headers: resp.http_headers,
791
+ http_status: resp.http_status,
792
+ json_body: resp.data,
793
+ code: error_data[:code],
794
+ }
795
+
796
+ case resp.http_status
797
+ when 400, 404
798
+ case error_data[:type]
799
+ when "idempotency_error"
800
+ IdempotencyError.new(error_data[:message], **opts)
801
+ else
802
+ InvalidRequestError.new(
803
+ error_data[:message], error_data[:param],
804
+ **opts
805
+ )
806
+ end
807
+ when 401
808
+ AuthenticationError.new(error_data[:message], **opts)
809
+ when 402
810
+ CardError.new(
811
+ error_data[:message], error_data[:param],
812
+ **opts
813
+ )
814
+ when 403
815
+ PermissionError.new(error_data[:message], **opts)
816
+ when 429
817
+ RateLimitError.new(error_data[:message], **opts)
818
+ else
819
+ APIError.new(error_data[:message], **opts)
820
+ end
821
+ end
822
+
823
+ private def specific_v2_api_error(resp, error_data, context)
824
+ Util.log_error("Stripe v2 API error",
825
+ status: resp.http_status,
826
+ error_code: error_data[:code],
827
+ error_message: error_data[:message],
828
+ error_param: error_data[:param],
829
+ error_type: error_data[:type],
830
+ idempotency_key: context.idempotency_key,
831
+ request_id: context.request_id,
832
+ config: config)
833
+
834
+ # The standard set of arguments that can be used to initialize most of
835
+ # the exceptions.
836
+ opts = {
837
+ http_body: resp.http_body,
838
+ http_headers: resp.http_headers,
839
+ http_status: resp.http_status,
840
+ json_body: resp.data,
841
+ code: error_data[:code],
842
+ }
843
+
844
+ case error_data[:type]
845
+ when "idempotency_error"
846
+ IdempotencyError.new(error_data[:message], **opts)
847
+ # switch cases: The beginning of the section generated from our OpenAPI spec
848
+ when "temporary_session_expired"
849
+ TemporarySessionExpiredError.new(error_data[:message], **opts)
850
+ # switch cases: The end of the section generated from our OpenAPI spec
851
+ else
852
+ specific_api_error(resp, error_data, context)
853
+ end
854
+ end
855
+
856
+ # Attempts to look at a response's error code and return an OAuth error if
857
+ # one matches. Will return `nil` if the code isn't recognized.
858
+ private def specific_oauth_error(resp, error_code, context)
859
+ description = resp.data[:error_description] || error_code
860
+
861
+ Util.log_error("Stripe OAuth error",
862
+ status: resp.http_status,
863
+ error_code: error_code,
864
+ error_description: description,
865
+ idempotency_key: context.idempotency_key,
866
+ request_id: context.request_id,
867
+ config: config)
868
+
869
+ args = {
870
+ http_status: resp.http_status, http_body: resp.http_body,
871
+ json_body: resp.data, http_headers: resp.http_headers,
872
+ }
873
+
874
+ case error_code
875
+ when "invalid_client"
876
+ OAuth::InvalidClientError.new(error_code, description, **args)
877
+ when "invalid_grant"
878
+ OAuth::InvalidGrantError.new(error_code, description, **args)
879
+ when "invalid_request"
880
+ OAuth::InvalidRequestError.new(error_code, description, **args)
881
+ when "invalid_scope"
882
+ OAuth::InvalidScopeError.new(error_code, description, **args)
883
+ when "unsupported_grant_type"
884
+ OAuth::UnsupportedGrantTypeError.new(error_code, description, **args)
885
+ when "unsupported_response_type"
886
+ OAuth::UnsupportedResponseTypeError.new(error_code, description, **args)
887
+ else
888
+ # We'd prefer that all errors are typed, but we create a generic
889
+ # OAuthError in case we run into a code that we don't recognize.
890
+ OAuth::OAuthError.new(error_code, description, **args)
891
+ end
892
+ end
893
+
894
+ private def handle_network_error(error, context, num_retries,
895
+ base_url)
896
+ Util.log_error("Stripe network error",
897
+ error_message: error.message,
898
+ idempotency_key: context.idempotency_key,
899
+ request_id: context.request_id,
900
+ config: config)
901
+
902
+ errors, message = NETWORK_ERROR_MESSAGES_MAP.detect do |(e, _)|
903
+ error.is_a?(e)
904
+ end
905
+
906
+ if errors.nil?
907
+ message = "Unexpected error #{error.class.name} communicating " \
908
+ "with Stripe. Please let us know at support@stripe.com."
909
+ end
910
+
911
+ message = message % base_url
912
+
913
+ message += " Request was retried #{num_retries} times." if num_retries > 0
914
+
915
+ raise APIConnectionError,
916
+ message + "\n\n(Network error: #{error.message})"
917
+ end
918
+
919
+ private def request_headers(method, api_mode, req_opts)
920
+ user_agent = "Stripe/#{api_mode} RubyBindings/#{Stripe::VERSION}"
921
+ user_agent += " " + format_app_info(Stripe.app_info) unless Stripe.app_info.nil?
922
+
923
+ headers = {
924
+ "User-Agent" => user_agent,
925
+ "Authorization" => "Bearer #{req_opts[:api_key]}",
926
+ }
927
+
928
+ if config.enable_telemetry?
929
+ begin
930
+ headers["X-Stripe-Client-Telemetry"] = JSON.generate(
931
+ last_request_metrics: @last_request_metrics.pop(true)&.payload
932
+ )
933
+ rescue ThreadError
934
+ # last_request_metrics is best effort, ignore failures when queue
935
+ # is empty. should fail if this is the first request ever sent
936
+ # on a requestor
937
+ end
938
+ end
939
+
940
+ headers["Idempotency-Key"] = req_opts[:idempotency_key] if req_opts[:idempotency_key]
941
+ # It is only safe to retry network failures on post and delete
942
+ # requests if we add an Idempotency-Key header
943
+ if %i[post delete].include?(method) && (api_mode == :v2 || config.max_network_retries > 0)
944
+ headers["Idempotency-Key"] ||= SecureRandom.uuid
945
+ end
946
+
947
+ headers["Stripe-Version"] = req_opts[:stripe_version] if req_opts[:stripe_version]
948
+ headers["Stripe-Account"] = req_opts[:stripe_account] if req_opts[:stripe_account]
949
+ headers["Stripe-Context"] = req_opts[:stripe_context] if req_opts[:stripe_context]
950
+
951
+ user_agent = @system_profiler.user_agent
952
+ begin
953
+ headers.update(
954
+ "X-Stripe-Client-User-Agent" => JSON.generate(user_agent)
955
+ )
956
+ rescue StandardError => e
957
+ headers.update(
958
+ "X-Stripe-Client-Raw-User-Agent" => user_agent.inspect,
959
+ :error => "#{e} (#{e.class})"
960
+ )
961
+ end
962
+
963
+ headers.update(req_opts[:headers])
964
+ end
965
+
966
+ private def log_request(context, num_retries)
967
+ Util.log_info("Request to Stripe API",
968
+ account: context.account,
969
+ api_version: context.api_version,
970
+ idempotency_key: context.idempotency_key,
971
+ method: context.method,
972
+ num_retries: num_retries,
973
+ path: context.path,
974
+ config: config)
975
+ Util.log_debug("Request details",
976
+ body: context.body,
977
+ idempotency_key: context.idempotency_key,
978
+ query: context.query,
979
+ config: config,
980
+ process_id: Process.pid,
981
+ thread_object_id: Thread.current.object_id,
982
+ log_timestamp: Util.monotonic_time)
983
+ end
984
+
985
+ private def log_response(context, request_start, status, body, resp)
986
+ Util.log_info("Response from Stripe API",
987
+ account: context.account,
988
+ api_version: context.api_version,
989
+ elapsed: Util.monotonic_time - request_start,
990
+ idempotency_key: context.idempotency_key,
991
+ method: context.method,
992
+ path: context.path,
993
+ request_id: context.request_id,
994
+ status: status,
995
+ config: config)
996
+ Util.log_debug("Response details",
997
+ body: body,
998
+ idempotency_key: context.idempotency_key,
999
+ request_id: context.request_id,
1000
+ config: config,
1001
+ process_id: Process.pid,
1002
+ thread_object_id: Thread.current.object_id,
1003
+ response_object_id: resp.object_id,
1004
+ log_timestamp: Util.monotonic_time)
1005
+
1006
+ return unless context.request_id
1007
+
1008
+ Util.log_debug("Dashboard link for request",
1009
+ idempotency_key: context.idempotency_key,
1010
+ request_id: context.request_id,
1011
+ url: Util.request_id_dashboard_url(context.request_id,
1012
+ context.api_key),
1013
+ config: config)
1014
+ end
1015
+
1016
+ private def log_response_error(context, request_start, error)
1017
+ elapsed = request_start ? Util.monotonic_time - request_start : nil
1018
+ Util.log_error("Request error",
1019
+ elapsed: elapsed,
1020
+ error_message: error.message,
1021
+ idempotency_key: context.idempotency_key,
1022
+ method: context.method,
1023
+ path: context.path,
1024
+ config: config)
1025
+ end
1026
+
1027
+ # RequestLogContext stores information about a request that's begin made so
1028
+ # that we can log certain information. It's useful because it means that we
1029
+ # don't have to pass around as many parameters.
1030
+ class RequestLogContext
1031
+ attr_accessor :body, :account, :api_key, :api_version, :idempotency_key, :method, :path, :query, :request_id
1032
+
1033
+ # The idea with this method is that we might want to update some of
1034
+ # context information because a response that we've received from the API
1035
+ # contains information that's more authoritative than what we started
1036
+ # with for a request. For example, we should trust whatever came back in
1037
+ # a `Stripe-Version` header beyond what configuration information that we
1038
+ # might have had available.
1039
+ def dup_from_response_headers(headers)
1040
+ context = dup
1041
+ context.account = headers["Stripe-Account"]
1042
+ context.api_version = headers["Stripe-Version"]
1043
+ context.idempotency_key = headers["Idempotency-Key"]
1044
+ context.request_id = headers["Request-Id"]
1045
+ context
1046
+ end
1047
+ end
1048
+
1049
+ # SystemProfiler extracts information about the system that we're running
1050
+ # in so that we can generate a rich user agent header to help debug
1051
+ # integrations.
1052
+ class SystemProfiler
1053
+ def self.uname
1054
+ if ::File.exist?("/proc/version")
1055
+ ::File.read("/proc/version").strip
1056
+ else
1057
+ case RbConfig::CONFIG["host_os"]
1058
+ when /linux|darwin|bsd|sunos|solaris|cygwin/i
1059
+ uname_from_system
1060
+ when /mswin|mingw/i
1061
+ uname_from_system_ver
1062
+ else
1063
+ "unknown platform"
1064
+ end
1065
+ end
1066
+ end
1067
+
1068
+ def self.uname_from_system
1069
+ (`uname -a 2>/dev/null` || "").strip
1070
+ rescue Errno::ENOENT
1071
+ "uname executable not found"
1072
+ rescue Errno::ENOMEM # couldn't create subprocess
1073
+ "uname lookup failed"
1074
+ end
1075
+
1076
+ def self.uname_from_system_ver
1077
+ (`ver` || "").strip
1078
+ rescue Errno::ENOENT
1079
+ "ver executable not found"
1080
+ rescue Errno::ENOMEM # couldn't create subprocess
1081
+ "uname lookup failed"
1082
+ end
1083
+
1084
+ def initialize
1085
+ @uname = self.class.uname
1086
+ end
1087
+
1088
+ def user_agent
1089
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} " \
1090
+ "(#{RUBY_RELEASE_DATE})"
1091
+
1092
+ {
1093
+ application: Stripe.app_info,
1094
+ bindings_version: Stripe::VERSION,
1095
+ lang: "ruby",
1096
+ lang_version: lang_version,
1097
+ platform: RUBY_PLATFORM,
1098
+ engine: defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
1099
+ publisher: "stripe",
1100
+ uname: @uname,
1101
+ hostname: Socket.gethostname,
1102
+ }.delete_if { |_k, v| v.nil? }
1103
+ end
1104
+ end
1105
+
1106
+ # StripeRequestMetrics tracks metadata to be reported to stripe for metrics
1107
+ # collection
1108
+ class StripeRequestMetrics
1109
+ # The Stripe request ID of the response.
1110
+ attr_accessor :request_id
1111
+
1112
+ # Request duration in milliseconds
1113
+ attr_accessor :request_duration_ms
1114
+
1115
+ # list of names of tracked behaviors associated with this request
1116
+ attr_accessor :usage
1117
+
1118
+ def initialize(request_id, request_duration_ms, usage: [])
1119
+ self.request_id = request_id
1120
+ self.request_duration_ms = request_duration_ms
1121
+ self.usage = usage
1122
+ end
1123
+
1124
+ def payload
1125
+ ret = { request_id: request_id, request_duration_ms: request_duration_ms }
1126
+ ret[:usage] = usage if !usage.nil? && !usage.empty?
1127
+ ret
1128
+ end
1129
+ end
1130
+ end
1131
+ end