diff --git a/lib/StripeResource.js b/lib/StripeResource.js index c9acf3f41a..d29f186478 100644 --- a/lib/StripeResource.js +++ b/lib/StripeResource.js @@ -273,7 +273,15 @@ StripeResource.prototype = { }, // For more on when and how to retry API requests, see https://stripe.com/docs/error-handling#safely-retrying-requests-with-idempotency - _shouldRetry(res, numRetries, maxRetries) { + _shouldRetry(res, numRetries, maxRetries, error) { + if ( + error && + numRetries === 0 && + HttpClient.CONNECTION_CLOSED_ERROR_CODES.includes(error.code) + ) { + return true; + } + // Do not retry if we are out of retries. if (numRetries >= maxRetries) { return false; @@ -502,7 +510,7 @@ StripeResource.prototype = { } }) .catch((error) => { - if (this._shouldRetry(null, requestRetries, maxRetries)) { + if (this._shouldRetry(null, requestRetries, maxRetries, error)) { return retryRequest( makeRequest, apiVersion, diff --git a/lib/net/HttpClient.js b/lib/net/HttpClient.js index eca551a084..053ab0d8e7 100644 --- a/lib/net/HttpClient.js +++ b/lib/net/HttpClient.js @@ -31,6 +31,7 @@ class HttpClient { } } +HttpClient.CONNECTION_CLOSED_ERROR_CODES = ['ECONNRESET', 'EPIPE']; HttpClient.TIMEOUT_ERROR_CODE = 'ETIMEDOUT'; class HttpClientResponse { diff --git a/test/StripeResource.spec.js b/test/StripeResource.spec.js index e84937cc5f..a548bef931 100644 --- a/test/StripeResource.spec.js +++ b/test/StripeResource.spec.js @@ -356,6 +356,39 @@ describe('StripeResource', () => { ); }); + it('retries closed connection errors once', (done) => { + nock(`https://${options.host}`) + .post(options.path, options.params) + .replyWithError({ + code: 'ECONNRESET', + errno: 'ECONNRESET', + }) + .post(options.path, options.params) + .reply(200, { + id: 'ch_123', + object: 'charge', + amount: 1000, + }); + + realStripe.charges.create(options.data, (err, charge) => { + expect(charge.id).to.equal('ch_123'); + done(err); + }); + }); + + it('throws on multiple closed connection errors', (done) => { + nock(`https://${options.host}`) + .post(options.path, options.params) + .replyWithError({ code: 'ECONNRESET' }) + .post(options.path, options.params) + .replyWithError({ code: 'ECONNRESET' }); + + realStripe.charges.create(options.data, (err) => { + expect(err.detail.code).to.deep.equal('ECONNRESET'); + done(); + }); + }); + it('should retry the request if max retries are set', (done) => { nock(`https://${options.host}`) .post(options.path, options.params)