stripe 4.9.0 → 5.1.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 +102 -2
- data/Gemfile +2 -10
- data/README.md +96 -40
- data/Rakefile +8 -7
- data/VERSION +1 -1
- data/lib/stripe.rb +64 -85
- 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 +141 -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/resources.rb +77 -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 +3 -2
- data/lib/stripe/{terminal → resources/terminal}/reader.rb +3 -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/singleton_api_resource.rb +3 -1
- data/lib/stripe/stripe_client.rb +347 -218
- data/lib/stripe/stripe_object.rb +72 -59
- data/lib/stripe/stripe_response.rb +53 -21
- data/lib/stripe/util.rb +54 -109
- data/lib/stripe/version.rb +1 -1
- data/lib/stripe/webhook.rb +5 -3
- data/stripe.gemspec +14 -5
- data/test/stripe/account_link_test.rb +1 -1
- data/test/stripe/account_test.rb +193 -32
- 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 +119 -30
- 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 +7 -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 +84 -0
- 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 +242 -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 +18 -1
- data/test/stripe/terminal/reader_test.rb +18 -1
- 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 +117 -125
- 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/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
@@ -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,
|
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.
|
data/lib/stripe/stripe_client.rb
CHANGED
@@ -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
|
-
|
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
|
+
# 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
|
91
|
-
# max_network_retry_delay.
|
92
|
-
sleep_seconds = [
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
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,
|
152
|
+
[res, self.class.current_thread_context.last_responses[object_id]]
|
117
153
|
ensure
|
118
|
-
|
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
|
-
|
172
|
+
body_params = nil
|
131
173
|
query_params = nil
|
132
|
-
case method
|
174
|
+
case method
|
133
175
|
when :get, :head, :delete
|
134
176
|
query_params = params
|
135
177
|
else
|
136
|
-
|
178
|
+
body_params = params
|
137
179
|
end
|
138
180
|
|
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
|
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 =
|
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.
|
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
|
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.
|
217
|
+
resp = StripeResponse.from_net_http(http_resp)
|
186
218
|
rescue JSON::ParserError
|
187
|
-
raise general_api_error(http_resp.
|
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
|
-
#
|
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
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
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
|
-
|
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.
|
260
|
-
|
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 =
|
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
|
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.
|
276
|
-
error_context = context.
|
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.
|
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
|
291
|
-
|
292
|
-
|
293
|
-
|
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.
|
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
|
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
|
-
|
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],
|
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"
|
413
|
-
|
414
|
-
when "
|
415
|
-
|
416
|
-
when "
|
417
|
-
|
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(
|
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:
|
577
|
+
error_message: error.message,
|
428
578
|
idempotency_key: context.idempotency_key,
|
429
579
|
request_id: context.request_id)
|
430
580
|
|
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."
|
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,
|
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(
|
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
|
-
|
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,
|
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,
|
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:
|
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 :
|
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
|
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}
|
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
|
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
|