stripe 3.0.3 → 3.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +23 -0
  3. data/Gemfile +1 -2
  4. data/History.txt +4 -0
  5. data/README.md +28 -3
  6. data/Rakefile +0 -12
  7. data/VERSION +1 -1
  8. data/lib/stripe.rb +24 -0
  9. data/lib/stripe/stripe_client.rb +166 -19
  10. data/lib/stripe/util.rb +127 -8
  11. data/lib/stripe/version.rb +1 -1
  12. data/test/api_stub_helpers.rb +0 -125
  13. data/test/stripe/account_test.rb +11 -13
  14. data/test/stripe/alipay_account_test.rb +2 -4
  15. data/test/stripe/api_resource_test.rb +112 -76
  16. data/test/stripe/apple_pay_domain_test.rb +4 -6
  17. data/test/stripe/application_fee_refund_test.rb +2 -5
  18. data/test/stripe/application_fee_test.rb +0 -2
  19. data/test/stripe/bank_account_test.rb +8 -13
  20. data/test/stripe/bitcoin_receiver_test.rb +13 -16
  21. data/test/stripe/bitcoin_transaction_test.rb +2 -4
  22. data/test/stripe/charge_test.rb +9 -11
  23. data/test/stripe/country_spec_test.rb +2 -4
  24. data/test/stripe/coupon_test.rb +6 -8
  25. data/test/stripe/customer_card_test.rb +13 -9
  26. data/test/stripe/customer_test.rb +16 -18
  27. data/test/stripe/dispute_test.rb +8 -10
  28. data/test/stripe/file_upload_test.rb +3 -3
  29. data/test/stripe/invoice_item_test.rb +9 -11
  30. data/test/stripe/invoice_line_item_test.rb +0 -1
  31. data/test/stripe/invoice_test.rb +39 -21
  32. data/test/stripe/list_object_test.rb +1 -1
  33. data/test/stripe/login_link_test.rb +6 -7
  34. data/test/stripe/order_return_test.rb +2 -4
  35. data/test/stripe/order_test.rb +10 -12
  36. data/test/stripe/payout_test.rb +7 -9
  37. data/test/stripe/plan_test.rb +8 -10
  38. data/test/stripe/product_test.rb +8 -10
  39. data/test/stripe/recipient_card_test.rb +13 -9
  40. data/test/stripe/recipient_test.rb +8 -10
  41. data/test/stripe/refund_test.rb +7 -9
  42. data/test/stripe/reversal_test.rb +5 -7
  43. data/test/stripe/sku_test.rb +9 -11
  44. data/test/stripe/source_test.rb +16 -15
  45. data/test/stripe/stripe_client_test.rb +190 -26
  46. data/test/stripe/subscription_item_test.rb +12 -14
  47. data/test/stripe/subscription_test.rb +10 -12
  48. data/test/stripe/three_d_secure_test.rb +3 -5
  49. data/test/stripe/transfer_test.rb +7 -9
  50. data/test/stripe/util_test.rb +164 -0
  51. data/test/test_helper.rb +33 -18
  52. metadata +2 -8
  53. data/openapi/fixtures.json +0 -1896
  54. data/openapi/fixtures.yaml +0 -1505
  55. data/openapi/spec2.json +0 -24601
  56. data/openapi/spec2.yaml +0 -18801
  57. data/test/api_fixtures.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71ad66341e1b3896e85749e3f18870b734386a86
4
- data.tar.gz: 67175bb4549ab20ca69f38016e9f7d96aedaa817
3
+ metadata.gz: 4e0d93e7805ba65b7f5dcfe753298a8be427fc1d
4
+ data.tar.gz: 3a59dcb60834e656c55f37368c8f957bb9d23486
5
5
  SHA512:
6
- metadata.gz: f740ee89ea7c34c45944dc299ba346ad715c96b736a05dc6ad2f13337125656d049ff16a33671de454d5e2511e98e7431ae58caf275c934d957e3f608cb7df91
7
- data.tar.gz: 04d28b541238aba0ce44820f5b1daf38a4a798892764d1d18e565d5d688b0d627895b037152ad976cbec982f1277dc6b7475bd5589ef152d6e7cbbebbb77b428
6
+ metadata.gz: 5a769766ed97097ae745300710ad092bc6646ec85d98f70686ffe71758159667e67f7758c6550a00ac74269978562a2dcf44162bc681960fb59c67ffbc04a525
7
+ data.tar.gz: 2c6782650e0069887f14fa28be69e135f2e4ccaeec68ce69422972cde7e8fc28e2310de86c903c88ade3017998cb9a11a68c5ffc921bf6098deb7877de5bd85e
@@ -13,3 +13,26 @@ notifications:
13
13
  on_success: never
14
14
 
15
15
  sudo: false
16
+
17
+ env:
18
+ global:
19
+ - STRIPE_MOCK_VERSION=0.1.12
20
+
21
+ cache:
22
+ directories:
23
+ - stripe-mock
24
+
25
+ before_install:
26
+ # Unpack and start stripe-mock so that the test suite can talk to it
27
+ - |
28
+ if [ ! -d "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}" ]; then
29
+ mkdir -p stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/
30
+ curl -L "https://github.com/stripe/stripe-mock/releases/download/v${STRIPE_MOCK_VERSION}/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz" -o "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz"
31
+ tar -zxf "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz" -C "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/"
32
+ fi
33
+ - |
34
+ stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/stripe-mock > /dev/null &
35
+ STRIPE_MOCK_PID=$!
36
+
37
+ script:
38
+ - bundle exec rake
data/Gemfile CHANGED
@@ -3,12 +3,11 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  group :development do
6
- gem 'committee', '2.0.0.pre6'
7
6
  gem 'mocha', '~> 0.13.2'
8
7
  gem 'rake'
9
8
  gem 'shoulda-context'
10
- gem 'sinatra'
11
9
  gem 'test-unit'
10
+ gem 'timecop'
12
11
  gem 'webmock'
13
12
 
14
13
  # Rack 2.0+ requires Ruby >= 2.2.2 which is problematic for the test suite on
@@ -1,3 +1,7 @@
1
+ === 3.1.0 2017-08-03
2
+
3
+ * Implement request logging with `Stripe.log_level` and `STRIPE_LOG`
4
+
1
5
  === 3.0.3 2017-07-28
2
6
 
3
7
  * Revert `nil` to empty string coercion from 3.0.2
data/README.md CHANGED
@@ -135,6 +135,24 @@ Please take care to set conservative read timeouts. Some API requests can take
135
135
  some time, and a short timeout increases the likelihood of a problem within our
136
136
  servers.
137
137
 
138
+ ### Logging
139
+
140
+ The library can be configured to emit logging that will give you better insight
141
+ into what it's doing. The `info` logging level is usually most appropriate for
142
+ production use, but `debug` is also available for more verbosity.
143
+
144
+ There are a few options for enabling it:
145
+
146
+ 1. Set the environment variable `STRIPE_LOG` to the value `debug` or `info`:
147
+ ```
148
+ $ export STRIPE_LOG=info
149
+ ```
150
+
151
+ 2. Set `Stripe.log_level`:
152
+ ``` ruby
153
+ Stripe.log_level = "info"
154
+ ```
155
+
138
156
  ### Writing a Plugin
139
157
 
140
158
  If you're writing a plugin that uses the library, we'd appreciate it if you
@@ -147,6 +165,13 @@ API.
147
165
 
148
166
  ## Development
149
167
 
168
+ The test suite depends on [stripe-mock], so make sure to fetch and run it from a
169
+ background terminal ([stripe-mock's README][stripe-mock] also contains
170
+ instructions for installing via Homebrew and other methods):
171
+
172
+ go get -u github.com/stripe/stripe-mock
173
+ stripe-mock
174
+
150
175
  Run all tests:
151
176
 
152
177
  bundle exec rake
@@ -163,15 +188,15 @@ Update bundled CA certificates from the [Mozilla cURL release][curl]:
163
188
 
164
189
  bundle exec rake update_certs
165
190
 
166
- Update bundled OpenAPI specification from the canonical repository:
167
-
168
- bundle exec rake update_openapi
191
+ Update the bundled [stripe-mock] by editing the version number found in
192
+ `.travis.yml`.
169
193
 
170
194
  [api-keys]: https://dashboard.stripe.com/account/apikeys
171
195
  [connect]: https://stripe.com/connect
172
196
  [curl]: http://curl.haxx.se/docs/caextract.html
173
197
  [faraday]: https://github.com/lostisland/faraday
174
198
  [idempotency-keys]: https://stripe.com/docs/api/ruby#idempotent_requests
199
+ [stripe-mock]: https://github.com/stripe/stripe-mock
175
200
 
176
201
  <!--
177
202
  # vim: set tw=79:
data/Rakefile CHANGED
@@ -14,18 +14,6 @@ task :update_certs do
14
14
  File.expand_path("../lib/data/ca-certificates.crt", __FILE__)
15
15
  end
16
16
 
17
- desc "Update OpenAPI specification"
18
- task :update_openapi do
19
- require "faraday"
20
-
21
- ["fixtures.json", "fixtures.yaml", "spec2.json", "spec2.yaml"].map { |file|
22
- Thread.new do
23
- fetch_file "https://raw.githubusercontent.com/stripe/openapi/master/openapi/#{file}",
24
- File.expand_path("../openapi/#{file}", __FILE__)
25
- end
26
- }.map { |t| t.join }
27
- end
28
-
29
17
  #
30
18
  # helpers
31
19
  #
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.3
1
+ 3.1.0
@@ -83,6 +83,8 @@ module Stripe
83
83
  @connect_base = 'https://connect.stripe.com'
84
84
  @uploads_base = 'https://uploads.stripe.com'
85
85
 
86
+ @log_level = nil
87
+
86
88
  @max_network_retries = 0
87
89
  @max_network_retry_delay = 2
88
90
  @initial_network_retry_delay = 0.5
@@ -142,6 +144,24 @@ module Stripe
142
144
  end
143
145
  end
144
146
 
147
+ LEVEL_DEBUG = "debug"
148
+ LEVEL_INFO = "info"
149
+
150
+ # When set prompts the library to log some extra information to $stdout about
151
+ # what it's doing. For example, it'll produce information about requests,
152
+ # responses, and errors that are received. Valid log levels are `debug` and
153
+ # `info`, with `debug` being a little more verbose in places.
154
+ def self.log_level
155
+ @log_level
156
+ end
157
+
158
+ def self.log_level=(val)
159
+ if val != nil && ![LEVEL_DEBUG, LEVEL_INFO].include?(val)
160
+ raise ArgumentError, "log_level should only be set to `nil`, `debug` or `info`"
161
+ end
162
+ @log_level = val
163
+ end
164
+
145
165
  def self.max_network_retries
146
166
  @max_network_retries
147
167
  end
@@ -174,3 +194,7 @@ module Stripe
174
194
  deprecate :uri_encode, "Stripe::Util#encode_parameters", 2016, 01
175
195
  end
176
196
  end
197
+
198
+ unless ENV["STRIPE_LOG"].nil?
199
+ Stripe.log_level = ENV["STRIPE_LOG"]
200
+ end
@@ -109,7 +109,7 @@ module Stripe
109
109
  end
110
110
  end
111
111
 
112
- def execute_request(method, url,
112
+ def execute_request(method, path,
113
113
  api_base: nil, api_key: nil, headers: {}, params: {})
114
114
 
115
115
  api_base ||= Stripe.api_base
@@ -118,7 +118,7 @@ module Stripe
118
118
  check_api_key!(api_key)
119
119
 
120
120
  params = Util.objects_to_ids(params)
121
- url = api_url(url, api_base)
121
+ url = api_url(path, api_base)
122
122
 
123
123
  case method.to_s.downcase.to_sym
124
124
  when :get, :head, :delete
@@ -133,14 +133,22 @@ module Stripe
133
133
  end
134
134
  end
135
135
 
136
- http_resp = execute_request_with_rescues(api_base, 0) do
137
- conn.run_request(
138
- method,
139
- url,
140
- payload,
141
- # TODO: Convert RestClient-style parameters.
142
- request_headers(api_key, method).update(headers)
143
- ) do |req|
136
+ headers = request_headers(api_key, method).
137
+ update(Util.normalize_headers(headers))
138
+
139
+ # stores information on the request we're about to make so that we don't
140
+ # have to pass as many parameters around for logging.
141
+ context = RequestLogContext.new(
142
+ api_key: api_key,
143
+ api_version: headers["Stripe-Version"] || Stripe.api_version,
144
+ idempotency_key: headers["Idempotency-Key"],
145
+ method: method,
146
+ path: path,
147
+ payload: payload,
148
+ )
149
+
150
+ http_resp = execute_request_with_rescues(api_base, context) do
151
+ conn.run_request(method, url, payload, headers) do |req|
144
152
  req.options.open_timeout = Stripe.open_timeout
145
153
  req.options.timeout = Stripe.read_timeout
146
154
  end
@@ -180,14 +188,31 @@ module Stripe
180
188
  end
181
189
  end
182
190
 
183
- def execute_request_with_rescues(api_base, retry_count, &block)
191
+ def execute_request_with_rescues(api_base, context, &block)
192
+ retry_count = 0
184
193
  begin
194
+ request_start = Time.now
195
+ log_request(context)
185
196
  resp = block.call
197
+ context = context.dup_from_response(resp)
198
+ log_response(context, request_start, resp.status, resp.body)
186
199
 
187
200
  # We rescue all exceptions from a request so that we have an easy spot to
188
201
  # implement our retry logic across the board. We'll re-raise if it's a type
189
202
  # of exception that we didn't expect to handle.
190
203
  rescue => e
204
+ # If we modify context we copy it into a new variable so as not to
205
+ # taint the original on a retry.
206
+ error_context = context
207
+
208
+ if e.respond_to?(:response) && e.response
209
+ error_context = context.dup_from_response(e.response)
210
+ log_response(error_context, request_start,
211
+ e.response[:status], e.response[:body])
212
+ else
213
+ log_response_error(error_context, request_start, e)
214
+ end
215
+
191
216
  if self.class.should_retry?(e, retry_count)
192
217
  retry_count = retry_count + 1
193
218
  sleep self.class.sleep_time(retry_count)
@@ -197,9 +222,9 @@ module Stripe
197
222
  case e
198
223
  when Faraday::ClientError
199
224
  if e.response
200
- handle_error_response(e.response)
225
+ handle_error_response(e.response, error_context)
201
226
  else
202
- handle_network_error(e, retry_count, api_base)
227
+ handle_network_error(e, error_context, retry_count, api_base)
203
228
  end
204
229
 
205
230
  # Only handle errors when we know we can do so, and re-raise otherwise.
@@ -229,7 +254,7 @@ module Stripe
229
254
  str
230
255
  end
231
256
 
232
- def handle_error_response(http_resp)
257
+ def handle_error_response(http_resp, context)
233
258
  begin
234
259
  resp = StripeResponse.from_faraday_hash(http_resp)
235
260
  error_data = resp.data[:error]
@@ -243,16 +268,26 @@ module Stripe
243
268
  end
244
269
 
245
270
  if error_data.is_a?(String)
246
- error = specific_oauth_error(resp, error_data)
271
+ error = specific_oauth_error(resp, error_data, context)
247
272
  else
248
- error = specific_api_error(resp, error_data)
273
+ error = specific_api_error(resp, error_data, context)
249
274
  end
250
275
 
251
276
  error.response = resp
252
277
  raise(error)
253
278
  end
254
279
 
255
- def specific_api_error(resp, error_data)
280
+ def specific_api_error(resp, error_data, context)
281
+ Util.log_info('Stripe API error',
282
+ status: resp.http_status,
283
+ error_code: error_data['code'],
284
+ error_message: error_data['message'],
285
+ error_param: error_data['param'],
286
+ error_type: error_data['type'],
287
+ idempotency_key: context.idempotency_key,
288
+ request_id: context.request_id
289
+ )
290
+
256
291
  case resp.http_status
257
292
  when 400, 404
258
293
  error = InvalidRequestError.new(
@@ -297,9 +332,17 @@ module Stripe
297
332
 
298
333
  # Attempts to look at a response's error code and return an OAuth error if
299
334
  # one matches. Will return `nil` if the code isn't recognized.
300
- def specific_oauth_error(resp, error_code)
335
+ def specific_oauth_error(resp, error_code, context)
301
336
  description = resp.data[:error_description] || error_code
302
337
 
338
+ Util.log_info('Stripe OAuth error',
339
+ status: resp.http_status,
340
+ error_code: error_code,
341
+ error_description: description,
342
+ idempotency_key: context.idempotency_key,
343
+ request_id: context.request_id
344
+ )
345
+
303
346
  args = [error_code, description, {
304
347
  http_status: resp.http_status, http_body: resp.http_body,
305
348
  json_body: resp.data, http_headers: resp.http_headers
@@ -319,7 +362,13 @@ module Stripe
319
362
  end
320
363
  end
321
364
 
322
- def handle_network_error(e, retry_count, api_base=nil)
365
+ def handle_network_error(e, context, retry_count, api_base=nil)
366
+ Util.log_info('Stripe OAuth error',
367
+ error_message: e.message,
368
+ idempotency_key: context.idempotency_key,
369
+ request_id: context.request_id
370
+ )
371
+
323
372
  case e
324
373
  when Faraday::ConnectionFailed
325
374
  message = "Unexpected error communicating when trying to connect to Stripe. " \
@@ -388,6 +437,104 @@ module Stripe
388
437
  headers
389
438
  end
390
439
 
440
+ def log_request(context)
441
+ Util.log_info("Request to Stripe API",
442
+ api_version: context.api_version,
443
+ idempotency_key: context.idempotency_key,
444
+ method: context.method,
445
+ path: context.path
446
+ )
447
+ Util.log_debug("Request details",
448
+ body: context.payload,
449
+ idempotency_key: context.idempotency_key
450
+ )
451
+ end
452
+ private :log_request
453
+
454
+ def log_response(context, request_start, status, body)
455
+ Util.log_info("Response from Stripe API",
456
+ api_version: context.api_version,
457
+ elapsed: Time.now - request_start,
458
+ idempotency_key: context.idempotency_key,
459
+ method: context.method,
460
+ path: context.path,
461
+ request_id: context.request_id,
462
+ status: status
463
+ )
464
+ Util.log_debug("Response details",
465
+ body: body,
466
+ idempotency_key: context.idempotency_key,
467
+ request_id: context.request_id,
468
+ )
469
+ if context.request_id
470
+ Util.log_debug("Dashboard link for request",
471
+ idempotency_key: context.idempotency_key,
472
+ request_id: context.request_id,
473
+ url: Util.request_id_dashboard_url(context.request_id, context.api_key)
474
+ )
475
+ end
476
+ end
477
+ private :log_response
478
+
479
+ def log_response_error(context, request_start, e)
480
+ Util.log_info("Request error",
481
+ elapsed: Time.now - request_start,
482
+ error_message: e.message,
483
+ idempotency_key: context.idempotency_key,
484
+ method: context.method,
485
+ path: context.path,
486
+ )
487
+ end
488
+ private :log_response_error
489
+
490
+ # RequestLogContext stores information about a request that's begin made so
491
+ # that we can log certain information. It's useful because it means that we
492
+ # don't have to pass around as many parameters.
493
+ class RequestLogContext
494
+ attr_accessor :api_key
495
+ attr_accessor :api_version
496
+ attr_accessor :idempotency_key
497
+ attr_accessor :method
498
+ attr_accessor :path
499
+ attr_accessor :payload
500
+ attr_accessor :request_id
501
+
502
+ def initialize(api_key: nil, api_version: nil, idempotency_key: nil,
503
+ method: nil, path: nil, payload: nil)
504
+ self.api_key = api_key
505
+ self.api_version = api_version
506
+ self.idempotency_key = idempotency_key
507
+ self.method = method
508
+ self.path = path
509
+ self.payload = payload
510
+ end
511
+
512
+ # The idea with this method is that we might want to update some of
513
+ # context information because a response that we've received from the API
514
+ # contains information that's more authoritative than what we started
515
+ # with for a request. For example, we should trust whatever came back in
516
+ # a `Stripe-Version` header beyond what configuration information that we
517
+ # might have had available.
518
+ def dup_from_response(resp)
519
+ return self if resp.nil?
520
+
521
+ # Faraday's API is a little unusual. Normally it'll produce a response
522
+ # object with a `headers` method, but on error what it puts into
523
+ # `e.response` is an untyped `Hash`.
524
+ headers = if resp.is_a?(Faraday::Response)
525
+ resp.headers
526
+ else
527
+ resp[:headers]
528
+ end
529
+
530
+ context = self.dup
531
+ context.api_version = headers["Stripe-Version"]
532
+ context.idempotency_key = headers["Idempotency-Key"]
533
+ context.request_id = headers["Request-Id"]
534
+ context
535
+ end
536
+ end
537
+
391
538
  # SystemProfiler extracts information about the system that we're running
392
539
  # in so that we can generate a rich user agent header to help debug
393
540
  # integrations.