@@ -18,6 +18,7 @@ StripeResource.method = require('./StripeMethod');
18
18
StripeResource . BASIC_METHODS = require ( './StripeMethod.basic' ) ;
19
19
20
20
StripeResource . MAX_BUFFERED_REQUEST_METRICS = 100 ;
21
+ const MAX_RETRY_AFTER_WAIT = 60 ;
21
22
22
23
/**
23
24
* Encapsulates request logic for a Stripe Resource
@@ -217,6 +218,7 @@ StripeResource.prototype = {
217
218
} ;
218
219
} ,
219
220
221
+ // For more on when and how to retry API requests, see https://stripe.com/docs/error-handling#safely-retrying-requests-with-idempotency
220
222
_shouldRetry ( res , numRetries ) {
221
223
// Do not retry if we are out of retries.
222
224
if ( numRetries >= this . _stripe . getMaxNetworkRetries ( ) ) {
@@ -228,21 +230,33 @@ StripeResource.prototype = {
228
230
return true ;
229
231
}
230
232
231
- // Retry on conflict and availability errors.
232
- if ( res . statusCode === 409 || res . statusCode === 503 ) {
233
+ // The API may ask us not to retry (eg; if doing so would be a no-op)
234
+ // or advise us to retry (eg; in cases of lock timeouts); we defer to that.
235
+ if ( res . headers && res . headers [ 'stripe-should-retry' ] === 'false' ) {
236
+ return false ;
237
+ }
238
+ if ( res . headers && res . headers [ 'stripe-should-retry' ] === 'true' ) {
239
+ return true ;
240
+ }
241
+
242
+ // Retry on conflict errors.
243
+ if ( res . statusCode === 409 ) {
233
244
return true ;
234
245
}
235
246
236
- // Retry on 5xx's, except POST's, which our idempotency framework
237
- // would just replay as 500's again anyway.
238
- if ( res . statusCode >= 500 && res . req . _requestEvent . method !== 'POST' ) {
247
+ // Retry on 500, 503, and other internal errors.
248
+ //
249
+ // Note that we expect the stripe-should-retry header to be false
250
+ // in most cases when a 500 is returned, since our idempotency framework
251
+ // would typically replay it anyway.
252
+ if ( res . statusCode >= 500 ) {
239
253
return true ;
240
254
}
241
255
242
256
return false ;
243
257
} ,
244
258
245
- _getSleepTimeInMS ( numRetries ) {
259
+ _getSleepTimeInMS ( numRetries , retryAfter = null ) {
246
260
const initialNetworkRetryDelay = this . _stripe . getInitialNetworkRetryDelay ( ) ;
247
261
const maxNetworkRetryDelay = this . _stripe . getMaxNetworkRetryDelay ( ) ;
248
262
@@ -261,6 +275,11 @@ StripeResource.prototype = {
261
275
// But never sleep less than the base sleep seconds.
262
276
sleepSeconds = Math . max ( initialNetworkRetryDelay , sleepSeconds ) ;
263
277
278
+ // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask.
279
+ if ( Number . isInteger ( retryAfter ) && retryAfter <= MAX_RETRY_AFTER_WAIT ) {
280
+ sleepSeconds = Math . max ( sleepSeconds , retryAfter ) ;
281
+ }
282
+
264
283
return sleepSeconds * 1000 ;
265
284
} ,
266
285
@@ -342,10 +361,16 @@ StripeResource.prototype = {
342
361
_request ( method , host , path , data , auth , options , callback ) {
343
362
let requestData ;
344
363
345
- const retryRequest = ( requestFn , apiVersion , headers , requestRetries ) => {
364
+ const retryRequest = (
365
+ requestFn ,
366
+ apiVersion ,
367
+ headers ,
368
+ requestRetries ,
369
+ retryAfter
370
+ ) => {
346
371
return setTimeout (
347
372
requestFn ,
348
- this . _getSleepTimeInMS ( requestRetries ) ,
373
+ this . _getSleepTimeInMS ( requestRetries , retryAfter ) ,
349
374
apiVersion ,
350
375
headers ,
351
376
requestRetries + 1
@@ -394,15 +419,27 @@ StripeResource.prototype = {
394
419
395
420
req . once ( 'response' , ( res ) => {
396
421
if ( this . _shouldRetry ( res , requestRetries ) ) {
397
- return retryRequest ( makeRequest , apiVersion , headers , requestRetries ) ;
422
+ return retryRequest (
423
+ makeRequest ,
424
+ apiVersion ,
425
+ headers ,
426
+ requestRetries ,
427
+ ( ( res || { } ) . headers || { } ) [ 'retry-after' ]
428
+ ) ;
398
429
} else {
399
430
return this . _responseHandler ( req , callback ) ( res ) ;
400
431
}
401
432
} ) ;
402
433
403
434
req . on ( 'error' , ( error ) => {
404
435
if ( this . _shouldRetry ( null , requestRetries ) ) {
405
- return retryRequest ( makeRequest , apiVersion , headers , requestRetries ) ;
436
+ return retryRequest (
437
+ makeRequest ,
438
+ apiVersion ,
439
+ headers ,
440
+ requestRetries ,
441
+ null
442
+ ) ;
406
443
} else {
407
444
return this . _errorHandler ( req , requestRetries , callback ) ( error ) ;
408
445
}
0 commit comments