From aa4ce141215b4d9b91a37f5a332e1645d6563794 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 25 Dec 2019 11:03:58 -0800 Subject: [PATCH 01/10] Deprecate snake_case options like stripe_account with warning messages --- lib/utils.js | 61 ++++++++++++++++------------ test/StripeResource.spec.js | 14 ++++--- test/flows.spec.js | 12 +++--- test/resources/Account.spec.js | 4 +- test/resources/Customers.spec.js | 10 ++--- test/resources/EphemeralKeys.spec.js | 4 +- test/utils.spec.js | 52 ++++++++++++++++-------- 7 files changed, 94 insertions(+), 63 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 33e2de23ca..34220b8b6a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,26 +7,30 @@ const crypto = require('crypto'); const hasOwn = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); -/** - * TODO: Remove snake case keys in a future major release - */ const OPTIONS_KEYS = [ - 'api_key', 'apiKey', - 'idempotency_key', 'idempotencyKey', - 'stripe_account', 'stripeAccount', - 'stripe_version', 'stripeVersion', 'maxNetworkRetries', 'timeout', ]; +const DEPRECATED_OPTIONS = { + api_key: 'apiKey', + idempotency_key: 'idempotencyKey', + stripe_account: 'stripeAccount', + stripe_version: 'stripeVersion', +}; +const DEPRECATED_OPTIONS_KEYS = Object.keys(DEPRECATED_OPTIONS); + const utils = (module.exports = { isOptionsHash(o) { return ( - o && typeof o === 'object' && OPTIONS_KEYS.some((prop) => hasOwn(o, prop)) + o && + typeof o === 'object' && + (OPTIONS_KEYS.some((prop) => hasOwn(o, prop)) || + DEPRECATED_OPTIONS_KEYS.some((prop) => hasOwn(o, prop))) ); }, @@ -140,28 +144,35 @@ const utils = (module.exports = { ); if (extraKeys.length) { - emitWarning( - `Invalid options found (${extraKeys.join(', ')}); ignoring.` - ); + const nonDeprecated = extraKeys.filter((key) => { + if (!DEPRECATED_OPTIONS[key]) { + return true; + } + const newParam = DEPRECATED_OPTIONS[key]; + /** + * TODO turn this into a hard error in a future major version (once we have fixed our docs). + */ + emitWarning(`'${key}' is deprecated; use '${newParam}' instead.`); + params[newParam] = params[key]; + }); + if (nonDeprecated.length) { + emitWarning( + `Invalid options found (${extraKeys.join(', ')}); ignoring.` + ); + } } - /** - * TODO: Remove snake case params in next major version - */ - if (params.apiKey || params.api_key) { - opts.auth = params.apiKey || params.api_key; + if (params.apiKey) { + opts.auth = params.apiKey; } - if (params.idempotencyKey || params.idempotency_key) { - opts.headers['Idempotency-Key'] = - params.idempotencyKey || params.idempotency_key; + if (params.idempotencyKey) { + opts.headers['Idempotency-Key'] = params.idempotencyKey; } - if (params.stripeAccount || params.stripe_account) { - opts.headers['Stripe-Account'] = - params.stripeAccount || params.stripe_account; + if (params.stripeAccount) { + opts.headers['Stripe-Account'] = params.stripeAccount; } - if (params.stripeVersion || params.stripe_version) { - opts.headers['Stripe-Version'] = - params.stripeVersion || params.stripe_version; + if (params.stripeVersion) { + opts.headers['Stripe-Version'] = params.stripeVersion; } if (Number.isInteger(params.maxNetworkRetries)) { opts.settings.maxNetworkRetries = params.maxNetworkRetries; diff --git a/test/StripeResource.spec.js b/test/StripeResource.spec.js index f51a5fddcd..b6d01bbb31 100644 --- a/test/StripeResource.spec.js +++ b/test/StripeResource.spec.js @@ -425,12 +425,14 @@ describe('StripeResource', () => { ]); }); - realStripe._setApiNumberField('maxNetworkRetries', 1); - - realStripe.charges.create(options.data, {idempotency_key: key}, () => { - expect(headers['idempotency-key']).to.equal(key); - done(); - }); + realStripe.charges.create( + options.data, + {idempotencyKey: key, maxNetworkRetries: 1}, + () => { + expect(headers['idempotency-key']).to.equal(key); + done(); + } + ); }); it('should allow the setting of network retries on a per-request basis', (done) => { diff --git a/test/flows.spec.js b/test/flows.spec.js index d148abd50e..0eb439ac37 100644 --- a/test/flows.spec.js +++ b/test/flows.spec.js @@ -349,9 +349,9 @@ describe('Flows', function() { not_a_param: 'garbage, please 400', }, { - stripe_version: apiVersion, - idempotency_key: idempotencyKey, - stripe_account: connectedAccountId, + stripeVersion: apiVersion, + idempotencyKey: idempotencyKey, + stripeAccount: connectedAccountId, } ) .then(null, () => { @@ -401,9 +401,9 @@ describe('Flows', function() { not_a_param: 'garbage, please 400', }, { - stripe_version: apiVersion, - idempotency_key: idempotencyKey, - stripe_account: connectedAccountId, + stripeVersion: apiVersion, + idempotencyKey: idempotencyKey, + stripeAccount: connectedAccountId, } ) .then(null, () => { diff --git a/test/resources/Account.spec.js b/test/resources/Account.spec.js index 783ca6662f..6ceb298405 100644 --- a/test/resources/Account.spec.js +++ b/test/resources/Account.spec.js @@ -90,10 +90,10 @@ describe('Account Resource', () => { }); it('Sends the correct request with secret key as first object', () => { - const params = {api_key: 'sk_12345678901234567890123456789012'}; + const params = {apiKey: 'sk_12345678901234567890123456789012'}; stripe.account.retrieve(params); expect(stripe.LAST_REQUEST).to.deep.equal({ - auth: params.api_key, + auth: params.apiKey, method: 'GET', url: '/v1/account', data: {}, diff --git a/test/resources/Customers.spec.js b/test/resources/Customers.spec.js index 2968084a97..73f16a104a 100644 --- a/test/resources/Customers.spec.js +++ b/test/resources/Customers.spec.js @@ -67,10 +67,10 @@ describe('Customers Resource', () => { }); }); - it('Sends the correct request [with specified idempotency_key in options]', () => { + it('Sends the correct request [with specified idempotencyKey in options]', () => { stripe.customers.create( {description: 'Some customer'}, - {idempotency_key: 'foo'} + {idempotencyKey: 'foo'} ); expect(stripe.LAST_REQUEST).to.deep.equal({ method: 'POST', @@ -84,7 +84,7 @@ describe('Customers Resource', () => { it('Sends the correct request [with specified auth in options]', () => { stripe.customers.create( {description: 'Some customer'}, - {api_key: TEST_AUTH_KEY} + {apiKey: TEST_AUTH_KEY} ); expect(stripe.LAST_REQUEST).to.deep.equal({ method: 'POST', @@ -99,7 +99,7 @@ describe('Customers Resource', () => { it('Sends the correct request [with specified auth and idempotent key in options]', () => { stripe.customers.create( {description: 'Some customer'}, - {api_key: TEST_AUTH_KEY, idempotency_key: 'foo'} + {apiKey: TEST_AUTH_KEY, idempotencyKey: 'foo'} ); expect(stripe.LAST_REQUEST).to.deep.equal({ method: 'POST', @@ -112,7 +112,7 @@ describe('Customers Resource', () => { }); it('Sends the correct request [with specified auth in options and no body]', () => { - stripe.customers.create({api_key: TEST_AUTH_KEY}); + stripe.customers.create({apiKey: TEST_AUTH_KEY}); expect(stripe.LAST_REQUEST).to.deep.equal({ method: 'POST', url: '/v1/customers', diff --git a/test/resources/EphemeralKeys.spec.js b/test/resources/EphemeralKeys.spec.js index 56b63e58a5..877856bba9 100644 --- a/test/resources/EphemeralKeys.spec.js +++ b/test/resources/EphemeralKeys.spec.js @@ -12,7 +12,7 @@ function errorsOnNoStripeVersion() { function sendsCorrectStripeVersion() { stripe.ephemeralKeys.create( {customer: 'cus_123'}, - {stripe_version: '2017-06-05'} + {stripeVersion: '2017-06-05'} ); expect(stripe.LAST_REQUEST).to.deep.equal({ @@ -33,7 +33,7 @@ describe('EphemeralKey Resource', () => { it('Sends the correct request', () => { stripe.ephemeralKeys.create( {customer: 'cus_123'}, - {stripe_version: '2017-05-25'} + {stripeVersion: '2017-05-25'} ); expect(stripe.LAST_REQUEST).to.deep.equal({ method: 'POST', diff --git a/test/utils.spec.js b/test/utils.spec.js index 7f53845b94..8ee1e7f9d3 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -310,23 +310,41 @@ describe('utils', () => { }); it('parses snake case for backwards compatibility', () => { - const args = [ - { - api_key: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', - idempotency_key: 'key', - stripe_account: 'acct_123', - stripe_version: '2019-08-08', - }, - ]; - - expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', - headers: { - 'Idempotency-Key': 'key', - 'Stripe-Version': '2019-08-08', - 'Stripe-Account': 'acct_123', - }, - settings: {}, + return new Promise((resolve, reject) => { + const args = [ + { + api_key: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', + idempotency_key: 'key', + stripe_account: 'acct_123', + stripe_version: '2019-08-08', + }, + ]; + const desiredWarnings = [ + "Stripe: 'api_key' is deprecated; use 'apiKey' instead.", + "Stripe: 'idempotency_key' is deprecated; use 'idempotencyKey' instead.", + "Stripe: 'stripe_account' is deprecated; use 'stripeAccount' instead.", + "Stripe: 'stripe_version' is deprecated; use 'stripeVersion' instead.", + ]; + + const warnings = []; + const onWarn = (message) => { + warnings.push(message); + if (warnings.length === desiredWarnings.length) { + expect(warnings).to.deep.equal(desiredWarnings); + resolve(); + } + }; + handleWarnings(() => { + expect(utils.getOptionsFromArgs(args)).to.deep.equal({ + auth: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', + headers: { + 'Idempotency-Key': 'key', + 'Stripe-Version': '2019-08-08', + 'Stripe-Account': 'acct_123', + }, + settings: {}, + }); + }, onWarn); }); }); From db320bf96c3e3d3513abea7071a6963fa7065720 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 25 Dec 2019 12:36:21 -0800 Subject: [PATCH 02/10] Remove deprecated Error base export, extend method, and populate method --- lib/Error.js | 51 ------------------------------------ test/Error.spec.js | 65 ---------------------------------------------- 2 files changed, 116 deletions(-) diff --git a/lib/Error.js b/lib/Error.js index bc9a6ad88b..f0f318c710 100644 --- a/lib/Error.js +++ b/lib/Error.js @@ -1,7 +1,5 @@ 'use strict'; -const utils = require('./utils'); - /** * StripeError is the base error from which all other more specific Stripe errors derive. * Specifically for errors returned from Stripe's REST API. @@ -9,21 +7,9 @@ const utils = require('./utils'); class StripeError extends Error { constructor(raw = {}) { super(raw.message); - // This splat is here for back-compat and should be removed in the next major version. - this.populate(...arguments); this.type = this.constructor.name; - } - /** - * DEPRECATED - * This will be inlined in the constructor in the future. - */ - populate(raw) { this.raw = raw; - if (!raw || typeof raw !== 'object') { - return; - } - this.rawType = raw.type; this.code = raw.code; this.param = raw.param; @@ -60,21 +46,6 @@ class StripeError extends Error { return new GenericError('Generic', 'Unknown Error'); } } - - /** - * DEPRECATED - * Please use class inheritance instead. - */ - static extend(options) { - const type = options.type; - class CustomError extends StripeError {} - Object.defineProperty(CustomError, 'name', { - value: type, - }); - delete options.type; - Object.assign(CustomError.prototype, options); - return CustomError; - } } // Specific Stripe Error types: @@ -145,28 +116,6 @@ class StripeIdempotencyError extends StripeError {} */ class StripeInvalidGrantError extends StripeError {} -/** - * DEPRECATED - * This is here for backwards compatibility and will be removed in the next major version. - */ -function _Error(raw) { - this.populate(...arguments); - this.stack = new Error(this.message).stack; -} -_Error.prototype = Object.create(Error.prototype); -_Error.prototype.type = 'GenericError'; -_Error.prototype.populate = function(type, message) { - this.type = type; - this.message = message; -}; -_Error.extend = utils.protoExtend; - -/** - * DEPRECATED. - * Do not use the default export; it may be removed or change in a future version. - */ -module.exports = _Error; - module.exports.StripeError = StripeError; module.exports.StripeCardError = StripeCardError; module.exports.StripeInvalidRequestError = StripeInvalidRequestError; diff --git a/test/Error.spec.js b/test/Error.spec.js index e4fb1229a0..77f0ab34ab 100644 --- a/test/Error.spec.js +++ b/test/Error.spec.js @@ -6,44 +6,6 @@ const Error = require('../lib/Error'); const expect = require('chai').expect; describe('Error', () => { - describe('Deprecated base export (DEPRECATED)', () => { - it('Populates with type and message params (DEPRECATED)', () => { - const e = new Error('FooError', 'Foo happened'); - expect(e).to.have.property('type', 'FooError'); - expect(e).to.have.property('message', 'Foo happened'); - expect(e).to.have.property('stack'); - }); - - it('can be extended via .extend method, with a weird signature (DEPRECATED)', () => { - const Custom = Error.extend({type: 'MyCustomErrorType'}); - const err = new Custom('MyOverriddenCustomErrorType', 'byaka'); - expect(err).to.be.instanceOf(Error); - expect(err).to.have.property('type', 'MyOverriddenCustomErrorType'); - expect(err).to.have.property('message', 'byaka'); - }); - - it('can be extended via .extend method, with `populate` overridden (DEPRECATED)', () => { - let populateArgs; - const Custom = Error.extend({ - type: 'MyCardError', - populate(...args) { - populateArgs = args; - this.message = 'overridden message'; - this.detail = args[0].hello; - this.customField = 'hi'; - }, - }); - const err = new Custom({hello: 'ee'}, 'foo', 'bar'); - expect(populateArgs).to.deep.equal([{hello: 'ee'}, 'foo', 'bar']); - expect(err).to.be.instanceOf(Error); - expect(err).to.have.property('type', 'MyCardError'); - expect(err).to.have.property('name', 'Error'); - expect(err).to.have.property('message', 'overridden message'); - expect(err).to.have.property('detail', 'ee'); - expect(err).to.have.property('customField', 'hi'); - }); - }); - describe('StripeError', () => { it('Generates specific instance depending on error-type', () => { expect(Error.StripeError.generate({type: 'card_error'})).to.be.instanceOf( @@ -100,32 +62,5 @@ describe('Error', () => { const err = new Foo({message: 'hi'}); expect(err).to.have.property('type', 'Foo'); }); - - it('can be extended via .extend method (DEPRECATED)', () => { - const Custom = Error.StripeError.extend({type: 'MyCustomErrorType'}); - const err = new Custom({message: 'byaka'}); - expect(err).to.be.instanceOf(Error.StripeError); - expect(err).to.have.property('type', 'MyCustomErrorType'); - expect(err).to.have.property('message', 'byaka'); - }); - - it('can create custom error via `extend` export (DEPRECATED)', () => { - let populateArgs; - const Custom = Error.StripeError.extend({ - type: 'MyCardError', - populate(...args) { - populateArgs = args; - this.detail = 'hello'; - this.customField = 'hi'; - }, - }); - const err = new Custom({message: 'ee'}, 'wat'); - expect(populateArgs).to.deep.equal([{message: 'ee'}, 'wat']); - expect(err).to.be.instanceOf(Error.StripeError); - expect(err).to.have.property('type', 'MyCardError'); - expect(err).to.have.property('message', 'ee'); - expect(err).to.have.property('detail', 'hello'); - expect(err).to.have.property('customField', 'hi'); - }); }); }); From 7a5df1dabdab30361cccfd70974a7239d12cff95 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 25 Dec 2019 12:50:00 -0800 Subject: [PATCH 03/10] Privatize several constants --- lib/stripe.js | 53 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/stripe.js b/lib/stripe.js index 6016c85e9c..caccbd3dbc 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -2,13 +2,13 @@ const resources = require('./resources'); -Stripe.DEFAULT_HOST = 'api.stripe.com'; -Stripe.DEFAULT_PORT = '443'; -Stripe.DEFAULT_BASE_PATH = '/v1/'; -Stripe.DEFAULT_API_VERSION = null; +const DEFAULT_HOST = 'api.stripe.com'; +const DEFAULT_PORT = '443'; +const DEFAULT_BASE_PATH = '/v1/'; +const DEFAULT_API_VERSION = null; // Use node's default timeout: -Stripe.DEFAULT_TIMEOUT = require('http').createServer().timeout; +const DEFAULT_TIMEOUT = require('http').createServer().timeout; Stripe.PACKAGE_VERSION = require('../package.json').version; @@ -24,8 +24,8 @@ Stripe.USER_AGENT = { Stripe.USER_AGENT_SERIALIZED = null; -Stripe.MAX_NETWORK_RETRY_DELAY_SEC = 2; -Stripe.INITIAL_NETWORK_RETRY_DELAY_SEC = 0.5; +const MAX_NETWORK_RETRY_DELAY_SEC = 2; +const INITIAL_NETWORK_RETRY_DELAY_SEC = 0.5; const APP_INFO_PROPERTIES = ['name', 'version', 'url', 'partner_id']; const ALLOWED_CONFIG_PROPERTIES = [ @@ -65,15 +65,11 @@ function Stripe(key, config = {}) { this._api = { auth: null, - host: props.host || Stripe.DEFAULT_HOST, - port: props.port || Stripe.DEFAULT_PORT, - basePath: Stripe.DEFAULT_BASE_PATH, - version: props.apiVersion || Stripe.DEFAULT_API_VERSION, - timeout: utils.validateInteger( - 'timeout', - props.timeout, - Stripe.DEFAULT_TIMEOUT - ), + host: props.host || DEFAULT_HOST, + port: props.port || DEFAULT_PORT, + basePath: DEFAULT_BASE_PATH, + version: props.apiVersion || DEFAULT_API_VERSION, + timeout: utils.validateInteger('timeout', props.timeout, DEFAULT_TIMEOUT), maxNetworkRetries: utils.validateInteger( 'maxNetworkRetries', props.maxNetworkRetries, @@ -154,10 +150,7 @@ Stripe.prototype = { }, setTimeout(timeout) { - this._setApiField( - 'timeout', - timeout == null ? Stripe.DEFAULT_TIMEOUT : timeout - ); + this._setApiField('timeout', timeout == null ? DEFAULT_TIMEOUT : timeout); }, setAppInfo(info) { @@ -217,6 +210,22 @@ Stripe.prototype = { }, getConstant: (c) => { + switch (c) { + case 'DEFAULT_HOST': + return DEFAULT_HOST; + case 'DEFAULT_PORT': + return DEFAULT_PORT; + case 'DEFAULT_BASE_PATH': + return DEFAULT_BASE_PATH; + case 'DEFAULT_API_VERSION': + return DEFAULT_API_VERSION; + case 'DEFAULT_TIMEOUT': + return DEFAULT_TIMEOUT; + case 'MAX_NETWORK_RETRY_DELAY_SEC': + return MAX_NETWORK_RETRY_DELAY_SEC; + case 'INITIAL_NETWORK_RETRY_DELAY_SEC': + return INITIAL_NETWORK_RETRY_DELAY_SEC; + } return Stripe[c]; }, @@ -243,11 +252,11 @@ Stripe.prototype = { }, getMaxNetworkRetryDelay() { - return this.getConstant('MAX_NETWORK_RETRY_DELAY_SEC'); + return MAX_NETWORK_RETRY_DELAY_SEC; }, getInitialNetworkRetryDelay() { - return this.getConstant('INITIAL_NETWORK_RETRY_DELAY_SEC'); + return INITIAL_NETWORK_RETRY_DELAY_SEC; }, // Gets a JSON version of a User-Agent and uses a cached version for a slight From bef5d8b24dfce10419cce5d7ce93412cfee66427 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 25 Dec 2019 12:58:46 -0800 Subject: [PATCH 04/10] Add protocol to config object for local proxies etc --- lib/stripe.js | 2 ++ types/lib.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/stripe.js b/lib/stripe.js index caccbd3dbc..5a06dba563 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -36,6 +36,7 @@ const ALLOWED_CONFIG_PROPERTIES = [ 'timeout', 'host', 'port', + 'protocol', 'telemetry', ]; @@ -67,6 +68,7 @@ function Stripe(key, config = {}) { auth: null, host: props.host || DEFAULT_HOST, port: props.port || DEFAULT_PORT, + protocol: props.protocol || 'https', basePath: DEFAULT_BASE_PATH, version: props.apiVersion || DEFAULT_API_VERSION, timeout: utils.validateInteger('timeout', props.timeout, DEFAULT_TIMEOUT), diff --git a/types/lib.d.ts b/types/lib.d.ts index c33cb817b0..b8f482a686 100644 --- a/types/lib.d.ts +++ b/types/lib.d.ts @@ -52,6 +52,8 @@ declare module 'stripe' { port?: string | number; + protocol?: 'https' | 'http'; + /** * Pass `telemetry: false` to disable headers that provide Stripe * with data about usage of the API. From d29faf3f0f3c620c5e0284d630f9b6b41d3d9119 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 25 Dec 2019 13:07:40 -0800 Subject: [PATCH 05/10] Add `appInfo` config option --- lib/stripe.js | 9 +++++++++ types/lib.d.ts | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/lib/stripe.js b/lib/stripe.js index 5a06dba563..9a3c798372 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -38,6 +38,7 @@ const ALLOWED_CONFIG_PROPERTIES = [ 'port', 'protocol', 'telemetry', + 'appInfo', ]; const EventEmitter = require('events').EventEmitter; @@ -91,6 +92,10 @@ function Stripe(key, config = {}) { Stripe.USER_AGENT.typescript = typescript; } + if (props.appInfo) { + this._setAppInfo(props.appInfo); + } + this._prepResources(); this.setApiKey(key); @@ -156,6 +161,10 @@ Stripe.prototype = { }, setAppInfo(info) { + this._setAppInfo(info); + }, + + _setAppInfo(info) { if (info && typeof info !== 'object') { throw new Error('AppInfo must be an object.'); } diff --git a/types/lib.d.ts b/types/lib.d.ts index b8f482a686..495cb7fdfe 100644 --- a/types/lib.d.ts +++ b/types/lib.d.ts @@ -60,6 +60,12 @@ declare module 'stripe' { * Currently, the only telemetry we send is latency metrics. */ telemetry?: boolean; + + /** + * For plugin authors to identify their code. + * @docs https://stripe.com/docs/building-plugins?lang=node#setappinfo + */ + appInfo?: AppInfo; } export interface RequestOptions { From 9c02d7e7f4358afffc38793491168833f6402272 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Thu, 26 Dec 2019 10:20:46 -0800 Subject: [PATCH 06/10] Mark all setter methods as deprecated, emit warnings. --- lib/stripe.js | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/lib/stripe.js b/lib/stripe.js index 9a3c798372..d77e37e1c2 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -43,6 +43,7 @@ const ALLOWED_CONFIG_PROPERTIES = [ const EventEmitter = require('events').EventEmitter; const utils = require('./utils'); +const {emitWarning} = utils; Stripe.StripeResource = require('./StripeResource'); Stripe.resources = resources; @@ -110,7 +111,20 @@ Stripe.errors = require('./Error'); Stripe.webhooks = require('./Webhooks'); Stripe.prototype = { + /** + * @deprecated will be removed in a future major version. Use the config object instead: + * + * const stripe = new Stripe(API_KEY, { + * host: 'example.com', + * port: '8080', + * protocol: 'http', + * }); + * + */ setHost(host, port, protocol) { + emitWarning( + '`setHost` is deprecated. Use the `host` config option instead.' + ); this._setApiField('host', host); if (port) { this.setPort(port); @@ -120,7 +134,18 @@ Stripe.prototype = { } }, + /** + * @deprecated will be removed in a future major version. Use the config object instead: + * + * const stripe = new Stripe(API_KEY, { + * protocol: 'http', + * }); + * + */ setProtocol(protocol) { + emitWarning( + '`setProtocol` is deprecated. Use the `protocol` config option instead.' + ); this._setApiField('protocol', protocol.toLowerCase()); }, @@ -133,6 +158,9 @@ Stripe.prototype = { * */ setPort(port) { + emitWarning( + '`setPort` is deprecated. Use the `port` config option instead.' + ); this._setApiField('port', port); }, @@ -145,22 +173,68 @@ Stripe.prototype = { * */ setApiVersion(version) { + emitWarning( + '`setApiVersion` is deprecated. Use the `stripeVersion` config or request option instead.' + ); if (version) { this._setApiField('version', version); } }, + /** + * @deprecated will be removed in a future major version. Use the config object instead: + * + * const stripe = new Stripe(API_KEY); + * + * Or, for Stripe Connect, use `stripeAccount` instead: + * + * const stripe = new Stripe(API_KEY, { + * stripeAccount: 'acct_...', + * }); + * + * Or, to use a different apiKey on a given request: + * + * stripe.customers.create(params, {apiKey: 'sk_test_...'}); + */ setApiKey(key) { + emitWarning( + '`setApiKey` is deprecated. Use the `apiKey` request option instead.' + ); if (key) { this._setApiField('auth', `Bearer ${key}`); } }, + /** + * @deprecated will be removed in a future major version. Use the config object instead: + * + * const stripe = new Stripe(API_KEY, { + * timeout: TIMEOUT_MS, + * }); + */ setTimeout(timeout) { + emitWarning( + '`setTimeout` is deprecated. Use the `timeout` config or request option instead.' + ); this._setApiField('timeout', timeout == null ? DEFAULT_TIMEOUT : timeout); }, + /** + * @deprecated will be removed in a future major version. Use the config object instead: + * + * const stripe = new Stripe(API_KEY, { + * appInfo: { + * name: 'MyPlugin', + * version: '1.4.2', + * url: 'https://myplugin.com', + * partner_id: '1234', + * }, + * }); + */ setAppInfo(info) { + emitWarning( + '`setAppInfo` is deprecated. Use the `appInfo` config option instead.' + ); this._setAppInfo(info); }, @@ -201,6 +275,9 @@ Stripe.prototype = { * */ setHttpAgent(agent) { + emitWarning( + '`setHttpAgent` is deprecated. Use the `httpAgent` config option instead.' + ); this._setApiField('agent', agent); }, @@ -320,7 +397,18 @@ Stripe.prototype = { return formatted; }, + /** + * @deprecated will be removed in a future major version. Use the config object instead: + * + * const stripe = new Stripe(API_KEY, { + * telemetry: false, + * }); + * + */ setTelemetryEnabled(enableTelemetry) { + emitWarning( + '`setTelemetryEnabled` is deprecated. Use the `telemetry` config option instead.' + ); this._enableTelemetry = enableTelemetry; }, From 8b96ff313912d4aead0a9885990fad760a58bb38 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Thu, 26 Dec 2019 10:21:28 -0800 Subject: [PATCH 07/10] Mark methods prefixed with an underscore as private --- lib/stripe.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/stripe.js b/lib/stripe.js index d77e37e1c2..d43a2feb2c 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -238,6 +238,10 @@ Stripe.prototype = { this._setAppInfo(info); }, + /** + * @private + * This may be removed in the future. + */ _setAppInfo(info) { if (info && typeof info !== 'object') { throw new Error('AppInfo must be an object.'); @@ -281,6 +285,10 @@ Stripe.prototype = { this._setApiField('agent', agent); }, + /** + * @private + * This may be removed in the future. + */ _setApiField(key, value) { this._api[key] = value; }, @@ -333,6 +341,10 @@ Stripe.prototype = { this._setApiNumberField('maxNetworkRetries', maxNetworkRetries); }, + /** + * @private + * This may be removed in the future. + */ _setApiNumberField(prop, n, defaultVal) { const val = utils.validateInteger(prop, n, defaultVal); @@ -416,12 +428,20 @@ Stripe.prototype = { return this._enableTelemetry; }, + /** + * @private + * This may be removed in the future. + */ _prepResources() { for (const name in resources) { this[utils.pascalToCamelCase(name)] = new resources[name](this); } }, + /** + * @private + * This may be removed in the future. + */ _getPropsFromConfig(config) { // If config is null or undefined, just bail early with no props if (!config) { From a0886d1f99cdc487a72471cf62fade4fad83d624 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Thu, 26 Dec 2019 10:22:31 -0800 Subject: [PATCH 08/10] Mark a few getter methods which are not prefixed with underscores as private --- lib/stripe.js | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/stripe.js b/lib/stripe.js index d43a2feb2c..3f2b9e95fa 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -293,6 +293,13 @@ Stripe.prototype = { this._api[key] = value; }, + /** + * @private + * Please open or upvote an issue at github.com/stripe/stripe-node + * if you use this, detailing your use-case. + * + * It may be deprecated and removed in the future. + */ getApiField(key) { return this._api[key]; }, @@ -305,6 +312,13 @@ Stripe.prototype = { return this._clientId; }, + /** + * @private + * Please open or upvote an issue at github.com/stripe/stripe-node + * if you use this, detailing your use-case. + * + * It may be deprecated and removed in the future. + */ getConstant: (c) => { switch (c) { case 'DEFAULT_HOST': @@ -359,8 +373,16 @@ Stripe.prototype = { return INITIAL_NETWORK_RETRY_DELAY_SEC; }, - // Gets a JSON version of a User-Agent and uses a cached version for a slight - // speed advantage. + /** + * @private + * Please open or upvote an issue at github.com/stripe/stripe-node + * if you use this, detailing your use-case. + * + * It may be deprecated and removed in the future. + * + * Gets a JSON version of a User-Agent and uses a cached version for a slight + * speed advantage. + */ getClientUserAgent(cb) { if (Stripe.USER_AGENT_SERIALIZED) { return cb(Stripe.USER_AGENT_SERIALIZED); @@ -371,8 +393,16 @@ Stripe.prototype = { }); }, - // Gets a JSON version of a User-Agent by encoding a seeded object and - // fetching a uname from the system. + /** + * @private + * Please open or upvote an issue at github.com/stripe/stripe-node + * if you use this, detailing your use-case. + * + * It may be deprecated and removed in the future. + * + * Gets a JSON version of a User-Agent by encoding a seeded object and + * fetching a uname from the system. + */ getClientUserAgentSeeded(seed, cb) { utils.safeExec('uname -a', (err, uname) => { const userAgent = {}; @@ -391,6 +421,13 @@ Stripe.prototype = { }); }, + /** + * @private + * Please open or upvote an issue at github.com/stripe/stripe-node + * if you use this, detailing your use-case. + * + * It may be deprecated and removed in the future. + */ getAppInfoAsString() { if (!this._appInfo) { return ''; From fe375e0725b02ab4816269ddaeba54f58e38c3e7 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Thu, 26 Dec 2019 11:07:52 -0800 Subject: [PATCH 09/10] Rename the request option stripeVersion to apiVersion to be consistent with the config option and our overall terminology --- lib/stripe.js | 2 +- lib/utils.js | 9 +++++---- test/flows.spec.js | 4 ++-- test/resources/EphemeralKeys.spec.js | 4 ++-- test/utils.spec.js | 12 +++++++----- types/lib.d.ts | 2 ++ types/test/typescriptTest.ts | 2 +- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/stripe.js b/lib/stripe.js index 3f2b9e95fa..cc04631bce 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -174,7 +174,7 @@ Stripe.prototype = { */ setApiVersion(version) { emitWarning( - '`setApiVersion` is deprecated. Use the `stripeVersion` config or request option instead.' + '`setApiVersion` is deprecated. Use the `apiVersion` config or request option instead.' ); if (version) { this._setApiField('version', version); diff --git a/lib/utils.js b/lib/utils.js index 34220b8b6a..32860467bb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -11,7 +11,7 @@ const OPTIONS_KEYS = [ 'apiKey', 'idempotencyKey', 'stripeAccount', - 'stripeVersion', + 'apiVersion', 'maxNetworkRetries', 'timeout', ]; @@ -20,7 +20,8 @@ const DEPRECATED_OPTIONS = { api_key: 'apiKey', idempotency_key: 'idempotencyKey', stripe_account: 'stripeAccount', - stripe_version: 'stripeVersion', + stripe_version: 'apiVersion', + stripeVersion: 'apiVersion', }; const DEPRECATED_OPTIONS_KEYS = Object.keys(DEPRECATED_OPTIONS); @@ -171,8 +172,8 @@ const utils = (module.exports = { if (params.stripeAccount) { opts.headers['Stripe-Account'] = params.stripeAccount; } - if (params.stripeVersion) { - opts.headers['Stripe-Version'] = params.stripeVersion; + if (params.apiVersion) { + opts.headers['Stripe-Version'] = params.apiVersion; } if (Number.isInteger(params.maxNetworkRetries)) { opts.settings.maxNetworkRetries = params.maxNetworkRetries; diff --git a/test/flows.spec.js b/test/flows.spec.js index 0eb439ac37..8cb4b25ae6 100644 --- a/test/flows.spec.js +++ b/test/flows.spec.js @@ -349,7 +349,7 @@ describe('Flows', function() { not_a_param: 'garbage, please 400', }, { - stripeVersion: apiVersion, + apiVersion: apiVersion, idempotencyKey: idempotencyKey, stripeAccount: connectedAccountId, } @@ -401,7 +401,7 @@ describe('Flows', function() { not_a_param: 'garbage, please 400', }, { - stripeVersion: apiVersion, + apiVersion: apiVersion, idempotencyKey: idempotencyKey, stripeAccount: connectedAccountId, } diff --git a/test/resources/EphemeralKeys.spec.js b/test/resources/EphemeralKeys.spec.js index 877856bba9..4149ac745d 100644 --- a/test/resources/EphemeralKeys.spec.js +++ b/test/resources/EphemeralKeys.spec.js @@ -12,7 +12,7 @@ function errorsOnNoStripeVersion() { function sendsCorrectStripeVersion() { stripe.ephemeralKeys.create( {customer: 'cus_123'}, - {stripeVersion: '2017-06-05'} + {apiVersion: '2017-06-05'} ); expect(stripe.LAST_REQUEST).to.deep.equal({ @@ -33,7 +33,7 @@ describe('EphemeralKey Resource', () => { it('Sends the correct request', () => { stripe.ephemeralKeys.create( {customer: 'cus_123'}, - {stripeVersion: '2017-05-25'} + {apiVersion: '2017-05-25'} ); expect(stripe.LAST_REQUEST).to.deep.equal({ method: 'POST', diff --git a/test/utils.spec.js b/test/utils.spec.js index 8ee1e7f9d3..620a14694f 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -243,7 +243,7 @@ describe('utils', () => { }); it('parses an api version', () => { - const args = [{foo: 'bar'}, {stripeVersion: '2003-03-30'}]; + const args = [{foo: 'bar'}, {apiVersion: '2003-03-30'}]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ auth: null, headers: {'Stripe-Version': '2003-03-30'}, @@ -258,7 +258,7 @@ describe('utils', () => { { apiKey: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', idempotencyKey: 'foo', - stripeVersion: '2010-01-10', + apiVersion: '2010-01-10', }, ]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ @@ -277,7 +277,7 @@ describe('utils', () => { { apiKey: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', idempotencyKey: 'foo', - stripeVersion: 'hunter2', + apiVersion: 'hunter2', }, ]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ @@ -317,13 +317,15 @@ describe('utils', () => { idempotency_key: 'key', stripe_account: 'acct_123', stripe_version: '2019-08-08', + stripeVersion: '2019-08-08', }, ]; const desiredWarnings = [ "Stripe: 'api_key' is deprecated; use 'apiKey' instead.", "Stripe: 'idempotency_key' is deprecated; use 'idempotencyKey' instead.", "Stripe: 'stripe_account' is deprecated; use 'stripeAccount' instead.", - "Stripe: 'stripe_version' is deprecated; use 'stripeVersion' instead.", + "Stripe: 'stripe_version' is deprecated; use 'apiVersion' instead.", + "Stripe: 'stripeVersion' is deprecated; use 'apiVersion' instead.", ]; const warnings = []; @@ -354,7 +356,7 @@ describe('utils', () => { { apiKey: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', idempotencyKey: 'foo', - stripeVersion: '2010-01-10', + apiVersion: '2010-01-10', fishsticks: true, custard: true, }, diff --git a/types/lib.d.ts b/types/lib.d.ts index 495cb7fdfe..ae351c9435 100644 --- a/types/lib.d.ts +++ b/types/lib.d.ts @@ -94,6 +94,8 @@ declare module 'stripe' { /** * The [API Version](https://stripe.com/docs/upgrades) to use for a given request (e.g., '2019-12-03'). */ + apiVersion?: string; + /** @deprecated Please use apiVersion instead. */ stripeVersion?: string; /** @deprecated Please use stripeVersion instead. */ stripe_version?: string; diff --git a/types/test/typescriptTest.ts b/types/test/typescriptTest.ts index c14e51b02e..02d9b5cbbb 100644 --- a/types/test/typescriptTest.ts +++ b/types/test/typescriptTest.ts @@ -49,7 +49,7 @@ stripe.setHost('host', 'port', 'protocol'); description: 'test', }; const opts: Stripe.RequestOptions = { - stripeVersion: '2019-12-03', + apiVersion: '2019-12-03', }; const customer: Stripe.Customer = await stripe.customers.create(params, opts); From e756c7f01fa7536179a5cd3cc2f54372bb12b1fe Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 8 Jan 2020 16:04:28 -0800 Subject: [PATCH 10/10] Throw an error if a deprecated and non-deprecated version of the same request option is passed --- lib/utils.js | 5 +++++ test/utils.spec.js | 48 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 32860467bb..818d98f790 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -150,6 +150,11 @@ const utils = (module.exports = { return true; } const newParam = DEPRECATED_OPTIONS[key]; + if (params[newParam]) { + throw Error( + `Both '${newParam}' and '${key}' were provided; please remove '${key}', which is deprecated.` + ); + } /** * TODO turn this into a hard error in a future major version (once we have fixed our docs). */ diff --git a/test/utils.spec.js b/test/utils.spec.js index 620a14694f..e9dc40c345 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -317,7 +317,6 @@ describe('utils', () => { idempotency_key: 'key', stripe_account: 'acct_123', stripe_version: '2019-08-08', - stripeVersion: '2019-08-08', }, ]; const desiredWarnings = [ @@ -325,7 +324,6 @@ describe('utils', () => { "Stripe: 'idempotency_key' is deprecated; use 'idempotencyKey' instead.", "Stripe: 'stripe_account' is deprecated; use 'stripeAccount' instead.", "Stripe: 'stripe_version' is deprecated; use 'apiVersion' instead.", - "Stripe: 'stripeVersion' is deprecated; use 'apiVersion' instead.", ]; const warnings = []; @@ -350,6 +348,52 @@ describe('utils', () => { }); }); + it('parses stripeVersion for backwards compatibility', () => { + return new Promise((resolve, reject) => { + const args = [ + { + apiKey: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', + stripeVersion: '2019-08-08', + }, + ]; + const desiredWarnings = [ + "Stripe: 'stripeVersion' is deprecated; use 'apiVersion' instead.", + ]; + + const warnings = []; + const onWarn = (message) => { + warnings.push(message); + if (warnings.length === desiredWarnings.length) { + expect(warnings).to.deep.equal(desiredWarnings); + resolve(); + } + }; + handleWarnings(() => { + expect(utils.getOptionsFromArgs(args)).to.deep.equal({ + auth: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', + headers: { + 'Stripe-Version': '2019-08-08', + }, + settings: {}, + }); + }, onWarn); + }); + }); + + it('errors if you pass both a deprecated and non-deprecated version of the same param', () => { + const args = [ + { + stripeVersion: 'bad', + apiVersion: 'good', + }, + ]; + expect(() => { + utils.getOptionsFromArgs(args); + }).to.throw( + "Both 'apiVersion' and 'stripeVersion' were provided; please remove 'stripeVersion', which is deprecated." + ); + }); + it('warns if the hash contains something that does not belong', (done) => { const args = [ {foo: 'bar'},