stripe 5.4.0 → 5.7.1

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