stripe 3.0.3 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.