UNPKG

13.8 kBJavaScriptView Raw
1'use strict';
2
3const resources = require('./resources');
4
5const DEFAULT_HOST = 'api.stripe.com';
6const DEFAULT_PORT = '443';
7const DEFAULT_BASE_PATH = '/v1/';
8const DEFAULT_API_VERSION = null;
9
10const DEFAULT_TIMEOUT = 80000;
11
12Stripe.PACKAGE_VERSION = require('../package.json').version;
13
14Stripe.USER_AGENT = {
15 bindings_version: Stripe.PACKAGE_VERSION,
16 lang: 'node',
17 lang_version: process.version,
18 platform: process.platform,
19 publisher: 'stripe',
20 uname: null,
21 typescript: false,
22};
23
24Stripe.USER_AGENT_SERIALIZED = null;
25
26const MAX_NETWORK_RETRY_DELAY_SEC = 2;
27const INITIAL_NETWORK_RETRY_DELAY_SEC = 0.5;
28
29const APP_INFO_PROPERTIES = ['name', 'version', 'url', 'partner_id'];
30const ALLOWED_CONFIG_PROPERTIES = [
31 'apiVersion',
32 'typescript',
33 'maxNetworkRetries',
34 'httpAgent',
35 'timeout',
36 'host',
37 'port',
38 'protocol',
39 'telemetry',
40 'appInfo',
41];
42
43const EventEmitter = require('events').EventEmitter;
44const utils = require('./utils');
45const {emitWarning} = utils;
46
47Stripe.StripeResource = require('./StripeResource');
48Stripe.resources = resources;
49
50function Stripe(key, config = {}) {
51 if (!(this instanceof Stripe)) {
52 return new Stripe(key, config);
53 }
54
55 const props = this._getPropsFromConfig(config);
56
57 Object.defineProperty(this, '_emitter', {
58 value: new EventEmitter(),
59 enumerable: false,
60 configurable: false,
61 writable: false,
62 });
63
64 this.on = this._emitter.on.bind(this._emitter);
65 this.once = this._emitter.once.bind(this._emitter);
66 this.off = this._emitter.removeListener.bind(this._emitter);
67
68 if (
69 props.protocol &&
70 props.protocol !== 'https' &&
71 (!props.host || /\.stripe\.com$/.test(props.host))
72 ) {
73 throw new Error(
74 'The `https` protocol must be used when sending requests to `*.stripe.com`'
75 );
76 }
77
78 this._api = {
79 auth: null,
80 host: props.host || DEFAULT_HOST,
81 port: props.port || DEFAULT_PORT,
82 protocol: props.protocol || 'https',
83 basePath: DEFAULT_BASE_PATH,
84 version: props.apiVersion || DEFAULT_API_VERSION,
85 timeout: utils.validateInteger('timeout', props.timeout, DEFAULT_TIMEOUT),
86 maxNetworkRetries: utils.validateInteger(
87 'maxNetworkRetries',
88 props.maxNetworkRetries,
89 0
90 ),
91 agent: props.httpAgent || null,
92 dev: false,
93 };
94
95 const typescript = props.typescript || false;
96 if (typescript !== Stripe.USER_AGENT.typescript) {
97 // The mutation here is uncomfortable, but likely fastest;
98 // serializing the user agent involves shelling out to the system,
99 // and given some users may instantiate the library many times without switching between TS and non-TS,
100 // we only want to incur the performance hit when that actually happens.
101 Stripe.USER_AGENT_SERIALIZED = null;
102 Stripe.USER_AGENT.typescript = typescript;
103 }
104
105 if (props.appInfo) {
106 this._setAppInfo(props.appInfo);
107 }
108
109 this._prepResources();
110 this._setApiKey(key);
111
112 this.errors = require('./Error');
113 this.webhooks = require('./Webhooks');
114
115 this._prevRequestMetrics = [];
116 this._enableTelemetry = props.telemetry !== false;
117
118 // Expose StripeResource on the instance too
119 this.StripeResource = Stripe.StripeResource;
120}
121
122Stripe.errors = require('./Error');
123Stripe.webhooks = require('./Webhooks');
124
125Stripe.prototype = {
126 /**
127 * @deprecated will be removed in a future major version. Use the config object instead:
128 *
129 * const stripe = new Stripe(API_KEY, {
130 * host: 'example.com',
131 * port: '8080',
132 * protocol: 'http',
133 * });
134 *
135 */
136 setHost(host, port, protocol) {
137 emitWarning(
138 '`setHost` is deprecated. Use the `host` config option instead.'
139 );
140 this._setApiField('host', host);
141 if (port) {
142 this.setPort(port);
143 }
144 if (protocol) {
145 this.setProtocol(protocol);
146 }
147 },
148
149 /**
150 * @deprecated will be removed in a future major version. Use the config object instead:
151 *
152 * const stripe = new Stripe(API_KEY, {
153 * protocol: 'http',
154 * });
155 *
156 */
157 setProtocol(protocol) {
158 emitWarning(
159 '`setProtocol` is deprecated. Use the `protocol` config option instead.'
160 );
161 this._setApiField('protocol', protocol.toLowerCase());
162 },
163
164 /**
165 * @deprecated will be removed in a future major version. Use the config object instead:
166 *
167 * const stripe = new Stripe(API_KEY, {
168 * port: 3000,
169 * });
170 *
171 */
172 setPort(port) {
173 emitWarning(
174 '`setPort` is deprecated. Use the `port` config option instead.'
175 );
176 this._setApiField('port', port);
177 },
178
179 /**
180 * @deprecated will be removed in a future major version. Use the config object instead:
181 *
182 * const stripe = new Stripe(API_KEY, {
183 * apiVersion: API_VERSION,
184 * });
185 *
186 */
187 setApiVersion(version) {
188 emitWarning(
189 '`setApiVersion` is deprecated. Use the `apiVersion` config or request option instead.'
190 );
191 if (version) {
192 this._setApiField('version', version);
193 }
194 },
195
196 /**
197 * @deprecated will be removed in a future major version. Use the config object instead:
198 *
199 * const stripe = new Stripe(API_KEY);
200 *
201 * Or, for Stripe Connect, use `stripeAccount` instead:
202 *
203 * const stripe = new Stripe(API_KEY, {
204 * stripeAccount: 'acct_...',
205 * });
206 *
207 * Or, to use a different apiKey on a given request:
208 *
209 * stripe.customers.create(params, {apiKey: 'sk_test_...'});
210 */
211 setApiKey(key) {
212 emitWarning(
213 '`setApiKey` is deprecated. Use the `apiKey` request option instead.'
214 );
215 this._setApiKey(key);
216 },
217
218 /**
219 * @private
220 */
221 _setApiKey(key) {
222 if (key) {
223 this._setApiField('auth', `Bearer ${key}`);
224 }
225 },
226
227 /**
228 * @deprecated will be removed in a future major version. Use the config object instead:
229 *
230 * const stripe = new Stripe(API_KEY, {
231 * timeout: TIMEOUT_MS,
232 * });
233 */
234 setTimeout(timeout) {
235 emitWarning(
236 '`setTimeout` is deprecated. Use the `timeout` config or request option instead.'
237 );
238 this._setApiField('timeout', timeout == null ? DEFAULT_TIMEOUT : timeout);
239 },
240
241 /**
242 * @deprecated will be removed in a future major version. Use the config object instead:
243 *
244 * const stripe = new Stripe(API_KEY, {
245 * appInfo: {
246 * name: 'MyPlugin',
247 * version: '1.4.2',
248 * url: 'https://myplugin.com',
249 * partner_id: '1234',
250 * },
251 * });
252 */
253 setAppInfo(info) {
254 emitWarning(
255 '`setAppInfo` is deprecated. Use the `appInfo` config option instead.'
256 );
257 this._setAppInfo(info);
258 },
259
260 /**
261 * @private
262 * This may be removed in the future.
263 */
264 _setAppInfo(info) {
265 if (info && typeof info !== 'object') {
266 throw new Error('AppInfo must be an object.');
267 }
268
269 if (info && !info.name) {
270 throw new Error('AppInfo.name is required');
271 }
272
273 info = info || {};
274
275 const appInfo = APP_INFO_PROPERTIES.reduce((accum, prop) => {
276 if (typeof info[prop] == 'string') {
277 accum = accum || {};
278
279 accum[prop] = info[prop];
280 }
281
282 return accum;
283 }, undefined);
284
285 // Kill the cached UA string because it may no longer be valid
286 Stripe.USER_AGENT_SERIALIZED = undefined;
287
288 this._appInfo = appInfo;
289 },
290
291 /**
292 * @deprecated will be removed in a future major version. Use the config object instead:
293 *
294 * const ProxyAgent = require('https-proxy-agent');
295 * const stripe = new Stripe(API_KEY, {
296 * httpAgent: new ProxyAgent(process.env.http_proxy),
297 * });
298 *
299 */
300 setHttpAgent(agent) {
301 emitWarning(
302 '`setHttpAgent` is deprecated. Use the `httpAgent` config option instead.'
303 );
304 this._setApiField('agent', agent);
305 },
306
307 /**
308 * @private
309 * This may be removed in the future.
310 */
311 _setApiField(key, value) {
312 this._api[key] = value;
313 },
314
315 /**
316 * @private
317 * Please open or upvote an issue at github.com/stripe/stripe-node
318 * if you use this, detailing your use-case.
319 *
320 * It may be deprecated and removed in the future.
321 */
322 getApiField(key) {
323 return this._api[key];
324 },
325
326 setClientId(clientId) {
327 this._clientId = clientId;
328 },
329
330 getClientId() {
331 return this._clientId;
332 },
333
334 /**
335 * @private
336 * Please open or upvote an issue at github.com/stripe/stripe-node
337 * if you use this, detailing your use-case.
338 *
339 * It may be deprecated and removed in the future.
340 */
341 getConstant: (c) => {
342 switch (c) {
343 case 'DEFAULT_HOST':
344 return DEFAULT_HOST;
345 case 'DEFAULT_PORT':
346 return DEFAULT_PORT;
347 case 'DEFAULT_BASE_PATH':
348 return DEFAULT_BASE_PATH;
349 case 'DEFAULT_API_VERSION':
350 return DEFAULT_API_VERSION;
351 case 'DEFAULT_TIMEOUT':
352 return DEFAULT_TIMEOUT;
353 case 'MAX_NETWORK_RETRY_DELAY_SEC':
354 return MAX_NETWORK_RETRY_DELAY_SEC;
355 case 'INITIAL_NETWORK_RETRY_DELAY_SEC':
356 return INITIAL_NETWORK_RETRY_DELAY_SEC;
357 }
358 return Stripe[c];
359 },
360
361 getMaxNetworkRetries() {
362 return this.getApiField('maxNetworkRetries');
363 },
364
365 /**
366 * @deprecated will be removed in a future major version. Use the config object instead:
367 *
368 * const stripe = new Stripe(API_KEY, {
369 * maxNetworkRetries: 2,
370 * });
371 *
372 */
373 setMaxNetworkRetries(maxNetworkRetries) {
374 this._setApiNumberField('maxNetworkRetries', maxNetworkRetries);
375 },
376
377 /**
378 * @private
379 * This may be removed in the future.
380 */
381 _setApiNumberField(prop, n, defaultVal) {
382 const val = utils.validateInteger(prop, n, defaultVal);
383
384 this._setApiField(prop, val);
385 },
386
387 getMaxNetworkRetryDelay() {
388 return MAX_NETWORK_RETRY_DELAY_SEC;
389 },
390
391 getInitialNetworkRetryDelay() {
392 return INITIAL_NETWORK_RETRY_DELAY_SEC;
393 },
394
395 /**
396 * @private
397 * Please open or upvote an issue at github.com/stripe/stripe-node
398 * if you use this, detailing your use-case.
399 *
400 * It may be deprecated and removed in the future.
401 *
402 * Gets a JSON version of a User-Agent and uses a cached version for a slight
403 * speed advantage.
404 */
405 getClientUserAgent(cb) {
406 if (Stripe.USER_AGENT_SERIALIZED) {
407 return cb(Stripe.USER_AGENT_SERIALIZED);
408 }
409 this.getClientUserAgentSeeded(Stripe.USER_AGENT, (cua) => {
410 Stripe.USER_AGENT_SERIALIZED = cua;
411 cb(Stripe.USER_AGENT_SERIALIZED);
412 });
413 },
414
415 /**
416 * @private
417 * Please open or upvote an issue at github.com/stripe/stripe-node
418 * if you use this, detailing your use-case.
419 *
420 * It may be deprecated and removed in the future.
421 *
422 * Gets a JSON version of a User-Agent by encoding a seeded object and
423 * fetching a uname from the system.
424 */
425 getClientUserAgentSeeded(seed, cb) {
426 utils.safeExec('uname -a', (err, uname) => {
427 const userAgent = {};
428 for (const field in seed) {
429 userAgent[field] = encodeURIComponent(seed[field]);
430 }
431
432 // URI-encode in case there are unusual characters in the system's uname.
433 userAgent.uname = encodeURIComponent(uname || 'UNKNOWN');
434
435 if (this._appInfo) {
436 userAgent.application = this._appInfo;
437 }
438
439 cb(JSON.stringify(userAgent));
440 });
441 },
442
443 /**
444 * @private
445 * Please open or upvote an issue at github.com/stripe/stripe-node
446 * if you use this, detailing your use-case.
447 *
448 * It may be deprecated and removed in the future.
449 */
450 getAppInfoAsString() {
451 if (!this._appInfo) {
452 return '';
453 }
454
455 let formatted = this._appInfo.name;
456
457 if (this._appInfo.version) {
458 formatted += `/${this._appInfo.version}`;
459 }
460
461 if (this._appInfo.url) {
462 formatted += ` (${this._appInfo.url})`;
463 }
464
465 return formatted;
466 },
467
468 /**
469 * @deprecated will be removed in a future major version. Use the config object instead:
470 *
471 * const stripe = new Stripe(API_KEY, {
472 * telemetry: false,
473 * });
474 *
475 */
476 setTelemetryEnabled(enableTelemetry) {
477 emitWarning(
478 '`setTelemetryEnabled` is deprecated. Use the `telemetry` config option instead.'
479 );
480 this._enableTelemetry = enableTelemetry;
481 },
482
483 getTelemetryEnabled() {
484 return this._enableTelemetry;
485 },
486
487 /**
488 * @private
489 * This may be removed in the future.
490 */
491 _prepResources() {
492 for (const name in resources) {
493 this[utils.pascalToCamelCase(name)] = new resources[name](this);
494 }
495 },
496
497 /**
498 * @private
499 * This may be removed in the future.
500 */
501 _getPropsFromConfig(config) {
502 // If config is null or undefined, just bail early with no props
503 if (!config) {
504 return {};
505 }
506
507 // config can be an object or a string
508 const isString = typeof config === 'string';
509 const isObject = config === Object(config) && !Array.isArray(config);
510
511 if (!isObject && !isString) {
512 throw new Error('Config must either be an object or a string');
513 }
514
515 // If config is a string, we assume the old behavior of passing in a string representation of the api version
516 if (isString) {
517 return {
518 apiVersion: config,
519 };
520 }
521
522 // If config is an object, we assume the new behavior and make sure it doesn't contain any unexpected values
523 const values = Object.keys(config).filter(
524 (value) => !ALLOWED_CONFIG_PROPERTIES.includes(value)
525 );
526
527 if (values.length > 0) {
528 throw new Error(
529 `Config object may only contain the following: ${ALLOWED_CONFIG_PROPERTIES.join(
530 ', '
531 )}`
532 );
533 }
534
535 return config;
536 },
537};
538
539module.exports = Stripe;
540
541// expose constructor as a named property to enable mocking with Sinon.JS
542module.exports.Stripe = Stripe;
543
544// Allow use with the TypeScript compiler without `esModuleInterop`.
545// We may also want to add `Object.defineProperty(exports, "__esModule", {value: true});` in the future, so that Babel users will use the `default` version.
546module.exports.default = Stripe;