stripe 5.4.0 → 5.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3a4f568f2aa887474af9b072e52b0b97de8ec04f72ee52595682192f3c75998
4
- data.tar.gz: 7791a3d16f6b79d8e100821d241981532326527bbd805be585f20f2bb70338d8
3
+ metadata.gz: dc4c445567a38782dc843a338247615185c7f7f37a41e7fc7112ae09a9b80863
4
+ data.tar.gz: 3feccf9203d4c39fcb2abb6ceee3cae6e501d2194a91117d6802fb2ecf151f2a
5
5
  SHA512:
6
- metadata.gz: 31a50d23fc166cf97222b0e51b307d5356693d92596d56ab1bdefb885f8c9de941567d2c38102dca0ce1ae8f92a38a6ccb36f52b98293ed16b1c944c5d298a45
7
- data.tar.gz: bf4c8207a3f9c6a4ec955309fd54f235002edb9e82a7a313d3c5dff0f7ff83f0b57bfb683c77ac693a3132977dd6c7ddf48f58176c985c1a34b6b9bfc072f806
6
+ metadata.gz: bb4acdf7e1d6c198f93b383fa6963a1222fcfc40fc3893ce005978e350a48995a2cd8c2507ac957284406a36eaeaa018db5b29d53092269b5dba1302dddfcc7f
7
+ data.tar.gz: 56f2cb939da176b60462370323bd5aff987b6d7ab2d12965833843d3fd8ad81bf9f1cd9f4ae65ebda1340a6c9eeba1a634d77ccaeff41e437abf3660c081dcb5
data/.rubocop.yml CHANGED
@@ -4,6 +4,12 @@ AllCops:
4
4
  DisplayCopNames: true
5
5
  TargetRubyVersion: 2.3
6
6
 
7
+ Exclude:
8
+ # brandur: Exclude ephmeral script-like files that I use to try and
9
+ # reproduce problems with the library. If you know of a better way of doing
10
+ # this (e.g. exclude files not tracked by Git), feel free to change it.
11
+ - "example_*"
12
+
7
13
  Layout/CaseIndentation:
8
14
  EnforcedStyle: end
9
15
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.7.1 - 2019-10-15
4
+ * [#869](https://github.com/stripe/stripe-ruby/pull/869) Fixes the misnamed `connection_base=` setter to be named `connect_base=`
5
+
6
+ ## 5.7.0 - 2019-10-10
7
+ * [#865](https://github.com/stripe/stripe-ruby/pull/865) Support backwards pagination with list's `#auto_paging_each`
8
+
9
+ ## 5.6.0 - 2019-10-04
10
+ * [#861](https://github.com/stripe/stripe-ruby/pull/861) Nicer error when specifying non-nil non-string opt value
11
+
12
+ ## 5.5.0 - 2019-10-03
13
+ * [#859](https://github.com/stripe/stripe-ruby/pull/859) User-friendly messages and retries for `EOFError`, `Errno::ECONNRESET`, `Errno::ETIMEDOUT`, and `Errno::EHOSTUNREACH` network errors
14
+
15
+ ## 5.4.1 - 2019-10-01
16
+ * [#858](https://github.com/stripe/stripe-ruby/pull/858) Drop Timecop dependency
17
+
3
18
  ## 5.4.0 - 2019-10-01
4
19
  * [#857](https://github.com/stripe/stripe-ruby/pull/857) Move to monotonic time for duration calculations
5
20
 
data/Gemfile CHANGED
@@ -11,7 +11,6 @@ group :development do
11
11
  gem "rake"
12
12
  gem "shoulda-context"
13
13
  gem "test-unit"
14
- gem "timecop"
15
14
  gem "webmock"
16
15
 
17
16
  # Rubocop changes pretty quickly: new cops get added and old cops change
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.4.0
1
+ 5.7.1
data/lib/stripe.rb CHANGED
@@ -139,8 +139,8 @@ module Stripe
139
139
  end
140
140
  end
141
141
 
142
- def self.connection_base=(connection_base)
143
- @connection_base = connection_base
142
+ def self.connect_base=(connect_base)
143
+ @connect_base = connect_base
144
144
  StripeClient.clear_all_connection_managers
145
145
  end
146
146
 
@@ -11,12 +11,7 @@ module Stripe
11
11
 
12
12
  # set filters so that we can fetch the same limit, expansions, and
13
13
  # predicates when accessing the next and previous pages
14
- #
15
- # just for general cleanliness, remove any paging options
16
14
  obj.filters = filters.dup
17
- obj.filters.delete(:ending_before)
18
- obj.filters.delete(:starting_after)
19
-
20
15
  obj
21
16
  end
22
17
  end
@@ -8,6 +8,8 @@ module Stripe
8
8
  warn_on_opts_in_params(params)
9
9
 
10
10
  opts = Util.normalize_opts(opts)
11
+ error_on_non_string_user_opts(opts)
12
+
11
13
  opts[:client] ||= StripeClient.active_client
12
14
 
13
15
  headers = opts.clone
@@ -31,10 +33,24 @@ module Stripe
31
33
  [resp, opts_to_persist]
32
34
  end
33
35
 
36
+ private def error_on_non_string_user_opts(opts)
37
+ Util::OPTS_USER_SPECIFIED.each do |opt|
38
+ next unless opts.key?(opt)
39
+
40
+ val = opts[opt]
41
+ next if val.nil?
42
+ next if val.is_a?(String)
43
+
44
+ raise ArgumentError,
45
+ "request option '#{opt}' should be a string value " \
46
+ "(was a #{val.class})"
47
+ end
48
+ end
49
+
34
50
  private def warn_on_opts_in_params(params)
35
51
  Util::OPTS_USER_SPECIFIED.each do |opt|
36
52
  if params.key?(opt)
37
- warn("WARNING: #{opt} should be in opts instead of params.")
53
+ warn("WARNING: '#{opt}' should be in opts instead of params.")
38
54
  end
39
55
  end
40
56
  end
@@ -51,6 +51,16 @@ module Stripe
51
51
  # Iterates through each resource in all pages, making additional fetches to
52
52
  # the API as necessary.
53
53
  #
54
+ # The default iteration direction is forwards according to Stripe's API
55
+ # "natural" ordering direction -- newer objects first, and moving towards
56
+ # older objects.
57
+ #
58
+ # However, if the initial list object was fetched using an `ending_before`
59
+ # cursor (and only `ending_before`, `starting_after` cannot also be
60
+ # included), the method assumes that the user is trying to iterate
61
+ # backwards compared to natural ordering and returns results that way --
62
+ # older objects first, and moving towards newer objects.
63
+ #
54
64
  # Note that this method will make as many API calls as necessary to fetch
55
65
  # all resources. For more granular control, please see +each+ and
56
66
  # +next_page+.
@@ -59,8 +69,18 @@ module Stripe
59
69
 
60
70
  page = self
61
71
  loop do
62
- page.each(&blk)
63
- page = page.next_page
72
+ # Backward iterating activates if we have an `ending_before` constraint
73
+ # and _just_ an `ending_before` constraint. If `starting_after` was
74
+ # also used, we iterate forwards normally.
75
+ if filters.include?(:ending_before) &&
76
+ !filters.include?(:starting_after)
77
+ page.reverse_each(&blk)
78
+ page = page.previous_page
79
+ else
80
+ page.each(&blk)
81
+ page = page.next_page
82
+ end
83
+
64
84
  break if page.empty?
65
85
  end
66
86
  end
@@ -96,6 +116,8 @@ module Stripe
96
116
  # This method will try to respect the limit of the current page. If none
97
117
  # was given, the default limit will be fetched again.
98
118
  def previous_page(params = {}, opts = {})
119
+ return self.class.empty_list(opts) unless has_more
120
+
99
121
  first_id = data.first.id
100
122
 
101
123
  params = filters.merge(ending_before: first_id).merge(params)
@@ -107,5 +129,11 @@ module Stripe
107
129
  url ||
108
130
  raise(ArgumentError, "List object does not contain a 'url' field.")
109
131
  end
132
+
133
+ # Iterates through each resource in the page represented by the current
134
+ # `ListObject` in reverse.
135
+ def reverse_each(&blk)
136
+ data.reverse_each(&blk)
137
+ end
110
138
  end
111
139
  end
@@ -81,17 +81,17 @@ module Stripe
81
81
  def self.should_retry?(error, method:, num_retries:)
82
82
  return false if num_retries >= Stripe.max_network_retries
83
83
 
84
- # Retry on timeout-related problems (either on open or read).
85
- return true if error.is_a?(Net::OpenTimeout)
86
- return true if error.is_a?(Net::ReadTimeout)
87
-
88
- # Destination refused the connection, the connection was reset, or a
89
- # variety of other connection failures. This could occur from a single
90
- # saturated server, so retry in case it's intermittent.
91
- return true if error.is_a?(Errno::ECONNREFUSED)
92
- return true if error.is_a?(SocketError)
93
-
94
- if error.is_a?(Stripe::StripeError)
84
+ case error
85
+ when Net::OpenTimeout, Net::ReadTimeout
86
+ # Retry on timeout-related problems (either on open or read).
87
+ true
88
+ when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET,
89
+ Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError
90
+ # Destination refused the connection, the connection was reset, or a
91
+ # variety of other connection failures. This could occur from a single
92
+ # saturated server, so retry in case it's intermittent.
93
+ true
94
+ when Stripe::StripeError
95
95
  # The API may ask us not to retry (e.g. if doing so would be a no-op),
96
96
  # or advise us to retry (e.g. in cases of lock timeouts). Defer to
97
97
  # those instructions if given.
@@ -119,10 +119,10 @@ module Stripe
119
119
  return true if error.http_status == 500 && method != :post
120
120
 
121
121
  # 503 Service Unavailable
122
- return true if error.http_status == 503
122
+ error.http_status == 503
123
+ else
124
+ false
123
125
  end
124
-
125
- false
126
126
  end
127
127
 
128
128
  def self.sleep_time(num_retries)
@@ -296,7 +296,11 @@ module Stripe
296
296
  # The original error message is also appended onto the final exception for
297
297
  # full transparency.
298
298
  NETWORK_ERROR_MESSAGES_MAP = {
299
+ EOFError => ERROR_MESSAGE_CONNECTION,
299
300
  Errno::ECONNREFUSED => ERROR_MESSAGE_CONNECTION,
301
+ Errno::ECONNRESET => ERROR_MESSAGE_CONNECTION,
302
+ Errno::EHOSTUNREACH => ERROR_MESSAGE_CONNECTION,
303
+ Errno::ETIMEDOUT => ERROR_MESSAGE_TIMEOUT_CONNECT,
300
304
  SocketError => ERROR_MESSAGE_CONNECTION,
301
305
 
302
306
  Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT,
data/lib/stripe/util.rb CHANGED
@@ -178,9 +178,8 @@ module Stripe
178
178
  # to calculate an elapsed duration.
179
179
  #
180
180
  # Shortcut for getting monotonic time, mostly for purposes of line length
181
- # and stubbing (Timecop doesn't freeze the monotonic clock). Returns time
182
- # in seconds since the event used for monotonic reference purposes by the
183
- # platform (e.g. system boot time).
181
+ # and test stubbing. Returns time in seconds since the event used for
182
+ # monotonic reference purposes by the platform (e.g. system boot time).
184
183
  def self.monotonic_time
185
184
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
186
185
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stripe
4
- VERSION = "5.4.0"
4
+ VERSION = "5.7.1"
5
5
  end
@@ -227,6 +227,23 @@ module Stripe
227
227
  end
228
228
  end
229
229
 
230
+ should "error if a user-specified opt is given a non-nil non-string value" do
231
+ stub_request(:post, "#{Stripe.api_base}/v1/charges")
232
+ .to_return(body: JSON.generate(charge_fixture))
233
+
234
+ # Works fine if not included or a string.
235
+ Stripe::Charge.create({ amount: 100, currency: "usd" }, {})
236
+ Stripe::Charge.create({ amount: 100, currency: "usd" }, idempotency_key: "12345")
237
+
238
+ # Errors on a non-string.
239
+ e = assert_raises(ArgumentError) do
240
+ Stripe::Charge.create({ amount: 100, currency: "usd" }, idempotency_key: :foo)
241
+ end
242
+ assert_equal "request option 'idempotency_key' should be a string value " \
243
+ "(was a Symbol)",
244
+ e.message
245
+ end
246
+
230
247
  should "requesting with a unicode ID should result in a request" do
231
248
  stub_request(:get, "#{Stripe.api_base}/v1/customers/%E2%98%83")
232
249
  .to_return(body: JSON.generate(make_missing_id_error), status: 404)
@@ -25,20 +25,80 @@ module Stripe
25
25
  assert_equal expected, list.each.to_a
26
26
  end
27
27
 
28
- should "provide #auto_paging_each" do
28
+ should "provide #reverse_each" do
29
29
  arr = [
30
30
  { id: 1 },
31
31
  { id: 2 },
32
32
  { id: 3 },
33
33
  ]
34
+ expected = Util.convert_to_stripe_object(arr.reverse, {})
35
+ list = Stripe::ListObject.construct_from(data: arr)
36
+ assert_equal expected, list.reverse_each.to_a
37
+ end
38
+
39
+ should "provide #auto_paging_each that supports forward pagination" do
40
+ arr = [
41
+ { id: 1 },
42
+ { id: 2 },
43
+ { id: 3 },
44
+ { id: 4 },
45
+ { id: 5 },
46
+ { id: 6 },
47
+ ]
34
48
  expected = Util.convert_to_stripe_object(arr, {})
35
49
 
50
+ # Initial list object to page on. Notably, its last data element will be
51
+ # used as a cursor to fetch the next page.
36
52
  list = TestListObject.construct_from(data: [{ id: 1 }],
37
53
  has_more: true,
38
54
  url: "/things")
55
+ list.filters = { limit: 3 }
56
+
57
+ # The test will start with the synthetic list object above, and use it as
58
+ # a starting point to fetch two more pages. The second page indicates
59
+ # that there are no more elements by setting `has_more` to `false`, and
60
+ # iteration stops.
39
61
  stub_request(:get, "#{Stripe.api_base}/things")
40
- .with(query: { starting_after: "1" })
41
- .to_return(body: JSON.generate(data: [{ id: 2 }, { id: 3 }], has_more: false))
62
+ .with(query: { starting_after: "1", limit: "3" })
63
+ .to_return(body: JSON.generate(data: [{ id: 2 }, { id: 3 }, { id: 4 }], has_more: true, url: "/things"))
64
+ stub_request(:get, "#{Stripe.api_base}/things")
65
+ .with(query: { starting_after: "4", limit: "3" })
66
+ .to_return(body: JSON.generate(data: [{ id: 5 }, { id: 6 }], has_more: false, url: "/things"))
67
+
68
+ assert_equal expected, list.auto_paging_each.to_a
69
+ end
70
+
71
+ should "provide #auto_paging_each that supports backward pagination with `ending_before`" do
72
+ arr = [
73
+ { id: 6 },
74
+ { id: 5 },
75
+ { id: 4 },
76
+ { id: 3 },
77
+ { id: 2 },
78
+ { id: 1 },
79
+ ]
80
+ expected = Util.convert_to_stripe_object(arr, {})
81
+
82
+ # Initial list object to page on. Notably, its first data element will be
83
+ # used as a cursor to fetch the next page.
84
+ list = TestListObject.construct_from(data: [{ id: 6 }],
85
+ has_more: true,
86
+ url: "/things")
87
+
88
+ # We also add an `ending_before` filter on the list to simulate backwards
89
+ # pagination.
90
+ list.filters = { ending_before: 7, limit: 3 }
91
+
92
+ # The test will start with the synthetic list object above, and use it as
93
+ # a starting point to fetch two more pages. The second page indicates
94
+ # that there are no more elements by setting `has_more` to `false`, and
95
+ # iteration stops.
96
+ stub_request(:get, "#{Stripe.api_base}/things")
97
+ .with(query: { ending_before: "6", limit: "3" })
98
+ .to_return(body: JSON.generate(data: [{ id: 3 }, { id: 4 }, { id: 5 }], has_more: true, url: "/things"))
99
+ stub_request(:get, "#{Stripe.api_base}/things")
100
+ .with(query: { ending_before: "3", limit: "3" })
101
+ .to_return(body: JSON.generate(data: [{ id: 1 }, { id: 2 }], has_more: false, url: "/things"))
42
102
 
43
103
  assert_equal expected, list.auto_paging_each.to_a
44
104
  end
@@ -97,7 +157,7 @@ module Stripe
97
157
  .with(query: { "expand[]" => "data.source", "limit" => "3", "starting_after" => "1" })
98
158
  .to_return(body: JSON.generate(data: [{ id: 2 }], has_more: false))
99
159
  next_list = list.next_page
100
- assert_equal({ expand: ["data.source"], limit: 3 }, next_list.filters)
160
+ assert_equal({ expand: ["data.source"], limit: 3, starting_after: 1 }, next_list.filters)
101
161
  end
102
162
 
103
163
  should "fetch an empty page through #next_page" do
@@ -114,23 +174,25 @@ module Stripe
114
174
 
115
175
  should "fetch a next page through #previous_page" do
116
176
  list = TestListObject.construct_from(data: [{ id: 2 }],
177
+ has_more: true,
117
178
  url: "/things")
118
179
  stub_request(:get, "#{Stripe.api_base}/things")
119
180
  .with(query: { ending_before: "2" })
120
- .to_return(body: JSON.generate(data: [{ id: 1 }]))
181
+ .to_return(body: JSON.generate(data: [{ id: 1 }], has_more: false))
121
182
  next_list = list.previous_page
122
183
  refute next_list.empty?
123
184
  end
124
185
 
125
186
  should "fetch a next page through #previous_page and respect limit" do
126
187
  list = TestListObject.construct_from(data: [{ id: 2 }],
188
+ has_more: true,
127
189
  url: "/things")
128
190
  list.filters = { expand: ["data.source"], limit: 3 }
129
191
  stub_request(:get, "#{Stripe.api_base}/things")
130
192
  .with(query: { "expand[]" => "data.source", "limit" => "3", "ending_before" => "2" })
131
- .to_return(body: JSON.generate(data: [{ id: 1 }]))
193
+ .to_return(body: JSON.generate(data: [{ id: 1 }], has_more: false))
132
194
  next_list = list.previous_page
133
- assert_equal({ expand: ["data.source"], limit: 3 }, next_list.filters)
195
+ assert_equal({ ending_before: 2, expand: ["data.source"], limit: 3 }, next_list.filters)
134
196
  end
135
197
  end
136
198
  end
@@ -172,6 +172,26 @@ module Stripe
172
172
  method: :post, num_retries: 0)
173
173
  end
174
174
 
175
+ should "retry on EOFError" do
176
+ assert StripeClient.should_retry?(EOFError.new,
177
+ method: :post, num_retries: 0)
178
+ end
179
+
180
+ should "retry on Errno::ECONNRESET" do
181
+ assert StripeClient.should_retry?(Errno::ECONNRESET.new,
182
+ method: :post, num_retries: 0)
183
+ end
184
+
185
+ should "retry on Errno::ETIMEDOUT" do
186
+ assert StripeClient.should_retry?(Errno::ETIMEDOUT.new,
187
+ method: :post, num_retries: 0)
188
+ end
189
+
190
+ should "retry on Errno::EHOSTUNREACH" do
191
+ assert StripeClient.should_retry?(Errno::EHOSTUNREACH.new,
192
+ method: :post, num_retries: 0)
193
+ end
194
+
175
195
  should "retry on Net::OpenTimeout" do
176
196
  assert StripeClient.should_retry?(Net::OpenTimeout.new,
177
197
  method: :post, num_retries: 0)
@@ -316,11 +336,11 @@ module Stripe
316
336
 
317
337
  context "logging" do
318
338
  setup do
319
- # Freeze time for the purposes of the `elapsed` parameter that we emit
320
- # for responses. Mocha's `anything` parameter can't match inside of a
321
- # hash and is therefore not useful for this purpose, and Timecop
322
- # doesn't freeze monotonic time. If we switch over to rspec-mocks at
323
- # some point, we can probably remove Timecop from the project.
339
+ # Freeze time for the purposes of the `elapsed` parameter that we
340
+ # emit for responses. Mocha's `anything` parameter can't match inside
341
+ # of a hash and is therefore not useful for this purpose. If we
342
+ # switch over to rspec-mocks at some point, we can probably remove
343
+ # this.
324
344
  Util.stubs(:monotonic_time).returns(0.0)
325
345
  end
326
346
 
data/test/test_helper.rb CHANGED
@@ -8,7 +8,6 @@ require "test/unit"
8
8
  require "mocha/setup"
9
9
  require "stringio"
10
10
  require "shoulda/context"
11
- require "timecop"
12
11
  require "webmock/test_unit"
13
12
 
14
13
  PROJECT_ROOT = ::File.expand_path("../", __dir__)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.4.0
4
+ version: 5.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-01 00:00:00.000000000 Z
11
+ date: 2019-10-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Stripe is the easiest way to accept payments online. See https://stripe.com
14
14
  for details.
@@ -246,7 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
246
  - !ruby/object:Gem::Version
247
247
  version: '0'
248
248
  requirements: []
249
- rubygems_version: 3.0.3
249
+ rubygems_version: 3.0.6
250
250
  signing_key:
251
251
  specification_version: 4
252
252
  summary: Ruby bindings for the Stripe API