From 34a9e54e14311187b39dc0f14393892618083381 Mon Sep 17 00:00:00 2001 From: Annie Li Date: Wed, 7 Sep 2022 10:26:19 -0700 Subject: [PATCH] Revert Typescript changes --- .eslintignore | 3 +- .eslintrc.js | 25 - .github/workflows/main.yml | 6 +- .gitignore | 1 - CHANGELOG.md | 6 - OPENAPI_VERSION | 2 +- VERSION | 2 +- lib/Error.js | 142 ++++ lib/ResourceNamespace.js | 22 + lib/StripeMethod.basic.js | 32 + lib/StripeMethod.js | 58 ++ lib/StripeResource.js | 619 ++++++++++++++++++ lib/Webhooks.js | 277 ++++++++ lib/apiVersion.js | 3 + lib/autoPagination.js | 278 ++++++++ lib/crypto/CryptoProvider.js | 36 + lib/crypto/NodeCryptoProvider.js | 26 + lib/crypto/SubtleCryptoProvider.js | 69 ++ lib/makeRequest.js | 122 ++++ lib/multipart.js | 96 +++ lib/net/FetchHttpClient.js | 138 ++++ lib/net/HttpClient.js | 69 ++ lib/net/NodeHttpClient.js | 125 ++++ lib/resources.js | 130 ++++ lib/resources/AccountLinks.js | 15 + lib/resources/Accounts.js | 130 ++++ lib/resources/ApplePayDomains.js | 31 + lib/resources/ApplicationFees.js | 42 ++ lib/resources/Apps/Secrets.js | 31 + lib/resources/Balance.js | 15 + lib/resources/BalanceTransactions.js | 21 + lib/resources/BillingPortal/Configurations.js | 31 + lib/resources/BillingPortal/Sessions.js | 15 + lib/resources/Charges.js | 42 ++ lib/resources/Checkout/Sessions.js | 37 ++ lib/resources/CountrySpecs.js | 21 + lib/resources/Coupons.js | 36 + lib/resources/CreditNotes.js | 53 ++ lib/resources/Customers.js | 157 +++++ lib/resources/Disputes.js | 31 + lib/resources/EphemeralKeys.js | 27 + lib/resources/Events.js | 21 + lib/resources/ExchangeRates.js | 21 + lib/resources/FileLinks.js | 31 + lib/resources/Files.js | 32 + .../FinancialConnections/Accounts.js | 37 ++ .../FinancialConnections/Sessions.js | 20 + lib/resources/Identity/VerificationReports.js | 21 + .../Identity/VerificationSessions.js | 41 ++ lib/resources/InvoiceItems.js | 36 + lib/resources/Invoices.js | 84 +++ lib/resources/Issuing/Authorizations.js | 36 + lib/resources/Issuing/Cardholders.js | 31 + lib/resources/Issuing/Cards.js | 31 + lib/resources/Issuing/Disputes.js | 36 + lib/resources/Issuing/Transactions.js | 26 + lib/resources/Mandates.js | 15 + lib/resources/OAuth.js | 55 ++ lib/resources/Orders.js | 52 ++ lib/resources/PaymentIntents.js | 67 ++ lib/resources/PaymentLinks.js | 37 ++ lib/resources/PaymentMethods.js | 41 ++ lib/resources/Payouts.js | 41 ++ lib/resources/Plans.js | 36 + lib/resources/Prices.js | 37 ++ lib/resources/Products.js | 42 ++ lib/resources/PromotionCodes.js | 31 + lib/resources/Quotes.js | 65 ++ lib/resources/Radar/EarlyFraudWarnings.js | 21 + lib/resources/Radar/ValueListItems.js | 31 + lib/resources/Radar/ValueLists.js | 36 + lib/resources/Refunds.js | 36 + lib/resources/Reporting/ReportRuns.js | 26 + lib/resources/Reporting/ReportTypes.js | 21 + lib/resources/Reviews.js | 26 + lib/resources/SKUs.js | 36 + lib/resources/SetupAttempts.js | 16 + lib/resources/SetupIntents.js | 46 ++ lib/resources/ShippingRates.js | 31 + lib/resources/Sigma/ScheduledQueryRuns.js | 21 + lib/resources/Sources.js | 36 + lib/resources/SubscriptionItems.js | 47 ++ lib/resources/SubscriptionSchedules.js | 41 ++ lib/resources/Subscriptions.js | 52 ++ lib/resources/TaxCodes.js | 21 + lib/resources/TaxRates.js | 31 + lib/resources/Terminal/Configurations.js | 36 + lib/resources/Terminal/ConnectionTokens.js | 15 + lib/resources/Terminal/Locations.js | 36 + lib/resources/Terminal/Readers.js | 56 ++ lib/resources/TestHelpers/Customers.js | 15 + lib/resources/TestHelpers/Issuing/Cards.js | 30 + lib/resources/TestHelpers/Refunds.js | 15 + lib/resources/TestHelpers/Terminal/Readers.js | 15 + lib/resources/TestHelpers/TestClocks.js | 36 + .../TestHelpers/Treasury/InboundTransfers.js | 25 + .../TestHelpers/Treasury/OutboundPayments.js | 25 + .../TestHelpers/Treasury/OutboundTransfers.js | 25 + .../TestHelpers/Treasury/ReceivedCredits.js | 15 + .../TestHelpers/Treasury/ReceivedDebits.js | 15 + lib/resources/Tokens.js | 20 + lib/resources/Topups.js | 36 + lib/resources/Transfers.js | 52 ++ lib/resources/Treasury/CreditReversals.js | 26 + lib/resources/Treasury/DebitReversals.js | 26 + lib/resources/Treasury/FinancialAccounts.js | 41 ++ lib/resources/Treasury/InboundTransfers.js | 31 + lib/resources/Treasury/OutboundPayments.js | 31 + lib/resources/Treasury/OutboundTransfers.js | 31 + lib/resources/Treasury/ReceivedCredits.js | 21 + lib/resources/Treasury/ReceivedDebits.js | 21 + lib/resources/Treasury/TransactionEntries.js | 21 + lib/resources/Treasury/Transactions.js | 21 + lib/resources/WebhookEndpoints.js | 36 + lib/stripe.js | 610 +++++++++++++++++ lib/utils.js | 451 +++++++++++++ package.json | 9 +- types/2022-08-01/Checkout/Sessions.d.ts | 2 +- types/2022-08-01/FileLinks.d.ts | 2 +- types/2022-08-01/Files.d.ts | 6 +- yarn.lock | 7 - 121 files changed, 6422 insertions(+), 58 deletions(-) create mode 100644 lib/Error.js create mode 100644 lib/ResourceNamespace.js create mode 100644 lib/StripeMethod.basic.js create mode 100644 lib/StripeMethod.js create mode 100644 lib/StripeResource.js create mode 100644 lib/Webhooks.js create mode 100644 lib/apiVersion.js create mode 100644 lib/autoPagination.js create mode 100644 lib/crypto/CryptoProvider.js create mode 100644 lib/crypto/NodeCryptoProvider.js create mode 100644 lib/crypto/SubtleCryptoProvider.js create mode 100644 lib/makeRequest.js create mode 100644 lib/multipart.js create mode 100644 lib/net/FetchHttpClient.js create mode 100644 lib/net/HttpClient.js create mode 100644 lib/net/NodeHttpClient.js create mode 100644 lib/resources.js create mode 100644 lib/resources/AccountLinks.js create mode 100644 lib/resources/Accounts.js create mode 100644 lib/resources/ApplePayDomains.js create mode 100644 lib/resources/ApplicationFees.js create mode 100644 lib/resources/Apps/Secrets.js create mode 100644 lib/resources/Balance.js create mode 100644 lib/resources/BalanceTransactions.js create mode 100644 lib/resources/BillingPortal/Configurations.js create mode 100644 lib/resources/BillingPortal/Sessions.js create mode 100644 lib/resources/Charges.js create mode 100644 lib/resources/Checkout/Sessions.js create mode 100644 lib/resources/CountrySpecs.js create mode 100644 lib/resources/Coupons.js create mode 100644 lib/resources/CreditNotes.js create mode 100644 lib/resources/Customers.js create mode 100644 lib/resources/Disputes.js create mode 100644 lib/resources/EphemeralKeys.js create mode 100644 lib/resources/Events.js create mode 100644 lib/resources/ExchangeRates.js create mode 100644 lib/resources/FileLinks.js create mode 100644 lib/resources/Files.js create mode 100644 lib/resources/FinancialConnections/Accounts.js create mode 100644 lib/resources/FinancialConnections/Sessions.js create mode 100644 lib/resources/Identity/VerificationReports.js create mode 100644 lib/resources/Identity/VerificationSessions.js create mode 100644 lib/resources/InvoiceItems.js create mode 100644 lib/resources/Invoices.js create mode 100644 lib/resources/Issuing/Authorizations.js create mode 100644 lib/resources/Issuing/Cardholders.js create mode 100644 lib/resources/Issuing/Cards.js create mode 100644 lib/resources/Issuing/Disputes.js create mode 100644 lib/resources/Issuing/Transactions.js create mode 100644 lib/resources/Mandates.js create mode 100644 lib/resources/OAuth.js create mode 100644 lib/resources/Orders.js create mode 100644 lib/resources/PaymentIntents.js create mode 100644 lib/resources/PaymentLinks.js create mode 100644 lib/resources/PaymentMethods.js create mode 100644 lib/resources/Payouts.js create mode 100644 lib/resources/Plans.js create mode 100644 lib/resources/Prices.js create mode 100644 lib/resources/Products.js create mode 100644 lib/resources/PromotionCodes.js create mode 100644 lib/resources/Quotes.js create mode 100644 lib/resources/Radar/EarlyFraudWarnings.js create mode 100644 lib/resources/Radar/ValueListItems.js create mode 100644 lib/resources/Radar/ValueLists.js create mode 100644 lib/resources/Refunds.js create mode 100644 lib/resources/Reporting/ReportRuns.js create mode 100644 lib/resources/Reporting/ReportTypes.js create mode 100644 lib/resources/Reviews.js create mode 100644 lib/resources/SKUs.js create mode 100644 lib/resources/SetupAttempts.js create mode 100644 lib/resources/SetupIntents.js create mode 100644 lib/resources/ShippingRates.js create mode 100644 lib/resources/Sigma/ScheduledQueryRuns.js create mode 100644 lib/resources/Sources.js create mode 100644 lib/resources/SubscriptionItems.js create mode 100644 lib/resources/SubscriptionSchedules.js create mode 100644 lib/resources/Subscriptions.js create mode 100644 lib/resources/TaxCodes.js create mode 100644 lib/resources/TaxRates.js create mode 100644 lib/resources/Terminal/Configurations.js create mode 100644 lib/resources/Terminal/ConnectionTokens.js create mode 100644 lib/resources/Terminal/Locations.js create mode 100644 lib/resources/Terminal/Readers.js create mode 100644 lib/resources/TestHelpers/Customers.js create mode 100644 lib/resources/TestHelpers/Issuing/Cards.js create mode 100644 lib/resources/TestHelpers/Refunds.js create mode 100644 lib/resources/TestHelpers/Terminal/Readers.js create mode 100644 lib/resources/TestHelpers/TestClocks.js create mode 100644 lib/resources/TestHelpers/Treasury/InboundTransfers.js create mode 100644 lib/resources/TestHelpers/Treasury/OutboundPayments.js create mode 100644 lib/resources/TestHelpers/Treasury/OutboundTransfers.js create mode 100644 lib/resources/TestHelpers/Treasury/ReceivedCredits.js create mode 100644 lib/resources/TestHelpers/Treasury/ReceivedDebits.js create mode 100644 lib/resources/Tokens.js create mode 100644 lib/resources/Topups.js create mode 100644 lib/resources/Transfers.js create mode 100644 lib/resources/Treasury/CreditReversals.js create mode 100644 lib/resources/Treasury/DebitReversals.js create mode 100644 lib/resources/Treasury/FinancialAccounts.js create mode 100644 lib/resources/Treasury/InboundTransfers.js create mode 100644 lib/resources/Treasury/OutboundPayments.js create mode 100644 lib/resources/Treasury/OutboundTransfers.js create mode 100644 lib/resources/Treasury/ReceivedCredits.js create mode 100644 lib/resources/Treasury/ReceivedDebits.js create mode 100644 lib/resources/Treasury/TransactionEntries.js create mode 100644 lib/resources/Treasury/Transactions.js create mode 100644 lib/resources/WebhookEndpoints.js create mode 100644 lib/stripe.js create mode 100644 lib/utils.js diff --git a/.eslintignore b/.eslintignore index aaaf6b9a4c..a6de844ce0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,2 @@ *.node*.js -node_modules -lib +node_modules \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 0f3c0a64f4..5111f4c6da 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -249,29 +249,4 @@ module.exports = { }, plugins: ['prettier'], extends: ['plugin:prettier/recommended'], - overrides: [ - { - files: ["**/*.ts"], - parser: "@typescript-eslint/parser", - plugins: ['@typescript-eslint', 'prettier'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - rules: { - '@typescript-eslint/no-use-before-define': 0, - '@typescript-eslint/no-empty-interface': 0, - '@typescript-eslint/no-unused-vars': 0, - '@typescript-eslint/triple-slash-reference': 0, - '@typescript-eslint/ban-ts-ignore': 0, - '@typescript-eslint/no-empty-function': 0, - '@typescript-eslint/camelcase': 0, - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/explicit-function-return-type': 0, - 'prefer-rest-params': 'off', - }, - }, - ], }; diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07a9335515..61f9d08d3e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,11 +41,11 @@ jobs: restore-keys: | ${{ runner.os }}-yarn- - - name: Build Typescript - run: yarn && yarn build + - name: Node check + run: find . -name "*.js" -type f -not -path "./node_modules/*" -not -path "./\.*" -exec node --check {} \; - name: Lint - run: yarn lint + run: yarn && yarn lint test: name: Test (${{ matrix.node }}) diff --git a/.gitignore b/.gitignore index 9c0b948ea6..ee483f2f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ tags .nyc_output coverage .idea -lib diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d1a8a8020..e35d7713c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,5 @@ # Changelog -## 10.8.0 - 2022-09-06 -* [#1544](https://github.com/stripe/stripe-node/pull/1544) API Updates - * Add support for new value `terminal_reader_splashscreen` on enums `File.purpose` and `FileListParams.purpose` -* [#1543](https://github.com/stripe/stripe-node/pull/1543) Migrate Stripe infrastructure to Typescript -* [#1539](https://github.com/stripe/stripe-node/pull/1539) Build Typescript and migrate StripeResource - ## 10.7.0 - 2022-08-31 * [#1540](https://github.com/stripe/stripe-node/pull/1540) API Updates * Add support for new values `de-CH`, `en-CH`, `en-PL`, `en-PT`, `fr-CH`, `it-CH`, `pl-PL`, and `pt-PT` on enums `OrderCreateParams.payment.settings.payment_method_options.klarna.preferred_locale`, `OrderUpdateParams.payment.settings.payment_method_options.klarna.preferred_locale`, `PaymentIntentConfirmParams.payment_method_options.klarna.preferred_locale`, `PaymentIntentCreateParams.payment_method_options.klarna.preferred_locale`, and `PaymentIntentUpdateParams.payment_method_options.klarna.preferred_locale` diff --git a/OPENAPI_VERSION b/OPENAPI_VERSION index ffd0a4e351..169d5f2568 100644 --- a/OPENAPI_VERSION +++ b/OPENAPI_VERSION @@ -1 +1 @@ -v189 \ No newline at end of file +v188 \ No newline at end of file diff --git a/VERSION b/VERSION index 2a3262d8a9..1bcdaf5fec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.8.0 +10.7.0 diff --git a/lib/Error.js b/lib/Error.js new file mode 100644 index 0000000000..2ad52c931a --- /dev/null +++ b/lib/Error.js @@ -0,0 +1,142 @@ +'use strict'; + +/** + * StripeError is the base error from which all other more specific Stripe errors derive. + * Specifically for errors returned from Stripe's REST API. + */ +class StripeError extends Error { + constructor(raw = {}) { + super(raw.message); + this.type = this.constructor.name; + + this.raw = raw; + this.rawType = raw.type; + this.code = raw.code; + this.doc_url = raw.doc_url; + this.param = raw.param; + this.detail = raw.detail; + this.headers = raw.headers; + this.requestId = raw.requestId; + this.statusCode = raw.statusCode; + this.message = raw.message; + + this.charge = raw.charge; + this.decline_code = raw.decline_code; + this.payment_intent = raw.payment_intent; + this.payment_method = raw.payment_method; + this.payment_method_type = raw.payment_method_type; + this.setup_intent = raw.setup_intent; + this.source = raw.source; + } + + /** + * Helper factory which takes raw stripe errors and outputs wrapping instances + */ + static generate(rawStripeError) { + switch (rawStripeError.type) { + case 'card_error': + return new StripeCardError(rawStripeError); + case 'invalid_request_error': + return new StripeInvalidRequestError(rawStripeError); + case 'api_error': + return new StripeAPIError(rawStripeError); + case 'authentication_error': + return new StripeAuthenticationError(rawStripeError); + case 'rate_limit_error': + return new StripeRateLimitError(rawStripeError); + case 'idempotency_error': + return new StripeIdempotencyError(rawStripeError); + case 'invalid_grant': + return new StripeInvalidGrantError(rawStripeError); + default: + return new StripeUnknownError(rawStripeError); + } + } +} + +// Specific Stripe Error types: + +/** + * CardError is raised when a user enters a card that can't be charged for + * some reason. + */ +class StripeCardError extends StripeError {} + +/** + * InvalidRequestError is raised when a request is initiated with invalid + * parameters. + */ +class StripeInvalidRequestError extends StripeError {} + +/** + * APIError is a generic error that may be raised in cases where none of the + * other named errors cover the problem. It could also be raised in the case + * that a new error has been introduced in the API, but this version of the + * Node.JS SDK doesn't know how to handle it. + */ +class StripeAPIError extends StripeError {} + +/** + * AuthenticationError is raised when invalid credentials are used to connect + * to Stripe's servers. + */ +class StripeAuthenticationError extends StripeError {} + +/** + * PermissionError is raised in cases where access was attempted on a resource + * that wasn't allowed. + */ +class StripePermissionError extends StripeError {} + +/** + * RateLimitError is raised in cases where an account is putting too much load + * on Stripe's API servers (usually by performing too many requests). Please + * back off on request rate. + */ +class StripeRateLimitError extends StripeError {} + +/** + * StripeConnectionError is raised in the event that the SDK can't connect to + * Stripe's servers. That can be for a variety of different reasons from a + * downed network to a bad TLS certificate. + */ +class StripeConnectionError extends StripeError {} + +/** + * SignatureVerificationError is raised when the signature verification for a + * webhook fails + */ +class StripeSignatureVerificationError extends StripeError {} + +/** + * IdempotencyError is raised in cases where an idempotency key was used + * improperly. + */ +class StripeIdempotencyError extends StripeError {} + +/** + * InvalidGrantError is raised when a specified code doesn't exist, is + * expired, has been used, or doesn't belong to you; a refresh token doesn't + * exist, or doesn't belong to you; or if an API key's mode (live or test) + * doesn't match the mode of a code or refresh token. + */ +class StripeInvalidGrantError extends StripeError {} + +/** + * Any other error from Stripe not specifically captured above + */ +class StripeUnknownError extends StripeError {} + +module.exports.generate = StripeError.generate; +module.exports.StripeError = StripeError; +module.exports.StripeCardError = StripeCardError; +module.exports.StripeInvalidRequestError = StripeInvalidRequestError; +module.exports.StripeAPIError = StripeAPIError; +module.exports.StripeAuthenticationError = StripeAuthenticationError; +module.exports.StripePermissionError = StripePermissionError; +module.exports.StripeRateLimitError = StripeRateLimitError; +module.exports.StripeConnectionError = StripeConnectionError; +module.exports.StripeSignatureVerificationError = StripeSignatureVerificationError; +module.exports.StripeIdempotencyError = StripeIdempotencyError; +module.exports.StripeInvalidGrantError = StripeInvalidGrantError; +module.exports.StripeUnknownError = StripeUnknownError; diff --git a/lib/ResourceNamespace.js b/lib/ResourceNamespace.js new file mode 100644 index 0000000000..0d76787d81 --- /dev/null +++ b/lib/ResourceNamespace.js @@ -0,0 +1,22 @@ +'use strict'; + +// ResourceNamespace allows you to create nested resources, i.e. `stripe.issuing.cards`. +// It also works recursively, so you could do i.e. `stripe.billing.invoicing.pay`. + +function ResourceNamespace(stripe, resources) { + for (const name in resources) { + const camelCaseName = name[0].toLowerCase() + name.substring(1); + + const resource = new resources[name](stripe); + + this[camelCaseName] = resource; + } +} + +module.exports = function(namespace, resources) { + return function(stripe) { + return new ResourceNamespace(stripe, resources); + }; +}; + +module.exports.ResourceNamespace = ResourceNamespace; diff --git a/lib/StripeMethod.basic.js b/lib/StripeMethod.basic.js new file mode 100644 index 0000000000..b9be5302e4 --- /dev/null +++ b/lib/StripeMethod.basic.js @@ -0,0 +1,32 @@ +'use strict'; + +const stripeMethod = require('./StripeMethod'); + +// DEPRECATED: These were kept for backwards compatibility in case users were +// using this, but basic methods are now explicitly defined on a resource. +module.exports = { + create: stripeMethod({ + method: 'POST', + }), + + list: stripeMethod({ + method: 'GET', + methodType: 'list', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + update: stripeMethod({ + method: 'POST', + path: '{id}', + }), + + // Avoid 'delete' keyword in JS + del: stripeMethod({ + method: 'DELETE', + path: '{id}', + }), +}; diff --git a/lib/StripeMethod.js b/lib/StripeMethod.js new file mode 100644 index 0000000000..78018da23a --- /dev/null +++ b/lib/StripeMethod.js @@ -0,0 +1,58 @@ +'use strict'; + +const utils = require('./utils'); +const makeRequest = require('./makeRequest'); +const makeAutoPaginationMethods = require('./autoPagination') + .makeAutoPaginationMethods; + +/** + * Create an API method from the declared spec. + * + * @param [spec.method='GET'] Request Method (POST, GET, DELETE, PUT) + * @param [spec.path=''] Path to be appended to the API BASE_PATH, joined with + * the instance's path (e.g. 'charges' or 'customers') + * @param [spec.fullPath=''] Fully qualified path to the method (eg. /v1/a/b/c). + * If this is specified, path should not be specified. + * @param [spec.urlParams=[]] Array of required arguments in the order that they + * must be passed by the consumer of the API. Subsequent optional arguments are + * optionally passed through a hash (Object) as the penultimate argument + * (preceding the also-optional callback argument + * @param [spec.encode] Function for mutating input parameters to a method. + * Usefully for applying transforms to data on a per-method basis. + * @param [spec.host] Hostname for the request. + */ +function stripeMethod(spec) { + if (spec.path !== undefined && spec.fullPath !== undefined) { + throw new Error( + `Method spec specified both a 'path' (${spec.path}) and a 'fullPath' (${spec.fullPath}).` + ); + } + return function(...args) { + const callback = typeof args[args.length - 1] == 'function' && args.pop(); + + spec.urlParams = utils.extractUrlParams( + spec.fullPath || this.createResourcePathWithSymbols(spec.path || '') + ); + + const requestPromise = utils.callbackifyPromiseWithTimeout( + makeRequest(this, args, spec, {}), + callback + ); + + // Please note `spec.methodType === 'search'` is beta functionality and this + // interface is subject to change/removal at any time. + if (spec.methodType === 'list' || spec.methodType === 'search') { + const autoPaginationMethods = makeAutoPaginationMethods( + this, + args, + spec, + requestPromise + ); + Object.assign(requestPromise, autoPaginationMethods); + } + + return requestPromise; + }; +} + +module.exports = stripeMethod; diff --git a/lib/StripeResource.js b/lib/StripeResource.js new file mode 100644 index 0000000000..f56bb9bc72 --- /dev/null +++ b/lib/StripeResource.js @@ -0,0 +1,619 @@ +'use strict'; + +const utils = require('./utils'); +const { + StripeConnectionError, + StripeAuthenticationError, + StripePermissionError, + StripeRateLimitError, + StripeError, + StripeAPIError, +} = require('./Error'); + +const {HttpClient} = require('./net/HttpClient'); + +// Provide extension mechanism for Stripe Resource Sub-Classes +StripeResource.extend = utils.protoExtend; + +// Expose method-creator & prepared (basic) methods +StripeResource.method = require('./StripeMethod'); +StripeResource.BASIC_METHODS = require('./StripeMethod.basic'); + +StripeResource.MAX_BUFFERED_REQUEST_METRICS = 100; +const MAX_RETRY_AFTER_WAIT = 60; + +/** + * Encapsulates request logic for a Stripe Resource + */ +function StripeResource(stripe, deprecatedUrlData) { + this._stripe = stripe; + if (deprecatedUrlData) { + throw new Error( + 'Support for curried url params was dropped in stripe-node v7.0.0. Instead, pass two ids.' + ); + } + + this.basePath = utils.makeURLInterpolator( + this.basePath || stripe.getApiField('basePath') + ); + this.resourcePath = this.path; + this.path = utils.makeURLInterpolator(this.path); + + // DEPRECATED: This was kept for backwards compatibility in case users were + // using this, but basic methods are now explicitly defined on a resource. + if (this.includeBasic) { + this.includeBasic.forEach(function(methodName) { + this[methodName] = StripeResource.BASIC_METHODS[methodName]; + }, this); + } + + this.initialize(...arguments); +} + +StripeResource.prototype = { + path: '', + + // Methods that don't use the API's default '/v1' path can override it with this setting. + basePath: null, + + initialize() {}, + + // Function to override the default data processor. This allows full control + // over how a StripeResource's request data will get converted into an HTTP + // body. This is useful for non-standard HTTP requests. The function should + // take method name, data, and headers as arguments. + requestDataProcessor: null, + + // Function to add a validation checks before sending the request, errors should + // be thrown, and they will be passed to the callback/promise. + validateRequest: null, + + createFullPath(commandPath, urlData) { + const urlParts = [this.basePath(urlData), this.path(urlData)]; + + if (typeof commandPath === 'function') { + const computedCommandPath = commandPath(urlData); + // If we have no actual command path, we just omit it to avoid adding a + // trailing slash. This is important for top-level listing requests, which + // do not have a command path. + if (computedCommandPath) { + urlParts.push(computedCommandPath); + } + } else { + urlParts.push(commandPath); + } + + return this._joinUrlParts(urlParts); + }, + + // Creates a relative resource path with symbols left in (unlike + // createFullPath which takes some data to replace them with). For example it + // might produce: /invoices/{id} + createResourcePathWithSymbols(pathWithSymbols) { + // If there is no path beyond the resource path, we want to produce just + // / rather than //. + if (pathWithSymbols) { + return `/${this._joinUrlParts([this.resourcePath, pathWithSymbols])}`; + } else { + return `/${this.resourcePath}`; + } + }, + + _joinUrlParts(parts) { + // Replace any accidentally doubled up slashes. This previously used + // path.join, which would do this as well. Unfortunately we need to do this + // as the functions for creating paths are technically part of the public + // interface and so we need to preserve backwards compatibility. + return parts.join('/').replace(/\/{2,}/g, '/'); + }, + + // DEPRECATED: Here for backcompat in case users relied on this. + wrapTimeout: utils.callbackifyPromiseWithTimeout, + + _timeoutHandler(timeout, req, callback) { + return () => { + const timeoutErr = new TypeError('ETIMEDOUT'); + timeoutErr.code = 'ETIMEDOUT'; + + req.destroy(timeoutErr); + }; + }, + + _addHeadersDirectlyToObject(obj, headers) { + // For convenience, make some headers easily accessible on + // lastResponse. + + // NOTE: Stripe responds with lowercase header names/keys. + obj.requestId = headers['request-id']; + obj.stripeAccount = obj.stripeAccount || headers['stripe-account']; + obj.apiVersion = obj.apiVersion || headers['stripe-version']; + obj.idempotencyKey = obj.idempotencyKey || headers['idempotency-key']; + }, + + _makeResponseEvent(requestEvent, statusCode, headers) { + const requestEndTime = Date.now(); + const requestDurationMs = requestEndTime - requestEvent.request_start_time; + + return utils.removeNullish({ + api_version: headers['stripe-version'], + account: headers['stripe-account'], + idempotency_key: headers['idempotency-key'], + method: requestEvent.method, + path: requestEvent.path, + status: statusCode, + request_id: this._getRequestId(headers), + elapsed: requestDurationMs, + request_start_time: requestEvent.request_start_time, + request_end_time: requestEndTime, + }); + }, + + _getRequestId(headers) { + return headers['request-id']; + }, + + /** + * Used by methods with spec.streaming === true. For these methods, we do not + * buffer successful responses into memory or do parse them into stripe + * objects, we delegate that all of that to the user and pass back the raw + * http.Response object to the callback. + * + * (Unsuccessful responses shouldn't make it here, they should + * still be buffered/parsed and handled by _jsonResponseHandler -- see + * makeRequest) + */ + _streamingResponseHandler(requestEvent, callback) { + return (res) => { + const headers = res.getHeaders(); + + const streamCompleteCallback = () => { + const responseEvent = this._makeResponseEvent( + requestEvent, + res.getStatusCode(), + headers + ); + this._stripe._emitter.emit('response', responseEvent); + this._recordRequestMetrics( + this._getRequestId(headers), + responseEvent.elapsed + ); + }; + + const stream = res.toStream(streamCompleteCallback); + + // This is here for backwards compatibility, as the stream is a raw + // HTTP response in Node and the legacy behavior was to mutate this + // response. + this._addHeadersDirectlyToObject(stream, headers); + + return callback(null, stream); + }; + }, + + /** + * Default handler for Stripe responses. Buffers the response into memory, + * parses the JSON and returns it (i.e. passes it to the callback) if there + * is no "error" field. Otherwise constructs/passes an appropriate Error. + */ + _jsonResponseHandler(requestEvent, callback) { + return (res) => { + const headers = res.getHeaders(); + const requestId = this._getRequestId(headers); + const statusCode = res.getStatusCode(); + + const responseEvent = this._makeResponseEvent( + requestEvent, + statusCode, + headers + ); + this._stripe._emitter.emit('response', responseEvent); + + res + .toJSON() + .then( + (jsonResponse) => { + if (jsonResponse.error) { + let err; + + // Convert OAuth error responses into a standard format + // so that the rest of the error logic can be shared + if (typeof jsonResponse.error === 'string') { + jsonResponse.error = { + type: jsonResponse.error, + message: jsonResponse.error_description, + }; + } + + jsonResponse.error.headers = headers; + jsonResponse.error.statusCode = statusCode; + jsonResponse.error.requestId = requestId; + + if (statusCode === 401) { + err = new StripeAuthenticationError(jsonResponse.error); + } else if (statusCode === 403) { + err = new StripePermissionError(jsonResponse.error); + } else if (statusCode === 429) { + err = new StripeRateLimitError(jsonResponse.error); + } else { + err = StripeError.generate(jsonResponse.error); + } + + throw err; + } + + return jsonResponse; + }, + (e) => { + throw new StripeAPIError({ + message: 'Invalid JSON received from the Stripe API', + exception: e, + requestId: headers['request-id'], + }); + } + ) + .then( + (jsonResponse) => { + this._recordRequestMetrics(requestId, responseEvent.elapsed); + + // Expose raw response object. + const rawResponse = res.getRawResponse(); + this._addHeadersDirectlyToObject(rawResponse, headers); + Object.defineProperty(jsonResponse, 'lastResponse', { + enumerable: false, + writable: false, + value: rawResponse, + }); + + callback.call(this, null, jsonResponse); + }, + (e) => callback.call(this, e, null) + ); + }; + }, + + _generateConnectionErrorMessage(requestRetries) { + return `An error occurred with our connection to Stripe.${ + requestRetries > 0 ? ` Request was retried ${requestRetries} times.` : '' + }`; + }, + + _errorHandler(req, requestRetries, callback) { + return (message, detail) => { + callback.call( + this, + new StripeConnectionError({ + message: this._generateConnectionErrorMessage(requestRetries), + detail: error, + }), + null + ); + }; + }, + + // 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, 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; + } + + // Retry on connection error. + if (!res) { + return true; + } + + // The API may ask us not to retry (e.g., if doing so would be a no-op) + // or advise us to retry (e.g., in cases of lock timeouts); we defer to that. + if (res.getHeaders()['stripe-should-retry'] === 'false') { + return false; + } + if (res.getHeaders()['stripe-should-retry'] === 'true') { + return true; + } + + // Retry on conflict errors. + if (res.getStatusCode() === 409) { + return true; + } + + // Retry on 500, 503, and other internal errors. + // + // Note that we expect the stripe-should-retry header to be false + // in most cases when a 500 is returned, since our idempotency framework + // would typically replay it anyway. + if (res.getStatusCode() >= 500) { + return true; + } + + return false; + }, + + _getSleepTimeInMS(numRetries, retryAfter = null) { + const initialNetworkRetryDelay = this._stripe.getInitialNetworkRetryDelay(); + const maxNetworkRetryDelay = this._stripe.getMaxNetworkRetryDelay(); + + // Apply exponential backoff with initialNetworkRetryDelay on the + // number of numRetries so far as inputs. Do not allow the number to exceed + // maxNetworkRetryDelay. + let sleepSeconds = Math.min( + initialNetworkRetryDelay * Math.pow(numRetries - 1, 2), + maxNetworkRetryDelay + ); + + // Apply some jitter by randomizing the value in the range of + // (sleepSeconds / 2) to (sleepSeconds). + sleepSeconds *= 0.5 * (1 + Math.random()); + + // But never sleep less than the base sleep seconds. + sleepSeconds = Math.max(initialNetworkRetryDelay, sleepSeconds); + + // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask. + if (Number.isInteger(retryAfter) && retryAfter <= MAX_RETRY_AFTER_WAIT) { + sleepSeconds = Math.max(sleepSeconds, retryAfter); + } + + return sleepSeconds * 1000; + }, + + // Max retries can be set on a per request basis. Favor those over the global setting + _getMaxNetworkRetries(settings = {}) { + return settings.maxNetworkRetries && + Number.isInteger(settings.maxNetworkRetries) + ? settings.maxNetworkRetries + : this._stripe.getMaxNetworkRetries(); + }, + + _defaultIdempotencyKey(method, settings) { + // If this is a POST and we allow multiple retries, ensure an idempotency key. + const maxRetries = this._getMaxNetworkRetries(settings); + + if (method === 'POST' && maxRetries > 0) { + return `stripe-node-retry-${utils.uuid4()}`; + } + return null; + }, + + _makeHeaders( + auth, + contentLength, + apiVersion, + clientUserAgent, + method, + userSuppliedHeaders, + userSuppliedSettings + ) { + const defaultHeaders = { + // Use specified auth token or use default from this stripe instance: + Authorization: auth ? `Bearer ${auth}` : this._stripe.getApiField('auth'), + Accept: 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': this._getUserAgentString(), + 'X-Stripe-Client-User-Agent': clientUserAgent, + 'X-Stripe-Client-Telemetry': this._getTelemetryHeader(), + 'Stripe-Version': apiVersion, + 'Stripe-Account': this._stripe.getApiField('stripeAccount'), + 'Idempotency-Key': this._defaultIdempotencyKey( + method, + userSuppliedSettings + ), + }; + + // As per https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2: + // A user agent SHOULD send a Content-Length in a request message when + // no Transfer-Encoding is sent and the request method defines a meaning + // for an enclosed payload body. For example, a Content-Length header + // field is normally sent in a POST request even when the value is 0 + // (indicating an empty payload body). A user agent SHOULD NOT send a + // Content-Length header field when the request message does not contain + // a payload body and the method semantics do not anticipate such a + // body. + // + // These method types are expected to have bodies and so we should always + // include a Content-Length. + const methodHasPayload = + method == 'POST' || method == 'PUT' || method == 'PATCH'; + + // If a content length was specified, we always include it regardless of + // whether the method semantics anticipate such a body. This keeps us + // consistent with historical behavior. We do however want to warn on this + // and fix these cases as they are semantically incorrect. + if (methodHasPayload || contentLength) { + if (!methodHasPayload) { + utils.emitWarning( + `${method} method had non-zero contentLength but no payload is expected for this verb` + ); + } + defaultHeaders['Content-Length'] = contentLength; + } + + return Object.assign( + utils.removeNullish(defaultHeaders), + // If the user supplied, say 'idempotency-key', override instead of appending by ensuring caps are the same. + utils.normalizeHeaders(userSuppliedHeaders) + ); + }, + + _getUserAgentString() { + const packageVersion = this._stripe.getConstant('PACKAGE_VERSION'); + const appInfo = this._stripe._appInfo + ? this._stripe.getAppInfoAsString() + : ''; + + return `Stripe/v1 NodeBindings/${packageVersion} ${appInfo}`.trim(); + }, + + _getTelemetryHeader() { + if ( + this._stripe.getTelemetryEnabled() && + this._stripe._prevRequestMetrics.length > 0 + ) { + const metrics = this._stripe._prevRequestMetrics.shift(); + return JSON.stringify({ + last_request_metrics: metrics, + }); + } + }, + + _recordRequestMetrics(requestId, requestDurationMs) { + if (this._stripe.getTelemetryEnabled() && requestId) { + if ( + this._stripe._prevRequestMetrics.length > + StripeResource.MAX_BUFFERED_REQUEST_METRICS + ) { + utils.emitWarning( + 'Request metrics buffer is full, dropping telemetry message.' + ); + } else { + this._stripe._prevRequestMetrics.push({ + request_id: requestId, + request_duration_ms: requestDurationMs, + }); + } + } + }, + + _request(method, host, path, data, auth, options = {}, callback) { + let requestData; + + const retryRequest = ( + requestFn, + apiVersion, + headers, + requestRetries, + retryAfter + ) => { + return setTimeout( + requestFn, + this._getSleepTimeInMS(requestRetries, retryAfter), + apiVersion, + headers, + requestRetries + 1 + ); + }; + + const makeRequest = (apiVersion, headers, numRetries) => { + // timeout can be set on a per-request basis. Favor that over the global setting + const timeout = + options.settings && + Number.isInteger(options.settings.timeout) && + options.settings.timeout >= 0 + ? options.settings.timeout + : this._stripe.getApiField('timeout'); + + const req = this._stripe + .getApiField('httpClient') + .makeRequest( + host || this._stripe.getApiField('host'), + this._stripe.getApiField('port'), + path, + method, + headers, + requestData, + this._stripe.getApiField('protocol'), + timeout + ); + + const requestStartTime = Date.now(); + + const requestEvent = utils.removeNullish({ + api_version: apiVersion, + account: headers['Stripe-Account'], + idempotency_key: headers['Idempotency-Key'], + method, + path, + request_start_time: requestStartTime, + }); + + const requestRetries = numRetries || 0; + + const maxRetries = this._getMaxNetworkRetries(options.settings); + + this._stripe._emitter.emit('request', requestEvent); + + req + .then((res) => { + if (this._shouldRetry(res, requestRetries, maxRetries)) { + return retryRequest( + makeRequest, + apiVersion, + headers, + requestRetries, + res.getHeaders()['retry-after'] + ); + } else if (options.streaming && res.getStatusCode() < 400) { + return this._streamingResponseHandler(requestEvent, callback)(res); + } else { + return this._jsonResponseHandler(requestEvent, callback)(res); + } + }) + .catch((error) => { + if (this._shouldRetry(null, requestRetries, maxRetries, error)) { + return retryRequest( + makeRequest, + apiVersion, + headers, + requestRetries, + null + ); + } else { + const isTimeoutError = + error.code && error.code === HttpClient.TIMEOUT_ERROR_CODE; + + return callback.call( + this, + new StripeConnectionError({ + message: isTimeoutError + ? `Request aborted due to timeout being reached (${timeout}ms)` + : this._generateConnectionErrorMessage(requestRetries), + detail: error, + }) + ); + } + }); + }; + + const prepareAndMakeRequest = (error, data) => { + if (error) { + return callback(error); + } + + requestData = data; + + this._stripe.getClientUserAgent((clientUserAgent) => { + const apiVersion = this._stripe.getApiField('version'); + const headers = this._makeHeaders( + auth, + requestData.length, + apiVersion, + clientUserAgent, + method, + options.headers, + options.settings + ); + + makeRequest(apiVersion, headers); + }); + }; + + if (this.requestDataProcessor) { + this.requestDataProcessor( + method, + data, + options.headers, + prepareAndMakeRequest + ); + } else { + prepareAndMakeRequest(null, utils.stringifyRequestData(data || {})); + } + }, +}; + +module.exports = StripeResource; diff --git a/lib/Webhooks.js b/lib/Webhooks.js new file mode 100644 index 0000000000..ffbe2004a9 --- /dev/null +++ b/lib/Webhooks.js @@ -0,0 +1,277 @@ +'use strict'; + +const utils = require('./utils'); +const {StripeError, StripeSignatureVerificationError} = require('./Error'); + +const Webhook = { + DEFAULT_TOLERANCE: 300, // 5 minutes + + constructEvent(payload, header, secret, tolerance, cryptoProvider) { + this.signature.verifyHeader( + payload, + header, + secret, + tolerance || Webhook.DEFAULT_TOLERANCE, + cryptoProvider + ); + + const jsonPayload = JSON.parse(payload); + return jsonPayload; + }, + + async constructEventAsync( + payload, + header, + secret, + tolerance, + cryptoProvider + ) { + await this.signature.verifyHeaderAsync( + payload, + header, + secret, + tolerance || Webhook.DEFAULT_TOLERANCE, + cryptoProvider + ); + + const jsonPayload = JSON.parse(payload); + return jsonPayload; + }, + + /** + * Generates a header to be used for webhook mocking + * + * @typedef {object} opts + * @property {number} timestamp - Timestamp of the header. Defaults to Date.now() + * @property {string} payload - JSON stringified payload object, containing the 'id' and 'object' parameters + * @property {string} secret - Stripe webhook secret 'whsec_...' + * @property {string} scheme - Version of API to hit. Defaults to 'v1'. + * @property {string} signature - Computed webhook signature + * @property {CryptoProvider} cryptoProvider - Crypto provider to use for computing the signature if none was provided. Defaults to NodeCryptoProvider. + */ + generateTestHeaderString: function(opts) { + if (!opts) { + throw new StripeError({ + message: 'Options are required', + }); + } + + opts.timestamp = + Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000); + opts.scheme = opts.scheme || signature.EXPECTED_SCHEME; + + opts.cryptoProvider = opts.cryptoProvider || getNodeCryptoProvider(); + + opts.signature = + opts.signature || + opts.cryptoProvider.computeHMACSignature( + opts.timestamp + '.' + opts.payload, + opts.secret + ); + + const generatedHeader = [ + 't=' + opts.timestamp, + opts.scheme + '=' + opts.signature, + ].join(','); + + return generatedHeader; + }, +}; + +const signature = { + EXPECTED_SCHEME: 'v1', + + verifyHeader( + encodedPayload, + encodedHeader, + secret, + tolerance, + cryptoProvider + ) { + const { + decodedHeader: header, + decodedPayload: payload, + details, + } = parseEventDetails(encodedPayload, encodedHeader, this.EXPECTED_SCHEME); + + cryptoProvider = cryptoProvider || getNodeCryptoProvider(); + const expectedSignature = cryptoProvider.computeHMACSignature( + makeHMACContent(payload, details), + secret + ); + + validateComputedSignature( + payload, + header, + details, + expectedSignature, + tolerance + ); + + return true; + }, + + async verifyHeaderAsync( + encodedPayload, + encodedHeader, + secret, + tolerance, + cryptoProvider + ) { + const { + decodedHeader: header, + decodedPayload: payload, + details, + } = parseEventDetails(encodedPayload, encodedHeader, this.EXPECTED_SCHEME); + + cryptoProvider = cryptoProvider || getNodeCryptoProvider(); + + const expectedSignature = await cryptoProvider.computeHMACSignatureAsync( + makeHMACContent(payload, details), + secret + ); + + return validateComputedSignature( + payload, + header, + details, + expectedSignature, + tolerance + ); + }, +}; + +function makeHMACContent(payload, details) { + return `${details.timestamp}.${payload}`; +} + +function parseEventDetails(encodedPayload, encodedHeader, expectedScheme) { + const decodedPayload = Buffer.isBuffer(encodedPayload) + ? encodedPayload.toString('utf8') + : encodedPayload; + + // Express's type for `Request#headers` is `string | []string` + // which is because the `set-cookie` header is an array, + // but no other headers are an array (docs: https://nodejs.org/api/http.html#http_message_headers) + // (Express's Request class is an extension of http.IncomingMessage, and doesn't appear to be relevantly modified: https://github.com/expressjs/express/blob/master/lib/request.js#L31) + if (Array.isArray(encodedHeader)) { + throw new Error( + 'Unexpected: An array was passed as a header, which should not be possible for the stripe-signature header.' + ); + } + + const decodedHeader = Buffer.isBuffer(encodedHeader) + ? encodedHeader.toString('utf8') + : encodedHeader; + + const details = parseHeader(decodedHeader, expectedScheme); + + if (!details || details.timestamp === -1) { + throw new StripeSignatureVerificationError({ + message: 'Unable to extract timestamp and signatures from header', + detail: { + decodedHeader, + decodedPayload, + }, + }); + } + + if (!details.signatures.length) { + throw new StripeSignatureVerificationError({ + message: 'No signatures found with expected scheme', + detail: { + decodedHeader, + decodedPayload, + }, + }); + } + + return { + decodedPayload, + decodedHeader, + details, + }; +} + +function validateComputedSignature( + payload, + header, + details, + expectedSignature, + tolerance +) { + const signatureFound = !!details.signatures.filter( + utils.secureCompare.bind(utils, expectedSignature) + ).length; + + if (!signatureFound) { + throw new StripeSignatureVerificationError({ + message: + 'No signatures found matching the expected signature for payload.' + + ' Are you passing the raw request body you received from Stripe?' + + ' https://github.com/stripe/stripe-node#webhook-signing', + detail: { + header, + payload, + }, + }); + } + + const timestampAge = Math.floor(Date.now() / 1000) - details.timestamp; + + if (tolerance > 0 && timestampAge > tolerance) { + throw new StripeSignatureVerificationError({ + message: 'Timestamp outside the tolerance zone', + detail: { + header, + payload, + }, + }); + } + + return true; +} + +function parseHeader(header, scheme) { + if (typeof header !== 'string') { + return null; + } + + return header.split(',').reduce( + (accum, item) => { + const kv = item.split('='); + + if (kv[0] === 't') { + accum.timestamp = kv[1]; + } + + if (kv[0] === scheme) { + accum.signatures.push(kv[1]); + } + + return accum; + }, + { + timestamp: -1, + signatures: [], + } + ); +} + +let webhooksNodeCryptoProviderInstance = null; + +/** + * Lazily instantiate a NodeCryptoProvider instance. This is a stateless object + * so a singleton can be used here. + */ +function getNodeCryptoProvider() { + if (!webhooksNodeCryptoProviderInstance) { + const NodeCryptoProvider = require('./crypto/NodeCryptoProvider'); + webhooksNodeCryptoProviderInstance = new NodeCryptoProvider(); + } + return webhooksNodeCryptoProviderInstance; +} + +Webhook.signature = signature; + +module.exports = Webhook; diff --git a/lib/apiVersion.js b/lib/apiVersion.js new file mode 100644 index 0000000000..704a3619eb --- /dev/null +++ b/lib/apiVersion.js @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec + +module.exports = {ApiVersion: '2022-08-01'}; diff --git a/lib/autoPagination.js b/lib/autoPagination.js new file mode 100644 index 0000000000..fe6f1f15b6 --- /dev/null +++ b/lib/autoPagination.js @@ -0,0 +1,278 @@ +'use strict'; + +const makeRequest = require('./makeRequest'); +const utils = require('./utils'); + +function makeAutoPaginationMethods(self, requestArgs, spec, firstPagePromise) { + const promiseCache = {currentPromise: null}; + const reverseIteration = isReverseIteration(requestArgs); + let pagePromise = firstPagePromise; + let i = 0; + + // Search and List methods iterate differently. + // Search relies on a `next_page` token and can only iterate in one direction. + // List relies on either an `ending_before` or `starting_after` field with + // an item ID to paginate and is bi-directional. + // + // Please note: spec.methodType === 'search' is beta functionality and is + // subject to change/removal at any time. + let getNextPagePromise; + if (spec.methodType === 'search') { + getNextPagePromise = (pageResult) => { + if (!pageResult.next_page) { + throw Error( + 'Unexpected: Stripe API response does not have a well-formed `next_page` field, but `has_more` was true.' + ); + } + return makeRequest(self, requestArgs, spec, { + page: pageResult.next_page, + }); + }; + } else { + getNextPagePromise = (pageResult) => { + const lastId = getLastId(pageResult, reverseIteration); + return makeRequest(self, requestArgs, spec, { + [reverseIteration ? 'ending_before' : 'starting_after']: lastId, + }); + }; + } + + function iterate(pageResult) { + if ( + !( + pageResult && + pageResult.data && + typeof pageResult.data.length === 'number' + ) + ) { + throw Error( + 'Unexpected: Stripe API response does not have a well-formed `data` array.' + ); + } + + if (i < pageResult.data.length) { + const idx = reverseIteration ? pageResult.data.length - 1 - i : i; + const value = pageResult.data[idx]; + i += 1; + + return {value, done: false}; + } else if (pageResult.has_more) { + // Reset counter, request next page, and recurse. + i = 0; + pagePromise = getNextPagePromise(pageResult); + return pagePromise.then(iterate); + } + return {value: undefined, done: true}; + } + + function asyncIteratorNext() { + return memoizedPromise(promiseCache, (resolve, reject) => { + return pagePromise + .then(iterate) + .then(resolve) + .catch(reject); + }); + } + + const autoPagingEach = makeAutoPagingEach(asyncIteratorNext); + const autoPagingToArray = makeAutoPagingToArray(autoPagingEach); + + const autoPaginationMethods = { + autoPagingEach, + autoPagingToArray, + + // Async iterator functions: + next: asyncIteratorNext, + return: () => { + // This is required for `break`. + return {}; + }, + [getAsyncIteratorSymbol()]: () => { + return autoPaginationMethods; + }, + }; + return autoPaginationMethods; +} + +module.exports.makeAutoPaginationMethods = makeAutoPaginationMethods; + +/** + * ---------------- + * Private Helpers: + * ---------------- + */ + +function getAsyncIteratorSymbol() { + if (typeof Symbol !== 'undefined' && Symbol.asyncIterator) { + return Symbol.asyncIterator; + } + // Follow the convention from libraries like iterall: https://github.com/leebyron/iterall#asynciterator-1 + return '@@asyncIterator'; +} + +function getDoneCallback(args) { + if (args.length < 2) { + return undefined; + } + const onDone = args[1]; + if (typeof onDone !== 'function') { + throw Error( + `The second argument to autoPagingEach, if present, must be a callback function; received ${typeof onDone}` + ); + } + return onDone; +} + +/** + * We allow four forms of the `onItem` callback (the middle two being equivalent), + * + * 1. `.autoPagingEach((item) => { doSomething(item); return false; });` + * 2. `.autoPagingEach(async (item) => { await doSomething(item); return false; });` + * 3. `.autoPagingEach((item) => doSomething(item).then(() => false));` + * 4. `.autoPagingEach((item, next) => { doSomething(item); next(false); });` + * + * In addition to standard validation, this helper + * coalesces the former forms into the latter form. + */ +function getItemCallback(args) { + if (args.length === 0) { + return undefined; + } + const onItem = args[0]; + if (typeof onItem !== 'function') { + throw Error( + `The first argument to autoPagingEach, if present, must be a callback function; received ${typeof onItem}` + ); + } + + // 4. `.autoPagingEach((item, next) => { doSomething(item); next(false); });` + if (onItem.length === 2) { + return onItem; + } + + if (onItem.length > 2) { + throw Error( + `The \`onItem\` callback function passed to autoPagingEach must accept at most two arguments; got ${onItem}` + ); + } + + // This magically handles all three of these usecases (the latter two being functionally identical): + // 1. `.autoPagingEach((item) => { doSomething(item); return false; });` + // 2. `.autoPagingEach(async (item) => { await doSomething(item); return false; });` + // 3. `.autoPagingEach((item) => doSomething(item).then(() => false));` + return function _onItem(item, next) { + const shouldContinue = onItem(item); + next(shouldContinue); + }; +} + +function getLastId(listResult, reverseIteration) { + const lastIdx = reverseIteration ? 0 : listResult.data.length - 1; + const lastItem = listResult.data[lastIdx]; + const lastId = lastItem && lastItem.id; + if (!lastId) { + throw Error( + 'Unexpected: No `id` found on the last item while auto-paging a list.' + ); + } + return lastId; +} + +/** + * If a user calls `.next()` multiple times in parallel, + * return the same result until something has resolved + * to prevent page-turning race conditions. + */ +function memoizedPromise(promiseCache, cb) { + if (promiseCache.currentPromise) { + return promiseCache.currentPromise; + } + promiseCache.currentPromise = new Promise(cb).then((ret) => { + promiseCache.currentPromise = undefined; + return ret; + }); + return promiseCache.currentPromise; +} + +function makeAutoPagingEach(asyncIteratorNext) { + return function autoPagingEach(/* onItem?, onDone? */) { + const args = [].slice.call(arguments); + const onItem = getItemCallback(args); + const onDone = getDoneCallback(args); + if (args.length > 2) { + throw Error(`autoPagingEach takes up to two arguments; received ${args}`); + } + + const autoPagePromise = wrapAsyncIteratorWithCallback( + asyncIteratorNext, + onItem + ); + return utils.callbackifyPromiseWithTimeout(autoPagePromise, onDone); + }; +} + +function makeAutoPagingToArray(autoPagingEach) { + return function autoPagingToArray(opts, onDone) { + const limit = opts && opts.limit; + if (!limit) { + throw Error( + 'You must pass a `limit` option to autoPagingToArray, e.g., `autoPagingToArray({limit: 1000});`.' + ); + } + if (limit > 10000) { + throw Error( + 'You cannot specify a limit of more than 10,000 items to fetch in `autoPagingToArray`; use `autoPagingEach` to iterate through longer lists.' + ); + } + const promise = new Promise((resolve, reject) => { + const items = []; + autoPagingEach((item) => { + items.push(item); + if (items.length >= limit) { + return false; + } + }) + .then(() => { + resolve(items); + }) + .catch(reject); + }); + return utils.callbackifyPromiseWithTimeout(promise, onDone); + }; +} + +function wrapAsyncIteratorWithCallback(asyncIteratorNext, onItem) { + return new Promise((resolve, reject) => { + function handleIteration(iterResult) { + if (iterResult.done) { + resolve(); + return; + } + + const item = iterResult.value; + return new Promise((next) => { + // Bit confusing, perhaps; we pass a `resolve` fn + // to the user, so they can decide when and if to continue. + // They can return false, or a promise which resolves to false, to break. + onItem(item, next); + }).then((shouldContinue) => { + if (shouldContinue === false) { + return handleIteration({done: true}); + } else { + return asyncIteratorNext().then(handleIteration); + } + }); + } + + asyncIteratorNext() + .then(handleIteration) + .catch(reject); + }); +} + +function isReverseIteration(requestArgs) { + const args = [].slice.call(requestArgs); + const dataFromArgs = utils.getDataFromArgs(args); + + return !!dataFromArgs.ending_before; +} diff --git a/lib/crypto/CryptoProvider.js b/lib/crypto/CryptoProvider.js new file mode 100644 index 0000000000..8f038d06ca --- /dev/null +++ b/lib/crypto/CryptoProvider.js @@ -0,0 +1,36 @@ +'use strict'; + +/** + * Interface encapsulating the various crypto computations used by the library, + * allowing pluggable underlying crypto implementations. + */ +class CryptoProvider { + /** + * Computes a SHA-256 HMAC given a secret and a payload (encoded in UTF-8). + * The output HMAC should be encoded in hexadecimal. + * + * Sample values for implementations: + * - computeHMACSignature('', 'test_secret') => 'f7f9bd47fb987337b5796fdc1fdb9ba221d0d5396814bfcaf9521f43fd8927fd' + * - computeHMACSignature('\ud83d\ude00', 'test_secret') => '837da296d05c4fe31f61d5d7ead035099d9585a5bcde87de952012a78f0b0c43 + */ + computeHMACSignature(payload, secret) { + throw new Error('computeHMACSignature not implemented.'); + } + + /** + * Asynchronous version of `computeHMACSignature`. Some implementations may + * only allow support async signature computation. + * + * Computes a SHA-256 HMAC given a secret and a payload (encoded in UTF-8). + * The output HMAC should be encoded in hexadecimal. + * + * Sample values for implementations: + * - computeHMACSignature('', 'test_secret') => 'f7f9bd47fb987337b5796fdc1fdb9ba221d0d5396814bfcaf9521f43fd8927fd' + * - computeHMACSignature('\ud83d\ude00', 'test_secret') => '837da296d05c4fe31f61d5d7ead035099d9585a5bcde87de952012a78f0b0c43 + */ + computeHMACSignatureAsync(payload, secret) { + throw new Error('computeHMACSignatureAsync not implemented.'); + } +} + +module.exports = CryptoProvider; diff --git a/lib/crypto/NodeCryptoProvider.js b/lib/crypto/NodeCryptoProvider.js new file mode 100644 index 0000000000..83c2037ea2 --- /dev/null +++ b/lib/crypto/NodeCryptoProvider.js @@ -0,0 +1,26 @@ +'use strict'; + +const crypto = require('crypto'); + +const CryptoProvider = require('./CryptoProvider'); + +/** + * `CryptoProvider which uses the Node `crypto` package for its computations. + */ +class NodeCryptoProvider extends CryptoProvider { + /** @override */ + computeHMACSignature(payload, secret) { + return crypto + .createHmac('sha256', secret) + .update(payload, 'utf8') + .digest('hex'); + } + + /** @override */ + async computeHMACSignatureAsync(payload, secret) { + const signature = await this.computeHMACSignature(payload, secret); + return signature; + } +} + +module.exports = NodeCryptoProvider; diff --git a/lib/crypto/SubtleCryptoProvider.js b/lib/crypto/SubtleCryptoProvider.js new file mode 100644 index 0000000000..45aa40dd46 --- /dev/null +++ b/lib/crypto/SubtleCryptoProvider.js @@ -0,0 +1,69 @@ +'use strict'; + +const CryptoProvider = require('./CryptoProvider'); + +/** + * `CryptoProvider which uses the SubtleCrypto interface of the Web Crypto API. + * + * This only supports asynchronous operations. + */ +class SubtleCryptoProvider extends CryptoProvider { + constructor(subtleCrypto) { + super(); + + // If no subtle crypto is interface, default to the global namespace. This + // is to allow custom interfaces (eg. using the Node webcrypto interface in + // tests). + this.subtleCrypto = subtleCrypto || crypto.subtle; + } + + /** @override */ + computeHMACSignature(payload, secret) { + throw new Error( + 'SubtleCryptoProvider cannot be used in a synchronous context.' + ); + } + + /** @override */ + async computeHMACSignatureAsync(payload, secret) { + const encoder = new TextEncoder('utf-8'); + + const key = await this.subtleCrypto.importKey( + 'raw', + encoder.encode(secret), + { + name: 'HMAC', + hash: {name: 'SHA-256'}, + }, + false, + ['sign'] + ); + + const signatureBuffer = await this.subtleCrypto.sign( + 'hmac', + key, + encoder.encode(payload) + ); + + // crypto.subtle returns the signature in base64 format. This must be + // encoded in hex to match the CryptoProvider contract. We map each byte in + // the buffer to its corresponding hex octet and then combine into a string. + const signatureBytes = new Uint8Array(signatureBuffer); + const signatureHexCodes = new Array(signatureBytes.length); + + for (let i = 0; i < signatureBytes.length; i++) { + signatureHexCodes[i] = byteHexMapping[signatureBytes[i]]; + } + + return signatureHexCodes.join(''); + } +} + +// Cached mapping of byte to hex representation. We do this once to avoid re- +// computing every time we need to convert the result of a signature to hex. +const byteHexMapping = new Array(256); +for (let i = 0; i < byteHexMapping.length; i++) { + byteHexMapping[i] = i.toString(16).padStart(2, '0'); +} + +module.exports = SubtleCryptoProvider; diff --git a/lib/makeRequest.js b/lib/makeRequest.js new file mode 100644 index 0000000000..7ea0737b73 --- /dev/null +++ b/lib/makeRequest.js @@ -0,0 +1,122 @@ +'use strict'; + +const utils = require('./utils'); + +function getRequestOpts(self, requestArgs, spec, overrideData) { + // Extract spec values with defaults. + const requestMethod = (spec.method || 'GET').toUpperCase(); + const urlParams = spec.urlParams || []; + const encode = spec.encode || ((data) => data); + + const isUsingFullPath = !!spec.fullPath; + const commandPath = utils.makeURLInterpolator( + isUsingFullPath ? spec.fullPath : spec.path || '' + ); + // When using fullPath, we ignore the resource path as it should already be + // fully qualified. + const path = isUsingFullPath + ? spec.fullPath + : self.createResourcePathWithSymbols(spec.path); + + // Don't mutate args externally. + const args = [].slice.call(requestArgs); + + // Generate and validate url params. + const urlData = urlParams.reduce((urlData, param) => { + const arg = args.shift(); + if (typeof arg !== 'string') { + throw new Error( + `Stripe: Argument "${param}" must be a string, but got: ${arg} (on API request to \`${requestMethod} ${path}\`)` + ); + } + + urlData[param] = arg; + return urlData; + }, {}); + + // Pull request data and options (headers, auth) from args. + const dataFromArgs = utils.getDataFromArgs(args); + const data = encode(Object.assign({}, dataFromArgs, overrideData)); + const options = utils.getOptionsFromArgs(args); + const host = options.host || spec.host; + const streaming = !!spec.streaming; + // Validate that there are no more args. + if (args.filter((x) => x != null).length) { + throw new Error( + `Stripe: Unknown arguments (${args}). Did you mean to pass an options object? See https://github.com/stripe/stripe-node/wiki/Passing-Options. (on API request to ${requestMethod} \`${path}\`)` + ); + } + + // When using full path, we can just invoke the URL interpolator directly + // as we don't need to use the resource to create a full path. + const requestPath = isUsingFullPath + ? commandPath(urlData) + : self.createFullPath(commandPath, urlData); + + const headers = Object.assign(options.headers, spec.headers); + + if (spec.validator) { + spec.validator(data, {headers}); + } + + const dataInQuery = spec.method === 'GET' || spec.method === 'DELETE'; + const bodyData = dataInQuery ? {} : data; + const queryData = dataInQuery ? data : {}; + + return { + requestMethod, + requestPath, + bodyData, + queryData, + auth: options.auth, + headers, + host, + streaming, + settings: options.settings, + }; +} + +function makeRequest(self, requestArgs, spec, overrideData) { + return new Promise((resolve, reject) => { + let opts; + try { + opts = getRequestOpts(self, requestArgs, spec, overrideData); + } catch (err) { + reject(err); + return; + } + + function requestCallback(err, response) { + if (err) { + reject(err); + } else { + resolve( + spec.transformResponseData + ? spec.transformResponseData(response) + : response + ); + } + } + + const emptyQuery = Object.keys(opts.queryData).length === 0; + const path = [ + opts.requestPath, + emptyQuery ? '' : '?', + utils.stringifyRequestData(opts.queryData), + ].join(''); + + const {headers, settings} = opts; + + self._request( + opts.requestMethod, + opts.host, + path, + opts.bodyData, + opts.auth, + {headers, settings, streaming: opts.streaming}, + requestCallback + ); + }); +} + +module.exports = makeRequest; diff --git a/lib/multipart.js b/lib/multipart.js new file mode 100644 index 0000000000..90682aedda --- /dev/null +++ b/lib/multipart.js @@ -0,0 +1,96 @@ +'use strict'; + +const utils = require('./utils'); +const {StripeError} = require('./Error'); + +class StreamProcessingError extends StripeError {} + +// Method for formatting HTTP body for the multipart/form-data specification +// Mostly taken from Fermata.js +// https://github.com/natevw/fermata/blob/5d9732a33d776ce925013a265935facd1626cc88/fermata.js#L315-L343 +const multipartDataGenerator = (method, data, headers) => { + const segno = ( + Math.round(Math.random() * 1e16) + Math.round(Math.random() * 1e16) + ).toString(); + headers['Content-Type'] = `multipart/form-data; boundary=${segno}`; + let buffer = Buffer.alloc(0); + + function push(l) { + const prevBuffer = buffer; + const newBuffer = l instanceof Buffer ? l : Buffer.from(l); + buffer = Buffer.alloc(prevBuffer.length + newBuffer.length + 2); + prevBuffer.copy(buffer); + newBuffer.copy(buffer, prevBuffer.length); + buffer.write('\r\n', buffer.length - 2); + } + + function q(s) { + return `"${s.replace(/"|"/g, '%22').replace(/\r\n|\r|\n/g, ' ')}"`; + } + + const flattenedData = utils.flattenAndStringify(data); + + for (const k in flattenedData) { + const v = flattenedData[k]; + push(`--${segno}`); + if (v.hasOwnProperty('data')) { + push( + `Content-Disposition: form-data; name=${q(k)}; filename=${q( + v.name || 'blob' + )}` + ); + push(`Content-Type: ${v.type || 'application/octet-stream'}`); + push(''); + push(v.data); + } else { + push(`Content-Disposition: form-data; name=${q(k)}`); + push(''); + push(v); + } + } + push(`--${segno}--`); + + return buffer; +}; + +const streamProcessor = (method, data, headers, callback) => { + const bufferArray = []; + data.file.data + .on('data', (line) => { + bufferArray.push(line); + }) + .once('end', () => { + const bufferData = Object.assign({}, data); + bufferData.file.data = Buffer.concat(bufferArray); + const buffer = multipartDataGenerator(method, bufferData, headers); + callback(null, buffer); + }) + .on('error', (err) => { + callback( + new StreamProcessingError({ + message: + 'An error occurred while attempting to process the file for upload.', + detail: err, + }), + null + ); + }); +}; + +const multipartRequestDataProcessor = (method, data, headers, callback) => { + data = data || {}; + + if (method !== 'POST') { + return callback(null, utils.stringifyRequestData(data)); + } + + const isStream = utils.checkForStream(data); + if (isStream) { + return streamProcessor(method, data, headers, callback); + } + + const buffer = multipartDataGenerator(method, data, headers); + return callback(null, buffer); +}; + +module.exports.multipartRequestDataProcessor = multipartRequestDataProcessor; diff --git a/lib/net/FetchHttpClient.js b/lib/net/FetchHttpClient.js new file mode 100644 index 0000000000..431282cd19 --- /dev/null +++ b/lib/net/FetchHttpClient.js @@ -0,0 +1,138 @@ +'use strict'; + +const {HttpClient, HttpClientResponse} = require('./HttpClient'); + +/** + * HTTP client which uses a `fetch` function to issue requests. + * + * By default relies on the global `fetch` function, but an optional function + * can be passed in. If passing in a function, it is expected to match the Web + * Fetch API. As an example, this could be the function provided by the + * node-fetch package (https://github.com/node-fetch/node-fetch). + */ +class FetchHttpClient extends HttpClient { + constructor(fetchFn) { + super(); + this._fetchFn = fetchFn; + } + + /** @override. */ + getClientName() { + return 'fetch'; + } + + makeRequest( + host, + port, + path, + method, + headers, + requestData, + protocol, + timeout + ) { + const isInsecureConnection = protocol === 'http'; + + const url = new URL( + path, + `${isInsecureConnection ? 'http' : 'https'}://${host}` + ); + url.port = port; + + // For methods which expect payloads, we should always pass a body value + // even when it is empty. Without this, some JS runtimes (eg. Deno) will + // inject a second Content-Length header. See https://github.com/stripe/stripe-node/issues/1519 + // for more details. + const methodHasPayload = + method == 'POST' || method == 'PUT' || method == 'PATCH'; + const body = requestData || (methodHasPayload ? '' : undefined); + + const fetchFn = this._fetchFn || fetch; + const fetchPromise = fetchFn(url.toString(), { + method, + headers, + body, + }); + + // The Fetch API does not support passing in a timeout natively, so a + // timeout promise is constructed to race against the fetch and preempt the + // request, simulating a timeout. + // + // This timeout behavior differs from Node: + // - Fetch uses a single timeout for the entire length of the request. + // - Node is more fine-grained and resets the timeout after each stage of + // the request. + // + // As an example, if the timeout is set to 30s and the connection takes 20s + // to be established followed by 20s for the body, Fetch would timeout but + // Node would not. The more fine-grained timeout cannot be implemented with + // fetch. + let pendingTimeoutId; + const timeoutPromise = new Promise((_, reject) => { + pendingTimeoutId = setTimeout(() => { + pendingTimeoutId = null; + reject(HttpClient.makeTimeoutError()); + }, timeout); + }); + + return Promise.race([fetchPromise, timeoutPromise]) + .then((res) => { + return new FetchHttpClientResponse(res); + }) + .finally(() => { + if (pendingTimeoutId) { + clearTimeout(pendingTimeoutId); + } + }); + } +} + +class FetchHttpClientResponse extends HttpClientResponse { + constructor(res) { + super( + res.status, + FetchHttpClientResponse._transformHeadersToObject(res.headers) + ); + this._res = res; + } + + getRawResponse() { + return this._res; + } + + toStream(streamCompleteCallback) { + // Unfortunately `fetch` does not have event handlers for when the stream is + // completely read. We therefore invoke the streamCompleteCallback right + // away. This callback emits a response event with metadata and completes + // metrics, so it's ok to do this without waiting for the stream to be + // completely read. + streamCompleteCallback(); + + // Fetch's `body` property is expected to be a readable stream of the body. + return this._res.body; + } + + toJSON() { + return this._res.json(); + } + + static _transformHeadersToObject(headers) { + // Fetch uses a Headers instance so this must be converted to a barebones + // JS object to meet the HttpClient interface. + const headersObj = {}; + + for (const entry of headers) { + if (!Array.isArray(entry) || entry.length != 2) { + throw new Error( + 'Response objects produced by the fetch function given to FetchHttpClient do not have an iterable headers map. Response#headers should be an iterable object.' + ); + } + + headersObj[entry[0]] = entry[1]; + } + + return headersObj; + } +} + +module.exports = {FetchHttpClient, FetchHttpClientResponse}; diff --git a/lib/net/HttpClient.js b/lib/net/HttpClient.js new file mode 100644 index 0000000000..b309ce7d9d --- /dev/null +++ b/lib/net/HttpClient.js @@ -0,0 +1,69 @@ +'use strict'; + +/** + * Encapsulates the logic for issuing a request to the Stripe API. + * + * A custom HTTP client should should implement: + * 1. A response class which extends HttpClientResponse and wraps around their + * own internal representation of a response. + * 2. A client class which extends HttpClient and implements all methods, + * returning their own response class when making requests. + */ +class HttpClient { + /** The client name used for diagnostics. */ + getClientName() { + throw new Error('getClientName not implemented.'); + } + + makeRequest( + host, + port, + path, + method, + headers, + requestData, + protocol, + timeout + ) { + throw new Error('makeRequest not implemented.'); + } + + /** Helper to make a consistent timeout error across implementations. */ + static makeTimeoutError() { + const timeoutErr = new TypeError(HttpClient.TIMEOUT_ERROR_CODE); + timeoutErr.code = HttpClient.TIMEOUT_ERROR_CODE; + return timeoutErr; + } +} + +HttpClient.CONNECTION_CLOSED_ERROR_CODES = ['ECONNRESET', 'EPIPE']; +HttpClient.TIMEOUT_ERROR_CODE = 'ETIMEDOUT'; + +class HttpClientResponse { + constructor(statusCode, headers) { + this._statusCode = statusCode; + this._headers = headers; + } + + getStatusCode() { + return this._statusCode; + } + + getHeaders() { + return this._headers; + } + + getRawResponse() { + throw new Error('getRawResponse not implemented.'); + } + + toStream(streamCompleteCallback) { + throw new Error('toStream not implemented.'); + } + + toJSON() { + throw new Error('toJSON not implemented.'); + } +} + +module.exports = {HttpClient, HttpClientResponse}; diff --git a/lib/net/NodeHttpClient.js b/lib/net/NodeHttpClient.js new file mode 100644 index 0000000000..222a446ce7 --- /dev/null +++ b/lib/net/NodeHttpClient.js @@ -0,0 +1,125 @@ +'use strict'; + +const http = require('http'); +const https = require('https'); + +const {HttpClient, HttpClientResponse} = require('./HttpClient'); + +const defaultHttpAgent = new http.Agent({keepAlive: true}); +const defaultHttpsAgent = new https.Agent({keepAlive: true}); + +/** + * HTTP client which uses the Node `http` and `https` packages to issue + * requests.` + */ +class NodeHttpClient extends HttpClient { + constructor(agent) { + super(); + this._agent = agent; + } + + /** @override. */ + getClientName() { + return 'node'; + } + + makeRequest( + host, + port, + path, + method, + headers, + requestData, + protocol, + timeout + ) { + const isInsecureConnection = protocol === 'http'; + + let agent = this._agent; + if (!agent) { + agent = isInsecureConnection ? defaultHttpAgent : defaultHttpsAgent; + } + + const requestPromise = new Promise((resolve, reject) => { + const req = (isInsecureConnection ? http : https).request({ + host: host, + port: port, + path, + method, + agent, + headers, + ciphers: 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:!MD5', + }); + + req.setTimeout(timeout, () => { + req.destroy(HttpClient.makeTimeoutError()); + }); + + req.on('response', (res) => { + resolve(new NodeHttpClientResponse(res)); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.once('socket', (socket) => { + if (socket.connecting) { + socket.once( + isInsecureConnection ? 'connect' : 'secureConnect', + () => { + // Send payload; we're safe: + req.write(requestData); + req.end(); + } + ); + } else { + // we're already connected + req.write(requestData); + req.end(); + } + }); + }); + + return requestPromise; + } +} + +class NodeHttpClientResponse extends HttpClientResponse { + constructor(res) { + super(res.statusCode, res.headers || {}); + this._res = res; + } + + getRawResponse() { + return this._res; + } + + toStream(streamCompleteCallback) { + // The raw response is itself the stream, so we just return that. To be + // backwards compatible, we should invoke the streamCompleteCallback only + // once the stream has been fully consumed. + this._res.once('end', () => streamCompleteCallback()); + return this._res; + } + + toJSON() { + return new Promise((resolve, reject) => { + let response = ''; + + this._res.setEncoding('utf8'); + this._res.on('data', (chunk) => { + response += chunk; + }); + this._res.once('end', () => { + try { + resolve(JSON.parse(response)); + } catch (e) { + reject(e); + } + }); + }); + } +} + +module.exports = {NodeHttpClient, NodeHttpClientResponse}; diff --git a/lib/resources.js b/lib/resources.js new file mode 100644 index 0000000000..825c8f1d9d --- /dev/null +++ b/lib/resources.js @@ -0,0 +1,130 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const resourceNamespace = require('./ResourceNamespace'); + +module.exports = { + Accounts: require('./resources/Accounts'), + // Support Accounts for consistency, Account for backwards compatibility + Account: require('./resources/Accounts'), + AccountLinks: require('./resources/AccountLinks'), + ApplePayDomains: require('./resources/ApplePayDomains'), + ApplicationFees: require('./resources/ApplicationFees'), + Balance: require('./resources/Balance'), + BalanceTransactions: require('./resources/BalanceTransactions'), + Charges: require('./resources/Charges'), + CountrySpecs: require('./resources/CountrySpecs'), + Coupons: require('./resources/Coupons'), + CreditNotes: require('./resources/CreditNotes'), + Customers: require('./resources/Customers'), + Disputes: require('./resources/Disputes'), + EphemeralKeys: require('./resources/EphemeralKeys'), + Events: require('./resources/Events'), + ExchangeRates: require('./resources/ExchangeRates'), + Files: require('./resources/Files'), + FileLinks: require('./resources/FileLinks'), + Invoices: require('./resources/Invoices'), + InvoiceItems: require('./resources/InvoiceItems'), + Mandates: require('./resources/Mandates'), + OAuth: require('./resources/OAuth'), + Orders: require('./resources/Orders'), + PaymentIntents: require('./resources/PaymentIntents'), + PaymentLinks: require('./resources/PaymentLinks'), + PaymentMethods: require('./resources/PaymentMethods'), + Payouts: require('./resources/Payouts'), + Plans: require('./resources/Plans'), + Prices: require('./resources/Prices'), + Products: require('./resources/Products'), + PromotionCodes: require('./resources/PromotionCodes'), + Quotes: require('./resources/Quotes'), + Refunds: require('./resources/Refunds'), + Reviews: require('./resources/Reviews'), + SetupAttempts: require('./resources/SetupAttempts'), + SetupIntents: require('./resources/SetupIntents'), + ShippingRates: require('./resources/ShippingRates'), + Skus: require('./resources/SKUs'), + Sources: require('./resources/Sources'), + Subscriptions: require('./resources/Subscriptions'), + SubscriptionItems: require('./resources/SubscriptionItems'), + SubscriptionSchedules: require('./resources/SubscriptionSchedules'), + TaxCodes: require('./resources/TaxCodes'), + TaxRates: require('./resources/TaxRates'), + Tokens: require('./resources/Tokens'), + Topups: require('./resources/Topups'), + Transfers: require('./resources/Transfers'), + WebhookEndpoints: require('./resources/WebhookEndpoints'), + Apps: resourceNamespace('apps', { + Secrets: require('./resources/Apps/Secrets'), + }), + BillingPortal: resourceNamespace('billingPortal', { + Configurations: require('./resources/BillingPortal/Configurations'), + Sessions: require('./resources/BillingPortal/Sessions'), + }), + Checkout: resourceNamespace('checkout', { + Sessions: require('./resources/Checkout/Sessions'), + }), + FinancialConnections: resourceNamespace('financialConnections', { + Accounts: require('./resources/FinancialConnections/Accounts'), + Sessions: require('./resources/FinancialConnections/Sessions'), + }), + Identity: resourceNamespace('identity', { + VerificationReports: require('./resources/Identity/VerificationReports'), + VerificationSessions: require('./resources/Identity/VerificationSessions'), + }), + Issuing: resourceNamespace('issuing', { + Authorizations: require('./resources/Issuing/Authorizations'), + Cards: require('./resources/Issuing/Cards'), + Cardholders: require('./resources/Issuing/Cardholders'), + Disputes: require('./resources/Issuing/Disputes'), + Transactions: require('./resources/Issuing/Transactions'), + }), + Radar: resourceNamespace('radar', { + EarlyFraudWarnings: require('./resources/Radar/EarlyFraudWarnings'), + ValueLists: require('./resources/Radar/ValueLists'), + ValueListItems: require('./resources/Radar/ValueListItems'), + }), + Reporting: resourceNamespace('reporting', { + ReportRuns: require('./resources/Reporting/ReportRuns'), + ReportTypes: require('./resources/Reporting/ReportTypes'), + }), + Sigma: resourceNamespace('sigma', { + ScheduledQueryRuns: require('./resources/Sigma/ScheduledQueryRuns'), + }), + Terminal: resourceNamespace('terminal', { + Configurations: require('./resources/Terminal/Configurations'), + ConnectionTokens: require('./resources/Terminal/ConnectionTokens'), + Locations: require('./resources/Terminal/Locations'), + Readers: require('./resources/Terminal/Readers'), + }), + TestHelpers: resourceNamespace('testHelpers', { + Customers: require('./resources/TestHelpers/Customers'), + Refunds: require('./resources/TestHelpers/Refunds'), + TestClocks: require('./resources/TestHelpers/TestClocks'), + Issuing: resourceNamespace('issuing', { + Cards: require('./resources/TestHelpers/Issuing/Cards'), + }), + Terminal: resourceNamespace('terminal', { + Readers: require('./resources/TestHelpers/Terminal/Readers'), + }), + Treasury: resourceNamespace('treasury', { + InboundTransfers: require('./resources/TestHelpers/Treasury/InboundTransfers'), + OutboundPayments: require('./resources/TestHelpers/Treasury/OutboundPayments'), + OutboundTransfers: require('./resources/TestHelpers/Treasury/OutboundTransfers'), + ReceivedCredits: require('./resources/TestHelpers/Treasury/ReceivedCredits'), + ReceivedDebits: require('./resources/TestHelpers/Treasury/ReceivedDebits'), + }), + }), + Treasury: resourceNamespace('treasury', { + CreditReversals: require('./resources/Treasury/CreditReversals'), + DebitReversals: require('./resources/Treasury/DebitReversals'), + FinancialAccounts: require('./resources/Treasury/FinancialAccounts'), + InboundTransfers: require('./resources/Treasury/InboundTransfers'), + OutboundPayments: require('./resources/Treasury/OutboundPayments'), + OutboundTransfers: require('./resources/Treasury/OutboundTransfers'), + ReceivedCredits: require('./resources/Treasury/ReceivedCredits'), + ReceivedDebits: require('./resources/Treasury/ReceivedDebits'), + Transactions: require('./resources/Treasury/Transactions'), + TransactionEntries: require('./resources/Treasury/TransactionEntries'), + }), +}; diff --git a/lib/resources/AccountLinks.js b/lib/resources/AccountLinks.js new file mode 100644 index 0000000000..41e02b1ec5 --- /dev/null +++ b/lib/resources/AccountLinks.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'account_links', + + create: stripeMethod({ + method: 'POST', + path: '', + }), +}); diff --git a/lib/resources/Accounts.js b/lib/resources/Accounts.js new file mode 100644 index 0000000000..351c055f23 --- /dev/null +++ b/lib/resources/Accounts.js @@ -0,0 +1,130 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +// Since path can either be `account` or `accounts`, support both through stripeMethod path; +module.exports = StripeResource.extend({ + path: '', + + create: stripeMethod({ + method: 'POST', + path: 'accounts', + }), + + retrieve(id) { + // No longer allow an api key to be passed as the first string to this function due to ambiguity between + // old account ids and api keys. To request the account for an api key, send null as the id + if (typeof id === 'string') { + return stripeMethod({ + method: 'GET', + path: 'accounts/{id}', + }).apply(this, arguments); + } else { + if (id === null || id === undefined) { + // Remove id as stripeMethod would complain of unexpected argument + [].shift.apply(arguments); + } + return stripeMethod({ + method: 'GET', + path: 'account', + }).apply(this, arguments); + } + }, + + update: stripeMethod({ + method: 'POST', + path: 'accounts/{account}', + }), + + list: stripeMethod({ + method: 'GET', + path: 'accounts', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: 'accounts/{account}', + }), + + reject: stripeMethod({ + method: 'POST', + path: 'accounts/{account}/reject', + }), + + retrieveCapability: stripeMethod({ + method: 'GET', + path: 'accounts/{account}/capabilities/{capability}', + }), + + updateCapability: stripeMethod({ + method: 'POST', + path: 'accounts/{account}/capabilities/{capability}', + }), + + listCapabilities: stripeMethod({ + method: 'GET', + path: 'accounts/{account}/capabilities', + methodType: 'list', + }), + + createExternalAccount: stripeMethod({ + method: 'POST', + path: 'accounts/{account}/external_accounts', + }), + + retrieveExternalAccount: stripeMethod({ + method: 'GET', + path: 'accounts/{account}/external_accounts/{id}', + }), + + updateExternalAccount: stripeMethod({ + method: 'POST', + path: 'accounts/{account}/external_accounts/{id}', + }), + + listExternalAccounts: stripeMethod({ + method: 'GET', + path: 'accounts/{account}/external_accounts', + methodType: 'list', + }), + + deleteExternalAccount: stripeMethod({ + method: 'DELETE', + path: 'accounts/{account}/external_accounts/{id}', + }), + + createLoginLink: stripeMethod({ + method: 'POST', + path: 'accounts/{account}/login_links', + }), + + createPerson: stripeMethod({ + method: 'POST', + path: 'accounts/{account}/persons', + }), + + retrievePerson: stripeMethod({ + method: 'GET', + path: 'accounts/{account}/persons/{person}', + }), + + updatePerson: stripeMethod({ + method: 'POST', + path: 'accounts/{account}/persons/{person}', + }), + + listPersons: stripeMethod({ + method: 'GET', + path: 'accounts/{account}/persons', + methodType: 'list', + }), + + deletePerson: stripeMethod({ + method: 'DELETE', + path: 'accounts/{account}/persons/{person}', + }), +}); diff --git a/lib/resources/ApplePayDomains.js b/lib/resources/ApplePayDomains.js new file mode 100644 index 0000000000..76a8c59585 --- /dev/null +++ b/lib/resources/ApplePayDomains.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'apple_pay/domains', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{domain}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{domain}', + }), +}); diff --git a/lib/resources/ApplicationFees.js b/lib/resources/ApplicationFees.js new file mode 100644 index 0000000000..ebd6372aca --- /dev/null +++ b/lib/resources/ApplicationFees.js @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'application_fees', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + createRefund: stripeMethod({ + method: 'POST', + path: '/{id}/refunds', + }), + + retrieveRefund: stripeMethod({ + method: 'GET', + path: '/{fee}/refunds/{id}', + }), + + updateRefund: stripeMethod({ + method: 'POST', + path: '/{fee}/refunds/{id}', + }), + + listRefunds: stripeMethod({ + method: 'GET', + path: '/{id}/refunds', + methodType: 'list', + }), +}); diff --git a/lib/resources/Apps/Secrets.js b/lib/resources/Apps/Secrets.js new file mode 100644 index 0000000000..238037508f --- /dev/null +++ b/lib/resources/Apps/Secrets.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'apps/secrets', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + deleteWhere: stripeMethod({ + method: 'POST', + path: '/delete', + }), + + find: stripeMethod({ + method: 'GET', + path: '/find', + }), +}); diff --git a/lib/resources/Balance.js b/lib/resources/Balance.js new file mode 100644 index 0000000000..ac7a9ae459 --- /dev/null +++ b/lib/resources/Balance.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'balance', + + retrieve: stripeMethod({ + method: 'GET', + path: '', + }), +}); diff --git a/lib/resources/BalanceTransactions.js b/lib/resources/BalanceTransactions.js new file mode 100644 index 0000000000..adbe3d41f9 --- /dev/null +++ b/lib/resources/BalanceTransactions.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'balance_transactions', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/BillingPortal/Configurations.js b/lib/resources/BillingPortal/Configurations.js new file mode 100644 index 0000000000..ff3200160a --- /dev/null +++ b/lib/resources/BillingPortal/Configurations.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'billing_portal/configurations', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{configuration}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{configuration}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/BillingPortal/Sessions.js b/lib/resources/BillingPortal/Sessions.js new file mode 100644 index 0000000000..04f0ef6805 --- /dev/null +++ b/lib/resources/BillingPortal/Sessions.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'billing_portal/sessions', + + create: stripeMethod({ + method: 'POST', + path: '', + }), +}); diff --git a/lib/resources/Charges.js b/lib/resources/Charges.js new file mode 100644 index 0000000000..f4546063f7 --- /dev/null +++ b/lib/resources/Charges.js @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'charges', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{charge}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{charge}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + capture: stripeMethod({ + method: 'POST', + path: '/{charge}/capture', + }), + + search: stripeMethod({ + method: 'GET', + path: '/search', + methodType: 'search', + }), +}); diff --git a/lib/resources/Checkout/Sessions.js b/lib/resources/Checkout/Sessions.js new file mode 100644 index 0000000000..1cef4d65d7 --- /dev/null +++ b/lib/resources/Checkout/Sessions.js @@ -0,0 +1,37 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'checkout/sessions', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{session}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + expire: stripeMethod({ + method: 'POST', + path: '/{session}/expire', + }), + + listLineItems: stripeMethod({ + method: 'GET', + path: '/{session}/line_items', + methodType: 'list', + }), +}); diff --git a/lib/resources/CountrySpecs.js b/lib/resources/CountrySpecs.js new file mode 100644 index 0000000000..789f99054a --- /dev/null +++ b/lib/resources/CountrySpecs.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'country_specs', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{country}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Coupons.js b/lib/resources/Coupons.js new file mode 100644 index 0000000000..11bed027fe --- /dev/null +++ b/lib/resources/Coupons.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'coupons', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{coupon}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{coupon}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{coupon}', + }), +}); diff --git a/lib/resources/CreditNotes.js b/lib/resources/CreditNotes.js new file mode 100644 index 0000000000..e435282d00 --- /dev/null +++ b/lib/resources/CreditNotes.js @@ -0,0 +1,53 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'credit_notes', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + listPreviewLineItems: stripeMethod({ + method: 'GET', + path: '/preview/lines', + methodType: 'list', + }), + + preview: stripeMethod({ + method: 'GET', + path: '/preview', + }), + + voidCreditNote: stripeMethod({ + method: 'POST', + path: '/{id}/void', + }), + + listLineItems: stripeMethod({ + method: 'GET', + path: '/{creditNote}/lines', + methodType: 'list', + }), +}); diff --git a/lib/resources/Customers.js b/lib/resources/Customers.js new file mode 100644 index 0000000000..e3b8e8fab8 --- /dev/null +++ b/lib/resources/Customers.js @@ -0,0 +1,157 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'customers', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{customer}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{customer}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{customer}', + }), + + createFundingInstructions: stripeMethod({ + method: 'POST', + path: '/{customer}/funding_instructions', + }), + + deleteDiscount: stripeMethod({ + method: 'DELETE', + path: '/{customer}/discount', + }), + + listPaymentMethods: stripeMethod({ + method: 'GET', + path: '/{customer}/payment_methods', + methodType: 'list', + }), + + retrievePaymentMethod: stripeMethod({ + method: 'GET', + path: '/{customer}/payment_methods/{paymentMethod}', + }), + + search: stripeMethod({ + method: 'GET', + path: '/search', + methodType: 'search', + }), + + retrieveCashBalance: stripeMethod({ + method: 'GET', + path: '/{customer}/cash_balance', + }), + + updateCashBalance: stripeMethod({ + method: 'POST', + path: '/{customer}/cash_balance', + }), + + createBalanceTransaction: stripeMethod({ + method: 'POST', + path: '/{customer}/balance_transactions', + }), + + retrieveBalanceTransaction: stripeMethod({ + method: 'GET', + path: '/{customer}/balance_transactions/{transaction}', + }), + + updateBalanceTransaction: stripeMethod({ + method: 'POST', + path: '/{customer}/balance_transactions/{transaction}', + }), + + listBalanceTransactions: stripeMethod({ + method: 'GET', + path: '/{customer}/balance_transactions', + methodType: 'list', + }), + + retrieveCashBalanceTransaction: stripeMethod({ + method: 'GET', + path: '/{customer}/cash_balance_transactions/{transaction}', + }), + + listCashBalanceTransactions: stripeMethod({ + method: 'GET', + path: '/{customer}/cash_balance_transactions', + methodType: 'list', + }), + + createSource: stripeMethod({ + method: 'POST', + path: '/{customer}/sources', + }), + + retrieveSource: stripeMethod({ + method: 'GET', + path: '/{customer}/sources/{id}', + }), + + updateSource: stripeMethod({ + method: 'POST', + path: '/{customer}/sources/{id}', + }), + + listSources: stripeMethod({ + method: 'GET', + path: '/{customer}/sources', + methodType: 'list', + }), + + deleteSource: stripeMethod({ + method: 'DELETE', + path: '/{customer}/sources/{id}', + }), + + verifySource: stripeMethod({ + method: 'POST', + path: '/{customer}/sources/{id}/verify', + }), + + createTaxId: stripeMethod({ + method: 'POST', + path: '/{customer}/tax_ids', + }), + + retrieveTaxId: stripeMethod({ + method: 'GET', + path: '/{customer}/tax_ids/{id}', + }), + + listTaxIds: stripeMethod({ + method: 'GET', + path: '/{customer}/tax_ids', + methodType: 'list', + }), + + deleteTaxId: stripeMethod({ + method: 'DELETE', + path: '/{customer}/tax_ids/{id}', + }), +}); diff --git a/lib/resources/Disputes.js b/lib/resources/Disputes.js new file mode 100644 index 0000000000..1318758949 --- /dev/null +++ b/lib/resources/Disputes.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'disputes', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{dispute}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{dispute}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + close: stripeMethod({ + method: 'POST', + path: '/{dispute}/close', + }), +}); diff --git a/lib/resources/EphemeralKeys.js b/lib/resources/EphemeralKeys.js new file mode 100644 index 0000000000..2f9b02694c --- /dev/null +++ b/lib/resources/EphemeralKeys.js @@ -0,0 +1,27 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'ephemeral_keys', + + create: stripeMethod({ + method: 'POST', + path: '', + validator: (data, options) => { + if (!options.headers || !options.headers['Stripe-Version']) { + throw new Error( + 'Passing apiVersion in a separate options hash is required to create an ephemeral key. See https://stripe.com/docs/api/versioning?lang=node' + ); + } + }, + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{key}', + }), +}); diff --git a/lib/resources/Events.js b/lib/resources/Events.js new file mode 100644 index 0000000000..79ee31d45a --- /dev/null +++ b/lib/resources/Events.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'events', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/ExchangeRates.js b/lib/resources/ExchangeRates.js new file mode 100644 index 0000000000..021e972336 --- /dev/null +++ b/lib/resources/ExchangeRates.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'exchange_rates', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{rateId}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/FileLinks.js b/lib/resources/FileLinks.js new file mode 100644 index 0000000000..7c7a6e117d --- /dev/null +++ b/lib/resources/FileLinks.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'file_links', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{link}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{link}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Files.js b/lib/resources/Files.js new file mode 100644 index 0000000000..5dee82d1d2 --- /dev/null +++ b/lib/resources/Files.js @@ -0,0 +1,32 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const {multipartRequestDataProcessor} = require('../multipart'); +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'files', + + create: stripeMethod({ + method: 'POST', + headers: { + 'Content-Type': 'multipart/form-data', + }, + host: 'files.stripe.com', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{file}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + requestDataProcessor: multipartRequestDataProcessor, +}); diff --git a/lib/resources/FinancialConnections/Accounts.js b/lib/resources/FinancialConnections/Accounts.js new file mode 100644 index 0000000000..674f42ec48 --- /dev/null +++ b/lib/resources/FinancialConnections/Accounts.js @@ -0,0 +1,37 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'financial_connections/accounts', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{account}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + disconnect: stripeMethod({ + method: 'POST', + path: '/{account}/disconnect', + }), + + listOwners: stripeMethod({ + method: 'GET', + path: '/{account}/owners', + methodType: 'list', + }), + + refresh: stripeMethod({ + method: 'POST', + path: '/{account}/refresh', + }), +}); diff --git a/lib/resources/FinancialConnections/Sessions.js b/lib/resources/FinancialConnections/Sessions.js new file mode 100644 index 0000000000..0e968a437b --- /dev/null +++ b/lib/resources/FinancialConnections/Sessions.js @@ -0,0 +1,20 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'financial_connections/sessions', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{session}', + }), +}); diff --git a/lib/resources/Identity/VerificationReports.js b/lib/resources/Identity/VerificationReports.js new file mode 100644 index 0000000000..6d78faa7a0 --- /dev/null +++ b/lib/resources/Identity/VerificationReports.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'identity/verification_reports', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{report}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Identity/VerificationSessions.js b/lib/resources/Identity/VerificationSessions.js new file mode 100644 index 0000000000..02345e1210 --- /dev/null +++ b/lib/resources/Identity/VerificationSessions.js @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'identity/verification_sessions', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{session}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{session}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{session}/cancel', + }), + + redact: stripeMethod({ + method: 'POST', + path: '/{session}/redact', + }), +}); diff --git a/lib/resources/InvoiceItems.js b/lib/resources/InvoiceItems.js new file mode 100644 index 0000000000..c7cb5586be --- /dev/null +++ b/lib/resources/InvoiceItems.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'invoiceitems', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{invoiceitem}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{invoiceitem}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{invoiceitem}', + }), +}); diff --git a/lib/resources/Invoices.js b/lib/resources/Invoices.js new file mode 100644 index 0000000000..21543c1f3a --- /dev/null +++ b/lib/resources/Invoices.js @@ -0,0 +1,84 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'invoices', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{invoice}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{invoice}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{invoice}', + }), + + finalizeInvoice: stripeMethod({ + method: 'POST', + path: '/{invoice}/finalize', + }), + + listUpcomingLines: stripeMethod({ + method: 'GET', + path: '/upcoming/lines', + methodType: 'list', + }), + + markUncollectible: stripeMethod({ + method: 'POST', + path: '/{invoice}/mark_uncollectible', + }), + + pay: stripeMethod({ + method: 'POST', + path: '/{invoice}/pay', + }), + + retrieveUpcoming: stripeMethod({ + method: 'GET', + path: '/upcoming', + }), + + search: stripeMethod({ + method: 'GET', + path: '/search', + methodType: 'search', + }), + + sendInvoice: stripeMethod({ + method: 'POST', + path: '/{invoice}/send', + }), + + voidInvoice: stripeMethod({ + method: 'POST', + path: '/{invoice}/void', + }), + + listLineItems: stripeMethod({ + method: 'GET', + path: '/{invoice}/lines', + methodType: 'list', + }), +}); diff --git a/lib/resources/Issuing/Authorizations.js b/lib/resources/Issuing/Authorizations.js new file mode 100644 index 0000000000..5310d0ea8b --- /dev/null +++ b/lib/resources/Issuing/Authorizations.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'issuing/authorizations', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{authorization}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{authorization}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + approve: stripeMethod({ + method: 'POST', + path: '/{authorization}/approve', + }), + + decline: stripeMethod({ + method: 'POST', + path: '/{authorization}/decline', + }), +}); diff --git a/lib/resources/Issuing/Cardholders.js b/lib/resources/Issuing/Cardholders.js new file mode 100644 index 0000000000..7b340e6912 --- /dev/null +++ b/lib/resources/Issuing/Cardholders.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'issuing/cardholders', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{cardholder}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{cardholder}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Issuing/Cards.js b/lib/resources/Issuing/Cards.js new file mode 100644 index 0000000000..efc2cbc10f --- /dev/null +++ b/lib/resources/Issuing/Cards.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'issuing/cards', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{card}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{card}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Issuing/Disputes.js b/lib/resources/Issuing/Disputes.js new file mode 100644 index 0000000000..f62a2be96d --- /dev/null +++ b/lib/resources/Issuing/Disputes.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'issuing/disputes', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{dispute}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{dispute}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + submit: stripeMethod({ + method: 'POST', + path: '/{dispute}/submit', + }), +}); diff --git a/lib/resources/Issuing/Transactions.js b/lib/resources/Issuing/Transactions.js new file mode 100644 index 0000000000..22b438429d --- /dev/null +++ b/lib/resources/Issuing/Transactions.js @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'issuing/transactions', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{transaction}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{transaction}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Mandates.js b/lib/resources/Mandates.js new file mode 100644 index 0000000000..6d9ced9f82 --- /dev/null +++ b/lib/resources/Mandates.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'mandates', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{mandate}', + }), +}); diff --git a/lib/resources/OAuth.js b/lib/resources/OAuth.js new file mode 100644 index 0000000000..70ce4a2358 --- /dev/null +++ b/lib/resources/OAuth.js @@ -0,0 +1,55 @@ +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; +const utils = require('../utils'); + +const oAuthHost = 'connect.stripe.com'; + +module.exports = StripeResource.extend({ + basePath: '/', + + authorizeUrl(params, options) { + params = params || {}; + options = options || {}; + + let path = 'oauth/authorize'; + + // For Express accounts, the path changes + if (options.express) { + path = `express/${path}`; + } + + if (!params.response_type) { + params.response_type = 'code'; + } + + if (!params.client_id) { + params.client_id = this._stripe.getClientId(); + } + + if (!params.scope) { + params.scope = 'read_write'; + } + + return `https://${oAuthHost}/${path}?${utils.stringifyRequestData(params)}`; + }, + + token: stripeMethod({ + method: 'POST', + path: 'oauth/token', + host: oAuthHost, + }), + + deauthorize(spec) { + if (!spec.client_id) { + spec.client_id = this._stripe.getClientId(); + } + + return stripeMethod({ + method: 'POST', + path: 'oauth/deauthorize', + host: oAuthHost, + }).apply(this, arguments); + }, +}); diff --git a/lib/resources/Orders.js b/lib/resources/Orders.js new file mode 100644 index 0000000000..a216b32fc7 --- /dev/null +++ b/lib/resources/Orders.js @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'orders', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{id}/cancel', + }), + + listLineItems: stripeMethod({ + method: 'GET', + path: '/{id}/line_items', + methodType: 'list', + }), + + reopen: stripeMethod({ + method: 'POST', + path: '/{id}/reopen', + }), + + submit: stripeMethod({ + method: 'POST', + path: '/{id}/submit', + }), +}); diff --git a/lib/resources/PaymentIntents.js b/lib/resources/PaymentIntents.js new file mode 100644 index 0000000000..d6addba0d3 --- /dev/null +++ b/lib/resources/PaymentIntents.js @@ -0,0 +1,67 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'payment_intents', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{intent}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{intent}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + applyCustomerBalance: stripeMethod({ + method: 'POST', + path: '/{intent}/apply_customer_balance', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{intent}/cancel', + }), + + capture: stripeMethod({ + method: 'POST', + path: '/{intent}/capture', + }), + + confirm: stripeMethod({ + method: 'POST', + path: '/{intent}/confirm', + }), + + incrementAuthorization: stripeMethod({ + method: 'POST', + path: '/{intent}/increment_authorization', + }), + + search: stripeMethod({ + method: 'GET', + path: '/search', + methodType: 'search', + }), + + verifyMicrodeposits: stripeMethod({ + method: 'POST', + path: '/{intent}/verify_microdeposits', + }), +}); diff --git a/lib/resources/PaymentLinks.js b/lib/resources/PaymentLinks.js new file mode 100644 index 0000000000..a357cedd89 --- /dev/null +++ b/lib/resources/PaymentLinks.js @@ -0,0 +1,37 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'payment_links', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{paymentLink}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{paymentLink}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + listLineItems: stripeMethod({ + method: 'GET', + path: '/{paymentLink}/line_items', + methodType: 'list', + }), +}); diff --git a/lib/resources/PaymentMethods.js b/lib/resources/PaymentMethods.js new file mode 100644 index 0000000000..bd74b2ee5f --- /dev/null +++ b/lib/resources/PaymentMethods.js @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'payment_methods', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{paymentMethod}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{paymentMethod}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + attach: stripeMethod({ + method: 'POST', + path: '/{paymentMethod}/attach', + }), + + detach: stripeMethod({ + method: 'POST', + path: '/{paymentMethod}/detach', + }), +}); diff --git a/lib/resources/Payouts.js b/lib/resources/Payouts.js new file mode 100644 index 0000000000..a3b8e0cd66 --- /dev/null +++ b/lib/resources/Payouts.js @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'payouts', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{payout}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{payout}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{payout}/cancel', + }), + + reverse: stripeMethod({ + method: 'POST', + path: '/{payout}/reverse', + }), +}); diff --git a/lib/resources/Plans.js b/lib/resources/Plans.js new file mode 100644 index 0000000000..bd55a157da --- /dev/null +++ b/lib/resources/Plans.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'plans', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{plan}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{plan}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{plan}', + }), +}); diff --git a/lib/resources/Prices.js b/lib/resources/Prices.js new file mode 100644 index 0000000000..c509fdcbc0 --- /dev/null +++ b/lib/resources/Prices.js @@ -0,0 +1,37 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'prices', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{price}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{price}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + search: stripeMethod({ + method: 'GET', + path: '/search', + methodType: 'search', + }), +}); diff --git a/lib/resources/Products.js b/lib/resources/Products.js new file mode 100644 index 0000000000..3886020cf8 --- /dev/null +++ b/lib/resources/Products.js @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'products', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{id}', + }), + + search: stripeMethod({ + method: 'GET', + path: '/search', + methodType: 'search', + }), +}); diff --git a/lib/resources/PromotionCodes.js b/lib/resources/PromotionCodes.js new file mode 100644 index 0000000000..c8afa0e478 --- /dev/null +++ b/lib/resources/PromotionCodes.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'promotion_codes', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{promotionCode}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{promotionCode}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Quotes.js b/lib/resources/Quotes.js new file mode 100644 index 0000000000..62aebb65a9 --- /dev/null +++ b/lib/resources/Quotes.js @@ -0,0 +1,65 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'quotes', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{quote}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{quote}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + accept: stripeMethod({ + method: 'POST', + path: '/{quote}/accept', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{quote}/cancel', + }), + + finalizeQuote: stripeMethod({ + method: 'POST', + path: '/{quote}/finalize', + }), + + listComputedUpfrontLineItems: stripeMethod({ + method: 'GET', + path: '/{quote}/computed_upfront_line_items', + methodType: 'list', + }), + + listLineItems: stripeMethod({ + method: 'GET', + path: '/{quote}/line_items', + methodType: 'list', + }), + + pdf: stripeMethod({ + host: 'files.stripe.com', + method: 'GET', + path: '/{quote}/pdf', + streaming: true, + }), +}); diff --git a/lib/resources/Radar/EarlyFraudWarnings.js b/lib/resources/Radar/EarlyFraudWarnings.js new file mode 100644 index 0000000000..9282648d86 --- /dev/null +++ b/lib/resources/Radar/EarlyFraudWarnings.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'radar/early_fraud_warnings', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{earlyFraudWarning}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Radar/ValueListItems.js b/lib/resources/Radar/ValueListItems.js new file mode 100644 index 0000000000..75bcb8947c --- /dev/null +++ b/lib/resources/Radar/ValueListItems.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'radar/value_list_items', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{item}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{item}', + }), +}); diff --git a/lib/resources/Radar/ValueLists.js b/lib/resources/Radar/ValueLists.js new file mode 100644 index 0000000000..ace1edaa2d --- /dev/null +++ b/lib/resources/Radar/ValueLists.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'radar/value_lists', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{valueList}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{valueList}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{valueList}', + }), +}); diff --git a/lib/resources/Refunds.js b/lib/resources/Refunds.js new file mode 100644 index 0000000000..c859fc577c --- /dev/null +++ b/lib/resources/Refunds.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'refunds', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{refund}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{refund}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{refund}/cancel', + }), +}); diff --git a/lib/resources/Reporting/ReportRuns.js b/lib/resources/Reporting/ReportRuns.js new file mode 100644 index 0000000000..84eb179f90 --- /dev/null +++ b/lib/resources/Reporting/ReportRuns.js @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'reporting/report_runs', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{reportRun}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Reporting/ReportTypes.js b/lib/resources/Reporting/ReportTypes.js new file mode 100644 index 0000000000..b4b25f8043 --- /dev/null +++ b/lib/resources/Reporting/ReportTypes.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'reporting/report_types', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{reportType}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Reviews.js b/lib/resources/Reviews.js new file mode 100644 index 0000000000..9400e8b89a --- /dev/null +++ b/lib/resources/Reviews.js @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'reviews', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{review}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + approve: stripeMethod({ + method: 'POST', + path: '/{review}/approve', + }), +}); diff --git a/lib/resources/SKUs.js b/lib/resources/SKUs.js new file mode 100644 index 0000000000..26812405ff --- /dev/null +++ b/lib/resources/SKUs.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'skus', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{id}', + }), +}); diff --git a/lib/resources/SetupAttempts.js b/lib/resources/SetupAttempts.js new file mode 100644 index 0000000000..9bb8c1d3e3 --- /dev/null +++ b/lib/resources/SetupAttempts.js @@ -0,0 +1,16 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'setup_attempts', + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/SetupIntents.js b/lib/resources/SetupIntents.js new file mode 100644 index 0000000000..7aea8dcf75 --- /dev/null +++ b/lib/resources/SetupIntents.js @@ -0,0 +1,46 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'setup_intents', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{intent}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{intent}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{intent}/cancel', + }), + + confirm: stripeMethod({ + method: 'POST', + path: '/{intent}/confirm', + }), + + verifyMicrodeposits: stripeMethod({ + method: 'POST', + path: '/{intent}/verify_microdeposits', + }), +}); diff --git a/lib/resources/ShippingRates.js b/lib/resources/ShippingRates.js new file mode 100644 index 0000000000..d7eb52ac16 --- /dev/null +++ b/lib/resources/ShippingRates.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'shipping_rates', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{shippingRateToken}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{shippingRateToken}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Sigma/ScheduledQueryRuns.js b/lib/resources/Sigma/ScheduledQueryRuns.js new file mode 100644 index 0000000000..a8cb8e60a6 --- /dev/null +++ b/lib/resources/Sigma/ScheduledQueryRuns.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'sigma/scheduled_query_runs', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{scheduledQueryRun}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Sources.js b/lib/resources/Sources.js new file mode 100644 index 0000000000..5008001c5c --- /dev/null +++ b/lib/resources/Sources.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'sources', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{source}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{source}', + }), + + listSourceTransactions: stripeMethod({ + method: 'GET', + path: '/{source}/source_transactions', + methodType: 'list', + }), + + verify: stripeMethod({ + method: 'POST', + path: '/{source}/verify', + }), +}); diff --git a/lib/resources/SubscriptionItems.js b/lib/resources/SubscriptionItems.js new file mode 100644 index 0000000000..62423a22cb --- /dev/null +++ b/lib/resources/SubscriptionItems.js @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'subscription_items', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{item}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{item}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{item}', + }), + + createUsageRecord: stripeMethod({ + method: 'POST', + path: '/{subscriptionItem}/usage_records', + }), + + listUsageRecordSummaries: stripeMethod({ + method: 'GET', + path: '/{subscriptionItem}/usage_record_summaries', + methodType: 'list', + }), +}); diff --git a/lib/resources/SubscriptionSchedules.js b/lib/resources/SubscriptionSchedules.js new file mode 100644 index 0000000000..9c9249e3d9 --- /dev/null +++ b/lib/resources/SubscriptionSchedules.js @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'subscription_schedules', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{schedule}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{schedule}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{schedule}/cancel', + }), + + release: stripeMethod({ + method: 'POST', + path: '/{schedule}/release', + }), +}); diff --git a/lib/resources/Subscriptions.js b/lib/resources/Subscriptions.js new file mode 100644 index 0000000000..bd0dc644c7 --- /dev/null +++ b/lib/resources/Subscriptions.js @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'subscriptions', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{subscriptionExposedId}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{subscriptionExposedId}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'DELETE', + path: '/{subscriptionExposedId}', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{subscriptionExposedId}', + }), + + deleteDiscount: stripeMethod({ + method: 'DELETE', + path: '/{subscriptionExposedId}/discount', + }), + + search: stripeMethod({ + method: 'GET', + path: '/search', + methodType: 'search', + }), +}); diff --git a/lib/resources/TaxCodes.js b/lib/resources/TaxCodes.js new file mode 100644 index 0000000000..57bf52ff3e --- /dev/null +++ b/lib/resources/TaxCodes.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'tax_codes', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/TaxRates.js b/lib/resources/TaxRates.js new file mode 100644 index 0000000000..e429895f7b --- /dev/null +++ b/lib/resources/TaxRates.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'tax_rates', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{taxRate}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{taxRate}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Terminal/Configurations.js b/lib/resources/Terminal/Configurations.js new file mode 100644 index 0000000000..4a7d069a8e --- /dev/null +++ b/lib/resources/Terminal/Configurations.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'terminal/configurations', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{configuration}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{configuration}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{configuration}', + }), +}); diff --git a/lib/resources/Terminal/ConnectionTokens.js b/lib/resources/Terminal/ConnectionTokens.js new file mode 100644 index 0000000000..2e1a1b5155 --- /dev/null +++ b/lib/resources/Terminal/ConnectionTokens.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'terminal/connection_tokens', + + create: stripeMethod({ + method: 'POST', + path: '', + }), +}); diff --git a/lib/resources/Terminal/Locations.js b/lib/resources/Terminal/Locations.js new file mode 100644 index 0000000000..161e0dd014 --- /dev/null +++ b/lib/resources/Terminal/Locations.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'terminal/locations', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{location}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{location}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{location}', + }), +}); diff --git a/lib/resources/Terminal/Readers.js b/lib/resources/Terminal/Readers.js new file mode 100644 index 0000000000..05a8a53ef5 --- /dev/null +++ b/lib/resources/Terminal/Readers.js @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'terminal/readers', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{reader}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{reader}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{reader}', + }), + + cancelAction: stripeMethod({ + method: 'POST', + path: '/{reader}/cancel_action', + }), + + processPaymentIntent: stripeMethod({ + method: 'POST', + path: '/{reader}/process_payment_intent', + }), + + processSetupIntent: stripeMethod({ + method: 'POST', + path: '/{reader}/process_setup_intent', + }), + + setReaderDisplay: stripeMethod({ + method: 'POST', + path: '/{reader}/set_reader_display', + }), +}); diff --git a/lib/resources/TestHelpers/Customers.js b/lib/resources/TestHelpers/Customers.js new file mode 100644 index 0000000000..64796a11da --- /dev/null +++ b/lib/resources/TestHelpers/Customers.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/customers', + + fundCashBalance: stripeMethod({ + method: 'POST', + path: '/{customer}/fund_cash_balance', + }), +}); diff --git a/lib/resources/TestHelpers/Issuing/Cards.js b/lib/resources/TestHelpers/Issuing/Cards.js new file mode 100644 index 0000000000..0f8c50f7d0 --- /dev/null +++ b/lib/resources/TestHelpers/Issuing/Cards.js @@ -0,0 +1,30 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/issuing/cards', + + deliverCard: stripeMethod({ + method: 'POST', + path: '/{card}/shipping/deliver', + }), + + failCard: stripeMethod({ + method: 'POST', + path: '/{card}/shipping/fail', + }), + + returnCard: stripeMethod({ + method: 'POST', + path: '/{card}/shipping/return', + }), + + shipCard: stripeMethod({ + method: 'POST', + path: '/{card}/shipping/ship', + }), +}); diff --git a/lib/resources/TestHelpers/Refunds.js b/lib/resources/TestHelpers/Refunds.js new file mode 100644 index 0000000000..7aac415e77 --- /dev/null +++ b/lib/resources/TestHelpers/Refunds.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/refunds', + + expire: stripeMethod({ + method: 'POST', + path: '/{refund}/expire', + }), +}); diff --git a/lib/resources/TestHelpers/Terminal/Readers.js b/lib/resources/TestHelpers/Terminal/Readers.js new file mode 100644 index 0000000000..ab660b0f95 --- /dev/null +++ b/lib/resources/TestHelpers/Terminal/Readers.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/terminal/readers', + + presentPaymentMethod: stripeMethod({ + method: 'POST', + path: '/{reader}/present_payment_method', + }), +}); diff --git a/lib/resources/TestHelpers/TestClocks.js b/lib/resources/TestHelpers/TestClocks.js new file mode 100644 index 0000000000..adae9ae446 --- /dev/null +++ b/lib/resources/TestHelpers/TestClocks.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/test_clocks', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{testClock}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{testClock}', + }), + + advance: stripeMethod({ + method: 'POST', + path: '/{testClock}/advance', + }), +}); diff --git a/lib/resources/TestHelpers/Treasury/InboundTransfers.js b/lib/resources/TestHelpers/Treasury/InboundTransfers.js new file mode 100644 index 0000000000..178816ad58 --- /dev/null +++ b/lib/resources/TestHelpers/Treasury/InboundTransfers.js @@ -0,0 +1,25 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/treasury/inbound_transfers', + + fail: stripeMethod({ + method: 'POST', + path: '/{id}/fail', + }), + + returnInboundTransfer: stripeMethod({ + method: 'POST', + path: '/{id}/return', + }), + + succeed: stripeMethod({ + method: 'POST', + path: '/{id}/succeed', + }), +}); diff --git a/lib/resources/TestHelpers/Treasury/OutboundPayments.js b/lib/resources/TestHelpers/Treasury/OutboundPayments.js new file mode 100644 index 0000000000..3a1b4d6516 --- /dev/null +++ b/lib/resources/TestHelpers/Treasury/OutboundPayments.js @@ -0,0 +1,25 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/treasury/outbound_payments', + + fail: stripeMethod({ + method: 'POST', + path: '/{id}/fail', + }), + + post: stripeMethod({ + method: 'POST', + path: '/{id}/post', + }), + + returnOutboundPayment: stripeMethod({ + method: 'POST', + path: '/{id}/return', + }), +}); diff --git a/lib/resources/TestHelpers/Treasury/OutboundTransfers.js b/lib/resources/TestHelpers/Treasury/OutboundTransfers.js new file mode 100644 index 0000000000..90d7d6236b --- /dev/null +++ b/lib/resources/TestHelpers/Treasury/OutboundTransfers.js @@ -0,0 +1,25 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/treasury/outbound_transfers', + + fail: stripeMethod({ + method: 'POST', + path: '/{outboundTransfer}/fail', + }), + + post: stripeMethod({ + method: 'POST', + path: '/{outboundTransfer}/post', + }), + + returnOutboundTransfer: stripeMethod({ + method: 'POST', + path: '/{outboundTransfer}/return', + }), +}); diff --git a/lib/resources/TestHelpers/Treasury/ReceivedCredits.js b/lib/resources/TestHelpers/Treasury/ReceivedCredits.js new file mode 100644 index 0000000000..8c3e5f0286 --- /dev/null +++ b/lib/resources/TestHelpers/Treasury/ReceivedCredits.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/treasury/received_credits', + + create: stripeMethod({ + method: 'POST', + path: '', + }), +}); diff --git a/lib/resources/TestHelpers/Treasury/ReceivedDebits.js b/lib/resources/TestHelpers/Treasury/ReceivedDebits.js new file mode 100644 index 0000000000..e25dc03bd2 --- /dev/null +++ b/lib/resources/TestHelpers/Treasury/ReceivedDebits.js @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'test_helpers/treasury/received_debits', + + create: stripeMethod({ + method: 'POST', + path: '', + }), +}); diff --git a/lib/resources/Tokens.js b/lib/resources/Tokens.js new file mode 100644 index 0000000000..76e4d33774 --- /dev/null +++ b/lib/resources/Tokens.js @@ -0,0 +1,20 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'tokens', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{token}', + }), +}); diff --git a/lib/resources/Topups.js b/lib/resources/Topups.js new file mode 100644 index 0000000000..e65fe5d14a --- /dev/null +++ b/lib/resources/Topups.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'topups', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{topup}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{topup}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{topup}/cancel', + }), +}); diff --git a/lib/resources/Transfers.js b/lib/resources/Transfers.js new file mode 100644 index 0000000000..656128c392 --- /dev/null +++ b/lib/resources/Transfers.js @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'transfers', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{transfer}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{transfer}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + createReversal: stripeMethod({ + method: 'POST', + path: '/{id}/reversals', + }), + + retrieveReversal: stripeMethod({ + method: 'GET', + path: '/{transfer}/reversals/{id}', + }), + + updateReversal: stripeMethod({ + method: 'POST', + path: '/{transfer}/reversals/{id}', + }), + + listReversals: stripeMethod({ + method: 'GET', + path: '/{id}/reversals', + methodType: 'list', + }), +}); diff --git a/lib/resources/Treasury/CreditReversals.js b/lib/resources/Treasury/CreditReversals.js new file mode 100644 index 0000000000..9039b816a2 --- /dev/null +++ b/lib/resources/Treasury/CreditReversals.js @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/credit_reversals', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{creditReversal}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Treasury/DebitReversals.js b/lib/resources/Treasury/DebitReversals.js new file mode 100644 index 0000000000..f95bfe5372 --- /dev/null +++ b/lib/resources/Treasury/DebitReversals.js @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/debit_reversals', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{debitReversal}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Treasury/FinancialAccounts.js b/lib/resources/Treasury/FinancialAccounts.js new file mode 100644 index 0000000000..a30b1273b6 --- /dev/null +++ b/lib/resources/Treasury/FinancialAccounts.js @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/financial_accounts', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{financialAccount}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{financialAccount}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + retrieveFeatures: stripeMethod({ + method: 'GET', + path: '/{financialAccount}/features', + }), + + updateFeatures: stripeMethod({ + method: 'POST', + path: '/{financialAccount}/features', + }), +}); diff --git a/lib/resources/Treasury/InboundTransfers.js b/lib/resources/Treasury/InboundTransfers.js new file mode 100644 index 0000000000..468adba0e7 --- /dev/null +++ b/lib/resources/Treasury/InboundTransfers.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/inbound_transfers', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{inboundTransfer}/cancel', + }), +}); diff --git a/lib/resources/Treasury/OutboundPayments.js b/lib/resources/Treasury/OutboundPayments.js new file mode 100644 index 0000000000..5d08eaad0d --- /dev/null +++ b/lib/resources/Treasury/OutboundPayments.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/outbound_payments', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{id}/cancel', + }), +}); diff --git a/lib/resources/Treasury/OutboundTransfers.js b/lib/resources/Treasury/OutboundTransfers.js new file mode 100644 index 0000000000..40d72465dd --- /dev/null +++ b/lib/resources/Treasury/OutboundTransfers.js @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/outbound_transfers', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{outboundTransfer}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + cancel: stripeMethod({ + method: 'POST', + path: '/{outboundTransfer}/cancel', + }), +}); diff --git a/lib/resources/Treasury/ReceivedCredits.js b/lib/resources/Treasury/ReceivedCredits.js new file mode 100644 index 0000000000..98b11b1699 --- /dev/null +++ b/lib/resources/Treasury/ReceivedCredits.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/received_credits', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Treasury/ReceivedDebits.js b/lib/resources/Treasury/ReceivedDebits.js new file mode 100644 index 0000000000..c472144aa9 --- /dev/null +++ b/lib/resources/Treasury/ReceivedDebits.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/received_debits', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Treasury/TransactionEntries.js b/lib/resources/Treasury/TransactionEntries.js new file mode 100644 index 0000000000..a52c432d68 --- /dev/null +++ b/lib/resources/Treasury/TransactionEntries.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/transaction_entries', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/Treasury/Transactions.js b/lib/resources/Treasury/Transactions.js new file mode 100644 index 0000000000..a810413116 --- /dev/null +++ b/lib/resources/Treasury/Transactions.js @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'treasury/transactions', + + retrieve: stripeMethod({ + method: 'GET', + path: '/{id}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), +}); diff --git a/lib/resources/WebhookEndpoints.js b/lib/resources/WebhookEndpoints.js new file mode 100644 index 0000000000..2f73d65031 --- /dev/null +++ b/lib/resources/WebhookEndpoints.js @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec + +'use strict'; + +const StripeResource = require('../StripeResource'); +const stripeMethod = StripeResource.method; + +module.exports = StripeResource.extend({ + path: 'webhook_endpoints', + + create: stripeMethod({ + method: 'POST', + path: '', + }), + + retrieve: stripeMethod({ + method: 'GET', + path: '/{webhookEndpoint}', + }), + + update: stripeMethod({ + method: 'POST', + path: '/{webhookEndpoint}', + }), + + list: stripeMethod({ + method: 'GET', + path: '', + methodType: 'list', + }), + + del: stripeMethod({ + method: 'DELETE', + path: '/{webhookEndpoint}', + }), +}); diff --git a/lib/stripe.js b/lib/stripe.js new file mode 100644 index 0000000000..f928ee5a51 --- /dev/null +++ b/lib/stripe.js @@ -0,0 +1,610 @@ +'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; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000000..7eed41defd --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,451 @@ +'use strict'; + +const EventEmitter = require('events').EventEmitter; +const qs = require('qs'); +const crypto = require('crypto'); + +const hasOwn = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); + +// Certain sandboxed environments (our known example right now are CloudFlare +// Workers) may make `child_process` unavailable. Because `exec` isn't critical +// to the operation of stripe-node, we handle this unavailability gracefully. +let exec = null; +try { + exec = require('child_process').exec; +} catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } +} + +const OPTIONS_KEYS = [ + 'apiKey', + 'idempotencyKey', + 'stripeAccount', + 'apiVersion', + 'maxNetworkRetries', + 'timeout', + 'host', +]; + +const DEPRECATED_OPTIONS = { + api_key: 'apiKey', + idempotency_key: 'idempotencyKey', + stripe_account: 'stripeAccount', + stripe_version: 'apiVersion', + stripeVersion: 'apiVersion', +}; +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)) || + DEPRECATED_OPTIONS_KEYS.some((prop) => hasOwn(o, prop))) + ); + }, + + /** + * Stringifies an Object, accommodating nested objects + * (forming the conventional key 'parent[child]=value') + */ + stringifyRequestData: (data) => { + return ( + qs + .stringify(data, { + serializeDate: (d) => Math.floor(d.getTime() / 1000), + }) + // Don't use strict form encoding by changing the square bracket control + // characters back to their literals. This is fine by the server, and + // makes these parameter strings easier to read. + .replace(/%5B/g, '[') + .replace(/%5D/g, ']') + ); + }, + + /** + * Outputs a new function with interpolated object property values. + * Use like so: + * const fn = makeURLInterpolator('some/url/{param1}/{param2}'); + * fn({ param1: 123, param2: 456 }); // => 'some/url/123/456' + */ + makeURLInterpolator: (() => { + const rc = { + '\n': '\\n', + '"': '\\"', + '\u2028': '\\u2028', + '\u2029': '\\u2029', + }; + return (str) => { + const cleanString = str.replace(/["\n\r\u2028\u2029]/g, ($0) => rc[$0]); + return (outputs) => { + return cleanString.replace(/\{([\s\S]+?)\}/g, ($0, $1) => + encodeURIComponent(outputs[$1] || '') + ); + }; + }; + })(), + + extractUrlParams: (path) => { + const params = path.match(/\{\w+\}/g); + if (!params) { + return []; + } + + return params.map((param) => param.replace(/[{}]/g, '')); + }, + + /** + * Return the data argument from a list of arguments + * + * @param {object[]} args + * @returns {object} + */ + getDataFromArgs(args) { + if (!Array.isArray(args) || !args[0] || typeof args[0] !== 'object') { + return {}; + } + + if (!utils.isOptionsHash(args[0])) { + return args.shift(); + } + + const argKeys = Object.keys(args[0]); + + const optionKeysInArgs = argKeys.filter((key) => + OPTIONS_KEYS.includes(key) + ); + + // In some cases options may be the provided as the first argument. + // Here we're detecting a case where there are two distinct arguments + // (the first being args and the second options) and with known + // option keys in the first so that we can warn the user about it. + if ( + optionKeysInArgs.length > 0 && + optionKeysInArgs.length !== argKeys.length + ) { + emitWarning( + `Options found in arguments (${optionKeysInArgs.join( + ', ' + )}). Did you mean to pass an options object? See https://github.com/stripe/stripe-node/wiki/Passing-Options.` + ); + } + + return {}; + }, + + /** + * Return the options hash from a list of arguments + */ + getOptionsFromArgs: (args) => { + const opts = { + auth: null, + headers: {}, + settings: {}, + }; + if (args.length > 0) { + const arg = args[args.length - 1]; + if (typeof arg === 'string') { + opts.auth = args.pop(); + } else if (utils.isOptionsHash(arg)) { + const params = {...args.pop()}; + + const extraKeys = Object.keys(params).filter( + (key) => !OPTIONS_KEYS.includes(key) + ); + + if (extraKeys.length) { + const nonDeprecated = extraKeys.filter((key) => { + if (!DEPRECATED_OPTIONS[key]) { + 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). + */ + emitWarning(`'${key}' is deprecated; use '${newParam}' instead.`); + params[newParam] = params[key]; + }); + if (nonDeprecated.length) { + emitWarning( + `Invalid options found (${extraKeys.join(', ')}); ignoring.` + ); + } + } + + if (params.apiKey) { + opts.auth = params.apiKey; + } + if (params.idempotencyKey) { + opts.headers['Idempotency-Key'] = params.idempotencyKey; + } + if (params.stripeAccount) { + opts.headers['Stripe-Account'] = params.stripeAccount; + } + if (params.apiVersion) { + opts.headers['Stripe-Version'] = params.apiVersion; + } + if (Number.isInteger(params.maxNetworkRetries)) { + opts.settings.maxNetworkRetries = params.maxNetworkRetries; + } + if (Number.isInteger(params.timeout)) { + opts.settings.timeout = params.timeout; + } + if (params.host) { + opts.host = params.host; + } + } + } + return opts; + }, + + /** + * Provide simple "Class" extension mechanism + */ + protoExtend(sub) { + const Super = this; + const Constructor = hasOwn(sub, 'constructor') + ? sub.constructor + : function(...args) { + Super.apply(this, args); + }; + + // This initialization logic is somewhat sensitive to be compatible with + // divergent JS implementations like the one found in Qt. See here for more + // context: + // + // https://github.com/stripe/stripe-node/pull/334 + Object.assign(Constructor, Super); + Constructor.prototype = Object.create(Super.prototype); + Object.assign(Constructor.prototype, sub); + + return Constructor; + }, + + /** + * Secure compare, from https://github.com/freewil/scmp + */ + secureCompare: (a, b) => { + a = Buffer.from(a); + b = Buffer.from(b); + + // return early here if buffer lengths are not equal since timingSafeEqual + // will throw if buffer lengths are not equal + if (a.length !== b.length) { + return false; + } + + // use crypto.timingSafeEqual if available (since Node.js v6.6.0), + // otherwise use our own scmp-internal function. + if (crypto.timingSafeEqual) { + return crypto.timingSafeEqual(a, b); + } + + const len = a.length; + let result = 0; + + for (let i = 0; i < len; ++i) { + result |= a[i] ^ b[i]; + } + return result === 0; + }, + + /** + * Remove empty values from an object + */ + removeNullish: (obj) => { + if (typeof obj !== 'object') { + throw new Error('Argument must be an object'); + } + + return Object.keys(obj).reduce((result, key) => { + if (obj[key] != null) { + result[key] = obj[key]; + } + return result; + }, {}); + }, + + /** + * Normalize standard HTTP Headers: + * {'foo-bar': 'hi'} + * becomes + * {'Foo-Bar': 'hi'} + */ + normalizeHeaders: (obj) => { + if (!(obj && typeof obj === 'object')) { + return obj; + } + + return Object.keys(obj).reduce((result, header) => { + result[utils.normalizeHeader(header)] = obj[header]; + return result; + }, {}); + }, + + /** + * Stolen from https://github.com/marten-de-vries/header-case-normalizer/blob/master/index.js#L36-L41 + * without the exceptions which are irrelevant to us. + */ + normalizeHeader: (header) => { + return header + .split('-') + .map( + (text) => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase() + ) + .join('-'); + }, + + /** + * Determine if file data is a derivative of EventEmitter class. + * https://nodejs.org/api/events.html#events_events + */ + checkForStream: (obj) => { + if (obj.file && obj.file.data) { + return obj.file.data instanceof EventEmitter; + } + return false; + }, + + callbackifyPromiseWithTimeout: (promise, callback) => { + if (callback) { + // Ensure callback is called outside of promise stack. + return promise.then( + (res) => { + setTimeout(() => { + callback(null, res); + }, 0); + }, + (err) => { + setTimeout(() => { + callback(err, null); + }, 0); + } + ); + } + + return promise; + }, + + /** + * Allow for special capitalization cases (such as OAuth) + */ + pascalToCamelCase: (name) => { + if (name === 'OAuth') { + return 'oauth'; + } else { + return name[0].toLowerCase() + name.substring(1); + } + }, + + emitWarning, + + /** + * Node's built in `exec` function sometimes throws outright, + * and sometimes has a callback with an error, + * depending on the type of error. + * + * This unifies that interface. + */ + safeExec: (cmd, cb) => { + // Occurs if we couldn't load the `child_process` module, which might + // happen in certain sandboxed environments like a CloudFlare Worker. + if (utils._exec === null) { + cb(new Error('exec not available'), null); + return; + } + + try { + utils._exec(cmd, cb); + } catch (e) { + cb(e, null); + } + }, + + // For mocking in tests. + _exec: exec, + + isObject: (obj) => { + const type = typeof obj; + return (type === 'function' || type === 'object') && !!obj; + }, + + // For use in multipart requests + flattenAndStringify: (data) => { + const result = {}; + + const step = (obj, prevKey) => { + Object.keys(obj).forEach((key) => { + const value = obj[key]; + + const newKey = prevKey ? `${prevKey}[${key}]` : key; + + if (utils.isObject(value)) { + if (!Buffer.isBuffer(value) && !value.hasOwnProperty('data')) { + // Non-buffer non-file Objects are recursively flattened + return step(value, newKey); + } else { + // Buffers and file objects are stored without modification + result[newKey] = value; + } + } else { + // Primitives are converted to strings + result[newKey] = String(value); + } + }); + }; + + step(data); + + return result; + }, + + /** + * https://stackoverflow.com/a/2117523 + */ + uuid4: () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); + }, + + validateInteger: (name, n, defaultVal) => { + if (!Number.isInteger(n)) { + if (defaultVal !== undefined) { + return defaultVal; + } else { + throw new Error(`${name} must be an integer`); + } + } + + return n; + }, + + determineProcessUserAgentProperties: () => { + return typeof process === 'undefined' + ? {} + : { + lang_version: process.version, + platform: process.platform, + }; + }, +}); + +function emitWarning(warning) { + if (typeof process.emitWarning !== 'function') { + return console.warn( + `Stripe: ${warning}` + ); /* eslint-disable-line no-console */ + } + + return process.emitWarning(warning, 'Stripe'); +} diff --git a/package.json b/package.json index 184ca78368..5a495950da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stripe", - "version": "10.8.0", + "version": "10.7.0", "description": "Stripe API wrapper", "keywords": [ "stripe", @@ -27,7 +27,6 @@ "main": "lib/stripe.js", "types": "types/2022-08-01/index.d.ts", "devDependencies": { - "@istanbuljs/nyc-config-typescript": "^1.0.2", "@typescript-eslint/eslint-plugin": "^2.13.0", "@typescript-eslint/parser": "^2.13.0", "chai": "~4.2.0", @@ -56,12 +55,10 @@ }, "license": "MIT", "scripts": { - "build": "tsc -p tsconfig.json", - "clean": "rm -rf ./.nyc_output ./node_modules/.cache ./coverage ./lib", - "prepack": "yarn install && yarn build", + "clean": "rm -rf ./.nyc_output ./node_modules/.cache ./coverage", "mocha": "nyc mocha --config=test/.mocharc.js", "mocha-only": "mocha --config=test/.mocharc.js", - "test": "yarn build && yarn test-typescript && yarn mocha", + "test": "yarn test-typescript && yarn mocha", "test-typescript": "tsc --build types/test", "lint": "eslint --ext .js,.jsx,.ts .", "fix": "yarn lint --fix && ./scripts/updateAPIVersion.js", diff --git a/types/2022-08-01/Checkout/Sessions.d.ts b/types/2022-08-01/Checkout/Sessions.d.ts index 04b258e607..e3fd770a05 100644 --- a/types/2022-08-01/Checkout/Sessions.d.ts +++ b/types/2022-08-01/Checkout/Sessions.d.ts @@ -1746,7 +1746,7 @@ declare module 'stripe' { enabled: boolean; /** - * The maximum quantity the customer can purchase for the Checkout Session. By default this value is 99. You can specify a value up to 999999. + * The maximum quantity the customer can purchase for the Checkout Session. By default this value is 99. You can specify a value up to 999. */ maximum?: number; diff --git a/types/2022-08-01/FileLinks.d.ts b/types/2022-08-01/FileLinks.d.ts index 7ae6dc56bf..75efff0ba5 100644 --- a/types/2022-08-01/FileLinks.d.ts +++ b/types/2022-08-01/FileLinks.d.ts @@ -56,7 +56,7 @@ declare module 'stripe' { interface FileLinkCreateParams { /** - * The ID of the file. The file's `purpose` must be one of the following: `business_icon`, `business_logo`, `customer_signature`, `dispute_evidence`, `finance_report_run`, `identity_document_downloadable`, `pci_document`, `selfie`, `sigma_scheduled_query`, `tax_document_user_upload`, or `terminal_reader_splashscreen`. + * The ID of the file. The file's `purpose` must be one of the following: `business_icon`, `business_logo`, `customer_signature`, `dispute_evidence`, `finance_report_run`, `identity_document_downloadable`, `pci_document`, `selfie`, `sigma_scheduled_query`, or `tax_document_user_upload`. */ file: string; diff --git a/types/2022-08-01/Files.d.ts b/types/2022-08-01/Files.d.ts index 8f1d6d8b49..5e1fa4da22 100644 --- a/types/2022-08-01/Files.d.ts +++ b/types/2022-08-01/Files.d.ts @@ -83,8 +83,7 @@ declare module 'stripe' { | 'pci_document' | 'selfie' | 'sigma_scheduled_query' - | 'tax_document_user_upload' - | 'terminal_reader_splashscreen'; + | 'tax_document_user_upload'; } interface FileCreateParams {} @@ -125,8 +124,7 @@ declare module 'stripe' { | 'pci_document' | 'selfie' | 'sigma_scheduled_query' - | 'tax_document_user_upload' - | 'terminal_reader_splashscreen'; + | 'tax_document_user_upload'; } class FilesResource { diff --git a/yarn.lock b/yarn.lock index 98be4cd4e4..7c7a6481f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -204,13 +204,6 @@ js-yaml "^3.13.1" resolve-from "^5.0.0" -"@istanbuljs/nyc-config-typescript@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz#1f5235b28540a07219ae0dd42014912a0b19cf89" - integrity sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - "@istanbuljs/schema@^0.1.2": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"