stripe 4.9.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +10 -0
  3. data/.rubocop.yml +28 -4
  4. data/.rubocop_todo.yml +11 -22
  5. data/.travis.yml +3 -6
  6. data/.vscode/extensions.json +7 -0
  7. data/.vscode/settings.json +8 -0
  8. data/CHANGELOG.md +102 -2
  9. data/Gemfile +2 -10
  10. data/README.md +96 -40
  11. data/Rakefile +8 -7
  12. data/VERSION +1 -1
  13. data/lib/stripe.rb +64 -85
  14. data/lib/stripe/api_operations/delete.rb +23 -1
  15. data/lib/stripe/api_operations/list.rb +0 -6
  16. data/lib/stripe/api_operations/nested_resource.rb +14 -7
  17. data/lib/stripe/api_operations/request.rb +3 -7
  18. data/lib/stripe/api_operations/save.rb +1 -3
  19. data/lib/stripe/api_resource.rb +50 -2
  20. data/lib/stripe/connection_manager.rb +141 -0
  21. data/lib/stripe/error_object.rb +94 -0
  22. data/lib/stripe/errors.rb +22 -9
  23. data/lib/stripe/list_object.rb +11 -5
  24. data/lib/stripe/multipart_encoder.rb +131 -0
  25. data/lib/stripe/object_types.rb +94 -0
  26. data/lib/stripe/resources.rb +77 -0
  27. data/lib/stripe/{account.rb → resources/account.rb} +49 -27
  28. data/lib/stripe/{account_link.rb → resources/account_link.rb} +1 -1
  29. data/lib/stripe/resources/alipay_account.rb +34 -0
  30. data/lib/stripe/{apple_pay_domain.rb → resources/apple_pay_domain.rb} +1 -1
  31. data/lib/stripe/resources/application_fee.rb +13 -0
  32. data/lib/stripe/resources/application_fee_refund.rb +30 -0
  33. data/lib/stripe/{balance.rb → resources/balance.rb} +1 -1
  34. data/lib/stripe/{balance_transaction.rb → resources/balance_transaction.rb} +1 -5
  35. data/lib/stripe/{bank_account.rb → resources/bank_account.rb} +14 -4
  36. data/lib/stripe/{bitcoin_receiver.rb → resources/bitcoin_receiver.rb} +3 -3
  37. data/lib/stripe/{bitcoin_transaction.rb → resources/bitcoin_transaction.rb} +1 -1
  38. data/lib/stripe/resources/capability.rb +33 -0
  39. data/lib/stripe/{card.rb → resources/card.rb} +12 -4
  40. data/lib/stripe/resources/charge.rb +22 -0
  41. data/lib/stripe/{checkout → resources/checkout}/session.rb +2 -2
  42. data/lib/stripe/{country_spec.rb → resources/country_spec.rb} +1 -1
  43. data/lib/stripe/{coupon.rb → resources/coupon.rb} +2 -2
  44. data/lib/stripe/resources/credit_note.rb +22 -0
  45. data/lib/stripe/resources/customer.rb +35 -0
  46. data/lib/stripe/resources/customer_balance_transaction.rb +30 -0
  47. data/lib/stripe/resources/discount.rb +7 -0
  48. data/lib/stripe/{dispute.rb → resources/dispute.rb} +9 -7
  49. data/lib/stripe/{ephemeral_key.rb → resources/ephemeral_key.rb} +5 -2
  50. data/lib/stripe/{event.rb → resources/event.rb} +1 -1
  51. data/lib/stripe/{exchange_rate.rb → resources/exchange_rate.rb} +1 -1
  52. data/lib/stripe/{file.rb → resources/file.rb} +8 -11
  53. data/lib/stripe/{file_link.rb → resources/file_link.rb} +2 -2
  54. data/lib/stripe/resources/invoice.rb +73 -0
  55. data/lib/stripe/{invoice_item.rb → resources/invoice_item.rb} +2 -2
  56. data/lib/stripe/{invoice_line_item.rb → resources/invoice_line_item.rb} +1 -1
  57. data/lib/stripe/resources/issuing/authorization.rb +33 -0
  58. data/lib/stripe/resources/issuing/card.rb +24 -0
  59. data/lib/stripe/{issuing → resources/issuing}/card_details.rb +1 -1
  60. data/lib/stripe/{issuing → resources/issuing}/cardholder.rb +2 -2
  61. data/lib/stripe/{issuing → resources/issuing}/dispute.rb +2 -2
  62. data/lib/stripe/{issuing → resources/issuing}/transaction.rb +2 -2
  63. data/lib/stripe/resources/login_link.rb +14 -0
  64. data/lib/stripe/resources/order.rb +32 -0
  65. data/lib/stripe/{order_return.rb → resources/order_return.rb} +1 -1
  66. data/lib/stripe/resources/payment_intent.rb +42 -0
  67. data/lib/stripe/resources/payment_method.rb +32 -0
  68. data/lib/stripe/resources/payout.rb +22 -0
  69. data/lib/stripe/{person.rb → resources/person.rb} +8 -3
  70. data/lib/stripe/{plan.rb → resources/plan.rb} +1 -1
  71. data/lib/stripe/{product.rb → resources/product.rb} +3 -3
  72. data/lib/stripe/resources/radar/early_fraud_warning.rb +11 -0
  73. data/lib/stripe/{radar → resources/radar}/value_list.rb +2 -2
  74. data/lib/stripe/{radar → resources/radar}/value_list_item.rb +2 -2
  75. data/lib/stripe/{recipient.rb → resources/recipient.rb} +2 -6
  76. data/lib/stripe/{recipient_transfer.rb → resources/recipient_transfer.rb} +1 -1
  77. data/lib/stripe/{refund.rb → resources/refund.rb} +1 -1
  78. data/lib/stripe/{reporting → resources/reporting}/report_run.rb +2 -2
  79. data/lib/stripe/{reporting → resources/reporting}/report_type.rb +2 -2
  80. data/lib/stripe/resources/reversal.rb +29 -0
  81. data/lib/stripe/resources/review.rb +20 -0
  82. data/lib/stripe/resources/setup_intent.rb +32 -0
  83. data/lib/stripe/{sigma → resources/sigma}/scheduled_query_run.rb +2 -2
  84. data/lib/stripe/{sku.rb → resources/sku.rb} +3 -3
  85. data/lib/stripe/{source.rb → resources/source.rb} +17 -15
  86. data/lib/stripe/{source_transaction.rb → resources/source_transaction.rb} +1 -1
  87. data/lib/stripe/resources/subscription.rb +25 -0
  88. data/lib/stripe/{subscription_item.rb → resources/subscription_item.rb} +5 -2
  89. data/lib/stripe/resources/subscription_schedule.rb +32 -0
  90. data/lib/stripe/resources/tax_id.rb +26 -0
  91. data/lib/stripe/resources/tax_rate.rb +11 -0
  92. data/lib/stripe/{terminal → resources/terminal}/connection_token.rb +2 -2
  93. data/lib/stripe/{terminal → resources/terminal}/location.rb +3 -2
  94. data/lib/stripe/{terminal → resources/terminal}/reader.rb +3 -2
  95. data/lib/stripe/{three_d_secure.rb → resources/three_d_secure.rb} +1 -1
  96. data/lib/stripe/{token.rb → resources/token.rb} +1 -1
  97. data/lib/stripe/resources/topup.rb +22 -0
  98. data/lib/stripe/resources/transfer.rb +26 -0
  99. data/lib/stripe/resources/usage_record.rb +7 -0
  100. data/lib/stripe/{usage_record_summary.rb → resources/usage_record_summary.rb} +1 -1
  101. data/lib/stripe/{webhook_endpoint.rb → resources/webhook_endpoint.rb} +2 -2
  102. data/lib/stripe/singleton_api_resource.rb +3 -1
  103. data/lib/stripe/stripe_client.rb +347 -218
  104. data/lib/stripe/stripe_object.rb +72 -59
  105. data/lib/stripe/stripe_response.rb +53 -21
  106. data/lib/stripe/util.rb +54 -109
  107. data/lib/stripe/version.rb +1 -1
  108. data/lib/stripe/webhook.rb +5 -3
  109. data/stripe.gemspec +14 -5
  110. data/test/stripe/account_link_test.rb +1 -1
  111. data/test/stripe/account_test.rb +193 -32
  112. data/test/stripe/alipay_account_test.rb +1 -1
  113. data/test/stripe/api_operations_test.rb +3 -4
  114. data/test/stripe/api_resource_test.rb +119 -30
  115. data/test/stripe/apple_pay_domain_test.rb +18 -5
  116. data/test/stripe/application_fee_refund_test.rb +1 -1
  117. data/test/stripe/application_fee_test.rb +45 -1
  118. data/test/stripe/balance_test.rb +1 -1
  119. data/test/stripe/balance_transaction_test.rb +20 -0
  120. data/test/stripe/bank_account_test.rb +1 -1
  121. data/test/stripe/capability_test.rb +45 -0
  122. data/test/stripe/charge_test.rb +13 -8
  123. data/test/stripe/checkout/session_test.rb +7 -1
  124. data/test/stripe/connection_manager_test.rb +138 -0
  125. data/test/stripe/country_spec_test.rb +1 -1
  126. data/test/stripe/coupon_test.rb +16 -6
  127. data/test/stripe/credit_note_test.rb +61 -0
  128. data/test/stripe/customer_balance_transaction_test.rb +37 -0
  129. data/test/stripe/customer_card_test.rb +1 -1
  130. data/test/stripe/customer_test.rb +151 -40
  131. data/test/stripe/dispute_test.rb +10 -1
  132. data/test/stripe/ephemeral_key_test.rb +8 -1
  133. data/test/stripe/errors_test.rb +30 -9
  134. data/test/stripe/exchange_rate_test.rb +1 -1
  135. data/test/stripe/file_link_test.rb +1 -1
  136. data/test/stripe/file_test.rb +19 -5
  137. data/test/stripe/invoice_item_test.rb +18 -7
  138. data/test/stripe/invoice_line_item_test.rb +1 -1
  139. data/test/stripe/invoice_test.rb +77 -9
  140. data/test/stripe/issuing/authorization_test.rb +33 -11
  141. data/test/stripe/issuing/card_test.rb +15 -6
  142. data/test/stripe/issuing/cardholder_test.rb +1 -1
  143. data/test/stripe/issuing/dispute_test.rb +1 -1
  144. data/test/stripe/issuing/transaction_test.rb +1 -1
  145. data/test/stripe/list_object_test.rb +1 -17
  146. data/test/stripe/login_link_test.rb +2 -2
  147. data/test/stripe/multipart_encoder_test.rb +130 -0
  148. data/test/stripe/oauth_test.rb +1 -1
  149. data/test/stripe/order_return_test.rb +1 -1
  150. data/test/stripe/order_test.rb +28 -3
  151. data/test/stripe/payment_intent_test.rb +31 -4
  152. data/test/stripe/payment_method_test.rb +84 -0
  153. data/test/stripe/payout_test.rb +8 -1
  154. data/test/stripe/person_test.rb +1 -1
  155. data/test/stripe/plan_test.rb +26 -20
  156. data/test/stripe/product_test.rb +16 -6
  157. data/test/stripe/radar/early_fraud_warning_test.rb +22 -0
  158. data/test/stripe/radar/value_list_item_test.rb +16 -6
  159. data/test/stripe/radar/value_list_test.rb +16 -6
  160. data/test/stripe/recipient_test.rb +18 -5
  161. data/test/stripe/refund_test.rb +1 -1
  162. data/test/stripe/reporting/report_run_test.rb +1 -1
  163. data/test/stripe/reporting/report_type_test.rb +1 -1
  164. data/test/stripe/reversal_test.rb +1 -1
  165. data/test/stripe/review_test.rb +1 -1
  166. data/test/stripe/setup_intent_test.rb +84 -0
  167. data/test/stripe/sigma/scheduled_query_run_test.rb +1 -1
  168. data/test/stripe/sku_test.rb +16 -6
  169. data/test/stripe/source_test.rb +14 -19
  170. data/test/stripe/source_transaction_test.rb +1 -1
  171. data/test/stripe/stripe_client_test.rb +242 -26
  172. data/test/stripe/stripe_object_test.rb +8 -36
  173. data/test/stripe/stripe_response_test.rb +71 -25
  174. data/test/stripe/subscription_item_test.rb +28 -6
  175. data/test/stripe/subscription_schedule_test.rb +19 -1
  176. data/test/stripe/subscription_test.rb +29 -9
  177. data/test/stripe/tax_id_test.rb +31 -0
  178. data/test/stripe/tax_rate_test.rb +43 -0
  179. data/test/stripe/terminal/connection_token_test.rb +1 -1
  180. data/test/stripe/terminal/location_test.rb +18 -1
  181. data/test/stripe/terminal/reader_test.rb +18 -1
  182. data/test/stripe/three_d_secure_test.rb +1 -1
  183. data/test/stripe/topup_test.rb +9 -1
  184. data/test/stripe/transfer_test.rb +46 -1
  185. data/test/stripe/usage_record_summary_test.rb +1 -1
  186. data/test/stripe/util_test.rb +1 -1
  187. data/test/stripe/webhook_endpoint_test.rb +18 -1
  188. data/test/stripe/webhook_test.rb +4 -4
  189. data/test/stripe_mock.rb +4 -3
  190. data/test/stripe_test.rb +1 -14
  191. data/test/test_helper.rb +14 -11
  192. metadata +117 -125
  193. data/lib/stripe/alipay_account.rb +0 -27
  194. data/lib/stripe/application_fee.rb +0 -23
  195. data/lib/stripe/application_fee_refund.rb +0 -22
  196. data/lib/stripe/charge.rb +0 -84
  197. data/lib/stripe/customer.rb +0 -90
  198. data/lib/stripe/invoice.rb +0 -48
  199. data/lib/stripe/issuer_fraud_record.rb +0 -9
  200. data/lib/stripe/issuing/authorization.rb +0 -22
  201. data/lib/stripe/issuing/card.rb +0 -18
  202. data/lib/stripe/login_link.rb +0 -11
  203. data/lib/stripe/order.rb +0 -31
  204. data/lib/stripe/payment_intent.rb +0 -26
  205. data/lib/stripe/payout.rb +0 -20
  206. data/lib/stripe/reversal.rb +0 -22
  207. data/lib/stripe/review.rb +0 -14
  208. data/lib/stripe/subscription.rb +0 -25
  209. data/lib/stripe/subscription_schedule.rb +0 -32
  210. data/lib/stripe/subscription_schedule_revision.rb +0 -25
  211. data/lib/stripe/topup.rb +0 -16
  212. data/lib/stripe/transfer.rb +0 -23
  213. data/lib/stripe/usage_record.rb +0 -14
  214. data/test/stripe/account_external_accounts_operations_test.rb +0 -69
  215. data/test/stripe/account_login_links_operations_test.rb +0 -21
  216. data/test/stripe/account_persons_operations_test.rb +0 -70
  217. data/test/stripe/application_fee_refunds_operations_test.rb +0 -56
  218. data/test/stripe/customer_sources_operations_test.rb +0 -64
  219. data/test/stripe/file_upload_test.rb +0 -76
  220. data/test/stripe/issuer_fraud_record_test.rb +0 -20
  221. data/test/stripe/subscription_schedule_revision_test.rb +0 -37
  222. data/test/stripe/subscription_schedule_revisions_operations_test.rb +0 -35
  223. data/test/stripe/transfer_reversals_operations_test.rb +0 -57
  224. data/test/stripe/usage_record_test.rb +0 -28
@@ -4,7 +4,9 @@ module Stripe
4
4
  class SingletonAPIResource < APIResource
5
5
  def self.resource_url
6
6
  if self == SingletonAPIResource
7
- raise NotImplementedError, "SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Account, etc.)"
7
+ raise NotImplementedError,
8
+ "SingletonAPIResource is an abstract class. You should " \
9
+ "perform actions on its subclasses (Balance, etc.)"
8
10
  end
9
11
  # Namespaces are separated in object names with periods (.) and in URLs
10
12
  # with forward slashes (/), so replace the former with the latter.
@@ -5,81 +5,108 @@ module Stripe
5
5
  # recover both a resource a call returns as well as a response object that
6
6
  # contains information on the HTTP call.
7
7
  class StripeClient
8
- attr_accessor :conn
9
-
10
- # Initializes a new StripeClient. Expects a Faraday connection object, and
11
- # uses a default connection unless one is passed.
12
- def initialize(conn = nil)
13
- self.conn = conn || self.class.default_conn
8
+ # A set of all known connection managers across all threads and a mutex to
9
+ # synchronize global access to them.
10
+ @all_connection_managers = []
11
+ @all_connection_managers_mutex = Mutex.new
12
+
13
+ attr_accessor :connection_manager
14
+
15
+ # Initializes a new `StripeClient`. Expects a `ConnectionManager` object,
16
+ # and uses a default connection manager unless one is passed.
17
+ def initialize(connection_manager = nil)
18
+ self.connection_manager = connection_manager ||
19
+ self.class.default_connection_manager
14
20
  @system_profiler = SystemProfiler.new
15
21
  @last_request_metrics = nil
16
22
  end
17
23
 
24
+ # Gets a currently active `StripeClient`. Set for the current thread when
25
+ # `StripeClient#request` is being run so that API operations being executed
26
+ # inside of that block can find the currently active client. It's reset to
27
+ # the original value (hopefully `nil`) after the block ends.
28
+ #
29
+ # For internal use only. Does not provide a stable API and may be broken
30
+ # with future non-major changes.
18
31
  def self.active_client
19
- Thread.current[:stripe_client] || default_client
32
+ current_thread_context.active_client || default_client
20
33
  end
21
34
 
22
- def self.default_client
23
- Thread.current[:stripe_client_default_client] ||= StripeClient.new(default_conn)
35
+ # Finishes any active connections by closing their TCP connection and
36
+ # clears them from internal tracking in all connection managers across all
37
+ # threads.
38
+ #
39
+ # For internal use only. Does not provide a stable API and may be broken
40
+ # with future non-major changes.
41
+ def self.clear_all_connection_managers
42
+ # Just a quick path for when configuration is being set for the first
43
+ # time before any connections have been opened. There is technically some
44
+ # potential for thread raciness here, but not in a practical sense.
45
+ return if @all_connection_managers.empty?
46
+
47
+ @all_connection_managers_mutex.synchronize do
48
+ @all_connection_managers.each(&:clear)
49
+ end
24
50
  end
25
51
 
26
- # A default Faraday connection to be used when one isn't configured. This
27
- # object should never be mutated, and instead instantiating your own
28
- # connection and wrapping it in a StripeClient object should be preferred.
29
- def self.default_conn
30
- # We're going to keep connections around so that we can take advantage
31
- # of connection re-use, so make sure that we have a separate connection
32
- # object per thread.
33
- Thread.current[:stripe_client_default_conn] ||= begin
34
- conn = Faraday.new do |builder|
35
- builder.use Faraday::Request::Multipart
36
- builder.use Faraday::Request::UrlEncoded
37
- builder.use Faraday::Response::RaiseError
38
-
39
- # Net::HTTP::Persistent doesn't seem to do well on Windows or JRuby,
40
- # so fall back to default there.
41
- if Gem.win_platform? || RUBY_PLATFORM == "java"
42
- builder.adapter :net_http
43
- else
44
- builder.adapter :net_http_persistent
45
- end
46
- end
52
+ # A default client for the current thread.
53
+ def self.default_client
54
+ current_thread_context.default_client ||=
55
+ StripeClient.new(default_connection_manager)
56
+ end
47
57
 
48
- if Stripe.verify_ssl_certs
49
- conn.ssl.verify = true
50
- conn.ssl.cert_store = Stripe.ca_store
51
- else
52
- conn.ssl.verify = false
58
+ # A default connection manager for the current thread.
59
+ def self.default_connection_manager
60
+ current_thread_context.default_connection_manager ||= begin
61
+ connection_manager = ConnectionManager.new
53
62
 
54
- unless @verify_ssl_warned
55
- @verify_ssl_warned = true
56
- $stderr.puts("WARNING: Running without SSL cert verification. " \
57
- "You should never do this in production. " \
58
- "Execute 'Stripe.verify_ssl_certs = true' to enable verification.")
59
- end
63
+ @all_connection_managers_mutex.synchronize do
64
+ @all_connection_managers << connection_manager
60
65
  end
61
66
 
62
- conn
67
+ connection_manager
63
68
  end
64
69
  end
65
70
 
66
- # Checks if an error is a problem that we should retry on. This includes both
67
- # socket errors that may represent an intermittent problem and some special
68
- # HTTP statuses.
69
- def self.should_retry?(e, num_retries)
71
+ # Checks if an error is a problem that we should retry on. This includes
72
+ # both socket errors that may represent an intermittent problem and some
73
+ # special HTTP statuses.
74
+ def self.should_retry?(error, method:, num_retries:)
70
75
  return false if num_retries >= Stripe.max_network_retries
71
76
 
72
77
  # Retry on timeout-related problems (either on open or read).
73
- return true if e.is_a?(Faraday::TimeoutError)
78
+ return true if error.is_a?(Net::OpenTimeout)
79
+ return true if error.is_a?(Net::ReadTimeout)
74
80
 
75
81
  # Destination refused the connection, the connection was reset, or a
76
82
  # variety of other connection failures. This could occur from a single
77
83
  # saturated server, so retry in case it's intermittent.
78
- return true if e.is_a?(Faraday::ConnectionFailed)
79
-
80
- if e.is_a?(Faraday::ClientError) && e.response
81
- # 409 conflict
82
- return true if e.response[:status] == 409
84
+ return true if error.is_a?(Errno::ECONNREFUSED)
85
+ return true if error.is_a?(SocketError)
86
+
87
+ if error.is_a?(Stripe::StripeError)
88
+ # 409 Conflict
89
+ return true if error.http_status == 409
90
+
91
+ # 429 Too Many Requests
92
+ #
93
+ # There are a few different problems that can lead to a 429. The most
94
+ # common is rate limiting, on which we *don't* want to retry because
95
+ # that'd likely contribute to more contention problems. However, some
96
+ # 429s are lock timeouts, which is when a request conflicted with
97
+ # another request or an internal process on some particular object.
98
+ # These 429s are safe to retry.
99
+ return true if error.http_status == 429 && error.code == "lock_timeout"
100
+
101
+ # 500 Internal Server Error
102
+ #
103
+ # We only bother retrying these for non-POST requests. POSTs end up
104
+ # being cached by the idempotency layer so there's no purpose in
105
+ # retrying them.
106
+ return true if error.http_status == 500 && method != :post
107
+
108
+ # 503 Service Unavailable
109
+ return true if error.http_status == 503
83
110
  end
84
111
 
85
112
  false
@@ -87,12 +114,15 @@ module Stripe
87
114
 
88
115
  def self.sleep_time(num_retries)
89
116
  # Apply exponential backoff with initial_network_retry_delay on the
90
- # number of num_retries so far as inputs. Do not allow the number to exceed
91
- # max_network_retry_delay.
92
- sleep_seconds = [Stripe.initial_network_retry_delay * (2**(num_retries - 1)), Stripe.max_network_retry_delay].min
93
-
94
- # Apply some jitter by randomizing the value in the range of (sleep_seconds
95
- # / 2) to (sleep_seconds).
117
+ # number of num_retries so far as inputs. Do not allow the number to
118
+ # exceed max_network_retry_delay.
119
+ sleep_seconds = [
120
+ Stripe.initial_network_retry_delay * (2**(num_retries - 1)),
121
+ Stripe.max_network_retry_delay,
122
+ ].min
123
+
124
+ # Apply some jitter by randomizing the value in the range of
125
+ # (sleep_seconds / 2) to (sleep_seconds).
96
126
  sleep_seconds *= (0.5 * (1 + rand))
97
127
 
98
128
  # But never sleep less than the base sleep seconds.
@@ -107,139 +137,200 @@ module Stripe
107
137
  # charge, resp = client.request { Charge.create }
108
138
  #
109
139
  def request
110
- @last_response = nil
111
- old_stripe_client = Thread.current[:stripe_client]
112
- Thread.current[:stripe_client] = self
140
+ old_stripe_client = self.class.current_thread_context.active_client
141
+ self.class.current_thread_context.active_client = self
142
+
143
+ if self.class.current_thread_context.last_responses&.key?(object_id)
144
+ raise "calls to StripeClient#request cannot be nested within a thread"
145
+ end
146
+
147
+ self.class.current_thread_context.last_responses ||= {}
148
+ self.class.current_thread_context.last_responses[object_id] = nil
113
149
 
114
150
  begin
115
151
  res = yield
116
- [res, @last_response]
152
+ [res, self.class.current_thread_context.last_responses[object_id]]
117
153
  ensure
118
- Thread.current[:stripe_client] = old_stripe_client
154
+ self.class.current_thread_context.active_client = old_stripe_client
155
+ self.class.current_thread_context.last_responses.delete(object_id)
119
156
  end
120
157
  end
121
158
 
122
159
  def execute_request(method, path,
123
160
  api_base: nil, api_key: nil, headers: {}, params: {})
161
+ raise ArgumentError, "method should be a symbol" \
162
+ unless method.is_a?(Symbol)
163
+ raise ArgumentError, "path should be a string" \
164
+ unless path.is_a?(String)
165
+
124
166
  api_base ||= Stripe.api_base
125
167
  api_key ||= Stripe.api_key
126
168
  params = Util.objects_to_ids(params)
127
169
 
128
170
  check_api_key!(api_key)
129
171
 
130
- body = nil
172
+ body_params = nil
131
173
  query_params = nil
132
- case method.to_s.downcase.to_sym
174
+ case method
133
175
  when :get, :head, :delete
134
176
  query_params = params
135
177
  else
136
- body = params
178
+ body_params = params
137
179
  end
138
180
 
139
- # This works around an edge case where we end up with both query
140
- # parameters in `query_params` and query parameters that are appended
141
- # onto the end of the given path. In this case, Faraday will silently
142
- # discard the URL's parameters which may break a request.
143
- #
144
- # Here we decode any parameters that were added onto the end of a path
145
- # and add them to `query_params` so that all parameters end up in one
146
- # place and all of them are correctly included in the final request.
147
- u = URI.parse(path)
148
- unless u.query.nil?
149
- query_params ||= {}
150
- query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
151
-
152
- # Reset the path minus any query parameters that were specified.
153
- path = u.path
154
- end
181
+ query_params, path = merge_query_params(query_params, path)
155
182
 
156
183
  headers = request_headers(api_key, method)
157
184
  .update(Util.normalize_headers(headers))
158
- params_encoder = FaradayStripeEncoder.new
159
185
  url = api_url(path, api_base)
160
186
 
187
+ # Merge given query parameters with any already encoded in the path.
188
+ query = query_params ? Util.encode_parameters(query_params) : nil
189
+
190
+ # Encoding body parameters is a little more complex because we may have
191
+ # to send a multipart-encoded body. `body_log` is produced separately as
192
+ # a log-friendly variant of the encoded form. File objects are displayed
193
+ # as such instead of as their file contents.
194
+ body, body_log =
195
+ body_params ? encode_body(body_params, headers) : [nil, nil]
196
+
161
197
  # stores information on the request we're about to make so that we don't
162
198
  # have to pass as many parameters around for logging.
163
199
  context = RequestLogContext.new
164
200
  context.account = headers["Stripe-Account"]
165
201
  context.api_key = api_key
166
202
  context.api_version = headers["Stripe-Version"]
167
- context.body = body ? params_encoder.encode(body) : nil
203
+ context.body = body_log
168
204
  context.idempotency_key = headers["Idempotency-Key"]
169
205
  context.method = method
170
206
  context.path = path
171
- context.query_params = query_params ? params_encoder.encode(query_params) : nil
172
-
173
- # note that both request body and query params will be passed through
174
- # `FaradayStripeEncoder`
175
- http_resp = execute_request_with_rescues(api_base, context) do
176
- conn.run_request(method, url, body, headers) do |req|
177
- req.options.open_timeout = Stripe.open_timeout
178
- req.options.params_encoder = params_encoder
179
- req.options.timeout = Stripe.read_timeout
180
- req.params = query_params unless query_params.nil?
181
- end
207
+ context.query = query
208
+
209
+ http_resp = execute_request_with_rescues(method, api_base, context) do
210
+ connection_manager.execute_request(method, url,
211
+ body: body,
212
+ headers: headers,
213
+ query: query)
182
214
  end
183
215
 
184
216
  begin
185
- resp = StripeResponse.from_faraday_response(http_resp)
217
+ resp = StripeResponse.from_net_http(http_resp)
186
218
  rescue JSON::ParserError
187
- raise general_api_error(http_resp.status, http_resp.body)
219
+ raise general_api_error(http_resp.code.to_i, http_resp.body)
220
+ end
221
+
222
+ # If being called from `StripeClient#request`, put the last response in
223
+ # thread-local memory so that it can be returned to the user. Don't store
224
+ # anything otherwise so that we don't leak memory.
225
+ if self.class.current_thread_context.last_responses&.key?(object_id)
226
+ self.class.current_thread_context.last_responses[object_id] = resp
188
227
  end
189
228
 
190
- # Allows StripeClient#request to return a response object to a caller.
191
- @last_response = resp
192
229
  [resp, api_key]
193
230
  end
194
231
 
195
- private
196
-
197
- # Used to workaround buggy behavior in Faraday: the library will try to
198
- # reshape anything that we pass to `req.params` with one of its default
199
- # encoders. I don't think this process is supposed to be lossy, but it is
200
- # -- in particular when we send our integer-indexed maps (i.e. arrays),
201
- # Faraday ends up stripping out the integer indexes.
202
232
  #
203
- # We work around the problem by implementing our own simplified encoder and
204
- # telling Faraday to use that.
233
+ # private
205
234
  #
206
- # The class also performs simple caching so that we don't have to encode
207
- # parameters twice for every request (once to build the request and once
208
- # for logging).
209
- #
210
- # When initialized with `multipart: true`, the encoder just inspects the
211
- # hash instead to get a decent representation for logging. In the case of a
212
- # multipart request, Faraday won't use the result of this encoder.
213
- class FaradayStripeEncoder
214
- def initialize
215
- @cache = {}
216
- end
217
235
 
218
- # This is quite subtle, but for a `multipart/form-data` request Faraday
219
- # will throw away the result of this encoder and build its body.
220
- def encode(hash)
221
- @cache.fetch(hash) do |k|
222
- @cache[k] = Util.encode_parameters(hash)
223
- end
224
- end
236
+ ERROR_MESSAGE_CONNECTION =
237
+ "Unexpected error communicating when trying to connect to " \
238
+ "Stripe (%s). You may be seeing this message because your DNS is not " \
239
+ "working or you don't have an internet connection. To check, try " \
240
+ "running `host stripe.com` from the command line."
241
+ ERROR_MESSAGE_SSL =
242
+ "Could not establish a secure connection to Stripe (%s), you " \
243
+ "may need to upgrade your OpenSSL version. To check, try running " \
244
+ "`openssl s_client -connect api.stripe.com:443` from the command " \
245
+ "line."
246
+
247
+ # Common error suffix sared by both connect and read timeout messages.
248
+ ERROR_MESSAGE_TIMEOUT_SUFFIX =
249
+ "Please check your internet connection and try again. " \
250
+ "If this problem persists, you should check Stripe's service " \
251
+ "status at https://status.stripe.com, or let us know at " \
252
+ "support@stripe.com."
253
+
254
+ ERROR_MESSAGE_TIMEOUT_CONNECT = (
255
+ "Timed out connecting to Stripe (%s). " +
256
+ ERROR_MESSAGE_TIMEOUT_SUFFIX
257
+ ).freeze
258
+
259
+ ERROR_MESSAGE_TIMEOUT_READ = (
260
+ "Timed out communicating with Stripe (%s). " +
261
+ ERROR_MESSAGE_TIMEOUT_SUFFIX
262
+ ).freeze
263
+
264
+ # Maps types of exceptions that we're likely to see during a network
265
+ # request to more user-friendly messages that we put in front of people.
266
+ # The original error message is also appended onto the final exception for
267
+ # full transparency.
268
+ NETWORK_ERROR_MESSAGES_MAP = {
269
+ Errno::ECONNREFUSED => ERROR_MESSAGE_CONNECTION,
270
+ SocketError => ERROR_MESSAGE_CONNECTION,
271
+
272
+ Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT,
273
+ Net::ReadTimeout => ERROR_MESSAGE_TIMEOUT_READ,
274
+
275
+ OpenSSL::SSL::SSLError => ERROR_MESSAGE_SSL,
276
+ }.freeze
277
+ private_constant :NETWORK_ERROR_MESSAGES_MAP
278
+
279
+ # A record representing any data that `StripeClient` puts into
280
+ # `Thread.current`. Making it a class likes this gives us a little extra
281
+ # type safety and lets us document what each field does.
282
+ #
283
+ # For internal use only. Does not provide a stable API and may be broken
284
+ # with future non-major changes.
285
+ class ThreadContext
286
+ # A `StripeClient` that's been flagged as currently active within a
287
+ # thread by `StripeClient#request`. A client stays active until the
288
+ # completion of the request block.
289
+ attr_accessor :active_client
290
+
291
+ # A default `StripeClient` object for the thread. Used in all cases where
292
+ # the user hasn't specified their own.
293
+ attr_accessor :default_client
294
+
295
+ # A default `ConnectionManager` for the thread. Normally shared between
296
+ # all `StripeClient` objects on a particular thread, and created so as to
297
+ # minimize the number of open connections that an application needs.
298
+ attr_accessor :default_connection_manager
299
+
300
+ # A temporary map of object IDs to responses from last executed API
301
+ # calls. Used to return a responses from calls to `StripeClient#request`.
302
+ #
303
+ # Stored in the thread data to make the use of a single `StripeClient`
304
+ # object safe across multiple threads. Stored as a map so that multiple
305
+ # `StripeClient` objects can run concurrently on the same thread.
306
+ #
307
+ # Responses are only left in as long as they're needed, which means
308
+ # they're removed as soon as a call leaves `StripeClient#request`, and
309
+ # because that's wrapped in an `ensure` block, they should never leave
310
+ # garbage in `Thread.current`.
311
+ attr_accessor :last_responses
312
+ end
225
313
 
226
- # We should never need to do this so it's not implemented.
227
- def decode(_str)
228
- raise NotImplementedError, "#{self.class.name} does not implement #decode"
229
- end
314
+ # Access data stored for `StripeClient` within the thread's current
315
+ # context. Returns `ThreadContext`.
316
+ #
317
+ # For internal use only. Does not provide a stable API and may be broken
318
+ # with future non-major changes.
319
+ def self.current_thread_context
320
+ Thread.current[:stripe_client__internal_use_only] ||= ThreadContext.new
230
321
  end
231
322
 
232
- def api_url(url = "", api_base = nil)
323
+ private def api_url(url = "", api_base = nil)
233
324
  (api_base || Stripe.api_base) + url
234
325
  end
235
326
 
236
- def check_api_key!(api_key)
327
+ private def check_api_key!(api_key)
237
328
  unless api_key
238
329
  raise AuthenticationError, "No API key provided. " \
239
330
  'Set your API key using "Stripe.api_key = <API-KEY>". ' \
240
331
  "You can generate API keys from the Stripe web interface. " \
241
- "See https://stripe.com/api for details, or email support@stripe.com " \
242
- "if you have any questions."
332
+ "See https://stripe.com/api for details, or email " \
333
+ "support@stripe.com if you have any questions."
243
334
  end
244
335
 
245
336
  return unless api_key =~ /\s/
@@ -250,49 +341,82 @@ module Stripe
250
341
  "email support@stripe.com if you have any questions.)"
251
342
  end
252
343
 
253
- def execute_request_with_rescues(api_base, context)
344
+ # Encodes a set of body parameters using multipart if `Content-Type` is set
345
+ # for that, or standard form-encoding otherwise. Returns the encoded body
346
+ # and a version of the encoded body that's safe to be logged.
347
+ private def encode_body(body_params, headers)
348
+ body = nil
349
+ flattened_params = Util.flatten_params(body_params)
350
+
351
+ if headers["Content-Type"] == MultipartEncoder::MULTIPART_FORM_DATA
352
+ body, content_type = MultipartEncoder.encode(flattened_params)
353
+
354
+ # Set a new content type that also includes the multipart boundary.
355
+ # See `MultipartEncoder` for details.
356
+ headers["Content-Type"] = content_type
357
+
358
+ # `#to_s` any complex objects like files and the like to build output
359
+ # that's more condusive to logging.
360
+ flattened_params =
361
+ flattened_params.map { |k, v| [k, v.is_a?(String) ? v : v.to_s] }.to_h
362
+ else
363
+ body = Util.encode_parameters(body_params)
364
+ end
365
+
366
+ # We don't use `Util.encode_parameters` partly as an optimization (to not
367
+ # redo work we've already done), and partly because the encoded forms of
368
+ # certain characters introduce a lot of visual noise and it's nice to
369
+ # have a clearer format for logs.
370
+ body_log = flattened_params.map { |k, v| "#{k}=#{v}" }.join("&")
371
+
372
+ [body, body_log]
373
+ end
374
+
375
+ private def execute_request_with_rescues(method, api_base, context)
254
376
  num_retries = 0
255
377
  begin
256
378
  request_start = Time.now
257
379
  log_request(context, num_retries)
258
380
  resp = yield
259
- context = context.dup_from_response(resp)
260
- log_response(context, request_start, resp.status, resp.body)
381
+ context = context.dup_from_response_headers(resp)
382
+
383
+ handle_error_response(resp, context) if resp.code.to_i >= 400
384
+
385
+ log_response(context, request_start, resp.code.to_i, resp.body)
261
386
 
262
387
  if Stripe.enable_telemetry? && context.request_id
263
388
  request_duration_ms = ((Time.now - request_start) * 1000).to_int
264
- @last_request_metrics = StripeRequestMetrics.new(context.request_id, request_duration_ms)
389
+ @last_request_metrics =
390
+ StripeRequestMetrics.new(context.request_id, request_duration_ms)
265
391
  end
266
392
 
267
393
  # We rescue all exceptions from a request so that we have an easy spot to
268
- # implement our retry logic across the board. We'll re-raise if it's a type
269
- # of exception that we didn't expect to handle.
394
+ # implement our retry logic across the board. We'll re-raise if it's a
395
+ # type of exception that we didn't expect to handle.
270
396
  rescue StandardError => e
271
397
  # If we modify context we copy it into a new variable so as not to
272
398
  # taint the original on a retry.
273
399
  error_context = context
274
400
 
275
- if e.respond_to?(:response) && e.response
276
- error_context = context.dup_from_response(e.response)
401
+ if e.is_a?(Stripe::StripeError)
402
+ error_context = context.dup_from_response_headers(e.http_headers)
277
403
  log_response(error_context, request_start,
278
- e.response[:status], e.response[:body])
404
+ e.http_status, e.http_body)
279
405
  else
280
406
  log_response_error(error_context, request_start, e)
281
407
  end
282
408
 
283
- if self.class.should_retry?(e, num_retries)
409
+ if self.class.should_retry?(e, method: method, num_retries: num_retries)
284
410
  num_retries += 1
285
411
  sleep self.class.sleep_time(num_retries)
286
412
  retry
287
413
  end
288
414
 
289
415
  case e
290
- when Faraday::ClientError
291
- if e.response
292
- handle_error_response(e.response, error_context)
293
- else
294
- handle_network_error(e, error_context, num_retries, api_base)
295
- end
416
+ when Stripe::StripeError
417
+ raise
418
+ when *NETWORK_ERROR_MESSAGES_MAP.keys
419
+ handle_network_error(e, error_context, num_retries, api_base)
296
420
 
297
421
  # Only handle errors when we know we can do so, and re-raise otherwise.
298
422
  # This should be pretty infrequent.
@@ -304,7 +428,7 @@ module Stripe
304
428
  resp
305
429
  end
306
430
 
307
- def general_api_error(status, body)
431
+ private def general_api_error(status, body)
308
432
  APIError.new("Invalid response object from API: #{body.inspect} " \
309
433
  "(HTTP response code was #{status})",
310
434
  http_status: status, http_body: body)
@@ -314,21 +438,21 @@ module Stripe
314
438
  # end of a User-Agent string where it'll be fairly prominent in places like
315
439
  # the Dashboard. Note that this formatting has been implemented to match
316
440
  # other libraries, and shouldn't be changed without universal consensus.
317
- def format_app_info(info)
441
+ private def format_app_info(info)
318
442
  str = info[:name]
319
443
  str = "#{str}/#{info[:version]}" unless info[:version].nil?
320
444
  str = "#{str} (#{info[:url]})" unless info[:url].nil?
321
445
  str
322
446
  end
323
447
 
324
- def handle_error_response(http_resp, context)
448
+ private def handle_error_response(http_resp, context)
325
449
  begin
326
- resp = StripeResponse.from_faraday_hash(http_resp)
450
+ resp = StripeResponse.from_net_http(http_resp)
327
451
  error_data = resp.data[:error]
328
452
 
329
453
  raise StripeError, "Indeterminate error" unless error_data
330
454
  rescue JSON::ParserError, StripeError
331
- raise general_api_error(http_resp[:status], http_resp[:body])
455
+ raise general_api_error(http_resp.code.to_i, http_resp.body)
332
456
  end
333
457
 
334
458
  error = if error_data.is_a?(String)
@@ -341,7 +465,29 @@ module Stripe
341
465
  raise(error)
342
466
  end
343
467
 
344
- def specific_api_error(resp, error_data, context)
468
+ # Works around an edge case where we end up with both query parameters from
469
+ # parameteers and query parameters that were appended onto the end of the
470
+ # given path.
471
+ #
472
+ # Decode any parameters that were added onto the end of a path and add them
473
+ # to a unified query parameter hash so that all parameters end up in one
474
+ # place and all of them are correctly included in the final request.
475
+ private def merge_query_params(query_params, path)
476
+ u = URI.parse(path)
477
+
478
+ # Return original results if there was nothing to be found.
479
+ return query_params, path if u.query.nil?
480
+
481
+ query_params ||= {}
482
+ query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
483
+
484
+ # Reset the path minus any query parameters that were specified.
485
+ path = u.path
486
+
487
+ [query_params, path]
488
+ end
489
+
490
+ private def specific_api_error(resp, error_data, context)
345
491
  Util.log_error("Stripe API error",
346
492
  status: resp.http_status,
347
493
  error_code: error_data[:code],
@@ -375,11 +521,8 @@ module Stripe
375
521
  when 401
376
522
  AuthenticationError.new(error_data[:message], opts)
377
523
  when 402
378
- # TODO: modify CardError constructor to make code a keyword argument
379
- # so we don't have to delete it from opts
380
- opts.delete(:code)
381
524
  CardError.new(
382
- error_data[:message], error_data[:param], error_data[:code],
525
+ error_data[:message], error_data[:param],
383
526
  opts
384
527
  )
385
528
  when 403
@@ -393,7 +536,7 @@ module Stripe
393
536
 
394
537
  # Attempts to look at a response's error code and return an OAuth error if
395
538
  # one matches. Will return `nil` if the code isn't recognized.
396
- def specific_oauth_error(resp, error_code, context)
539
+ private def specific_oauth_error(resp, error_code, context)
397
540
  description = resp.data[:error_description] || error_code
398
541
 
399
542
  Util.log_error("Stripe OAuth error",
@@ -409,12 +552,18 @@ module Stripe
409
552
  },]
410
553
 
411
554
  case error_code
412
- when "invalid_client" then OAuth::InvalidClientError.new(*args)
413
- when "invalid_grant" then OAuth::InvalidGrantError.new(*args)
414
- when "invalid_request" then OAuth::InvalidRequestError.new(*args)
415
- when "invalid_scope" then OAuth::InvalidScopeError.new(*args)
416
- when "unsupported_grant_type" then OAuth::UnsupportedGrantTypeError.new(*args)
417
- when "unsupported_response_type" then OAuth::UnsupportedResponseTypeError.new(*args)
555
+ when "invalid_client"
556
+ OAuth::InvalidClientError.new(*args)
557
+ when "invalid_grant"
558
+ OAuth::InvalidGrantError.new(*args)
559
+ when "invalid_request"
560
+ OAuth::InvalidRequestError.new(*args)
561
+ when "invalid_scope"
562
+ OAuth::InvalidScopeError.new(*args)
563
+ when "unsupported_grant_type"
564
+ OAuth::UnsupportedGrantTypeError.new(*args)
565
+ when "unsupported_response_type"
566
+ OAuth::UnsupportedResponseTypeError.new(*args)
418
567
  else
419
568
  # We'd prefer that all errors are typed, but we create a generic
420
569
  # OAuthError in case we run into a code that we don't recognize.
@@ -422,43 +571,32 @@ module Stripe
422
571
  end
423
572
  end
424
573
 
425
- def handle_network_error(e, context, num_retries, api_base = nil)
574
+ private def handle_network_error(error, context, num_retries,
575
+ api_base = nil)
426
576
  Util.log_error("Stripe network error",
427
- error_message: e.message,
577
+ error_message: error.message,
428
578
  idempotency_key: context.idempotency_key,
429
579
  request_id: context.request_id)
430
580
 
431
- case e
432
- when Faraday::ConnectionFailed
433
- message = "Unexpected error communicating when trying to connect to Stripe. " \
434
- "You may be seeing this message because your DNS is not working. " \
435
- "To check, try running 'host stripe.com' from the command line."
436
-
437
- when Faraday::SSLError
438
- message = "Could not establish a secure connection to Stripe, you may " \
439
- "need to upgrade your OpenSSL version. To check, try running " \
440
- "'openssl s_client -connect api.stripe.com:443' from the " \
441
- "command line."
442
-
443
- when Faraday::TimeoutError
444
- api_base ||= Stripe.api_base
445
- message = "Could not connect to Stripe (#{api_base}). " \
446
- "Please check your internet connection and try again. " \
447
- "If this problem persists, you should check Stripe's service status at " \
448
- "https://twitter.com/stripestatus, or let us know at support@stripe.com."
449
-
450
- else
451
- message = "Unexpected error communicating with Stripe. " \
452
- "If this problem persists, let us know at support@stripe.com."
581
+ errors, message = NETWORK_ERROR_MESSAGES_MAP.detect do |(e, _)|
582
+ error.is_a?(e)
583
+ end
453
584
 
585
+ if errors.nil?
586
+ message = "Unexpected error #{error.class.name} communicating " \
587
+ "with Stripe. Please let us know at support@stripe.com."
454
588
  end
455
589
 
590
+ api_base ||= Stripe.api_base
591
+ message = message % api_base
592
+
456
593
  message += " Request was retried #{num_retries} times." if num_retries > 0
457
594
 
458
- raise APIConnectionError, message + "\n\n(Network error: #{e.message})"
595
+ raise APIConnectionError,
596
+ message + "\n\n(Network error: #{error.message})"
459
597
  end
460
598
 
461
- def request_headers(api_key, method)
599
+ private def request_headers(api_key, method)
462
600
  user_agent = "Stripe/v1 RubyBindings/#{Stripe::VERSION}"
463
601
  unless Stripe.app_info.nil?
464
602
  user_agent += " " + format_app_info(Stripe.app_info)
@@ -471,7 +609,9 @@ module Stripe
471
609
  }
472
610
 
473
611
  if Stripe.enable_telemetry? && !@last_request_metrics.nil?
474
- headers["X-Stripe-Client-Telemetry"] = JSON.generate(last_request_metrics: @last_request_metrics.payload)
612
+ headers["X-Stripe-Client-Telemetry"] = JSON.generate(
613
+ last_request_metrics: @last_request_metrics.payload
614
+ )
475
615
  end
476
616
 
477
617
  # It is only safe to retry network failures on post and delete
@@ -498,7 +638,7 @@ module Stripe
498
638
  headers
499
639
  end
500
640
 
501
- def log_request(context, num_retries)
641
+ private def log_request(context, num_retries)
502
642
  Util.log_info("Request to Stripe API",
503
643
  account: context.account,
504
644
  api_version: context.api_version,
@@ -509,11 +649,10 @@ module Stripe
509
649
  Util.log_debug("Request details",
510
650
  body: context.body,
511
651
  idempotency_key: context.idempotency_key,
512
- query_params: context.query_params)
652
+ query: context.query)
513
653
  end
514
- private :log_request
515
654
 
516
- def log_response(context, request_start, status, body)
655
+ private def log_response(context, request_start, status, body)
517
656
  Util.log_info("Response from Stripe API",
518
657
  account: context.account,
519
658
  api_version: context.api_version,
@@ -533,19 +672,18 @@ module Stripe
533
672
  Util.log_debug("Dashboard link for request",
534
673
  idempotency_key: context.idempotency_key,
535
674
  request_id: context.request_id,
536
- url: Util.request_id_dashboard_url(context.request_id, context.api_key))
675
+ url: Util.request_id_dashboard_url(context.request_id,
676
+ context.api_key))
537
677
  end
538
- private :log_response
539
678
 
540
- def log_response_error(context, request_start, e)
679
+ private def log_response_error(context, request_start, error)
541
680
  Util.log_error("Request error",
542
681
  elapsed: Time.now - request_start,
543
- error_message: e.message,
682
+ error_message: error.message,
544
683
  idempotency_key: context.idempotency_key,
545
684
  method: context.method,
546
685
  path: context.path)
547
686
  end
548
- private :log_response_error
549
687
 
550
688
  # RequestLogContext stores information about a request that's begin made so
551
689
  # that we can log certain information. It's useful because it means that we
@@ -558,7 +696,7 @@ module Stripe
558
696
  attr_accessor :idempotency_key
559
697
  attr_accessor :method
560
698
  attr_accessor :path
561
- attr_accessor :query_params
699
+ attr_accessor :query
562
700
  attr_accessor :request_id
563
701
 
564
702
  # The idea with this method is that we might want to update some of
@@ -567,18 +705,7 @@ module Stripe
567
705
  # with for a request. For example, we should trust whatever came back in
568
706
  # a `Stripe-Version` header beyond what configuration information that we
569
707
  # might have had available.
570
- def dup_from_response(resp)
571
- return self if resp.nil?
572
-
573
- # Faraday's API is a little unusual. Normally it'll produce a response
574
- # object with a `headers` method, but on error what it puts into
575
- # `e.response` is an untyped `Hash`.
576
- headers = if resp.is_a?(Faraday::Response)
577
- resp.headers
578
- else
579
- resp[:headers]
580
- end
581
-
708
+ def dup_from_response_headers(headers)
582
709
  context = dup
583
710
  context.account = headers["Stripe-Account"]
584
711
  context.api_version = headers["Stripe-Version"]
@@ -628,7 +755,8 @@ module Stripe
628
755
  end
629
756
 
630
757
  def user_agent
631
- lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
758
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} " \
759
+ "(#{RUBY_RELEASE_DATE})"
632
760
 
633
761
  {
634
762
  application: Stripe.app_info,
@@ -644,7 +772,8 @@ module Stripe
644
772
  end
645
773
  end
646
774
 
647
- # StripeRequestMetrics tracks metadata to be reported to stripe for metrics collection
775
+ # StripeRequestMetrics tracks metadata to be reported to stripe for metrics
776
+ # collection
648
777
  class StripeRequestMetrics
649
778
  # The Stripe request ID of the response.
650
779
  attr_accessor :request_id