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.
- 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
|