611 lines
15 KiB
JavaScript
611 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
const resources = require('./resources');
|
|
|
|
const DEFAULT_HOST = 'api.stripe.com';
|
|
const DEFAULT_PORT = '443';
|
|
const DEFAULT_BASE_PATH = '/v1/';
|
|
const DEFAULT_API_VERSION = null;
|
|
|
|
const DEFAULT_TIMEOUT = 80000;
|
|
|
|
Stripe.PACKAGE_VERSION = require('../package.json').version;
|
|
|
|
const utils = require('./utils');
|
|
const {determineProcessUserAgentProperties, emitWarning} = utils;
|
|
|
|
Stripe.USER_AGENT = {
|
|
bindings_version: Stripe.PACKAGE_VERSION,
|
|
lang: 'node',
|
|
publisher: 'stripe',
|
|
uname: null,
|
|
typescript: false,
|
|
...determineProcessUserAgentProperties(),
|
|
};
|
|
|
|
/** @private */
|
|
Stripe._UNAME_CACHE = null;
|
|
|
|
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 = [
|
|
'apiVersion',
|
|
'typescript',
|
|
'maxNetworkRetries',
|
|
'httpAgent',
|
|
'httpClient',
|
|
'timeout',
|
|
'host',
|
|
'port',
|
|
'protocol',
|
|
'telemetry',
|
|
'appInfo',
|
|
'stripeAccount',
|
|
];
|
|
|
|
const EventEmitter = require('events').EventEmitter;
|
|
|
|
Stripe.StripeResource = require('./StripeResource');
|
|
Stripe.resources = resources;
|
|
|
|
const {HttpClient, HttpClientResponse} = require('./net/HttpClient');
|
|
Stripe.HttpClient = HttpClient;
|
|
Stripe.HttpClientResponse = HttpClientResponse;
|
|
|
|
const CryptoProvider = require('./crypto/CryptoProvider');
|
|
Stripe.CryptoProvider = CryptoProvider;
|
|
|
|
function Stripe(key, config = {}) {
|
|
if (!(this instanceof Stripe)) {
|
|
return new Stripe(key, config);
|
|
}
|
|
|
|
const props = this._getPropsFromConfig(config);
|
|
|
|
Object.defineProperty(this, '_emitter', {
|
|
value: new EventEmitter(),
|
|
enumerable: false,
|
|
configurable: false,
|
|
writable: false,
|
|
});
|
|
|
|
this.VERSION = Stripe.PACKAGE_VERSION;
|
|
|
|
this.on = this._emitter.on.bind(this._emitter);
|
|
this.once = this._emitter.once.bind(this._emitter);
|
|
this.off = this._emitter.removeListener.bind(this._emitter);
|
|
|
|
if (
|
|
props.protocol &&
|
|
props.protocol !== 'https' &&
|
|
(!props.host || /\.stripe\.com$/.test(props.host))
|
|
) {
|
|
throw new Error(
|
|
'The `https` protocol must be used when sending requests to `*.stripe.com`'
|
|
);
|
|
}
|
|
|
|
const agent = props.httpAgent || null;
|
|
|
|
this._api = {
|
|
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),
|
|
maxNetworkRetries: utils.validateInteger(
|
|
'maxNetworkRetries',
|
|
props.maxNetworkRetries,
|
|
0
|
|
),
|
|
agent: agent,
|
|
httpClient: props.httpClient || Stripe.createNodeHttpClient(agent),
|
|
dev: false,
|
|
stripeAccount: props.stripeAccount || null,
|
|
};
|
|
|
|
const typescript = props.typescript || false;
|
|
if (typescript !== Stripe.USER_AGENT.typescript) {
|
|
// The mutation here is uncomfortable, but likely fastest;
|
|
// serializing the user agent involves shelling out to the system,
|
|
// and given some users may instantiate the library many times without switching between TS and non-TS,
|
|
// we only want to incur the performance hit when that actually happens.
|
|
Stripe.USER_AGENT.typescript = typescript;
|
|
}
|
|
|
|
if (props.appInfo) {
|
|
this._setAppInfo(props.appInfo);
|
|
}
|
|
|
|
this._prepResources();
|
|
this._setApiKey(key);
|
|
|
|
this.errors = require('./Error');
|
|
this.webhooks = require('./Webhooks');
|
|
|
|
this._prevRequestMetrics = [];
|
|
this._enableTelemetry = props.telemetry !== false;
|
|
|
|
// Expose StripeResource on the instance too
|
|
this.StripeResource = Stripe.StripeResource;
|
|
}
|
|
|
|
Stripe.errors = require('./Error');
|
|
Stripe.webhooks = require('./Webhooks');
|
|
|
|
Stripe.createNodeHttpClient = (agent) => {
|
|
const {NodeHttpClient} = require('./net/NodeHttpClient');
|
|
return new NodeHttpClient(agent);
|
|
};
|
|
|
|
/**
|
|
* Creates an HTTP client for issuing Stripe API requests which uses the Web
|
|
* Fetch API.
|
|
*
|
|
* A fetch function can optionally be passed in as a parameter. If none is
|
|
* passed, will default to the default `fetch` function in the global scope.
|
|
*/
|
|
Stripe.createFetchHttpClient = (fetchFn) => {
|
|
const {FetchHttpClient} = require('./net/FetchHttpClient');
|
|
return new FetchHttpClient(fetchFn);
|
|
};
|
|
|
|
/**
|
|
* Create a CryptoProvider which uses the built-in Node crypto libraries for
|
|
* its crypto operations.
|
|
*/
|
|
Stripe.createNodeCryptoProvider = () => {
|
|
const NodeCryptoProvider = require('./crypto/NodeCryptoProvider');
|
|
return new NodeCryptoProvider();
|
|
};
|
|
|
|
/**
|
|
* Creates a CryptoProvider which uses the Subtle Crypto API from the Web
|
|
* Crypto API spec for its crypto operations.
|
|
*
|
|
* A SubtleCrypto interface can optionally be passed in as a parameter. If none
|
|
* is passed, will default to the default `crypto.subtle` object in the global
|
|
* scope.
|
|
*/
|
|
Stripe.createSubtleCryptoProvider = (subtleCrypto) => {
|
|
const SubtleCryptoProvider = require('./crypto/SubtleCryptoProvider');
|
|
return new SubtleCryptoProvider(subtleCrypto);
|
|
};
|
|
|
|
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);
|
|
}
|
|
if (protocol) {
|
|
this.setProtocol(protocol);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @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());
|
|
},
|
|
|
|
/**
|
|
* @deprecated will be removed in a future major version. Use the config object instead:
|
|
*
|
|
* const stripe = new Stripe(API_KEY, {
|
|
* port: 3000,
|
|
* });
|
|
*
|
|
*/
|
|
setPort(port) {
|
|
emitWarning(
|
|
'`setPort` is deprecated. Use the `port` config option instead.'
|
|
);
|
|
this._setApiField('port', port);
|
|
},
|
|
|
|
/**
|
|
* @deprecated will be removed in a future major version. Use the config object instead:
|
|
*
|
|
* const stripe = new Stripe(API_KEY, {
|
|
* apiVersion: API_VERSION,
|
|
* });
|
|
*
|
|
*/
|
|
setApiVersion(version) {
|
|
emitWarning(
|
|
'`setApiVersion` is deprecated. Use the `apiVersion` 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.'
|
|
);
|
|
this._setApiKey(key);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_setApiKey(key) {
|
|
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);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* This may be removed in the future.
|
|
*/
|
|
_setAppInfo(info) {
|
|
if (info && typeof info !== 'object') {
|
|
throw new Error('AppInfo must be an object.');
|
|
}
|
|
|
|
if (info && !info.name) {
|
|
throw new Error('AppInfo.name is required');
|
|
}
|
|
|
|
info = info || {};
|
|
|
|
const appInfo = APP_INFO_PROPERTIES.reduce((accum, prop) => {
|
|
if (typeof info[prop] == 'string') {
|
|
accum = accum || {};
|
|
|
|
accum[prop] = info[prop];
|
|
}
|
|
|
|
return accum;
|
|
}, undefined);
|
|
|
|
this._appInfo = appInfo;
|
|
},
|
|
|
|
/**
|
|
* @deprecated will be removed in a future major version. Use the config object instead:
|
|
*
|
|
* const ProxyAgent = require('https-proxy-agent');
|
|
* const stripe = new Stripe(API_KEY, {
|
|
* httpAgent: new ProxyAgent(process.env.http_proxy),
|
|
* });
|
|
*
|
|
*/
|
|
setHttpAgent(agent) {
|
|
emitWarning(
|
|
'`setHttpAgent` is deprecated. Use the `httpAgent` config option instead.'
|
|
);
|
|
this._setApiField('agent', agent);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* This may be removed in the future.
|
|
*/
|
|
_setApiField(key, value) {
|
|
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];
|
|
},
|
|
|
|
setClientId(clientId) {
|
|
this._clientId = clientId;
|
|
},
|
|
|
|
getClientId() {
|
|
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':
|
|
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];
|
|
},
|
|
|
|
getMaxNetworkRetries() {
|
|
return this.getApiField('maxNetworkRetries');
|
|
},
|
|
|
|
/**
|
|
* @deprecated will be removed in a future major version. Use the config object instead:
|
|
*
|
|
* const stripe = new Stripe(API_KEY, {
|
|
* maxNetworkRetries: 2,
|
|
* });
|
|
*
|
|
*/
|
|
setMaxNetworkRetries(maxNetworkRetries) {
|
|
this._setApiNumberField('maxNetworkRetries', maxNetworkRetries);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* This may be removed in the future.
|
|
*/
|
|
_setApiNumberField(prop, n, defaultVal) {
|
|
const val = utils.validateInteger(prop, n, defaultVal);
|
|
|
|
this._setApiField(prop, val);
|
|
},
|
|
|
|
getMaxNetworkRetryDelay() {
|
|
return MAX_NETWORK_RETRY_DELAY_SEC;
|
|
},
|
|
|
|
getInitialNetworkRetryDelay() {
|
|
return INITIAL_NETWORK_RETRY_DELAY_SEC;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getUname(cb) {
|
|
if (!Stripe._UNAME_CACHE) {
|
|
Stripe._UNAME_CACHE = new Promise((resolve) => {
|
|
utils.safeExec('uname -a', (err, uname) => {
|
|
resolve(uname);
|
|
});
|
|
});
|
|
}
|
|
Stripe._UNAME_CACHE.then((uname) => cb(uname));
|
|
},
|
|
|
|
/**
|
|
* @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) {
|
|
return this.getClientUserAgentSeeded(Stripe.USER_AGENT, cb);
|
|
},
|
|
|
|
/**
|
|
* @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) {
|
|
this.getUname((uname) => {
|
|
const userAgent = {};
|
|
for (const field in seed) {
|
|
userAgent[field] = encodeURIComponent(seed[field]);
|
|
}
|
|
|
|
// URI-encode in case there are unusual characters in the system's uname.
|
|
userAgent.uname = encodeURIComponent(uname || 'UNKNOWN');
|
|
|
|
const client = this.getApiField('httpClient');
|
|
if (client) {
|
|
userAgent.httplib = encodeURIComponent(client.getClientName());
|
|
}
|
|
|
|
if (this._appInfo) {
|
|
userAgent.application = this._appInfo;
|
|
}
|
|
|
|
cb(JSON.stringify(userAgent));
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @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 '';
|
|
}
|
|
|
|
let formatted = this._appInfo.name;
|
|
|
|
if (this._appInfo.version) {
|
|
formatted += `/${this._appInfo.version}`;
|
|
}
|
|
|
|
if (this._appInfo.url) {
|
|
formatted += ` (${this._appInfo.url})`;
|
|
}
|
|
|
|
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;
|
|
},
|
|
|
|
getTelemetryEnabled() {
|
|
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) {
|
|
return {};
|
|
}
|
|
|
|
// config can be an object or a string
|
|
const isString = typeof config === 'string';
|
|
const isObject = config === Object(config) && !Array.isArray(config);
|
|
|
|
if (!isObject && !isString) {
|
|
throw new Error('Config must either be an object or a string');
|
|
}
|
|
|
|
// If config is a string, we assume the old behavior of passing in a string representation of the api version
|
|
if (isString) {
|
|
return {
|
|
apiVersion: config,
|
|
};
|
|
}
|
|
|
|
// If config is an object, we assume the new behavior and make sure it doesn't contain any unexpected values
|
|
const values = Object.keys(config).filter(
|
|
(value) => !ALLOWED_CONFIG_PROPERTIES.includes(value)
|
|
);
|
|
|
|
if (values.length > 0) {
|
|
throw new Error(
|
|
`Config object may only contain the following: ${ALLOWED_CONFIG_PROPERTIES.join(
|
|
', '
|
|
)}`
|
|
);
|
|
}
|
|
|
|
return config;
|
|
},
|
|
};
|
|
|
|
module.exports = Stripe;
|
|
|
|
// expose constructor as a named property to enable mocking with Sinon.JS
|
|
module.exports.Stripe = Stripe;
|
|
|
|
// Allow use with the TypeScript compiler without `esModuleInterop`.
|
|
// We may also want to add `Object.defineProperty(exports, "__esModule", {value: true});` in the future, so that Babel users will use the `default` version.
|
|
module.exports.default = Stripe;
|