stripe 4.10.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +10 -0
- data/.rubocop.yml +28 -4
- data/.rubocop_todo.yml +11 -22
- data/.travis.yml +3 -6
- data/.vscode/extensions.json +7 -0
- data/.vscode/settings.json +8 -0
- data/CHANGELOG.md +86 -1
- data/Gemfile +2 -10
- data/README.md +96 -40
- data/Rakefile +8 -7
- data/VERSION +1 -1
- data/lib/stripe/api_operations/delete.rb +23 -1
- data/lib/stripe/api_operations/list.rb +0 -6
- data/lib/stripe/api_operations/nested_resource.rb +14 -7
- data/lib/stripe/api_operations/request.rb +3 -7
- data/lib/stripe/api_operations/save.rb +1 -3
- data/lib/stripe/api_resource.rb +50 -2
- data/lib/stripe/connection_manager.rb +131 -0
- data/lib/stripe/error_object.rb +94 -0
- data/lib/stripe/errors.rb +22 -9
- data/lib/stripe/list_object.rb +11 -5
- data/lib/stripe/multipart_encoder.rb +131 -0
- data/lib/stripe/object_types.rb +94 -0
- data/lib/stripe/{account.rb → resources/account.rb} +49 -27
- data/lib/stripe/{account_link.rb → resources/account_link.rb} +1 -1
- data/lib/stripe/resources/alipay_account.rb +34 -0
- data/lib/stripe/{apple_pay_domain.rb → resources/apple_pay_domain.rb} +1 -1
- data/lib/stripe/resources/application_fee.rb +13 -0
- data/lib/stripe/resources/application_fee_refund.rb +30 -0
- data/lib/stripe/{balance.rb → resources/balance.rb} +1 -1
- data/lib/stripe/{balance_transaction.rb → resources/balance_transaction.rb} +1 -5
- data/lib/stripe/{bank_account.rb → resources/bank_account.rb} +14 -4
- data/lib/stripe/{bitcoin_receiver.rb → resources/bitcoin_receiver.rb} +3 -3
- data/lib/stripe/{bitcoin_transaction.rb → resources/bitcoin_transaction.rb} +1 -1
- data/lib/stripe/resources/capability.rb +33 -0
- data/lib/stripe/{card.rb → resources/card.rb} +12 -4
- data/lib/stripe/resources/charge.rb +22 -0
- data/lib/stripe/{checkout → resources/checkout}/session.rb +2 -2
- data/lib/stripe/{country_spec.rb → resources/country_spec.rb} +1 -1
- data/lib/stripe/{coupon.rb → resources/coupon.rb} +2 -2
- data/lib/stripe/resources/credit_note.rb +22 -0
- data/lib/stripe/resources/customer.rb +35 -0
- data/lib/stripe/resources/customer_balance_transaction.rb +30 -0
- data/lib/stripe/resources/discount.rb +7 -0
- data/lib/stripe/{dispute.rb → resources/dispute.rb} +9 -7
- data/lib/stripe/{ephemeral_key.rb → resources/ephemeral_key.rb} +5 -2
- data/lib/stripe/{event.rb → resources/event.rb} +1 -1
- data/lib/stripe/{exchange_rate.rb → resources/exchange_rate.rb} +1 -1
- data/lib/stripe/{file.rb → resources/file.rb} +8 -11
- data/lib/stripe/{file_link.rb → resources/file_link.rb} +2 -2
- data/lib/stripe/resources/invoice.rb +73 -0
- data/lib/stripe/{invoice_item.rb → resources/invoice_item.rb} +2 -2
- data/lib/stripe/{invoice_line_item.rb → resources/invoice_line_item.rb} +1 -1
- data/lib/stripe/resources/issuing/authorization.rb +33 -0
- data/lib/stripe/resources/issuing/card.rb +24 -0
- data/lib/stripe/{issuing → resources/issuing}/card_details.rb +1 -1
- data/lib/stripe/{issuing → resources/issuing}/cardholder.rb +2 -2
- data/lib/stripe/{issuing → resources/issuing}/dispute.rb +2 -2
- data/lib/stripe/{issuing → resources/issuing}/transaction.rb +2 -2
- data/lib/stripe/resources/login_link.rb +14 -0
- data/lib/stripe/resources/order.rb +32 -0
- data/lib/stripe/{order_return.rb → resources/order_return.rb} +1 -1
- data/lib/stripe/resources/payment_intent.rb +42 -0
- data/lib/stripe/resources/payment_method.rb +32 -0
- data/lib/stripe/resources/payout.rb +22 -0
- data/lib/stripe/{person.rb → resources/person.rb} +8 -3
- data/lib/stripe/{plan.rb → resources/plan.rb} +1 -1
- data/lib/stripe/{product.rb → resources/product.rb} +3 -3
- data/lib/stripe/resources/radar/early_fraud_warning.rb +11 -0
- data/lib/stripe/{radar → resources/radar}/value_list.rb +2 -2
- data/lib/stripe/{radar → resources/radar}/value_list_item.rb +2 -2
- data/lib/stripe/{recipient.rb → resources/recipient.rb} +2 -6
- data/lib/stripe/{recipient_transfer.rb → resources/recipient_transfer.rb} +1 -1
- data/lib/stripe/{refund.rb → resources/refund.rb} +1 -1
- data/lib/stripe/{reporting → resources/reporting}/report_run.rb +2 -2
- data/lib/stripe/{reporting → resources/reporting}/report_type.rb +2 -2
- data/lib/stripe/resources/reversal.rb +29 -0
- data/lib/stripe/resources/review.rb +20 -0
- data/lib/stripe/resources/setup_intent.rb +32 -0
- data/lib/stripe/{sigma → resources/sigma}/scheduled_query_run.rb +2 -2
- data/lib/stripe/{sku.rb → resources/sku.rb} +3 -3
- data/lib/stripe/{source.rb → resources/source.rb} +17 -15
- data/lib/stripe/{source_transaction.rb → resources/source_transaction.rb} +1 -1
- data/lib/stripe/resources/subscription.rb +25 -0
- data/lib/stripe/{subscription_item.rb → resources/subscription_item.rb} +5 -2
- data/lib/stripe/resources/subscription_schedule.rb +32 -0
- data/lib/stripe/resources/tax_id.rb +26 -0
- data/lib/stripe/resources/tax_rate.rb +11 -0
- data/lib/stripe/{terminal → resources/terminal}/connection_token.rb +2 -2
- data/lib/stripe/{terminal → resources/terminal}/location.rb +2 -2
- data/lib/stripe/{terminal → resources/terminal}/reader.rb +2 -2
- data/lib/stripe/{three_d_secure.rb → resources/three_d_secure.rb} +1 -1
- data/lib/stripe/{token.rb → resources/token.rb} +1 -1
- data/lib/stripe/resources/topup.rb +22 -0
- data/lib/stripe/resources/transfer.rb +26 -0
- data/lib/stripe/resources/usage_record.rb +7 -0
- data/lib/stripe/{usage_record_summary.rb → resources/usage_record_summary.rb} +1 -1
- data/lib/stripe/{webhook_endpoint.rb → resources/webhook_endpoint.rb} +2 -2
- data/lib/stripe/resources.rb +77 -0
- data/lib/stripe/singleton_api_resource.rb +3 -1
- data/lib/stripe/stripe_client.rb +337 -218
- data/lib/stripe/stripe_object.rb +54 -59
- data/lib/stripe/stripe_response.rb +53 -21
- data/lib/stripe/util.rb +54 -110
- data/lib/stripe/version.rb +1 -1
- data/lib/stripe/webhook.rb +5 -3
- data/lib/stripe.rb +64 -86
- data/stripe.gemspec +14 -5
- data/test/stripe/account_link_test.rb +1 -1
- data/test/stripe/account_test.rb +186 -30
- data/test/stripe/alipay_account_test.rb +1 -1
- data/test/stripe/api_operations_test.rb +3 -4
- data/test/stripe/api_resource_test.rb +105 -18
- data/test/stripe/apple_pay_domain_test.rb +18 -5
- data/test/stripe/application_fee_refund_test.rb +1 -1
- data/test/stripe/application_fee_test.rb +45 -1
- data/test/stripe/balance_test.rb +1 -1
- data/test/stripe/balance_transaction_test.rb +20 -0
- data/test/stripe/bank_account_test.rb +1 -1
- data/test/stripe/capability_test.rb +45 -0
- data/test/stripe/charge_test.rb +13 -8
- data/test/stripe/checkout/session_test.rb +1 -1
- data/test/stripe/connection_manager_test.rb +138 -0
- data/test/stripe/country_spec_test.rb +1 -1
- data/test/stripe/coupon_test.rb +16 -6
- data/test/stripe/credit_note_test.rb +61 -0
- data/test/stripe/customer_balance_transaction_test.rb +37 -0
- data/test/stripe/customer_card_test.rb +1 -1
- data/test/stripe/customer_test.rb +151 -40
- data/test/stripe/dispute_test.rb +10 -1
- data/test/stripe/ephemeral_key_test.rb +8 -1
- data/test/stripe/errors_test.rb +30 -9
- data/test/stripe/exchange_rate_test.rb +1 -1
- data/test/stripe/file_link_test.rb +1 -1
- data/test/stripe/file_test.rb +19 -5
- data/test/stripe/invoice_item_test.rb +18 -7
- data/test/stripe/invoice_line_item_test.rb +1 -1
- data/test/stripe/invoice_test.rb +77 -9
- data/test/stripe/issuing/authorization_test.rb +33 -11
- data/test/stripe/issuing/card_test.rb +15 -6
- data/test/stripe/issuing/cardholder_test.rb +1 -1
- data/test/stripe/issuing/dispute_test.rb +1 -1
- data/test/stripe/issuing/transaction_test.rb +1 -1
- data/test/stripe/list_object_test.rb +1 -17
- data/test/stripe/login_link_test.rb +2 -2
- data/test/stripe/multipart_encoder_test.rb +130 -0
- data/test/stripe/oauth_test.rb +1 -1
- data/test/stripe/order_return_test.rb +1 -1
- data/test/stripe/order_test.rb +28 -3
- data/test/stripe/payment_intent_test.rb +31 -4
- data/test/stripe/payment_method_test.rb +19 -1
- data/test/stripe/payout_test.rb +8 -1
- data/test/stripe/person_test.rb +1 -1
- data/test/stripe/plan_test.rb +26 -20
- data/test/stripe/product_test.rb +16 -6
- data/test/stripe/radar/early_fraud_warning_test.rb +22 -0
- data/test/stripe/radar/value_list_item_test.rb +16 -6
- data/test/stripe/radar/value_list_test.rb +16 -6
- data/test/stripe/recipient_test.rb +18 -5
- data/test/stripe/refund_test.rb +1 -1
- data/test/stripe/reporting/report_run_test.rb +1 -1
- data/test/stripe/reporting/report_type_test.rb +1 -1
- data/test/stripe/reversal_test.rb +1 -1
- data/test/stripe/review_test.rb +1 -1
- data/test/stripe/setup_intent_test.rb +84 -0
- data/test/stripe/sigma/scheduled_query_run_test.rb +1 -1
- data/test/stripe/sku_test.rb +16 -6
- data/test/stripe/source_test.rb +14 -19
- data/test/stripe/source_transaction_test.rb +1 -1
- data/test/stripe/stripe_client_test.rb +230 -26
- data/test/stripe/stripe_object_test.rb +8 -36
- data/test/stripe/stripe_response_test.rb +71 -25
- data/test/stripe/subscription_item_test.rb +28 -6
- data/test/stripe/subscription_schedule_test.rb +19 -1
- data/test/stripe/subscription_test.rb +29 -9
- data/test/stripe/tax_id_test.rb +31 -0
- data/test/stripe/tax_rate_test.rb +43 -0
- data/test/stripe/terminal/connection_token_test.rb +1 -1
- data/test/stripe/terminal/location_test.rb +16 -6
- data/test/stripe/terminal/reader_test.rb +16 -6
- data/test/stripe/three_d_secure_test.rb +1 -1
- data/test/stripe/topup_test.rb +9 -1
- data/test/stripe/transfer_test.rb +46 -1
- data/test/stripe/usage_record_summary_test.rb +1 -1
- data/test/stripe/util_test.rb +1 -1
- data/test/stripe/webhook_endpoint_test.rb +18 -1
- data/test/stripe/webhook_test.rb +4 -4
- data/test/stripe_mock.rb +4 -3
- data/test/stripe_test.rb +1 -14
- data/test/test_helper.rb +14 -11
- metadata +114 -124
- data/lib/stripe/alipay_account.rb +0 -27
- data/lib/stripe/application_fee.rb +0 -23
- data/lib/stripe/application_fee_refund.rb +0 -22
- data/lib/stripe/charge.rb +0 -84
- data/lib/stripe/customer.rb +0 -90
- data/lib/stripe/invoice.rb +0 -48
- data/lib/stripe/issuer_fraud_record.rb +0 -9
- data/lib/stripe/issuing/authorization.rb +0 -22
- data/lib/stripe/issuing/card.rb +0 -18
- data/lib/stripe/login_link.rb +0 -11
- data/lib/stripe/order.rb +0 -31
- data/lib/stripe/payment_intent.rb +0 -26
- data/lib/stripe/payment_method.rb +0 -23
- data/lib/stripe/payout.rb +0 -20
- data/lib/stripe/reversal.rb +0 -22
- data/lib/stripe/review.rb +0 -14
- data/lib/stripe/subscription.rb +0 -25
- data/lib/stripe/subscription_schedule.rb +0 -32
- data/lib/stripe/subscription_schedule_revision.rb +0 -25
- data/lib/stripe/topup.rb +0 -16
- data/lib/stripe/transfer.rb +0 -23
- data/lib/stripe/usage_record.rb +0 -14
- data/test/stripe/account_external_accounts_operations_test.rb +0 -69
- data/test/stripe/account_login_links_operations_test.rb +0 -21
- data/test/stripe/account_persons_operations_test.rb +0 -70
- data/test/stripe/application_fee_refunds_operations_test.rb +0 -56
- data/test/stripe/customer_sources_operations_test.rb +0 -64
- data/test/stripe/file_upload_test.rb +0 -76
- data/test/stripe/issuer_fraud_record_test.rb +0 -20
- data/test/stripe/subscription_schedule_revision_test.rb +0 -37
- data/test/stripe/subscription_schedule_revisions_operations_test.rb +0 -35
- data/test/stripe/transfer_reversals_operations_test.rb +0 -57
- data/test/stripe/usage_record_test.rb +0 -28
data/lib/stripe/stripe_client.rb
CHANGED
@@ -5,81 +5,98 @@ 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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
32
|
+
current_thread_context.active_client || default_client
|
20
33
|
end
|
21
34
|
|
22
|
-
|
23
|
-
|
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
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
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
|
67
|
-
# socket errors that may represent an intermittent problem and some
|
68
|
-
# HTTP statuses.
|
69
|
-
def self.should_retry?(
|
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
|
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
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
+
# 500 Internal Server Error
|
92
|
+
#
|
93
|
+
# We only bother retrying these for non-POST requests. POSTs end up
|
94
|
+
# being cached by the idempotency layer so there's no purpose in
|
95
|
+
# retrying them.
|
96
|
+
return true if error.http_status == 500 && method != :post
|
97
|
+
|
98
|
+
# 503 Service Unavailable
|
99
|
+
return true if error.http_status == 503
|
83
100
|
end
|
84
101
|
|
85
102
|
false
|
@@ -87,12 +104,15 @@ module Stripe
|
|
87
104
|
|
88
105
|
def self.sleep_time(num_retries)
|
89
106
|
# 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
|
91
|
-
# max_network_retry_delay.
|
92
|
-
sleep_seconds = [
|
93
|
-
|
94
|
-
|
95
|
-
|
107
|
+
# number of num_retries so far as inputs. Do not allow the number to
|
108
|
+
# exceed max_network_retry_delay.
|
109
|
+
sleep_seconds = [
|
110
|
+
Stripe.initial_network_retry_delay * (2**(num_retries - 1)),
|
111
|
+
Stripe.max_network_retry_delay,
|
112
|
+
].min
|
113
|
+
|
114
|
+
# Apply some jitter by randomizing the value in the range of
|
115
|
+
# (sleep_seconds / 2) to (sleep_seconds).
|
96
116
|
sleep_seconds *= (0.5 * (1 + rand))
|
97
117
|
|
98
118
|
# But never sleep less than the base sleep seconds.
|
@@ -107,139 +127,200 @@ module Stripe
|
|
107
127
|
# charge, resp = client.request { Charge.create }
|
108
128
|
#
|
109
129
|
def request
|
110
|
-
|
111
|
-
|
112
|
-
|
130
|
+
old_stripe_client = self.class.current_thread_context.active_client
|
131
|
+
self.class.current_thread_context.active_client = self
|
132
|
+
|
133
|
+
if self.class.current_thread_context.last_responses&.key?(object_id)
|
134
|
+
raise "calls to StripeClient#request cannot be nested within a thread"
|
135
|
+
end
|
136
|
+
|
137
|
+
self.class.current_thread_context.last_responses ||= {}
|
138
|
+
self.class.current_thread_context.last_responses[object_id] = nil
|
113
139
|
|
114
140
|
begin
|
115
141
|
res = yield
|
116
|
-
[res,
|
142
|
+
[res, self.class.current_thread_context.last_responses[object_id]]
|
117
143
|
ensure
|
118
|
-
|
144
|
+
self.class.current_thread_context.active_client = old_stripe_client
|
145
|
+
self.class.current_thread_context.last_responses.delete(object_id)
|
119
146
|
end
|
120
147
|
end
|
121
148
|
|
122
149
|
def execute_request(method, path,
|
123
150
|
api_base: nil, api_key: nil, headers: {}, params: {})
|
151
|
+
raise ArgumentError, "method should be a symbol" \
|
152
|
+
unless method.is_a?(Symbol)
|
153
|
+
raise ArgumentError, "path should be a string" \
|
154
|
+
unless path.is_a?(String)
|
155
|
+
|
124
156
|
api_base ||= Stripe.api_base
|
125
157
|
api_key ||= Stripe.api_key
|
126
158
|
params = Util.objects_to_ids(params)
|
127
159
|
|
128
160
|
check_api_key!(api_key)
|
129
161
|
|
130
|
-
|
162
|
+
body_params = nil
|
131
163
|
query_params = nil
|
132
|
-
case method
|
164
|
+
case method
|
133
165
|
when :get, :head, :delete
|
134
166
|
query_params = params
|
135
167
|
else
|
136
|
-
|
168
|
+
body_params = params
|
137
169
|
end
|
138
170
|
|
139
|
-
|
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
|
171
|
+
query_params, path = merge_query_params(query_params, path)
|
155
172
|
|
156
173
|
headers = request_headers(api_key, method)
|
157
174
|
.update(Util.normalize_headers(headers))
|
158
|
-
params_encoder = FaradayStripeEncoder.new
|
159
175
|
url = api_url(path, api_base)
|
160
176
|
|
177
|
+
# Merge given query parameters with any already encoded in the path.
|
178
|
+
query = query_params ? Util.encode_parameters(query_params) : nil
|
179
|
+
|
180
|
+
# Encoding body parameters is a little more complex because we may have
|
181
|
+
# to send a multipart-encoded body. `body_log` is produced separately as
|
182
|
+
# a log-friendly variant of the encoded form. File objects are displayed
|
183
|
+
# as such instead of as their file contents.
|
184
|
+
body, body_log =
|
185
|
+
body_params ? encode_body(body_params, headers) : [nil, nil]
|
186
|
+
|
161
187
|
# stores information on the request we're about to make so that we don't
|
162
188
|
# have to pass as many parameters around for logging.
|
163
189
|
context = RequestLogContext.new
|
164
190
|
context.account = headers["Stripe-Account"]
|
165
191
|
context.api_key = api_key
|
166
192
|
context.api_version = headers["Stripe-Version"]
|
167
|
-
context.body =
|
193
|
+
context.body = body_log
|
168
194
|
context.idempotency_key = headers["Idempotency-Key"]
|
169
195
|
context.method = method
|
170
196
|
context.path = path
|
171
|
-
context.
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
197
|
+
context.query = query
|
198
|
+
|
199
|
+
http_resp = execute_request_with_rescues(method, api_base, context) do
|
200
|
+
connection_manager.execute_request(method, url,
|
201
|
+
body: body,
|
202
|
+
headers: headers,
|
203
|
+
query: query)
|
182
204
|
end
|
183
205
|
|
184
206
|
begin
|
185
|
-
resp = StripeResponse.
|
207
|
+
resp = StripeResponse.from_net_http(http_resp)
|
186
208
|
rescue JSON::ParserError
|
187
|
-
raise general_api_error(http_resp.
|
209
|
+
raise general_api_error(http_resp.code.to_i, http_resp.body)
|
210
|
+
end
|
211
|
+
|
212
|
+
# If being called from `StripeClient#request`, put the last response in
|
213
|
+
# thread-local memory so that it can be returned to the user. Don't store
|
214
|
+
# anything otherwise so that we don't leak memory.
|
215
|
+
if self.class.current_thread_context.last_responses&.key?(object_id)
|
216
|
+
self.class.current_thread_context.last_responses[object_id] = resp
|
188
217
|
end
|
189
218
|
|
190
|
-
# Allows StripeClient#request to return a response object to a caller.
|
191
|
-
@last_response = resp
|
192
219
|
[resp, api_key]
|
193
220
|
end
|
194
221
|
|
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
222
|
#
|
203
|
-
#
|
204
|
-
# telling Faraday to use that.
|
223
|
+
# private
|
205
224
|
#
|
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
225
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
226
|
+
ERROR_MESSAGE_CONNECTION =
|
227
|
+
"Unexpected error communicating when trying to connect to " \
|
228
|
+
"Stripe (%s). You may be seeing this message because your DNS is not " \
|
229
|
+
"working or you don't have an internet connection. To check, try " \
|
230
|
+
"running `host stripe.com` from the command line."
|
231
|
+
ERROR_MESSAGE_SSL =
|
232
|
+
"Could not establish a secure connection to Stripe (%s), you " \
|
233
|
+
"may need to upgrade your OpenSSL version. To check, try running " \
|
234
|
+
"`openssl s_client -connect api.stripe.com:443` from the command " \
|
235
|
+
"line."
|
236
|
+
|
237
|
+
# Common error suffix sared by both connect and read timeout messages.
|
238
|
+
ERROR_MESSAGE_TIMEOUT_SUFFIX =
|
239
|
+
"Please check your internet connection and try again. " \
|
240
|
+
"If this problem persists, you should check Stripe's service " \
|
241
|
+
"status at https://status.stripe.com, or let us know at " \
|
242
|
+
"support@stripe.com."
|
243
|
+
|
244
|
+
ERROR_MESSAGE_TIMEOUT_CONNECT = (
|
245
|
+
"Timed out connecting to Stripe (%s). " +
|
246
|
+
ERROR_MESSAGE_TIMEOUT_SUFFIX
|
247
|
+
).freeze
|
248
|
+
|
249
|
+
ERROR_MESSAGE_TIMEOUT_READ = (
|
250
|
+
"Timed out communicating with Stripe (%s). " +
|
251
|
+
ERROR_MESSAGE_TIMEOUT_SUFFIX
|
252
|
+
).freeze
|
253
|
+
|
254
|
+
# Maps types of exceptions that we're likely to see during a network
|
255
|
+
# request to more user-friendly messages that we put in front of people.
|
256
|
+
# The original error message is also appended onto the final exception for
|
257
|
+
# full transparency.
|
258
|
+
NETWORK_ERROR_MESSAGES_MAP = {
|
259
|
+
Errno::ECONNREFUSED => ERROR_MESSAGE_CONNECTION,
|
260
|
+
SocketError => ERROR_MESSAGE_CONNECTION,
|
261
|
+
|
262
|
+
Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT,
|
263
|
+
Net::ReadTimeout => ERROR_MESSAGE_TIMEOUT_READ,
|
264
|
+
|
265
|
+
OpenSSL::SSL::SSLError => ERROR_MESSAGE_SSL,
|
266
|
+
}.freeze
|
267
|
+
private_constant :NETWORK_ERROR_MESSAGES_MAP
|
268
|
+
|
269
|
+
# A record representing any data that `StripeClient` puts into
|
270
|
+
# `Thread.current`. Making it a class likes this gives us a little extra
|
271
|
+
# type safety and lets us document what each field does.
|
272
|
+
#
|
273
|
+
# For internal use only. Does not provide a stable API and may be broken
|
274
|
+
# with future non-major changes.
|
275
|
+
class ThreadContext
|
276
|
+
# A `StripeClient` that's been flagged as currently active within a
|
277
|
+
# thread by `StripeClient#request`. A client stays active until the
|
278
|
+
# completion of the request block.
|
279
|
+
attr_accessor :active_client
|
280
|
+
|
281
|
+
# A default `StripeClient` object for the thread. Used in all cases where
|
282
|
+
# the user hasn't specified their own.
|
283
|
+
attr_accessor :default_client
|
284
|
+
|
285
|
+
# A default `ConnectionManager` for the thread. Normally shared between
|
286
|
+
# all `StripeClient` objects on a particular thread, and created so as to
|
287
|
+
# minimize the number of open connections that an application needs.
|
288
|
+
attr_accessor :default_connection_manager
|
289
|
+
|
290
|
+
# A temporary map of object IDs to responses from last executed API
|
291
|
+
# calls. Used to return a responses from calls to `StripeClient#request`.
|
292
|
+
#
|
293
|
+
# Stored in the thread data to make the use of a single `StripeClient`
|
294
|
+
# object safe across multiple threads. Stored as a map so that multiple
|
295
|
+
# `StripeClient` objects can run concurrently on the same thread.
|
296
|
+
#
|
297
|
+
# Responses are only left in as long as they're needed, which means
|
298
|
+
# they're removed as soon as a call leaves `StripeClient#request`, and
|
299
|
+
# because that's wrapped in an `ensure` block, they should never leave
|
300
|
+
# garbage in `Thread.current`.
|
301
|
+
attr_accessor :last_responses
|
302
|
+
end
|
225
303
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
304
|
+
# Access data stored for `StripeClient` within the thread's current
|
305
|
+
# context. Returns `ThreadContext`.
|
306
|
+
#
|
307
|
+
# For internal use only. Does not provide a stable API and may be broken
|
308
|
+
# with future non-major changes.
|
309
|
+
def self.current_thread_context
|
310
|
+
Thread.current[:stripe_client__internal_use_only] ||= ThreadContext.new
|
230
311
|
end
|
231
312
|
|
232
|
-
def api_url(url = "", api_base = nil)
|
313
|
+
private def api_url(url = "", api_base = nil)
|
233
314
|
(api_base || Stripe.api_base) + url
|
234
315
|
end
|
235
316
|
|
236
|
-
def check_api_key!(api_key)
|
317
|
+
private def check_api_key!(api_key)
|
237
318
|
unless api_key
|
238
319
|
raise AuthenticationError, "No API key provided. " \
|
239
320
|
'Set your API key using "Stripe.api_key = <API-KEY>". ' \
|
240
321
|
"You can generate API keys from the Stripe web interface. " \
|
241
|
-
"See https://stripe.com/api for details, or email
|
242
|
-
"if you have any questions."
|
322
|
+
"See https://stripe.com/api for details, or email " \
|
323
|
+
"support@stripe.com if you have any questions."
|
243
324
|
end
|
244
325
|
|
245
326
|
return unless api_key =~ /\s/
|
@@ -250,49 +331,82 @@ module Stripe
|
|
250
331
|
"email support@stripe.com if you have any questions.)"
|
251
332
|
end
|
252
333
|
|
253
|
-
|
334
|
+
# Encodes a set of body parameters using multipart if `Content-Type` is set
|
335
|
+
# for that, or standard form-encoding otherwise. Returns the encoded body
|
336
|
+
# and a version of the encoded body that's safe to be logged.
|
337
|
+
private def encode_body(body_params, headers)
|
338
|
+
body = nil
|
339
|
+
flattened_params = Util.flatten_params(body_params)
|
340
|
+
|
341
|
+
if headers["Content-Type"] == MultipartEncoder::MULTIPART_FORM_DATA
|
342
|
+
body, content_type = MultipartEncoder.encode(flattened_params)
|
343
|
+
|
344
|
+
# Set a new content type that also includes the multipart boundary.
|
345
|
+
# See `MultipartEncoder` for details.
|
346
|
+
headers["Content-Type"] = content_type
|
347
|
+
|
348
|
+
# `#to_s` any complex objects like files and the like to build output
|
349
|
+
# that's more condusive to logging.
|
350
|
+
flattened_params =
|
351
|
+
flattened_params.map { |k, v| [k, v.is_a?(String) ? v : v.to_s] }.to_h
|
352
|
+
else
|
353
|
+
body = Util.encode_parameters(body_params)
|
354
|
+
end
|
355
|
+
|
356
|
+
# We don't use `Util.encode_parameters` partly as an optimization (to not
|
357
|
+
# redo work we've already done), and partly because the encoded forms of
|
358
|
+
# certain characters introduce a lot of visual noise and it's nice to
|
359
|
+
# have a clearer format for logs.
|
360
|
+
body_log = flattened_params.map { |k, v| "#{k}=#{v}" }.join("&")
|
361
|
+
|
362
|
+
[body, body_log]
|
363
|
+
end
|
364
|
+
|
365
|
+
private def execute_request_with_rescues(method, api_base, context)
|
254
366
|
num_retries = 0
|
255
367
|
begin
|
256
368
|
request_start = Time.now
|
257
369
|
log_request(context, num_retries)
|
258
370
|
resp = yield
|
259
|
-
context = context.
|
260
|
-
|
371
|
+
context = context.dup_from_response_headers(resp)
|
372
|
+
|
373
|
+
handle_error_response(resp, context) if resp.code.to_i >= 400
|
374
|
+
|
375
|
+
log_response(context, request_start, resp.code.to_i, resp.body)
|
261
376
|
|
262
377
|
if Stripe.enable_telemetry? && context.request_id
|
263
378
|
request_duration_ms = ((Time.now - request_start) * 1000).to_int
|
264
|
-
@last_request_metrics =
|
379
|
+
@last_request_metrics =
|
380
|
+
StripeRequestMetrics.new(context.request_id, request_duration_ms)
|
265
381
|
end
|
266
382
|
|
267
383
|
# 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
|
269
|
-
# of exception that we didn't expect to handle.
|
384
|
+
# implement our retry logic across the board. We'll re-raise if it's a
|
385
|
+
# type of exception that we didn't expect to handle.
|
270
386
|
rescue StandardError => e
|
271
387
|
# If we modify context we copy it into a new variable so as not to
|
272
388
|
# taint the original on a retry.
|
273
389
|
error_context = context
|
274
390
|
|
275
|
-
if e.
|
276
|
-
error_context = context.
|
391
|
+
if e.is_a?(Stripe::StripeError)
|
392
|
+
error_context = context.dup_from_response_headers(e.http_headers)
|
277
393
|
log_response(error_context, request_start,
|
278
|
-
e.
|
394
|
+
e.http_status, e.http_body)
|
279
395
|
else
|
280
396
|
log_response_error(error_context, request_start, e)
|
281
397
|
end
|
282
398
|
|
283
|
-
if self.class.should_retry?(e, num_retries)
|
399
|
+
if self.class.should_retry?(e, method: method, num_retries: num_retries)
|
284
400
|
num_retries += 1
|
285
401
|
sleep self.class.sleep_time(num_retries)
|
286
402
|
retry
|
287
403
|
end
|
288
404
|
|
289
405
|
case e
|
290
|
-
when
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
handle_network_error(e, error_context, num_retries, api_base)
|
295
|
-
end
|
406
|
+
when Stripe::StripeError
|
407
|
+
raise
|
408
|
+
when *NETWORK_ERROR_MESSAGES_MAP.keys
|
409
|
+
handle_network_error(e, error_context, num_retries, api_base)
|
296
410
|
|
297
411
|
# Only handle errors when we know we can do so, and re-raise otherwise.
|
298
412
|
# This should be pretty infrequent.
|
@@ -304,7 +418,7 @@ module Stripe
|
|
304
418
|
resp
|
305
419
|
end
|
306
420
|
|
307
|
-
def general_api_error(status, body)
|
421
|
+
private def general_api_error(status, body)
|
308
422
|
APIError.new("Invalid response object from API: #{body.inspect} " \
|
309
423
|
"(HTTP response code was #{status})",
|
310
424
|
http_status: status, http_body: body)
|
@@ -314,21 +428,21 @@ module Stripe
|
|
314
428
|
# end of a User-Agent string where it'll be fairly prominent in places like
|
315
429
|
# the Dashboard. Note that this formatting has been implemented to match
|
316
430
|
# other libraries, and shouldn't be changed without universal consensus.
|
317
|
-
def format_app_info(info)
|
431
|
+
private def format_app_info(info)
|
318
432
|
str = info[:name]
|
319
433
|
str = "#{str}/#{info[:version]}" unless info[:version].nil?
|
320
434
|
str = "#{str} (#{info[:url]})" unless info[:url].nil?
|
321
435
|
str
|
322
436
|
end
|
323
437
|
|
324
|
-
def handle_error_response(http_resp, context)
|
438
|
+
private def handle_error_response(http_resp, context)
|
325
439
|
begin
|
326
|
-
resp = StripeResponse.
|
440
|
+
resp = StripeResponse.from_net_http(http_resp)
|
327
441
|
error_data = resp.data[:error]
|
328
442
|
|
329
443
|
raise StripeError, "Indeterminate error" unless error_data
|
330
444
|
rescue JSON::ParserError, StripeError
|
331
|
-
raise general_api_error(http_resp
|
445
|
+
raise general_api_error(http_resp.code.to_i, http_resp.body)
|
332
446
|
end
|
333
447
|
|
334
448
|
error = if error_data.is_a?(String)
|
@@ -341,7 +455,29 @@ module Stripe
|
|
341
455
|
raise(error)
|
342
456
|
end
|
343
457
|
|
344
|
-
|
458
|
+
# Works around an edge case where we end up with both query parameters from
|
459
|
+
# parameteers and query parameters that were appended onto the end of the
|
460
|
+
# given path.
|
461
|
+
#
|
462
|
+
# Decode any parameters that were added onto the end of a path and add them
|
463
|
+
# to a unified query parameter hash so that all parameters end up in one
|
464
|
+
# place and all of them are correctly included in the final request.
|
465
|
+
private def merge_query_params(query_params, path)
|
466
|
+
u = URI.parse(path)
|
467
|
+
|
468
|
+
# Return original results if there was nothing to be found.
|
469
|
+
return query_params, path if u.query.nil?
|
470
|
+
|
471
|
+
query_params ||= {}
|
472
|
+
query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
|
473
|
+
|
474
|
+
# Reset the path minus any query parameters that were specified.
|
475
|
+
path = u.path
|
476
|
+
|
477
|
+
[query_params, path]
|
478
|
+
end
|
479
|
+
|
480
|
+
private def specific_api_error(resp, error_data, context)
|
345
481
|
Util.log_error("Stripe API error",
|
346
482
|
status: resp.http_status,
|
347
483
|
error_code: error_data[:code],
|
@@ -375,11 +511,8 @@ module Stripe
|
|
375
511
|
when 401
|
376
512
|
AuthenticationError.new(error_data[:message], opts)
|
377
513
|
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
514
|
CardError.new(
|
382
|
-
error_data[:message], error_data[:param],
|
515
|
+
error_data[:message], error_data[:param],
|
383
516
|
opts
|
384
517
|
)
|
385
518
|
when 403
|
@@ -393,7 +526,7 @@ module Stripe
|
|
393
526
|
|
394
527
|
# Attempts to look at a response's error code and return an OAuth error if
|
395
528
|
# one matches. Will return `nil` if the code isn't recognized.
|
396
|
-
def specific_oauth_error(resp, error_code, context)
|
529
|
+
private def specific_oauth_error(resp, error_code, context)
|
397
530
|
description = resp.data[:error_description] || error_code
|
398
531
|
|
399
532
|
Util.log_error("Stripe OAuth error",
|
@@ -409,12 +542,18 @@ module Stripe
|
|
409
542
|
},]
|
410
543
|
|
411
544
|
case error_code
|
412
|
-
when "invalid_client"
|
413
|
-
|
414
|
-
when "
|
415
|
-
|
416
|
-
when "
|
417
|
-
|
545
|
+
when "invalid_client"
|
546
|
+
OAuth::InvalidClientError.new(*args)
|
547
|
+
when "invalid_grant"
|
548
|
+
OAuth::InvalidGrantError.new(*args)
|
549
|
+
when "invalid_request"
|
550
|
+
OAuth::InvalidRequestError.new(*args)
|
551
|
+
when "invalid_scope"
|
552
|
+
OAuth::InvalidScopeError.new(*args)
|
553
|
+
when "unsupported_grant_type"
|
554
|
+
OAuth::UnsupportedGrantTypeError.new(*args)
|
555
|
+
when "unsupported_response_type"
|
556
|
+
OAuth::UnsupportedResponseTypeError.new(*args)
|
418
557
|
else
|
419
558
|
# We'd prefer that all errors are typed, but we create a generic
|
420
559
|
# OAuthError in case we run into a code that we don't recognize.
|
@@ -422,43 +561,32 @@ module Stripe
|
|
422
561
|
end
|
423
562
|
end
|
424
563
|
|
425
|
-
def handle_network_error(
|
564
|
+
private def handle_network_error(error, context, num_retries,
|
565
|
+
api_base = nil)
|
426
566
|
Util.log_error("Stripe network error",
|
427
|
-
error_message:
|
567
|
+
error_message: error.message,
|
428
568
|
idempotency_key: context.idempotency_key,
|
429
569
|
request_id: context.request_id)
|
430
570
|
|
431
|
-
|
432
|
-
|
433
|
-
|
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."
|
571
|
+
errors, message = NETWORK_ERROR_MESSAGES_MAP.detect do |(e, _)|
|
572
|
+
error.is_a?(e)
|
573
|
+
end
|
453
574
|
|
575
|
+
if errors.nil?
|
576
|
+
message = "Unexpected error #{error.class.name} communicating " \
|
577
|
+
"with Stripe. Please let us know at support@stripe.com."
|
454
578
|
end
|
455
579
|
|
580
|
+
api_base ||= Stripe.api_base
|
581
|
+
message = message % api_base
|
582
|
+
|
456
583
|
message += " Request was retried #{num_retries} times." if num_retries > 0
|
457
584
|
|
458
|
-
raise APIConnectionError,
|
585
|
+
raise APIConnectionError,
|
586
|
+
message + "\n\n(Network error: #{error.message})"
|
459
587
|
end
|
460
588
|
|
461
|
-
def request_headers(api_key, method)
|
589
|
+
private def request_headers(api_key, method)
|
462
590
|
user_agent = "Stripe/v1 RubyBindings/#{Stripe::VERSION}"
|
463
591
|
unless Stripe.app_info.nil?
|
464
592
|
user_agent += " " + format_app_info(Stripe.app_info)
|
@@ -471,7 +599,9 @@ module Stripe
|
|
471
599
|
}
|
472
600
|
|
473
601
|
if Stripe.enable_telemetry? && !@last_request_metrics.nil?
|
474
|
-
headers["X-Stripe-Client-Telemetry"] = JSON.generate(
|
602
|
+
headers["X-Stripe-Client-Telemetry"] = JSON.generate(
|
603
|
+
last_request_metrics: @last_request_metrics.payload
|
604
|
+
)
|
475
605
|
end
|
476
606
|
|
477
607
|
# It is only safe to retry network failures on post and delete
|
@@ -498,7 +628,7 @@ module Stripe
|
|
498
628
|
headers
|
499
629
|
end
|
500
630
|
|
501
|
-
def log_request(context, num_retries)
|
631
|
+
private def log_request(context, num_retries)
|
502
632
|
Util.log_info("Request to Stripe API",
|
503
633
|
account: context.account,
|
504
634
|
api_version: context.api_version,
|
@@ -509,11 +639,10 @@ module Stripe
|
|
509
639
|
Util.log_debug("Request details",
|
510
640
|
body: context.body,
|
511
641
|
idempotency_key: context.idempotency_key,
|
512
|
-
|
642
|
+
query: context.query)
|
513
643
|
end
|
514
|
-
private :log_request
|
515
644
|
|
516
|
-
def log_response(context, request_start, status, body)
|
645
|
+
private def log_response(context, request_start, status, body)
|
517
646
|
Util.log_info("Response from Stripe API",
|
518
647
|
account: context.account,
|
519
648
|
api_version: context.api_version,
|
@@ -533,19 +662,18 @@ module Stripe
|
|
533
662
|
Util.log_debug("Dashboard link for request",
|
534
663
|
idempotency_key: context.idempotency_key,
|
535
664
|
request_id: context.request_id,
|
536
|
-
url: Util.request_id_dashboard_url(context.request_id,
|
665
|
+
url: Util.request_id_dashboard_url(context.request_id,
|
666
|
+
context.api_key))
|
537
667
|
end
|
538
|
-
private :log_response
|
539
668
|
|
540
|
-
def log_response_error(context, request_start,
|
669
|
+
private def log_response_error(context, request_start, error)
|
541
670
|
Util.log_error("Request error",
|
542
671
|
elapsed: Time.now - request_start,
|
543
|
-
error_message:
|
672
|
+
error_message: error.message,
|
544
673
|
idempotency_key: context.idempotency_key,
|
545
674
|
method: context.method,
|
546
675
|
path: context.path)
|
547
676
|
end
|
548
|
-
private :log_response_error
|
549
677
|
|
550
678
|
# RequestLogContext stores information about a request that's begin made so
|
551
679
|
# that we can log certain information. It's useful because it means that we
|
@@ -558,7 +686,7 @@ module Stripe
|
|
558
686
|
attr_accessor :idempotency_key
|
559
687
|
attr_accessor :method
|
560
688
|
attr_accessor :path
|
561
|
-
attr_accessor :
|
689
|
+
attr_accessor :query
|
562
690
|
attr_accessor :request_id
|
563
691
|
|
564
692
|
# The idea with this method is that we might want to update some of
|
@@ -567,18 +695,7 @@ module Stripe
|
|
567
695
|
# with for a request. For example, we should trust whatever came back in
|
568
696
|
# a `Stripe-Version` header beyond what configuration information that we
|
569
697
|
# might have had available.
|
570
|
-
def
|
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
|
-
|
698
|
+
def dup_from_response_headers(headers)
|
582
699
|
context = dup
|
583
700
|
context.account = headers["Stripe-Account"]
|
584
701
|
context.api_version = headers["Stripe-Version"]
|
@@ -628,7 +745,8 @@ module Stripe
|
|
628
745
|
end
|
629
746
|
|
630
747
|
def user_agent
|
631
|
-
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL}
|
748
|
+
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} " \
|
749
|
+
"(#{RUBY_RELEASE_DATE})"
|
632
750
|
|
633
751
|
{
|
634
752
|
application: Stripe.app_info,
|
@@ -644,7 +762,8 @@ module Stripe
|
|
644
762
|
end
|
645
763
|
end
|
646
764
|
|
647
|
-
# StripeRequestMetrics tracks metadata to be reported to stripe for metrics
|
765
|
+
# StripeRequestMetrics tracks metadata to be reported to stripe for metrics
|
766
|
+
# collection
|
648
767
|
class StripeRequestMetrics
|
649
768
|
# The Stripe request ID of the response.
|
650
769
|
attr_accessor :request_id
|