From 801197de361d3f53aff28755972a347e8f116628 Mon Sep 17 00:00:00 2001 From: "stripe-openapi[bot]" <105521251+stripe-openapi[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:36:12 -0700 Subject: [PATCH 1/7] Update generated code (#2179) * Update generated code for v1267 * Update generated code for v1268 * Update generated code for v1268 --------- Co-authored-by: Stripe OpenAPI <105521251+stripe-openapi[bot]@users.noreply.github.com> --- OPENAPI_VERSION | 2 +- src/apiVersion.ts | 2 +- src/resources.ts | 6 + src/resources/Billing/CreditBalanceSummary.ts | 10 + .../Billing/CreditBalanceTransactions.ts | 15 ++ src/resources/Billing/CreditGrants.ts | 28 +++ .../resources/generated_examples_test.spec.js | 11 - types/Billing/Alerts.d.ts | 30 +-- types/Billing/AlertsResource.d.ts | 39 ++-- types/Billing/CreditBalanceSummary.d.ts | 94 ++++++++ .../Billing/CreditBalanceSummaryResource.d.ts | 64 +++++ types/Billing/CreditBalanceTransactions.d.ts | 159 +++++++++++++ .../CreditBalanceTransactionsResource.d.ts | 54 +++++ types/Billing/CreditGrants.d.ts | 124 ++++++++++ types/Billing/CreditGrantsResource.d.ts | 219 ++++++++++++++++++ .../BillingPortal/ConfigurationsResource.d.ts | 4 +- types/Capabilities.d.ts | 2 +- types/Checkout/SessionsResource.d.ts | 2 +- types/CreditNoteLineItems.d.ts | 30 +++ types/CreditNotes.d.ts | 30 +++ types/Customers.d.ts | 5 +- types/EventTypes.d.ts | 2 + types/InvoiceLineItems.d.ts | 36 +++ types/Invoices.d.ts | 38 +++ types/Margins.d.ts | 56 +++++ types/ProductsResource.d.ts | 29 ++- types/PromotionCodes.d.ts | 2 +- types/PromotionCodesResource.d.ts | 4 +- types/SubscriptionsResource.d.ts | 6 +- types/Tax/Settings.d.ts | 2 +- types/Terminal/ReadersResource.d.ts | 17 +- types/Treasury/ReceivedCredits.d.ts | 6 +- types/WebhookEndpointsResource.d.ts | 3 +- types/index.d.ts | 10 + types/lib.d.ts | 2 +- types/test/typescriptTest.ts | 6 +- 36 files changed, 1078 insertions(+), 71 deletions(-) create mode 100644 src/resources/Billing/CreditBalanceSummary.ts create mode 100644 src/resources/Billing/CreditBalanceTransactions.ts create mode 100644 src/resources/Billing/CreditGrants.ts create mode 100644 types/Billing/CreditBalanceSummary.d.ts create mode 100644 types/Billing/CreditBalanceSummaryResource.d.ts create mode 100644 types/Billing/CreditBalanceTransactions.d.ts create mode 100644 types/Billing/CreditBalanceTransactionsResource.d.ts create mode 100644 types/Billing/CreditGrants.d.ts create mode 100644 types/Billing/CreditGrantsResource.d.ts create mode 100644 types/Margins.d.ts diff --git a/OPENAPI_VERSION b/OPENAPI_VERSION index 5f5b311191..8f166ae2e0 100644 --- a/OPENAPI_VERSION +++ b/OPENAPI_VERSION @@ -1 +1 @@ -v1267 \ No newline at end of file +v1268 \ No newline at end of file diff --git a/src/apiVersion.ts b/src/apiVersion.ts index 1966b582c5..c59d0bde0f 100644 --- a/src/apiVersion.ts +++ b/src/apiVersion.ts @@ -1,3 +1,3 @@ // File generated from our OpenAPI spec -export const ApiVersion = '2024-06-20'; +export const ApiVersion = '2024-09-30.acacia'; diff --git a/src/resources.ts b/src/resources.ts index 109e6e9eb8..857661b713 100644 --- a/src/resources.ts +++ b/src/resources.ts @@ -14,6 +14,9 @@ import {Configurations as BillingPortalConfigurations} from './resources/Billing import {Configurations as TerminalConfigurations} from './resources/Terminal/Configurations.js'; import {ConfirmationTokens as TestHelpersConfirmationTokens} from './resources/TestHelpers/ConfirmationTokens.js'; import {ConnectionTokens as TerminalConnectionTokens} from './resources/Terminal/ConnectionTokens.js'; +import {CreditBalanceSummary as BillingCreditBalanceSummary} from './resources/Billing/CreditBalanceSummary.js'; +import {CreditBalanceTransactions as BillingCreditBalanceTransactions} from './resources/Billing/CreditBalanceTransactions.js'; +import {CreditGrants as BillingCreditGrants} from './resources/Billing/CreditGrants.js'; import {CreditReversals as TreasuryCreditReversals} from './resources/Treasury/CreditReversals.js'; import {Customers as TestHelpersCustomers} from './resources/TestHelpers/Customers.js'; import {DebitReversals as TreasuryDebitReversals} from './resources/Treasury/DebitReversals.js'; @@ -122,6 +125,9 @@ export {WebhookEndpoints} from './resources/WebhookEndpoints.js'; export const Apps = resourceNamespace('apps', {Secrets: AppsSecrets}); export const Billing = resourceNamespace('billing', { Alerts: BillingAlerts, + CreditBalanceSummary: BillingCreditBalanceSummary, + CreditBalanceTransactions: BillingCreditBalanceTransactions, + CreditGrants: BillingCreditGrants, MeterEventAdjustments: BillingMeterEventAdjustments, MeterEvents: BillingMeterEvents, Meters: BillingMeters, diff --git a/src/resources/Billing/CreditBalanceSummary.ts b/src/resources/Billing/CreditBalanceSummary.ts new file mode 100644 index 0000000000..0258a0ec98 --- /dev/null +++ b/src/resources/Billing/CreditBalanceSummary.ts @@ -0,0 +1,10 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const CreditBalanceSummary = StripeResource.extend({ + retrieve: stripeMethod({ + method: 'GET', + fullPath: '/v1/billing/credit_balance_summary', + }), +}); diff --git a/src/resources/Billing/CreditBalanceTransactions.ts b/src/resources/Billing/CreditBalanceTransactions.ts new file mode 100644 index 0000000000..5f4cca0d2e --- /dev/null +++ b/src/resources/Billing/CreditBalanceTransactions.ts @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const CreditBalanceTransactions = StripeResource.extend({ + retrieve: stripeMethod({ + method: 'GET', + fullPath: '/v1/billing/credit_balance_transactions/{id}', + }), + list: stripeMethod({ + method: 'GET', + fullPath: '/v1/billing/credit_balance_transactions', + methodType: 'list', + }), +}); diff --git a/src/resources/Billing/CreditGrants.ts b/src/resources/Billing/CreditGrants.ts new file mode 100644 index 0000000000..19622cb64f --- /dev/null +++ b/src/resources/Billing/CreditGrants.ts @@ -0,0 +1,28 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const CreditGrants = StripeResource.extend({ + create: stripeMethod({method: 'POST', fullPath: '/v1/billing/credit_grants'}), + retrieve: stripeMethod({ + method: 'GET', + fullPath: '/v1/billing/credit_grants/{id}', + }), + update: stripeMethod({ + method: 'POST', + fullPath: '/v1/billing/credit_grants/{id}', + }), + list: stripeMethod({ + method: 'GET', + fullPath: '/v1/billing/credit_grants', + methodType: 'list', + }), + expire: stripeMethod({ + method: 'POST', + fullPath: '/v1/billing/credit_grants/{id}/expire', + }), + voidGrant: stripeMethod({ + method: 'POST', + fullPath: '/v1/billing/credit_grants/{id}/void', + }), +}); diff --git a/test/resources/generated_examples_test.spec.js b/test/resources/generated_examples_test.spec.js index 8a87f2869d..8a4e86cfaa 100644 --- a/test/resources/generated_examples_test.spec.js +++ b/test/resources/generated_examples_test.spec.js @@ -2988,17 +2988,6 @@ describe('Generated tests', function() { expect(reader).not.to.be.null; }); - it('test_terminal_readers_process_setup_intent_post', async function() { - const reader = await stripe.terminal.readers.processSetupIntent( - 'tmr_xxxxxxxxxxxxx', - { - setup_intent: 'seti_xxxxxxxxxxxxx', - customer_consent_collected: true, - } - ); - expect(reader).not.to.be.null; - }); - it('test_test_helpers_customers_fund_cash_balance_post', async function() { const customerCashBalanceTransaction = await stripe.testHelpers.customers.fundCashBalance( 'cus_123', diff --git a/types/Billing/Alerts.d.ts b/types/Billing/Alerts.d.ts index 3989d21281..4184264ffb 100644 --- a/types/Billing/Alerts.d.ts +++ b/types/Billing/Alerts.d.ts @@ -22,11 +22,6 @@ declare module 'stripe' { */ alert_type: 'usage_threshold'; - /** - * Limits the scope of the alert to a specific [customer](https://stripe.com/docs/api/customers). - */ - filter: Alert.Filter | null; - /** * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. */ @@ -45,20 +40,18 @@ declare module 'stripe' { /** * Encapsulates configuration of the alert to monitor usage on a specific [Billing Meter](https://stripe.com/docs/api/billing/meter). */ - usage_threshold_config: Alert.UsageThresholdConfig | null; + usage_threshold: Alert.UsageThreshold | null; } namespace Alert { - interface Filter { + type Status = 'active' | 'archived' | 'inactive'; + + interface UsageThreshold { /** - * Limit the scope of the alert to this customer ID + * The filters allow limiting the scope of this usage alert. You can only specify up to one filter at this time. */ - customer: string | Stripe.Customer | null; - } + filters: Array | null; - type Status = 'active' | 'archived' | 'inactive'; - - interface UsageThresholdConfig { /** * The value at which this alert will trigger. */ @@ -74,6 +67,17 @@ declare module 'stripe' { */ recurrence: 'one_time'; } + + namespace UsageThreshold { + interface Filter { + /** + * Limit the scope of the alert to this customer ID + */ + customer: string | Stripe.Customer | null; + + type: 'customer'; + } + } } } } diff --git a/types/Billing/AlertsResource.d.ts b/types/Billing/AlertsResource.d.ts index 6090503b89..fa0fdec94e 100644 --- a/types/Billing/AlertsResource.d.ts +++ b/types/Billing/AlertsResource.d.ts @@ -19,36 +19,19 @@ declare module 'stripe' { */ expand?: Array; - /** - * Filters to limit the scope of an alert. - */ - filter?: AlertCreateParams.Filter; - /** * The configuration of the usage threshold. */ - usage_threshold_config?: AlertCreateParams.UsageThresholdConfig; + usage_threshold?: AlertCreateParams.UsageThreshold; } namespace AlertCreateParams { - interface Filter { - /** - * Limit the scope to this alert only to this customer. - */ - customer?: string; - + interface UsageThreshold { /** - * Limit the scope of this rated usage alert to this subscription. + * The filters allows limiting the scope of this usage alert. You can only specify up to one filter at this time. */ - subscription?: string; + filters?: Array; - /** - * Limit the scope of this rated usage alert to this subscription item. - */ - subscription_item?: string; - } - - interface UsageThresholdConfig { /** * Defines at which value the alert will fire. */ @@ -64,6 +47,20 @@ declare module 'stripe' { */ recurrence: 'one_time'; } + + namespace UsageThreshold { + interface Filter { + /** + * Limit the scope to this usage alert only to this customer. + */ + customer?: string; + + /** + * What type of filter is being applied to this usage alert. + */ + type: 'customer'; + } + } } interface AlertRetrieveParams { diff --git a/types/Billing/CreditBalanceSummary.d.ts b/types/Billing/CreditBalanceSummary.d.ts new file mode 100644 index 0000000000..95c82c1f5d --- /dev/null +++ b/types/Billing/CreditBalanceSummary.d.ts @@ -0,0 +1,94 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace Billing { + /** + * Indicates the credit balance for credits granted to a customer. + */ + interface CreditBalanceSummary { + /** + * String representing the object's type. Objects of the same type share the same value. + */ + object: 'billing.credit_balance_summary'; + + /** + * The credit balances. One entry per credit grant currency. If a customer only has credit grants in a single currency, then this will have a single balance entry. + */ + balances: Array; + + /** + * The customer the balance is for. + */ + customer: string | Stripe.Customer | Stripe.DeletedCustomer; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + } + + namespace CreditBalanceSummary { + interface Balance { + available_balance: Balance.AvailableBalance; + + ledger_balance: Balance.LedgerBalance; + } + + namespace Balance { + interface AvailableBalance { + /** + * The monetary amount. + */ + monetary: AvailableBalance.Monetary | null; + + /** + * The type of this amount. We currently only support `monetary` credits. + */ + type: 'monetary'; + } + + namespace AvailableBalance { + interface Monetary { + /** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Must be a [supported currency](https://stripe.com/docs/currencies). + */ + currency: string; + + /** + * A positive integer representing the amount. + */ + value: number; + } + } + + interface LedgerBalance { + /** + * The monetary amount. + */ + monetary: LedgerBalance.Monetary | null; + + /** + * The type of this amount. We currently only support `monetary` credits. + */ + type: 'monetary'; + } + + namespace LedgerBalance { + interface Monetary { + /** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Must be a [supported currency](https://stripe.com/docs/currencies). + */ + currency: string; + + /** + * A positive integer representing the amount. + */ + value: number; + } + } + } + } + } + } +} diff --git a/types/Billing/CreditBalanceSummaryResource.d.ts b/types/Billing/CreditBalanceSummaryResource.d.ts new file mode 100644 index 0000000000..a2c9312449 --- /dev/null +++ b/types/Billing/CreditBalanceSummaryResource.d.ts @@ -0,0 +1,64 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace Billing { + interface CreditBalanceSummaryRetrieveParams { + /** + * The customer for which to fetch credit balance summary. + */ + customer: string; + + /** + * The filter criteria for the credit balance summary. + */ + filter: CreditBalanceSummaryRetrieveParams.Filter; + + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + } + + namespace CreditBalanceSummaryRetrieveParams { + interface Filter { + /** + * The credit applicability scope for which to fetch balance summary. + */ + applicability_scope?: Filter.ApplicabilityScope; + + /** + * The credit grant for which to fetch balance summary. + */ + credit_grant?: string; + + /** + * Specify the type of this filter. + */ + type: Filter.Type; + } + + namespace Filter { + interface ApplicabilityScope { + /** + * The price type to which credit grants can apply to. We currently only support `metered` price type. + */ + price_type: 'metered'; + } + + type Type = 'applicability_scope' | 'credit_grant'; + } + } + + class CreditBalanceSummaryResource { + /** + * Retrieves the credit balance summary for a customer + */ + retrieve( + params: CreditBalanceSummaryRetrieveParams, + options?: RequestOptions + ): Promise>; + } + } + } +} diff --git a/types/Billing/CreditBalanceTransactions.d.ts b/types/Billing/CreditBalanceTransactions.d.ts new file mode 100644 index 0000000000..91b1b93dd4 --- /dev/null +++ b/types/Billing/CreditBalanceTransactions.d.ts @@ -0,0 +1,159 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace Billing { + /** + * A credit balance transaction is a resource representing a transaction (either a credit or a debit) against an existing credit grant. + */ + interface CreditBalanceTransaction { + /** + * Unique identifier for the object. + */ + id: string; + + /** + * String representing the object's type. Objects of the same type share the same value. + */ + object: 'billing.credit_balance_transaction'; + + /** + * Time at which the object was created. Measured in seconds since the Unix epoch. + */ + created: number; + + /** + * Credit details for this balance transaction. Only present if type is `credit`. + */ + credit: CreditBalanceTransaction.Credit | null; + + /** + * The credit grant associated with this balance transaction. + */ + credit_grant: string | Stripe.Billing.CreditGrant; + + /** + * Debit details for this balance transaction. Only present if type is `debit`. + */ + debit: CreditBalanceTransaction.Debit | null; + + /** + * The effective time of this balance transaction. + */ + effective_at: number; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + + /** + * ID of the test clock this credit balance transaction belongs to. + */ + test_clock: string | Stripe.TestHelpers.TestClock | null; + + /** + * The type of balance transaction (credit or debit). + */ + type: CreditBalanceTransaction.Type | null; + } + + namespace CreditBalanceTransaction { + interface Credit { + amount: Credit.Amount; + + /** + * The type of credit transaction. + */ + type: 'credits_granted'; + } + + namespace Credit { + interface Amount { + /** + * The monetary amount. + */ + monetary: Amount.Monetary | null; + + /** + * The type of this amount. We currently only support `monetary` credits. + */ + type: 'monetary'; + } + + namespace Amount { + interface Monetary { + /** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Must be a [supported currency](https://stripe.com/docs/currencies). + */ + currency: string; + + /** + * A positive integer representing the amount. + */ + value: number; + } + } + } + + interface Debit { + amount: Debit.Amount; + + /** + * Details of how the credits were applied to an invoice. Only present if `type` is `credits_applied`. + */ + credits_applied: Debit.CreditsApplied | null; + + /** + * The type of debit transaction. + */ + type: Debit.Type; + } + + namespace Debit { + interface Amount { + /** + * The monetary amount. + */ + monetary: Amount.Monetary | null; + + /** + * The type of this amount. We currently only support `monetary` credits. + */ + type: 'monetary'; + } + + namespace Amount { + interface Monetary { + /** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Must be a [supported currency](https://stripe.com/docs/currencies). + */ + currency: string; + + /** + * A positive integer representing the amount. + */ + value: number; + } + } + + interface CreditsApplied { + /** + * The invoice to which the credits were applied. + */ + invoice: string | Stripe.Invoice; + + /** + * The invoice line item to which the credits were applied. + */ + invoice_line_item: string; + } + + type Type = 'credits_applied' | 'credits_expired' | 'credits_voided'; + } + + type Type = 'credit' | 'debit'; + } + } + } +} diff --git a/types/Billing/CreditBalanceTransactionsResource.d.ts b/types/Billing/CreditBalanceTransactionsResource.d.ts new file mode 100644 index 0000000000..22eb502afa --- /dev/null +++ b/types/Billing/CreditBalanceTransactionsResource.d.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace Billing { + interface CreditBalanceTransactionRetrieveParams { + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + } + + interface CreditBalanceTransactionListParams extends PaginationParams { + /** + * The customer for which to fetch credit balance transactions. + */ + customer: string; + + /** + * The credit grant for which to fetch credit balance transactions. + */ + credit_grant?: string; + + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + } + + class CreditBalanceTransactionsResource { + /** + * Retrieves a credit balance transaction + */ + retrieve( + id: string, + params?: CreditBalanceTransactionRetrieveParams, + options?: RequestOptions + ): Promise>; + retrieve( + id: string, + options?: RequestOptions + ): Promise>; + + /** + * Retrieve a list of credit balance transactions + */ + list( + params: CreditBalanceTransactionListParams, + options?: RequestOptions + ): ApiListPromise; + } + } + } +} diff --git a/types/Billing/CreditGrants.d.ts b/types/Billing/CreditGrants.d.ts new file mode 100644 index 0000000000..9c93b0c433 --- /dev/null +++ b/types/Billing/CreditGrants.d.ts @@ -0,0 +1,124 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace Billing { + /** + * A credit grant is a resource that records a grant of some credit to a customer. + */ + interface CreditGrant { + /** + * Unique identifier for the object. + */ + id: string; + + /** + * String representing the object's type. Objects of the same type share the same value. + */ + object: 'billing.credit_grant'; + + amount: CreditGrant.Amount; + + applicability_config: CreditGrant.ApplicabilityConfig; + + /** + * The category of this credit grant. + */ + category: CreditGrant.Category; + + /** + * Time at which the object was created. Measured in seconds since the Unix epoch. + */ + created: number; + + /** + * Id of the customer to whom the credit was granted. + */ + customer: string | Stripe.Customer | Stripe.DeletedCustomer; + + /** + * The time when the credit becomes effective i.e when it is eligible to be used. + */ + effective_at: number | null; + + /** + * The time when the credit will expire. If not present, the credit will never expire. + */ + expires_at: number | null; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + + /** + * Set of [key-value pairs](https://stripe.com/docs/api/metadata) that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + */ + metadata: Stripe.Metadata; + + /** + * A descriptive name shown in dashboard and on invoices. + */ + name: string | null; + + /** + * ID of the test clock this credit grant belongs to. + */ + test_clock: string | Stripe.TestHelpers.TestClock | null; + + /** + * Time at which the object was last updated. Measured in seconds since the Unix epoch. + */ + updated: number; + + /** + * The time when this credit grant was voided. If not present, the credit grant hasn't been voided. + */ + voided_at: number | null; + } + + namespace CreditGrant { + interface Amount { + /** + * The monetary amount. + */ + monetary: Amount.Monetary | null; + + /** + * The type of this amount. We currently only support `monetary` credits. + */ + type: 'monetary'; + } + + namespace Amount { + interface Monetary { + /** + * Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Must be a [supported currency](https://stripe.com/docs/currencies). + */ + currency: string; + + /** + * A positive integer representing the amount. + */ + value: number; + } + } + + interface ApplicabilityConfig { + scope: ApplicabilityConfig.Scope; + } + + namespace ApplicabilityConfig { + interface Scope { + /** + * The price type to which credit grants can apply to. We currently only support `metered` price type. + */ + price_type: 'metered'; + } + } + + type Category = 'paid' | 'promotional'; + } + } + } +} diff --git a/types/Billing/CreditGrantsResource.d.ts b/types/Billing/CreditGrantsResource.d.ts new file mode 100644 index 0000000000..82d5cc5f0c --- /dev/null +++ b/types/Billing/CreditGrantsResource.d.ts @@ -0,0 +1,219 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace Billing { + interface CreditGrantCreateParams { + /** + * Amount of this credit grant. + */ + amount: CreditGrantCreateParams.Amount; + + /** + * Configuration specifying what this credit grant applies to. + */ + applicability_config: CreditGrantCreateParams.ApplicabilityConfig; + + /** + * The category of this credit grant. + */ + category: CreditGrantCreateParams.Category; + + /** + * Id of the customer to whom the credit should be granted. + */ + customer: string; + + /** + * The time when the credit becomes effective i.e when it is eligible to be used. Defaults to the current timestamp if not specified. + */ + effective_at?: number; + + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + + /** + * The time when the credit will expire. If not specified, the credit will never expire. + */ + expires_at?: number; + + /** + * Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object (ex: cost basis) in a structured format. + */ + metadata?: Stripe.MetadataParam; + + /** + * A descriptive name shown in dashboard and on invoices. + */ + name?: string; + } + + namespace CreditGrantCreateParams { + interface Amount { + /** + * The monetary amount. + */ + monetary?: Amount.Monetary; + + /** + * Specify the type of this amount. We currently only support `monetary` credits. + */ + type: 'monetary'; + } + + namespace Amount { + interface Monetary { + /** + * Three-letter [ISO code for the currency](https://stripe.com/docs/currencies) of the `value` parameter. + */ + currency: string; + + /** + * A positive integer representing the amount of the credit grant. + */ + value: number; + } + } + + interface ApplicabilityConfig { + /** + * Specify the scope of this applicability config. + */ + scope: ApplicabilityConfig.Scope; + } + + namespace ApplicabilityConfig { + interface Scope { + /** + * The price type to which credit grants can apply to. We currently only support `metered` price type. + */ + price_type: 'metered'; + } + } + + type Category = 'paid' | 'promotional'; + } + + interface CreditGrantRetrieveParams { + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + } + + interface CreditGrantUpdateParams { + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + + /** + * The time when the credit created by this credit grant will expire. If set to empty, the credit will never expire. + */ + expires_at?: Stripe.Emptyable; + + /** + * Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object (ex: cost basis) in a structured format. + */ + metadata?: Stripe.MetadataParam; + } + + interface CreditGrantListParams extends PaginationParams { + /** + * Only return credit grants for this customer. + */ + customer?: string; + + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + } + + interface CreditGrantExpireParams { + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + } + + interface CreditGrantVoidGrantParams { + /** + * Specifies which fields in the response should be expanded. + */ + expand?: Array; + } + + class CreditGrantsResource { + /** + * Creates a credit grant + */ + create( + params: CreditGrantCreateParams, + options?: RequestOptions + ): Promise>; + + /** + * Retrieves a credit grant + */ + retrieve( + id: string, + params?: CreditGrantRetrieveParams, + options?: RequestOptions + ): Promise>; + retrieve( + id: string, + options?: RequestOptions + ): Promise>; + + /** + * Updates a credit grant + */ + update( + id: string, + params?: CreditGrantUpdateParams, + options?: RequestOptions + ): Promise>; + + /** + * Retrieve a list of credit grants + */ + list( + params?: CreditGrantListParams, + options?: RequestOptions + ): ApiListPromise; + list( + options?: RequestOptions + ): ApiListPromise; + + /** + * Expires a credit grant + */ + expire( + id: string, + params?: CreditGrantExpireParams, + options?: RequestOptions + ): Promise>; + expire( + id: string, + options?: RequestOptions + ): Promise>; + + /** + * Voids a credit grant + */ + voidGrant( + id: string, + params?: CreditGrantVoidGrantParams, + options?: RequestOptions + ): Promise>; + voidGrant( + id: string, + options?: RequestOptions + ): Promise>; + } + } + } +} diff --git a/types/BillingPortal/ConfigurationsResource.d.ts b/types/BillingPortal/ConfigurationsResource.d.ts index 636feb34ec..949b391d2a 100644 --- a/types/BillingPortal/ConfigurationsResource.d.ts +++ b/types/BillingPortal/ConfigurationsResource.d.ts @@ -178,7 +178,7 @@ declare module 'stripe' { /** * The types of subscription updates that are supported. When empty, subscriptions are not updateable. */ - default_allowed_updates: Stripe.Emptyable< + default_allowed_updates?: Stripe.Emptyable< Array >; @@ -190,7 +190,7 @@ declare module 'stripe' { /** * The list of up to 10 products that support subscription updates. */ - products: Stripe.Emptyable>; + products?: Stripe.Emptyable>; /** * Determines how to handle prorations resulting from subscription updates. Valid values are `none`, `create_prorations`, and `always_invoice`. diff --git a/types/Capabilities.d.ts b/types/Capabilities.d.ts index 195e9d57cd..1e844cb75b 100644 --- a/types/Capabilities.d.ts +++ b/types/Capabilities.d.ts @@ -38,7 +38,7 @@ declare module 'stripe' { requirements?: Capability.Requirements; /** - * The status of the capability. Can be `active`, `inactive`, `pending`, or `unrequested`. + * The status of the capability. */ status: Capability.Status; } diff --git a/types/Checkout/SessionsResource.d.ts b/types/Checkout/SessionsResource.d.ts index 84383ac49a..a4d770e700 100644 --- a/types/Checkout/SessionsResource.d.ts +++ b/types/Checkout/SessionsResource.d.ts @@ -690,7 +690,7 @@ declare module 'stripe' { namespace LineItem { interface AdjustableQuantity { /** - * Set to true if the quantity can be adjusted to any non-negative integer. By default customers will be able to remove the line item by setting the quantity to 0. + * Set to true if the quantity can be adjusted to any non-negative integer. */ enabled: boolean; diff --git a/types/CreditNoteLineItems.d.ts b/types/CreditNoteLineItems.d.ts index e56b6079dc..9395b6a7b0 100644 --- a/types/CreditNoteLineItems.d.ts +++ b/types/CreditNoteLineItems.d.ts @@ -51,6 +51,8 @@ declare module 'stripe' { */ livemode: boolean; + pretax_credit_amounts?: Array; + /** * The number of units of product being credited. */ @@ -100,6 +102,34 @@ declare module 'stripe' { discount: string | Stripe.Discount | Stripe.DeletedDiscount; } + interface PretaxCreditAmount { + /** + * The amount, in cents (or local equivalent), of the pretax credit amount. + */ + amount: number; + + /** + * The credit balance transaction that was applied to get this pretax credit amount. + */ + credit_balance_transaction?: + | string + | Stripe.Billing.CreditBalanceTransaction; + + /** + * The discount that was applied to get this pretax credit amount. + */ + discount?: string | Stripe.Discount | Stripe.DeletedDiscount; + + /** + * Type of the pretax credit amount referenced. + */ + type: PretaxCreditAmount.Type; + } + + namespace PretaxCreditAmount { + type Type = 'credit_balance_transaction' | 'discount'; + } + interface TaxAmount { /** * The amount, in cents (or local equivalent), of the tax. diff --git a/types/CreditNotes.d.ts b/types/CreditNotes.d.ts index ee619a0f94..9906a58586 100644 --- a/types/CreditNotes.d.ts +++ b/types/CreditNotes.d.ts @@ -106,6 +106,8 @@ declare module 'stripe' { */ pdf: string; + pretax_credit_amounts?: Array; + /** * Reason for issuing this credit note, one of `duplicate`, `fraudulent`, `order_change`, or `product_unsatisfactory` */ @@ -175,6 +177,34 @@ declare module 'stripe' { discount: string | Stripe.Discount | Stripe.DeletedDiscount; } + interface PretaxCreditAmount { + /** + * The amount, in cents (or local equivalent), of the pretax credit amount. + */ + amount: number; + + /** + * The credit balance transaction that was applied to get this pretax credit amount. + */ + credit_balance_transaction?: + | string + | Stripe.Billing.CreditBalanceTransaction; + + /** + * The discount that was applied to get this pretax credit amount. + */ + discount?: string | Stripe.Discount | Stripe.DeletedDiscount; + + /** + * Type of the pretax credit amount referenced. + */ + type: PretaxCreditAmount.Type; + } + + namespace PretaxCreditAmount { + type Type = 'credit_balance_transaction' | 'discount'; + } + type Reason = | 'duplicate' | 'fraudulent' diff --git a/types/Customers.d.ts b/types/Customers.d.ts index 7ea03d7ea6..3c55da594b 100644 --- a/types/Customers.d.ts +++ b/types/Customers.d.ts @@ -3,9 +3,8 @@ declare module 'stripe' { namespace Stripe { /** - * This object represents a customer of your business. Use it to create recurring charges and track payments that belong to the same customer. - * - * Related guide: [Save a card during payment](https://stripe.com/docs/payments/save-during-payment) + * This object represents a customer of your business. Use it to [create recurring charges](https://stripe.com/docs/invoicing/customer), [save payment](https://stripe.com/docs/payments/save-during-payment) and contact information, + * and track payments that belong to the same customer. */ interface Customer { /** diff --git a/types/EventTypes.d.ts b/types/EventTypes.d.ts index 54f2e9c05f..d22e466207 100644 --- a/types/EventTypes.d.ts +++ b/types/EventTypes.d.ts @@ -240,7 +240,9 @@ declare module 'stripe' { | TreasuryReceivedCreditFailedEvent | TreasuryReceivedCreditSucceededEvent | TreasuryReceivedDebitCreatedEvent; + } + namespace Stripe { /** * Occurs whenever a user authorizes an application. Sent to the related application only. */ diff --git a/types/InvoiceLineItems.d.ts b/types/InvoiceLineItems.d.ts index 205dfdce99..31a5f894e0 100644 --- a/types/InvoiceLineItems.d.ts +++ b/types/InvoiceLineItems.d.ts @@ -80,6 +80,8 @@ declare module 'stripe' { */ plan: Stripe.Plan | null; + pretax_credit_amounts?: Array | null; + /** * The price of the line item. */ @@ -156,6 +158,40 @@ declare module 'stripe' { start: number; } + interface PretaxCreditAmount { + /** + * The amount, in cents (or local equivalent), of the pretax credit amount. + */ + amount: number; + + /** + * The credit balance transaction that was applied to get this pretax credit amount. + */ + credit_balance_transaction?: + | string + | Stripe.Billing.CreditBalanceTransaction + | null; + + /** + * The discount that was applied to get this pretax credit amount. + */ + discount?: string | Stripe.Discount | Stripe.DeletedDiscount; + + /** + * The margin that was applied to get this pretax credit amount. + */ + margin?: string | Stripe.Margin; + + /** + * Type of the pretax credit amount referenced. + */ + type: PretaxCreditAmount.Type; + } + + namespace PretaxCreditAmount { + type Type = 'credit_balance_transaction' | 'discount'; + } + interface ProrationDetails { /** * For a credit proration `line_item`, the original debit line_items to which the credit proration applies. diff --git a/types/Invoices.d.ts b/types/Invoices.d.ts index 4d64c4566d..ed832e9a84 100644 --- a/types/Invoices.d.ts +++ b/types/Invoices.d.ts @@ -461,6 +461,10 @@ declare module 'stripe' { */ total_excluding_tax: number | null; + total_pretax_credit_amounts?: Array< + Invoice.TotalPretaxCreditAmount + > | null; + /** * The aggregate amounts calculated per tax rate for all line items. */ @@ -1396,6 +1400,40 @@ declare module 'stripe' { discount: string | Stripe.Discount | Stripe.DeletedDiscount; } + interface TotalPretaxCreditAmount { + /** + * The amount, in cents (or local equivalent), of the pretax credit amount. + */ + amount: number; + + /** + * The credit balance transaction that was applied to get this pretax credit amount. + */ + credit_balance_transaction?: + | string + | Stripe.Billing.CreditBalanceTransaction + | null; + + /** + * The discount that was applied to get this pretax credit amount. + */ + discount?: string | Stripe.Discount | Stripe.DeletedDiscount; + + /** + * The margin that was applied to get this pretax credit amount. + */ + margin?: string | Stripe.Margin; + + /** + * Type of the pretax credit amount referenced. + */ + type: TotalPretaxCreditAmount.Type; + } + + namespace TotalPretaxCreditAmount { + type Type = 'credit_balance_transaction' | 'discount'; + } + interface TotalTaxAmount { /** * The amount, in cents (or local equivalent), of the tax. diff --git a/types/Margins.d.ts b/types/Margins.d.ts new file mode 100644 index 0000000000..deb8ef1eb7 --- /dev/null +++ b/types/Margins.d.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + /** + * A (partner) margin represents a specific discount distributed in partner reseller programs to business partners who + * resell products and services and earn a discount (margin) for doing so. + */ + interface Margin { + /** + * Unique identifier for the object. + */ + id: string; + + /** + * String representing the object's type. Objects of the same type share the same value. + */ + object: 'margin'; + + /** + * Whether the margin can be applied to invoices, invoice items, or invoice line items. Defaults to `true`. + */ + active: boolean; + + /** + * Time at which the object was created. Measured in seconds since the Unix epoch. + */ + created: number; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + + /** + * Set of [key-value pairs](https://stripe.com/docs/api/metadata) that you can attach to an object. This can be useful for storing additional information about the object in a structured format. + */ + metadata: Stripe.Metadata | null; + + /** + * Name of the margin that's displayed on, for example, invoices. + */ + name: string | null; + + /** + * Percent that will be taken off the subtotal before tax (after all other discounts and promotions) of any invoice to which the margin is applied. + */ + percent_off: number; + + /** + * Time at which the object was last updated. Measured in seconds since the Unix epoch. + */ + updated: number; + } + } +} diff --git a/types/ProductsResource.d.ts b/types/ProductsResource.d.ts index fb1e54db28..e5fdab2bfd 100644 --- a/types/ProductsResource.d.ts +++ b/types/ProductsResource.d.ts @@ -101,6 +101,11 @@ declare module 'stripe' { [key: string]: DefaultPriceData.CurrencyOptions; }; + /** + * When set, provides configuration for the amount to be adjusted by the customer during Checkout Sessions and Payment Links. + */ + custom_unit_amount?: DefaultPriceData.CustomUnitAmount; + /** * The recurring components of a price such as `interval` and `interval_count`. */ @@ -112,7 +117,7 @@ declare module 'stripe' { tax_behavior?: DefaultPriceData.TaxBehavior; /** - * A positive integer in cents (or local equivalent) (or 0 for a free price) representing how much to charge. One of `unit_amount` or `unit_amount_decimal` is required. + * A positive integer in cents (or local equivalent) (or 0 for a free price) representing how much to charge. One of `unit_amount`, `unit_amount_decimal`, or `custom_unit_amount` is required. */ unit_amount?: number; @@ -203,6 +208,28 @@ declare module 'stripe' { } } + interface CustomUnitAmount { + /** + * Pass in `true` to enable `custom_unit_amount`, otherwise omit `custom_unit_amount`. + */ + enabled: boolean; + + /** + * The maximum unit amount the customer can specify for this item. + */ + maximum?: number; + + /** + * The minimum unit amount the customer can specify for this item. Must be at least the minimum charge amount. + */ + minimum?: number; + + /** + * The starting unit amount which can be updated by the customer. + */ + preset?: number; + } + interface Recurring { /** * Specifies billing frequency. Either `day`, `week`, `month` or `year`. diff --git a/types/PromotionCodes.d.ts b/types/PromotionCodes.d.ts index a89780e7fa..905af1ca04 100644 --- a/types/PromotionCodes.d.ts +++ b/types/PromotionCodes.d.ts @@ -23,7 +23,7 @@ declare module 'stripe' { active: boolean; /** - * The customer-facing code. Regardless of case, this code must be unique across all active promotion codes for each customer. + * The customer-facing code. Regardless of case, this code must be unique across all active promotion codes for each customer. Valid characters are lower case letters (a-z), upper case letters (A-Z), and digits (0-9). */ code: string; diff --git a/types/PromotionCodesResource.d.ts b/types/PromotionCodesResource.d.ts index 620838bb23..1381825bfe 100644 --- a/types/PromotionCodesResource.d.ts +++ b/types/PromotionCodesResource.d.ts @@ -14,7 +14,9 @@ declare module 'stripe' { active?: boolean; /** - * The customer-facing code. Regardless of case, this code must be unique across all active promotion codes for a specific customer. If left blank, we will generate one automatically. + * The customer-facing code. Regardless of case, this code must be unique across all active promotion codes for a specific customer. Valid characters are lower case letters (a-z), upper case letters (A-Z), and digits (0-9). + * + * If left blank, we will generate one automatically. */ code?: string; diff --git a/types/SubscriptionsResource.d.ts b/types/SubscriptionsResource.d.ts index 44484a8a14..5033c38baf 100644 --- a/types/SubscriptionsResource.d.ts +++ b/types/SubscriptionsResource.d.ts @@ -1972,11 +1972,11 @@ declare module 'stripe' { list(options?: RequestOptions): ApiListPromise; /** - * Cancels a customer's subscription immediately. The customer will not be charged again for the subscription. + * Cancels a customer's subscription immediately. The customer won't be charged again for the subscription. After it's canceled, you can no longer update the subscription or its [metadata](https://stripe.com/metadata). * - * Note, however, that any pending invoice items that you've created will still be charged for at the end of the period, unless manually [deleted](https://stripe.com/docs/api#delete_invoiceitem). If you've set the subscription to cancel at the end of the period, any pending prorations will also be left in place and collected at the end of the period. But if the subscription is set to cancel immediately, pending prorations will be removed. + * Any pending invoice items that you've created are still charged at the end of the period, unless manually [deleted](https://stripe.com/docs/api#delete_invoiceitem). If you've set the subscription to cancel at the end of the period, any pending prorations are also left in place and collected at the end of the period. But if the subscription is set to cancel immediately, pending prorations are removed. * - * By default, upon subscription cancellation, Stripe will stop automatic collection of all finalized invoices for the customer. This is intended to prevent unexpected payment attempts after the customer has canceled a subscription. However, you can resume automatic collection of the invoices manually after subscription cancellation to have us proceed. Or, you could check for unpaid invoices before allowing the customer to cancel the subscription at all. + * By default, upon subscription cancellation, Stripe stops automatic collection of all finalized invoices for the customer. This is intended to prevent unexpected payment attempts after the customer has canceled a subscription. However, you can resume automatic collection of the invoices manually after subscription cancellation to have us proceed. Or, you could check for unpaid invoices before allowing the customer to cancel the subscription at all. */ cancel( id: string, diff --git a/types/Tax/Settings.d.ts b/types/Tax/Settings.d.ts index e8135e6f03..b21b988351 100644 --- a/types/Tax/Settings.d.ts +++ b/types/Tax/Settings.d.ts @@ -27,7 +27,7 @@ declare module 'stripe' { livemode: boolean; /** - * The `active` status indicates you have all required settings to calculate tax. A status can transition out of `active` when new required settings are introduced. + * The status of the Tax `Settings`. */ status: Settings.Status; diff --git a/types/Terminal/ReadersResource.d.ts b/types/Terminal/ReadersResource.d.ts index f419ea13df..d8a4f2738a 100644 --- a/types/Terminal/ReadersResource.d.ts +++ b/types/Terminal/ReadersResource.d.ts @@ -123,6 +123,11 @@ declare module 'stripe' { namespace ReaderProcessPaymentIntentParams { interface ProcessConfig { + /** + * This field indicates whether this payment method can be shown again to its customer in a checkout flow. Stripe products such as Checkout and Elements use this field to determine whether a payment method can be shown as a saved payment method in a checkout flow. + */ + allow_redisplay?: ProcessConfig.AllowRedisplay; + /** * Enables cancel button on transaction screens. */ @@ -140,6 +145,8 @@ declare module 'stripe' { } namespace ProcessConfig { + type AllowRedisplay = 'always' | 'limited' | 'unspecified'; + interface Tipping { /** * Amount used to calculate tip suggestions on tipping selection screen for this transaction. Must be a positive integer in the smallest currency unit (e.g., 100 cents to represent $1.00 or 100 to represent ¥100, a zero-decimal currency). @@ -151,14 +158,14 @@ declare module 'stripe' { interface ReaderProcessSetupIntentParams { /** - * SetupIntent ID + * This field indicates whether this payment method can be shown again to its customer in a checkout flow. Stripe products such as Checkout and Elements use this field to determine whether a payment method can be shown as a saved payment method in a checkout flow. */ - setup_intent: string; + allow_redisplay: ReaderProcessSetupIntentParams.AllowRedisplay; /** - * Customer Consent Collected + * SetupIntent ID */ - customer_consent_collected?: boolean; + setup_intent: string; /** * Specifies which fields in the response should be expanded. @@ -172,6 +179,8 @@ declare module 'stripe' { } namespace ReaderProcessSetupIntentParams { + type AllowRedisplay = 'always' | 'limited' | 'unspecified'; + interface ProcessConfig { /** * Enables cancel button on transaction screens. diff --git a/types/Treasury/ReceivedCredits.d.ts b/types/Treasury/ReceivedCredits.d.ts index 3716c3ff5d..f306394452 100644 --- a/types/Treasury/ReceivedCredits.d.ts +++ b/types/Treasury/ReceivedCredits.d.ts @@ -83,7 +83,11 @@ declare module 'stripe' { } namespace ReceivedCredit { - type FailureCode = 'account_closed' | 'account_frozen' | 'other'; + type FailureCode = + | 'account_closed' + | 'account_frozen' + | 'international_transaction' + | 'other'; interface InitiatingPaymentMethodDetails { /** diff --git a/types/WebhookEndpointsResource.d.ts b/types/WebhookEndpointsResource.d.ts index eebd26330d..c4a38488ed 100644 --- a/types/WebhookEndpointsResource.d.ts +++ b/types/WebhookEndpointsResource.d.ts @@ -142,7 +142,8 @@ declare module 'stripe' { | '2023-08-16' | '2023-10-16' | '2024-04-10' - | '2024-06-20'; + | '2024-06-20' + | '2024-09-30.acacia'; type EnabledEvent = | '*' diff --git a/types/index.d.ts b/types/index.d.ts index dea69dcf12..74854c0daf 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -18,6 +18,9 @@ /// /// /// +/// +/// +/// /// /// /// @@ -138,6 +141,9 @@ /// /// /// +/// +/// +/// /// /// /// @@ -199,6 +205,7 @@ /// /// /// +/// /// /// /// @@ -339,6 +346,9 @@ declare module 'stripe' { }; billing: { alerts: Stripe.Billing.AlertsResource; + creditBalanceSummary: Stripe.Billing.CreditBalanceSummaryResource; + creditBalanceTransactions: Stripe.Billing.CreditBalanceTransactionsResource; + creditGrants: Stripe.Billing.CreditGrantsResource; meters: Stripe.Billing.MetersResource; meterEvents: Stripe.Billing.MeterEventsResource; meterEventAdjustments: Stripe.Billing.MeterEventAdjustmentsResource; diff --git a/types/lib.d.ts b/types/lib.d.ts index d82037bbf7..f23fdfea3c 100644 --- a/types/lib.d.ts +++ b/types/lib.d.ts @@ -27,7 +27,7 @@ declare module 'stripe' { }): (...args: any[]) => Response; //eslint-disable-line @typescript-eslint/no-explicit-any static MAX_BUFFERED_REQUEST_METRICS: number; } - export type LatestApiVersion = '2024-06-20'; + export type LatestApiVersion = '2024-09-30.acacia'; export type HttpAgent = Agent; export type HttpProtocol = 'http' | 'https'; diff --git a/types/test/typescriptTest.ts b/types/test/typescriptTest.ts index 09a0c3e58f..e83aaa73c3 100644 --- a/types/test/typescriptTest.ts +++ b/types/test/typescriptTest.ts @@ -9,7 +9,7 @@ import Stripe from 'stripe'; let stripe = new Stripe('sk_test_123', { - apiVersion: '2024-06-20', + apiVersion: '2024-09-30.acacia', }); stripe = new Stripe('sk_test_123'); @@ -26,7 +26,7 @@ stripe = new Stripe('sk_test_123', { // Check config object. stripe = new Stripe('sk_test_123', { - apiVersion: '2024-06-20', + apiVersion: '2024-09-30.acacia', typescript: true, maxNetworkRetries: 1, timeout: 1000, @@ -44,7 +44,7 @@ stripe = new Stripe('sk_test_123', { description: 'test', }; const opts: Stripe.RequestOptions = { - apiVersion: '2024-06-20', + apiVersion: '2024-09-30.acacia', }; const customer: Stripe.Customer = await stripe.customers.create(params, opts); From d83368c748c1d9a16f5fe0317914469f2d6bbbd5 Mon Sep 17 00:00:00 2001 From: prathmesh-stripe <165320323+prathmesh-stripe@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:41:47 -0400 Subject: [PATCH 2/7] Adding support for the new Usage Billing APIs (#2184) --- README.md | 34 + examples/snippets/README.md | 27 + examples/snippets/meter_event_stream.ts | 39 ++ examples/snippets/new_example.ts | 7 + examples/snippets/package.json | 13 + examples/snippets/stripe_webhook_handler.js | 36 ++ examples/snippets/yarn.lock | 597 ++++++++++++++++++ src/Error.ts | 39 +- src/RequestSender.ts | 402 ++++++++---- src/StripeResource.ts | 15 +- src/Types.d.ts | 53 +- src/Webhooks.ts | 2 +- src/autoPagination.ts | 83 ++- src/crypto/CryptoProvider.ts | 7 + src/crypto/NodeCryptoProvider.ts | 10 + src/crypto/SubtleCryptoProvider.ts | 5 + src/multipart.ts | 4 +- src/net/FetchHttpClient.ts | 2 +- src/net/HttpClient.ts | 4 +- src/net/NodeHttpClient.ts | 2 +- src/resources.ts | 1 + src/resources/OAuth.ts | 4 +- src/resources/V2.ts | 12 + src/resources/V2/Billing.ts | 16 + .../V2/Billing/MeterEventAdjustments.ts | 10 + src/resources/V2/Billing/MeterEventSession.ts | 10 + src/resources/V2/Billing/MeterEventStream.ts | 11 + src/resources/V2/Billing/MeterEvents.ts | 7 + src/resources/V2/Core.ts | 10 + src/resources/V2/Core/Events.ts | 12 + src/stripe.core.ts | 89 ++- src/utils.ts | 94 ++- test/Error.spec.ts | 23 + test/RequestSender.spec.ts | 231 ++++++- test/autoPagination.spec.ts | 102 ++- test/crypto/helpers.ts | 11 + test/net/helpers.ts | 2 +- .../resources/generated_examples_test.spec.js | 13 + test/stripe.spec.ts | 391 +++++++++++- test/testUtils.ts | 25 +- test/utils.spec.ts | 57 +- testProjects/mjs/index.js | 3 +- types/Errors.d.ts | 34 +- types/ThinEvent.d.ts | 38 ++ types/V2/Billing/MeterEventAdjustments.d.ts | 65 ++ .../MeterEventAdjustmentsResource.d.ts | 47 ++ .../V2/Billing/MeterEventSessionResource.d.ts | 26 + types/V2/Billing/MeterEventSessions.d.ts | 45 ++ .../V2/Billing/MeterEventStreamResource.d.ts | 62 ++ types/V2/Billing/MeterEvents.d.ts | 54 ++ types/V2/Billing/MeterEventsResource.d.ts | 52 ++ types/V2/BillingResource.d.ts | 14 + types/V2/Core/EventsResource.d.ts | 49 ++ types/V2/CoreResource.d.ts | 11 + types/V2/EventTypes.d.ts | 214 +++++++ types/V2/Events.d.ts | 75 +++ types/V2Resource.d.ts | 10 + types/index.d.ts | 118 ++++ types/lib.d.ts | 7 + 59 files changed, 3199 insertions(+), 237 deletions(-) create mode 100644 examples/snippets/README.md create mode 100644 examples/snippets/meter_event_stream.ts create mode 100644 examples/snippets/new_example.ts create mode 100644 examples/snippets/package.json create mode 100644 examples/snippets/stripe_webhook_handler.js create mode 100644 examples/snippets/yarn.lock create mode 100644 src/resources/V2.ts create mode 100644 src/resources/V2/Billing.ts create mode 100644 src/resources/V2/Billing/MeterEventAdjustments.ts create mode 100644 src/resources/V2/Billing/MeterEventSession.ts create mode 100644 src/resources/V2/Billing/MeterEventStream.ts create mode 100644 src/resources/V2/Billing/MeterEvents.ts create mode 100644 src/resources/V2/Core.ts create mode 100644 src/resources/V2/Core/Events.ts create mode 100644 types/ThinEvent.d.ts create mode 100644 types/V2/Billing/MeterEventAdjustments.d.ts create mode 100644 types/V2/Billing/MeterEventAdjustmentsResource.d.ts create mode 100644 types/V2/Billing/MeterEventSessionResource.d.ts create mode 100644 types/V2/Billing/MeterEventSessions.d.ts create mode 100644 types/V2/Billing/MeterEventStreamResource.d.ts create mode 100644 types/V2/Billing/MeterEvents.d.ts create mode 100644 types/V2/Billing/MeterEventsResource.d.ts create mode 100644 types/V2/BillingResource.d.ts create mode 100644 types/V2/Core/EventsResource.d.ts create mode 100644 types/V2/CoreResource.d.ts create mode 100644 types/V2/EventTypes.d.ts create mode 100644 types/V2/Events.d.ts create mode 100644 types/V2Resource.d.ts diff --git a/README.md b/README.md index 5adfcd7f5a..2300d84ba8 100644 --- a/README.md +++ b/README.md @@ -517,6 +517,40 @@ const stripe = new Stripe('sk_test_...', { }); ``` +### Custom requests + +If you would like to send a request to an undocumented API (for example you are in a private beta), or if you prefer to bypass the method definitions in the library and specify your request details directly, you can use the `rawRequest` method on the StripeClient object. + +```javascript +const client = new Stripe('sk_test_...'); + +client.rawRequest( + 'POST', + '/v1/beta_endpoint', + { param: 123 }, + { apiVersion: '2022-11-15; feature_beta=v3' } + ) + .then((response) => /* handle response */ ) + .catch((error) => console.error(error)); +``` + +Or using ES modules and `async`/`await`: + +```javascript +import Stripe from 'stripe'; +const stripe = new Stripe('sk_test_...'); + +const response = await stripe.rawRequest( + 'POST', + '/v1/beta_endpoint', + { param: 123 }, + { apiVersion: '2022-11-15; feature_beta=v3' } +); + +// handle response +``` + + ## Support New features and bug fixes are released on the latest major version of the `stripe` package. If you are on an older major version, we recommend that you upgrade to the latest in order to use the new features and bug fixes including those for security vulnerabilities. Older major versions of the package will continue to be available for use, but will not be receiving any updates. diff --git a/examples/snippets/README.md b/examples/snippets/README.md new file mode 100644 index 0000000000..05216ba79f --- /dev/null +++ b/examples/snippets/README.md @@ -0,0 +1,27 @@ +## Setup + +1. From the stripe-node root folder, run `yarn build` to build the modules. +2. Then, from this snippets folder, run `yarn` to install node dependencies for the example snippets. This will reference the local Stripe SDK modules created in step 1. + +If on step 2 you see an error `Error: unsure how to copy this: /Users/jar/stripe/sdks/node/.git/fsmonitor--daemon.ipc`: +run `rm /path/to/node/sdk/.git/fsmonitor--daemon.ipc && yarn` +This file is used by a file monitor built into git. Removing it temporarily does not seem to affect its operation, and this one liner will let `yarn` succeed. + +Note that if you modify the stripe-node code, you must delete your snippets `node_modules` folder and rerun these steps. + +## Running an example + +If your example is in typescript, run: +`yarn run ts-node your_example.ts` + +If your example is in javascript, run: +`node your_example.js` +or +`node your_example.mjs` + +## Adding a new example + +1. Clone new_example.ts +2. Implement your example +3. Run it (as per above) +4. 👍 diff --git a/examples/snippets/meter_event_stream.ts b/examples/snippets/meter_event_stream.ts new file mode 100644 index 0000000000..6039ede351 --- /dev/null +++ b/examples/snippets/meter_event_stream.ts @@ -0,0 +1,39 @@ +import {Stripe} from 'stripe'; + +const apiKey = '{{API_KEY}}'; +const customerId = '{{CUSTOMER_ID}}'; + +let meterEventSession: null | any = null; + +async function refreshMeterEventSession() { + if ( + meterEventSession === null || + new Date(meterEventSession.expires_at * 1000) <= new Date() + ) { + // Create a new meter event session in case the existing session expired + const client = new Stripe(apiKey); + meterEventSession = await client.v2.billing.meterEventSession.create(); + } +} + +async function sendMeterEvent(meterEvent: any) { + // Refresh the meter event session, if necessary + await refreshMeterEventSession(); + + // Create a meter event + const client = new Stripe(meterEventSession.authentication_token); + await client.v2.billing.meterEventStream.create({ + events: [meterEvent], + }); +} + +// Send meter events +sendMeterEvent({ + event_name: 'alpaca_ai_tokens', + payload: { + stripe_customer_id: customerId, // Replace with actual customer ID + value: '27', + }, +}).catch((error) => { + console.error('Error sending meter event:', error); +}); diff --git a/examples/snippets/new_example.ts b/examples/snippets/new_example.ts new file mode 100644 index 0000000000..e521280c83 --- /dev/null +++ b/examples/snippets/new_example.ts @@ -0,0 +1,7 @@ +import {Stripe} from 'stripe'; + +const apiKey = '{{API_KEY}}'; + +console.log('Hello World'); +// const client = new Stripe(apiKey); +// client.v2.... diff --git a/examples/snippets/package.json b/examples/snippets/package.json new file mode 100644 index 0000000000..ba3738f172 --- /dev/null +++ b/examples/snippets/package.json @@ -0,0 +1,13 @@ +{ + "name": "snippets", + "version": "1.0.0", + "description": "example Stripe SDK code snippets", + "main": "index.js", + "license": "ISC", + "dependencies": { + "express": "^4.21.0", + "stripe": "file:../../", + "ts-node": "^10.9.2", + "typescript": "^5.6.2" + } +} diff --git a/examples/snippets/stripe_webhook_handler.js b/examples/snippets/stripe_webhook_handler.js new file mode 100644 index 0000000000..d4549a6d87 --- /dev/null +++ b/examples/snippets/stripe_webhook_handler.js @@ -0,0 +1,36 @@ +const express = require('express'); +const {Stripe} = require('stripe'); + +const app = express(); + +const apiKey = process.env.STRIPE_API_KEY; +const webhookSecret = process.env.WEBHOOK_SECRET; + +const client = new Stripe(apiKey); + +app.post( + '/webhook', + express.raw({type: 'application/json'}), + async (req, res) => { + const sig = req.headers['stripe-signature']; + + try { + const thinEvent = client.parseThinEvent(req.body, sig, webhookSecret); + + // Fetch the event data to understand the failure + const event = await client.v2.core.events.retrieve(thinEvent.id); + if (event.type == 'v1.billing.meter.error_report_triggered') { + const meter = await event.fetchRelatedObject(); + const meterId = meter.id; + // Record the failures and alert your team + // Add your logic here + } + res.sendStatus(200); + } catch (err) { + console.log(`Webhook Error: ${err.stack}`); + res.status(400).send(`Webhook Error: ${err.message}`); + } + } +); + +app.listen(4242, () => console.log('Running on port 4242')); diff --git a/examples/snippets/yarn.lock b/examples/snippets/yarn.lock new file mode 100644 index 0000000000..622721e44e --- /dev/null +++ b/examples/snippets/yarn.lock @@ -0,0 +1,597 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/node@>=8.1.0": + version "22.6.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.6.1.tgz#e531a45f4d78f14a8468cb9cdc29dc9602afc7ac" + integrity sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw== + dependencies: + undici-types "~6.19.2" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express@^4.21.0: + version "4.21.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" + integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.10" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@6.13.0, qs@^6.11.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"stripe@file:../..": + version "16.12.0" + dependencies: + "@types/node" ">=8.1.0" + qs "^6.11.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/src/Error.ts b/src/Error.ts index aa094c0952..bfe2aee00a 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -1,8 +1,11 @@ /* eslint-disable camelcase */ +/* eslint-disable no-warning-comments */ import {RawErrorType, StripeRawError} from './Types.js'; -export const generate = (rawStripeError: StripeRawError): StripeError => { +export const generateV1Error = ( + rawStripeError: StripeRawError +): StripeError => { switch (rawStripeError.type) { case 'card_error': return new StripeCardError(rawStripeError); @@ -23,12 +26,34 @@ export const generate = (rawStripeError: StripeRawError): StripeError => { } }; +// eslint-disable-next-line complexity +export const generateV2Error = ( + rawStripeError: StripeRawError +): StripeError => { + switch (rawStripeError.type) { + // switchCases: The beginning of the section generated from our OpenAPI spec + case 'temporary_session_expired': + return new TemporarySessionExpiredError(rawStripeError); + // switchCases: The end of the section generated from our OpenAPI spec + } + + // Special handling for requests with missing required fields in V2 APIs. + // invalid_field response in V2 APIs returns the field 'code' instead of 'type'. + switch (rawStripeError.code) { + case 'invalid_fields': + return new StripeInvalidRequestError(rawStripeError); + } + + return generateV1Error(rawStripeError); +}; + /** * StripeError is the base error from which all other more specific Stripe errors derive. * Specifically for errors returned from Stripe's REST API. */ export class StripeError extends Error { readonly message: string; + readonly userMessage?: string; readonly type: string; readonly raw: unknown; readonly rawType?: RawErrorType; @@ -64,7 +89,7 @@ export class StripeError extends Error { this.statusCode = raw.statusCode; // @ts-ignore this.message = raw.message; - + this.userMessage = raw.user_message; this.charge = raw.charge; this.decline_code = raw.decline_code; this.payment_intent = raw.payment_intent; @@ -77,7 +102,7 @@ export class StripeError extends Error { /** * Helper factory which takes raw stripe errors and outputs wrapping instances */ - static generate = generate; + static generate = generateV1Error; } // Specific Stripe Error types: @@ -205,3 +230,11 @@ export class StripeUnknownError extends StripeError { super(raw, 'StripeUnknownError'); } } + +// classDefinitions: The beginning of the section generated from our OpenAPI spec +export class TemporarySessionExpiredError extends StripeError { + constructor(rawStripeError: StripeRawError = {}) { + super(rawStripeError, 'TemporarySessionExpiredError'); + } +} +// classDefinitions: The end of the section generated from our OpenAPI spec diff --git a/src/RequestSender.ts b/src/RequestSender.ts index d69c3c0b87..59bbaf5d2b 100644 --- a/src/RequestSender.ts +++ b/src/RequestSender.ts @@ -5,14 +5,9 @@ import { StripeError, StripePermissionError, StripeRateLimitError, + generateV1Error, + generateV2Error, } from './Error.js'; -import { - emitWarning, - normalizeHeaders, - removeNullish, - stringifyRequestData, -} from './utils.js'; -import {HttpClient, HttpClientResponseInterface} from './net/HttpClient.js'; import { StripeObject, RequestHeaders, @@ -20,11 +15,27 @@ import { ResponseEvent, RequestCallback, RequestCallbackReturn, - RequestSettings, RequestData, - RequestOptions, RequestDataProcessor, + RequestOptions, + RequestSettings, + StripeRequest, + RequestOpts, + RequestArgs, + RequestAuthenticator, + ApiMode, } from './Types.js'; +import {HttpClient, HttpClientResponseInterface} from './net/HttpClient.js'; +import { + emitWarning, + jsonStringifyRequestData, + normalizeHeaders, + queryStringifyRequestData, + removeNullish, + getAPIMode, + getOptionsFromArgs, + getDataFromArgs, +} from './utils.js'; export type HttpClientResponseError = {code: string}; @@ -126,6 +137,7 @@ export class RequestSender { */ _jsonResponseHandler( requestEvent: RequestEvent, + apiMode: 'v1' | 'v2', usage: Array, callback: RequestCallback ) { @@ -167,8 +179,10 @@ export class RequestSender { err = new StripePermissionError(jsonResponse.error); } else if (statusCode === 429) { err = new StripeRateLimitError(jsonResponse.error); + } else if (apiMode === 'v2') { + err = generateV2Error(jsonResponse.error); } else { - err = StripeError.generate(jsonResponse.error); + err = generateV1Error(jsonResponse.error); } throw err; @@ -272,7 +286,7 @@ export class RequestSender { // 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), + initialNetworkRetryDelay * Math.pow(2, numRetries - 1), maxNetworkRetryDelay ); @@ -301,39 +315,65 @@ export class RequestSender { _defaultIdempotencyKey( method: string, - settings: RequestSettings + settings: RequestSettings, + apiMode: ApiMode ): string | null { // 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-${this._stripe._platformFunctions.uuid4()}`; + const genKey = (): string => + `stripe-node-retry-${this._stripe._platformFunctions.uuid4()}`; + + // more verbose than it needs to be, but gives clear separation between V1 and V2 behavior + if (apiMode === 'v2') { + if (method === 'POST' || method === 'DELETE') { + return genKey(); + } + } else if (apiMode === 'v1') { + if (method === 'POST' && maxRetries > 0) { + return genKey(); + } } + return null; } - _makeHeaders( - auth: string | null, - contentLength: number, - apiVersion: string, - clientUserAgent: string, - method: string, - userSuppliedHeaders: RequestHeaders | null, - userSuppliedSettings: RequestSettings - ): RequestHeaders { + _makeHeaders({ + contentType, + contentLength, + apiVersion, + clientUserAgent, + method, + userSuppliedHeaders, + userSuppliedSettings, + stripeAccount, + stripeContext, + apiMode, + }: { + contentType: string; + contentLength: number; + apiVersion: string | null; + clientUserAgent: string; + method: string; + userSuppliedHeaders: RequestHeaders | null; + userSuppliedSettings: RequestSettings; + stripeAccount: string | null; + stripeContext: string | null; + apiMode: ApiMode; + }): RequestHeaders { 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(), + 'Content-Type': contentType, + 'User-Agent': this._getUserAgentString(apiMode), 'X-Stripe-Client-User-Agent': clientUserAgent, 'X-Stripe-Client-Telemetry': this._getTelemetryHeader(), 'Stripe-Version': apiVersion, - 'Stripe-Account': this._stripe.getApiField('stripeAccount'), + 'Stripe-Account': stripeAccount, + 'Stripe-Context': stripeContext, 'Idempotency-Key': this._defaultIdempotencyKey( method, - userSuppliedSettings + userSuppliedSettings, + apiMode ), } as RequestHeaders; @@ -372,13 +412,13 @@ export class RequestSender { ); } - _getUserAgentString(): string { + _getUserAgentString(apiMode: string): string { const packageVersion = this._stripe.getConstant('PACKAGE_VERSION'); const appInfo = this._stripe._appInfo ? this._stripe.getAppInfoAsString() : ''; - return `Stripe/v1 NodeBindings/${packageVersion} ${appInfo}`.trim(); + return `Stripe/${apiMode} NodeBindings/${packageVersion} ${appInfo}`.trim(); } _getTelemetryHeader(): string | undefined { @@ -422,19 +462,96 @@ export class RequestSender { } } + _rawRequest( + method: string, + path: string, + params?: RequestData, + options?: RequestOptions + ): Promise { + const requestPromise = new Promise((resolve, reject) => { + let opts: RequestOpts; + try { + const requestMethod = method.toUpperCase(); + if ( + requestMethod !== 'POST' && + params && + Object.keys(params).length !== 0 + ) { + throw new Error( + 'rawRequest only supports params on POST requests. Please pass null and add your parameters to path.' + ); + } + const args: RequestArgs = [].slice.call([params, options]); + + // Pull request data and options (headers, auth) from args. + const dataFromArgs = getDataFromArgs(args); + const data = Object.assign({}, dataFromArgs); + const calculatedOptions = getOptionsFromArgs(args); + + const headers = calculatedOptions.headers; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const authenticator: RequestAuthenticator = calculatedOptions.authenticator!; + opts = { + requestMethod, + requestPath: path, + bodyData: data, + queryData: {}, + authenticator, + headers, + host: null, + streaming: false, + settings: {}, + usage: ['raw_request'], + }; + } catch (err) { + reject(err); + return; + } + + function requestCallback( + err: any, + response: HttpClientResponseInterface + ): void { + if (err) { + reject(err); + } else { + resolve(response); + } + } + + const {headers, settings} = opts; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const authenticator: RequestAuthenticator = opts.authenticator!; + + this._request( + opts.requestMethod, + opts.host, + path, + opts.bodyData, + authenticator, + {headers, settings, streaming: opts.streaming}, + opts.usage, + requestCallback + ); + }); + + return requestPromise; + } + _request( method: string, host: string | null, path: string, - data: RequestData, - auth: string | null, - options: RequestOptions = {}, + data: RequestData | null, + authenticator: RequestAuthenticator, + options: RequestOptions, usage: Array = [], callback: RequestCallback, requestDataProcessor: RequestDataProcessor | null = null ): void { let requestData: string; - + authenticator = authenticator ?? this._stripe._authenticator ?? null; + const apiMode: ApiMode = getAPIMode(path); const retryRequest = ( requestFn: typeof makeRequest, apiVersion: string, @@ -465,88 +582,113 @@ export class RequestSender { ? 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(); - - // @ts-ignore - const requestEvent: RequestEvent = removeNullish({ - api_version: apiVersion, - account: headers['Stripe-Account'], - idempotency_key: headers['Idempotency-Key'], - method, - path, - request_start_time: requestStartTime, - }); + const request = { + host: host || this._stripe.getApiField('host'), + port: this._stripe.getApiField('port'), + path: path, + method: method, + headers: Object.assign({}, headers), + body: requestData, + protocol: this._stripe.getApiField('protocol'), + }; - const requestRetries = numRetries || 0; - - const maxRetries = this._getMaxNetworkRetries(options.settings || {}); - this._stripe._emitter.emit('request', requestEvent); - - req - .then((res: HttpClientResponseInterface) => { - if (RequestSender._shouldRetry(res, requestRetries, maxRetries)) { - return retryRequest( - makeRequest, - apiVersion, - headers, - requestRetries, - // @ts-ignore - res.getHeaders()['retry-after'] + authenticator(request) + .then(() => { + const req = this._stripe + .getApiField('httpClient') + .makeRequest( + request.host, + request.port, + request.path, + request.method, + request.headers, + request.body, + request.protocol, + timeout ); - } else if (options.streaming && res.getStatusCode() < 400) { - return this._streamingResponseHandler( - requestEvent, - usage, - callback - )(res); - } else { - return this._jsonResponseHandler( - requestEvent, - usage, - callback - )(res); - } + + const requestStartTime = Date.now(); + + // @ts-ignore + const requestEvent: RequestEvent = 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: HttpClientResponseInterface) => { + if (RequestSender._shouldRetry(res, requestRetries, maxRetries)) { + return retryRequest( + makeRequest, + apiVersion, + headers, + requestRetries, + // @ts-ignore + res.getHeaders()['retry-after'] + ); + } else if (options.streaming && res.getStatusCode() < 400) { + return this._streamingResponseHandler( + requestEvent, + usage, + callback + )(res); + } else { + return this._jsonResponseHandler( + requestEvent, + apiMode, + usage, + callback + )(res); + } + }) + .catch((error: HttpClientResponseError) => { + if ( + RequestSender._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( + new StripeConnectionError({ + message: isTimeoutError + ? `Request aborted due to timeout being reached (${timeout}ms)` + : RequestSender._generateConnectionErrorMessage( + requestRetries + ), + // @ts-ignore + detail: error, + }) + ); + } + }); }) - .catch((error: HttpClientResponseError) => { - if ( - RequestSender._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( - new StripeConnectionError({ - message: isTimeoutError - ? `Request aborted due to timeout being reached (${timeout}ms)` - : RequestSender._generateConnectionErrorMessage( - requestRetries - ), - // @ts-ignore - detail: error, - }) - ); - } + .catch((e: any) => { + throw new StripeError({ + message: 'Unable to authenticate the request', + exception: e, + }); }); }; @@ -559,15 +701,23 @@ export class RequestSender { this._stripe.getClientUserAgent((clientUserAgent: string) => { const apiVersion = this._stripe.getApiField('version'); - const headers = this._makeHeaders( - auth, - requestData.length, - apiVersion, + const headers = this._makeHeaders({ + contentType: + apiMode == 'v2' + ? 'application/json' + : 'application/x-www-form-urlencoded', + contentLength: requestData.length, + apiVersion: apiVersion, clientUserAgent, method, - options.headers ?? null, - options.settings ?? {} - ); + userSuppliedHeaders: options.headers, + userSuppliedSettings: options.settings, + stripeAccount: + apiMode == 'v2' ? null : this._stripe.getApiField('stripeAccount'), + stripeContext: + apiMode == 'v2' ? this._stripe.getApiField('stripeContext') : null, + apiMode: apiMode, + }); makeRequest(apiVersion, headers, 0); }); @@ -581,7 +731,15 @@ export class RequestSender { prepareAndMakeRequest ); } else { - prepareAndMakeRequest(null, stringifyRequestData(data || {})); + let stringifiedData: string; + + if (apiMode == 'v2') { + stringifiedData = data ? jsonStringifyRequestData(data) : ''; + } else { + stringifiedData = queryStringifyRequestData(data || {}, apiMode); + } + + prepareAndMakeRequest(null, stringifiedData); } } } diff --git a/src/StripeResource.ts b/src/StripeResource.ts index 59dc3fbe77..12da1fa007 100644 --- a/src/StripeResource.ts +++ b/src/StripeResource.ts @@ -3,7 +3,8 @@ import { getOptionsFromArgs, makeURLInterpolator, protoExtend, - stringifyRequestData, + queryStringifyRequestData, + getAPIMode, } from './utils.js'; import {stripeMethod} from './StripeMethod.js'; import { @@ -186,7 +187,7 @@ StripeResource.prototype = { requestPath, bodyData, queryData, - auth: options.auth, + authenticator: options.authenticator ?? null, headers, host: host ?? null, streaming, @@ -228,7 +229,7 @@ StripeResource.prototype = { const path = [ opts.requestPath, emptyQuery ? '' : '?', - stringifyRequestData(opts.queryData), + queryStringifyRequestData(opts.queryData, getAPIMode(opts.requestPath)), ].join(''); const {headers, settings} = opts; @@ -238,8 +239,12 @@ StripeResource.prototype = { opts.host, path, opts.bodyData, - opts.auth, - {headers, settings, streaming: opts.streaming}, + opts.authenticator, + { + headers, + settings, + streaming: opts.streaming, + }, opts.usage, requestCallback, this.requestDataProcessor?.bind(this) diff --git a/src/Types.d.ts b/src/Types.d.ts index 6bb97a4ff1..48f0ba9d85 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -7,6 +7,7 @@ import { import {PlatformFunctions} from './platform/PlatformFunctions.js'; export type AppInfo = {name?: string} & Record; +export type ApiMode = 'v1' | 'v2'; export type BufferedFile = { name: string; type: string; @@ -27,6 +28,7 @@ export type MethodSpec = { usage?: Array; }; export type MultipartRequestData = RequestData | StreamingFile | BufferedFile; +// rawErrorTypeEnum: The beginning of the section generated from our OpenAPI spec export type RawErrorType = | 'card_error' | 'invalid_request_error' @@ -34,8 +36,20 @@ export type RawErrorType = | 'idempotency_error' | 'rate_limit_error' | 'authentication_error' - | 'invalid_grant'; + | 'invalid_grant' + | 'temporary_session_expired'; +// rawErrorTypeEnum: The end of the section generated from our OpenAPI spec export type RequestArgs = Array; +export type StripeRequest = { + host: string; + port: string; + path: string; + method: string; + headers: RequestHeaders; + body: string; + protocol: string; +}; +export type RequestAuthenticator = (request: StripeRequest) => Promise; export type RequestCallback = ( this: void, error: Error | null, @@ -54,16 +68,16 @@ export type RequestEvent = { }; export type RequestHeaders = Record; export type RequestOptions = { - settings?: RequestSettings; - streaming?: boolean; - headers?: RequestHeaders; + settings: RequestSettings; + streaming: boolean; + headers: RequestHeaders; }; export type RequestOpts = { + authenticator: RequestAuthenticator | null; requestMethod: string; requestPath: string; bodyData: RequestData | null; queryData: RequestData; - auth: string | null; headers: RequestHeaders; host: string | null; streaming: boolean; @@ -124,13 +138,11 @@ export type StripeObject = { webhooks: any; _prepResources: () => void; _setAppInfo: (appInfo: AppInfo) => void; - _setApiKey: (apiKey: string) => void; _prevRequestMetrics: Array<{ request_id: string; request_duration_ms: number; }>; _api: { - auth: string | null; host: string; port: string | number; protocol: string; @@ -139,24 +151,42 @@ export type StripeObject = { timeout: number; maxNetworkRetries: number; agent: string; - httpClient: any; + httpClient: HttpClientInterface; dev: boolean; stripeAccount: string | null; + stripeContext: string | null; }; + _authenticator?: RequestAuthenticator; _emitter: EventEmitter; _enableTelemetry: boolean; _requestSender: RequestSender; _getPropsFromConfig: (config: Record) => UserProvidedConfig; _clientId?: string; _platformFunctions: PlatformFunctions; + _setAuthenticator: ( + key: string, + authenticator: RequestAuthenticator | undefined + ) => void; + rawRequest: ( + method: string, + path: string, + data: RequestData, + options: RequestOptions + ) => Promise; }; export type RequestSender = { + _rawRequest( + method: string, + path: string, + params?: RequestData, + options?: RequestOptions + ): Promise; _request( method: string, host: string | null, path: string, data: RequestData | null, - auth: string | null, + authenticator: RequestAuthenticator | null, options: RequestOptions, usage: Array, callback: RequestCallback, @@ -165,6 +195,7 @@ export type RequestSender = { }; export type StripeRawError = { message?: string; + user_message?: string; type?: RawErrorType; headers?: {[header: string]: string}; statusCode?: number; @@ -211,12 +242,13 @@ export type StripeResourceObject = { }; export type RequestDataProcessor = ( method: string, - data: RequestData, + data: RequestData | null, headers: RequestHeaders | undefined, prepareAndMakeRequest: (error: Error | null, data: string) => void ) => void; export type UrlInterpolator = (params: Record) => string; export type UserProvidedConfig = { + authenticator?: RequestAuthenticator; apiVersion?: string; protocol?: string; host?: string; @@ -226,6 +258,7 @@ export type UserProvidedConfig = { maxNetworkRetries?: number; httpClient?: HttpClientInterface; stripeAccount?: string; + stripeContext?: string; typescript?: boolean; telemetry?: boolean; appInfo?: AppInfo; diff --git a/src/Webhooks.ts b/src/Webhooks.ts index fc3dec8907..e0ffa5ea5c 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -25,7 +25,7 @@ type WebhookTestHeaderOptions = { cryptoProvider: CryptoProvider; }; -type WebhookEvent = Record; +export type WebhookEvent = Record; type WebhookPayload = string | Uint8Array; type WebhookSignatureObject = { verifyHeader: ( diff --git a/src/autoPagination.ts b/src/autoPagination.ts index 19cb4a8984..24dd990c35 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -1,5 +1,9 @@ import {MethodSpec, RequestArgs, StripeResourceObject} from './Types.js'; -import {callbackifyPromiseWithTimeout, getDataFromArgs} from './utils.js'; +import { + callbackifyPromiseWithTimeout, + getDataFromArgs, + getAPIMode, +} from './utils.js'; type PromiseCache = { currentPromise: Promise | undefined | null; @@ -31,9 +35,10 @@ type AutoPaginationMethods = { type PageResult = { data: Array; has_more: boolean; - next_page: string | null; + next_page?: string | null; + next_page_url?: string | null; }; -class StripeIterator implements AsyncIterator { +class V1Iterator implements AsyncIterator { private index: number; private pagePromise: Promise>; private promiseCache: PromiseCache; @@ -117,7 +122,7 @@ class StripeIterator implements AsyncIterator { } } -class ListIterator extends StripeIterator { +class V1ListIterator extends V1Iterator { getNextPage(pageResult: PageResult): Promise> { const reverseIteration = isReverseIteration(this.requestArgs); const lastId = getLastId(pageResult, reverseIteration); @@ -127,7 +132,7 @@ class ListIterator extends StripeIterator { } } -class SearchIterator extends StripeIterator { +class V1SearchIterator extends V1Iterator { getNextPage(pageResult: PageResult): Promise> { if (!pageResult.next_page) { throw Error( @@ -140,6 +145,60 @@ class SearchIterator extends StripeIterator { } } +class V2ListIterator implements AsyncIterator { + private currentPageIterator: Promise>; + private nextPageUrl: Promise; + private requestArgs: RequestArgs; + private spec: MethodSpec; + private stripeResource: StripeResourceObject; + constructor( + firstPagePromise: Promise>, + requestArgs: RequestArgs, + spec: MethodSpec, + stripeResource: StripeResourceObject + ) { + this.currentPageIterator = (async (): Promise> => { + const page = await firstPagePromise; + return page.data[Symbol.iterator](); + })(); + + this.nextPageUrl = (async (): Promise => { + const page = await firstPagePromise; + return page.next_page_url || null; + })(); + + this.requestArgs = requestArgs; + this.spec = spec; + this.stripeResource = stripeResource; + } + private async turnPage(): Promise | null> { + const nextPageUrl = await this.nextPageUrl; + if (!nextPageUrl) return null; + this.spec.fullPath = nextPageUrl; + const page = await this.stripeResource._makeRequest( + this.requestArgs, + this.spec, + {} + ); + this.nextPageUrl = Promise.resolve(page.next_page_url); + this.currentPageIterator = Promise.resolve(page.data[Symbol.iterator]()); + return this.currentPageIterator; + } + async next(): Promise> { + { + const result = (await this.currentPageIterator).next(); + if (!result.done) return {done: false, value: result.value}; + } + const nextPageIterator = await this.turnPage(); + if (!nextPageIterator) { + return {done: true, value: undefined}; + } + const result = nextPageIterator.next(); + if (!result.done) return {done: false, value: result.value}; + return {done: true, value: undefined}; + } +} + export const makeAutoPaginationMethods = < TMethodSpec extends MethodSpec, TItem extends {id: string} @@ -149,14 +208,20 @@ export const makeAutoPaginationMethods = < spec: TMethodSpec, firstPagePromise: Promise> ): AutoPaginationMethods | null => { - if (spec.methodType === 'search') { + const apiMode = getAPIMode(spec.fullPath || spec.path); + if (apiMode !== 'v2' && spec.methodType === 'search') { + return makeAutoPaginationMethodsFromIterator( + new V1SearchIterator(firstPagePromise, requestArgs, spec, stripeResource) + ); + } + if (apiMode !== 'v2' && spec.methodType === 'list') { return makeAutoPaginationMethodsFromIterator( - new SearchIterator(firstPagePromise, requestArgs, spec, stripeResource) + new V1ListIterator(firstPagePromise, requestArgs, spec, stripeResource) ); } - if (spec.methodType === 'list') { + if (apiMode === 'v2' && spec.methodType === 'list') { return makeAutoPaginationMethodsFromIterator( - new ListIterator(firstPagePromise, requestArgs, spec, stripeResource) + new V2ListIterator(firstPagePromise, requestArgs, spec, stripeResource) ); } return null; diff --git a/src/crypto/CryptoProvider.ts b/src/crypto/CryptoProvider.ts index f362061c42..5d11947bb8 100644 --- a/src/crypto/CryptoProvider.ts +++ b/src/crypto/CryptoProvider.ts @@ -29,6 +29,13 @@ export class CryptoProvider { computeHMACSignatureAsync(payload: string, secret: string): Promise { throw new Error('computeHMACSignatureAsync not implemented.'); } + + /** + * Computes a SHA-256 hash of the data. + */ + computeSHA256Async(data: Uint8Array): Promise { + throw new Error('computeSHA256 not implemented.'); + } } /** diff --git a/src/crypto/NodeCryptoProvider.ts b/src/crypto/NodeCryptoProvider.ts index 477ce41039..fa86682151 100644 --- a/src/crypto/NodeCryptoProvider.ts +++ b/src/crypto/NodeCryptoProvider.ts @@ -21,4 +21,14 @@ export class NodeCryptoProvider extends CryptoProvider { const signature = await this.computeHMACSignature(payload, secret); return signature; } + + /** @override */ + async computeSHA256Async(data: Uint8Array): Promise { + return new Uint8Array( + await crypto + .createHash('sha256') + .update(data) + .digest() + ); + } } diff --git a/src/crypto/SubtleCryptoProvider.ts b/src/crypto/SubtleCryptoProvider.ts index 51ef0e9caa..db3b51a2c8 100644 --- a/src/crypto/SubtleCryptoProvider.ts +++ b/src/crypto/SubtleCryptoProvider.ts @@ -63,6 +63,11 @@ export class SubtleCryptoProvider extends CryptoProvider { return signatureHexCodes.join(''); } + + /** @override */ + async computeSHA256Async(data: Uint8Array): Promise { + return new Uint8Array(await this.subtleCrypto.digest('SHA-256', data)); + } } // Cached mapping of byte to hex representation. We do this once to avoid re- diff --git a/src/multipart.ts b/src/multipart.ts index 1e28eb44f9..85b6ce024b 100644 --- a/src/multipart.ts +++ b/src/multipart.ts @@ -4,7 +4,7 @@ import { RequestHeaders, StripeResourceObject, } from './Types.js'; -import {flattenAndStringify, stringifyRequestData} from './utils.js'; +import {flattenAndStringify, queryStringifyRequestData} from './utils.js'; type MultipartCallbackReturn = any; type MultipartCallback = ( @@ -87,7 +87,7 @@ export function multipartRequestDataProcessor( data = data || {}; if (method !== 'POST') { - return callback(null, stringifyRequestData(data)); + return callback(null, queryStringifyRequestData(data)); } this._stripe._platformFunctions diff --git a/src/net/FetchHttpClient.ts b/src/net/FetchHttpClient.ts index bea479b463..df5b3cf6b4 100644 --- a/src/net/FetchHttpClient.ts +++ b/src/net/FetchHttpClient.ts @@ -115,7 +115,7 @@ export class FetchHttpClient extends HttpClient implements HttpClientInterface { path: string, method: string, headers: RequestHeaders, - requestData: RequestData, + requestData: string, protocol: string, timeout: number ): Promise { diff --git a/src/net/HttpClient.ts b/src/net/HttpClient.ts index 3e886b6358..673be72a2e 100644 --- a/src/net/HttpClient.ts +++ b/src/net/HttpClient.ts @@ -10,7 +10,7 @@ export interface HttpClientInterface { path: string, method: string, headers: RequestHeaders, - requestData: RequestData, + requestData: string, protocol: string, timeout: number ) => Promise; @@ -48,7 +48,7 @@ export class HttpClient implements HttpClientInterface { path: string, method: string, headers: RequestHeaders, - requestData: RequestData, + requestData: string, protocol: string, timeout: number ): Promise { diff --git a/src/net/NodeHttpClient.ts b/src/net/NodeHttpClient.ts index e6f94f3a4b..562051582b 100644 --- a/src/net/NodeHttpClient.ts +++ b/src/net/NodeHttpClient.ts @@ -43,7 +43,7 @@ export class NodeHttpClient extends HttpClient { path: string, method: string, headers: RequestHeaders, - requestData: RequestData, + requestData: string, protocol: string, timeout: number ): Promise { diff --git a/src/resources.ts b/src/resources.ts index 857661b713..b9ca904bf8 100644 --- a/src/resources.ts +++ b/src/resources.ts @@ -121,6 +121,7 @@ export {TaxRates} from './resources/TaxRates.js'; export {Tokens} from './resources/Tokens.js'; export {Topups} from './resources/Topups.js'; export {Transfers} from './resources/Transfers.js'; +export {V2} from './resources/V2.js'; export {WebhookEndpoints} from './resources/WebhookEndpoints.js'; export const Apps = resourceNamespace('apps', {Secrets: AppsSecrets}); export const Billing = resourceNamespace('billing', { diff --git a/src/resources/OAuth.ts b/src/resources/OAuth.ts index 318d49835c..78f046a992 100644 --- a/src/resources/OAuth.ts +++ b/src/resources/OAuth.ts @@ -1,7 +1,7 @@ 'use strict'; import {StripeResource} from '../StripeResource.js'; -import {stringifyRequestData} from '../utils.js'; +import {queryStringifyRequestData} from '../utils.js'; type OAuthAuthorizeUrlParams = { response_type?: 'code'; @@ -48,7 +48,7 @@ export const OAuth = StripeResource.extend({ params.scope = 'read_write'; } - return `https://${oAuthHost}/${path}?${stringifyRequestData(params)}`; + return `https://${oAuthHost}/${path}?${queryStringifyRequestData(params)}`; }, token: stripeMethod({ diff --git a/src/resources/V2.ts b/src/resources/V2.ts new file mode 100644 index 0000000000..c8ad50c2e0 --- /dev/null +++ b/src/resources/V2.ts @@ -0,0 +1,12 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../StripeResource.js'; +import {Billing} from './V2/Billing.js'; +import {Core} from './V2/Core.js'; +export const V2 = StripeResource.extend({ + constructor: function(...args: any) { + StripeResource.apply(this, args); + this.billing = new Billing(...args); + this.core = new Core(...args); + }, +}); diff --git a/src/resources/V2/Billing.ts b/src/resources/V2/Billing.ts new file mode 100644 index 0000000000..752a0d3a10 --- /dev/null +++ b/src/resources/V2/Billing.ts @@ -0,0 +1,16 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../StripeResource.js'; +import {MeterEventSession} from './Billing/MeterEventSession.js'; +import {MeterEventAdjustments} from './Billing/MeterEventAdjustments.js'; +import {MeterEventStream} from './Billing/MeterEventStream.js'; +import {MeterEvents} from './Billing/MeterEvents.js'; +export const Billing = StripeResource.extend({ + constructor: function(...args: any) { + StripeResource.apply(this, args); + this.meterEventSession = new MeterEventSession(...args); + this.meterEventAdjustments = new MeterEventAdjustments(...args); + this.meterEventStream = new MeterEventStream(...args); + this.meterEvents = new MeterEvents(...args); + }, +}); diff --git a/src/resources/V2/Billing/MeterEventAdjustments.ts b/src/resources/V2/Billing/MeterEventAdjustments.ts new file mode 100644 index 0000000000..e6bc084da5 --- /dev/null +++ b/src/resources/V2/Billing/MeterEventAdjustments.ts @@ -0,0 +1,10 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const MeterEventAdjustments = StripeResource.extend({ + create: stripeMethod({ + method: 'POST', + fullPath: '/v2/billing/meter_event_adjustments', + }), +}); diff --git a/src/resources/V2/Billing/MeterEventSession.ts b/src/resources/V2/Billing/MeterEventSession.ts new file mode 100644 index 0000000000..2fa6fb9f8e --- /dev/null +++ b/src/resources/V2/Billing/MeterEventSession.ts @@ -0,0 +1,10 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const MeterEventSession = StripeResource.extend({ + create: stripeMethod({ + method: 'POST', + fullPath: '/v2/billing/meter_event_session', + }), +}); diff --git a/src/resources/V2/Billing/MeterEventStream.ts b/src/resources/V2/Billing/MeterEventStream.ts new file mode 100644 index 0000000000..c74537f037 --- /dev/null +++ b/src/resources/V2/Billing/MeterEventStream.ts @@ -0,0 +1,11 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const MeterEventStream = StripeResource.extend({ + create: stripeMethod({ + method: 'POST', + fullPath: '/v2/billing/meter_event_stream', + host: 'meter-events.stripe.com', + }), +}); diff --git a/src/resources/V2/Billing/MeterEvents.ts b/src/resources/V2/Billing/MeterEvents.ts new file mode 100644 index 0000000000..ae425b29b5 --- /dev/null +++ b/src/resources/V2/Billing/MeterEvents.ts @@ -0,0 +1,7 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const MeterEvents = StripeResource.extend({ + create: stripeMethod({method: 'POST', fullPath: '/v2/billing/meter_events'}), +}); diff --git a/src/resources/V2/Core.ts b/src/resources/V2/Core.ts new file mode 100644 index 0000000000..4f13f0cc90 --- /dev/null +++ b/src/resources/V2/Core.ts @@ -0,0 +1,10 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../StripeResource.js'; +import {Events} from './Core/Events.js'; +export const Core = StripeResource.extend({ + constructor: function(...args: any) { + StripeResource.apply(this, args); + this.events = new Events(...args); + }, +}); diff --git a/src/resources/V2/Core/Events.ts b/src/resources/V2/Core/Events.ts new file mode 100644 index 0000000000..e0bba400ce --- /dev/null +++ b/src/resources/V2/Core/Events.ts @@ -0,0 +1,12 @@ +// File generated from our OpenAPI spec + +import {StripeResource} from '../../../StripeResource.js'; +const stripeMethod = StripeResource.method; +export const Events = StripeResource.extend({ + retrieve: stripeMethod({method: 'GET', fullPath: '/v2/core/events/{id}'}), + list: stripeMethod({ + method: 'GET', + fullPath: '/v2/core/events', + methodType: 'list', + }), +}); diff --git a/src/stripe.core.ts b/src/stripe.core.ts index b777ce7cdb..2a22fbd5d8 100644 --- a/src/stripe.core.ts +++ b/src/stripe.core.ts @@ -1,14 +1,22 @@ import * as _Error from './Error.js'; import {RequestSender} from './RequestSender.js'; import {StripeResource} from './StripeResource.js'; -import {AppInfo, StripeObject, UserProvidedConfig} from './Types.js'; -import {WebhookObject, createWebhooks} from './Webhooks.js'; -import * as apiVersion from './apiVersion.js'; +import { + AppInfo, + RequestAuthenticator, + StripeObject, + UserProvidedConfig, + RequestData, + RequestOptions, +} from './Types.js'; +import {WebhookObject, WebhookEvent, createWebhooks} from './Webhooks.js'; +import {ApiVersion} from './apiVersion.js'; import {CryptoProvider} from './crypto/CryptoProvider.js'; import {HttpClient, HttpClientResponse} from './net/HttpClient.js'; import {PlatformFunctions} from './platform/PlatformFunctions.js'; import * as resources from './resources.js'; import { + createApiKeyAuthenticator, determineProcessUserAgentProperties, pascalToCamelCase, validateInteger, @@ -17,15 +25,16 @@ import { const DEFAULT_HOST = 'api.stripe.com'; const DEFAULT_PORT = '443'; const DEFAULT_BASE_PATH = '/v1/'; -const DEFAULT_API_VERSION = apiVersion.ApiVersion; +const DEFAULT_API_VERSION = ApiVersion; const DEFAULT_TIMEOUT = 80000; -const MAX_NETWORK_RETRY_DELAY_SEC = 2; +const MAX_NETWORK_RETRY_DELAY_SEC = 5; const INITIAL_NETWORK_RETRY_DELAY_SEC = 0.5; const APP_INFO_PROPERTIES = ['name', 'version', 'url', 'partner_id']; const ALLOWED_CONFIG_PROPERTIES = [ + 'authenticator', 'apiVersion', 'typescript', 'maxNetworkRetries', @@ -38,6 +47,7 @@ const ALLOWED_CONFIG_PROPERTIES = [ 'telemetry', 'appInfo', 'stripeAccount', + 'stripeContext', ]; type RequestSenderFactory = (stripe: StripeObject) => RequestSender; @@ -107,7 +117,6 @@ export function createStripe( const agent = props.httpAgent || null; this._api = { - auth: null, host: props.host || DEFAULT_HOST, port: props.port || DEFAULT_PORT, protocol: props.protocol || 'https', @@ -117,7 +126,7 @@ export function createStripe( maxNetworkRetries: validateInteger( 'maxNetworkRetries', props.maxNetworkRetries, - 1 + 2 ), agent: agent, httpClient: @@ -127,6 +136,7 @@ export function createStripe( : this._platformFunctions.createDefaultHttpClient()), dev: false, stripeAccount: props.stripeAccount || null, + stripeContext: props.stripeContext || null, }; const typescript = props.typescript || false; @@ -143,7 +153,7 @@ export function createStripe( } this._prepResources(); - this._setApiKey(key); + this._setAuthenticator(key, props.authenticator); this.errors = _Error; @@ -208,13 +218,33 @@ export function createStripe( _requestSender: null!, _platformFunctions: null!, + rawRequest( + method: string, + path: string, + params?: RequestData, + options?: RequestOptions + ): Promise { + return this._requestSender._rawRequest(method, path, params, options); + }, + /** * @private */ - _setApiKey(key: string): void { - if (key) { - this._setApiField('auth', `Bearer ${key}`); + _setAuthenticator( + key: string, + authenticator: RequestAuthenticator | undefined + ): void { + if (key && authenticator) { + throw new Error("Can't specify both apiKey and authenticator"); } + + if (!key && !authenticator) { + throw new Error('Neither apiKey nor config.authenticator provided'); + } + + this._authenticator = key + ? createApiKeyAuthenticator(key) + : authenticator; }, /** @@ -469,6 +499,43 @@ export function createStripe( return config; }, + + parseThinEvent( + payload: string | Uint8Array, + header: string | Uint8Array, + secret: string, + tolerance?: number, + cryptoProvider?: CryptoProvider, + receivedAt?: number + ): WebhookEvent { + // parses and validates the event payload all in one go + return this.webhooks.constructEvent( + payload, + header, + secret, + tolerance, + cryptoProvider, + receivedAt + ); + }, + + parseSnapshotEvent( + payload: string | Uint8Array, + header: string | Uint8Array, + secret: string, + tolerance?: number, + cryptoProvider?: CryptoProvider, + receivedAt?: number + ): WebhookEvent { + return this.webhooks.constructEvent( + payload, + header, + secret, + tolerance, + cryptoProvider, + receivedAt + ); + }, } as StripeObject; return Stripe; diff --git a/src/utils.ts b/src/utils.ts index 005256d56e..a9dfe7b143 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,6 +6,9 @@ import { StripeResourceObject, RequestHeaders, MultipartRequestData, + RequestAuthenticator, + StripeRequest, + ApiMode, } from './Types.js'; const OPTIONS_KEYS = [ @@ -16,6 +19,9 @@ const OPTIONS_KEYS = [ 'maxNetworkRetries', 'timeout', 'host', + 'authenticator', + 'stripeContext', + 'additionalHeaders', ]; type Settings = { @@ -24,11 +30,11 @@ type Settings = { }; type Options = { - auth: string | null; + authenticator?: RequestAuthenticator | null; host: string | null; settings: Settings; streaming?: boolean; - headers: Record; + headers: RequestHeaders; }; export function isOptionsHash(o: unknown): boolean | unknown { @@ -43,11 +49,15 @@ export function isOptionsHash(o: unknown): boolean | unknown { * Stringifies an Object, accommodating nested objects * (forming the conventional key 'parent[child]=value') */ -export function stringifyRequestData(data: RequestData | string): string { +export function queryStringifyRequestData( + data: RequestData | string, + apiMode?: ApiMode +): string { return ( qs .stringify(data, { serializeDate: (d: Date) => Math.floor(d.getTime() / 1000).toString(), + arrayFormat: apiMode == 'v2' ? 'repeat' : 'indices', }) // Don't use strict form encoding by changing the square bracket control // characters back to their literals. This is fine by the server, and @@ -132,7 +142,6 @@ export function getDataFromArgs(args: RequestArgs): RequestData { */ export function getOptionsFromArgs(args: RequestArgs): Options { const opts: Options = { - auth: null, host: null, headers: {}, settings: {}, @@ -140,7 +149,7 @@ export function getOptionsFromArgs(args: RequestArgs): Options { if (args.length > 0) { const arg = args[args.length - 1]; if (typeof arg === 'string') { - opts.auth = args.pop() as string; + opts.authenticator = createApiKeyAuthenticator(args.pop() as string); } else if (isOptionsHash(arg)) { const params = {...(args.pop() as Record)}; @@ -155,16 +164,24 @@ export function getOptionsFromArgs(args: RequestArgs): Options { } if (params.apiKey) { - opts.auth = params.apiKey as string; + opts.authenticator = createApiKeyAuthenticator(params.apiKey as string); } if (params.idempotencyKey) { - opts.headers['Idempotency-Key'] = params.idempotencyKey; + opts.headers['Idempotency-Key'] = params.idempotencyKey as string; } if (params.stripeAccount) { - opts.headers['Stripe-Account'] = params.stripeAccount; + opts.headers['Stripe-Account'] = params.stripeAccount as string; + } + if (params.stripeContext) { + if (opts.headers['Stripe-Account']) { + throw new Error( + "Can't specify both stripeAccount and stripeContext." + ); + } + opts.headers['Stripe-Context'] = params.stripeContext as string; } if (params.apiVersion) { - opts.headers['Stripe-Version'] = params.apiVersion; + opts.headers['Stripe-Version'] = params.apiVersion as string; } if (Number.isInteger(params.maxNetworkRetries)) { opts.settings.maxNetworkRetries = params.maxNetworkRetries as number; @@ -175,6 +192,23 @@ export function getOptionsFromArgs(args: RequestArgs): Options { if (params.host) { opts.host = params.host as string; } + if (params.authenticator) { + if (params.apiKey) { + throw new Error("Can't specify both apiKey and authenticator."); + } + if (typeof params.authenticator !== 'function') { + throw new Error( + 'The authenticator must be a function ' + + 'receiving a request as the first parameter.' + ); + } + opts.authenticator = params.authenticator as RequestAuthenticator; + } + if (params.additionalHeaders) { + opts.headers = params.additionalHeaders as { + [headerName: string]: string; + }; + } } } return opts; @@ -361,6 +395,20 @@ export function determineProcessUserAgentProperties(): Record { }; } +export function createApiKeyAuthenticator( + apiKey: string +): RequestAuthenticator { + const authenticator = (request: StripeRequest): Promise => { + request.headers.Authorization = 'Bearer ' + apiKey; + return Promise.resolve(); + }; + + // For testing + authenticator._apiKey = apiKey; + + return authenticator; +} + /** * Joins an array of Uint8Arrays into a single Uint8Array */ @@ -376,3 +424,31 @@ export function concat(arrays: Array): Uint8Array { return merged; } + +/** + * Replaces Date objects with Unix timestamps + */ +function dateTimeReplacer(this: any, key: string, value: any): string { + if (this[key] instanceof Date) { + return Math.floor(this[key].getTime() / 1000).toString(); + } + + return value; +} + +/** + * JSON stringifies an Object, replacing Date objects with Unix timestamps + */ +export function jsonStringifyRequestData(data: RequestData | string): string { + return JSON.stringify(data, dateTimeReplacer); +} + +/** + * Inspects the given path to determine if the endpoint is for v1 or v2 API + */ +export function getAPIMode(path?: string): ApiMode { + if (!path) { + return 'v1'; + } + return path.startsWith('/v2') ? 'v2' : 'v1'; +} diff --git a/test/Error.spec.ts b/test/Error.spec.ts index a899766a7a..7efa021179 100644 --- a/test/Error.spec.ts +++ b/test/Error.spec.ts @@ -23,6 +23,29 @@ describe('Error', () => { ).to.be.instanceOf(Error.StripeUnknownError); }); + it('Generates specific instance of v2 errors depending on error-type', () => { + // Falls back to V1 parsing logic if code is absent + expect(Error.generateV2Error({type: 'card_error'})).to.be.instanceOf( + Error.StripeCardError + ); + // Falls back to V1 parsing logic if code is unrecognized + expect( + Error.generateV2Error({type: 'card_error', code: 'no_such_error'}) + ).to.be.instanceOf(Error.StripeCardError); + expect( + Error.generateV2Error({ + code: 'invalid_fields', + }) + ).to.be.instanceOf(Error.StripeInvalidRequestError); + expect( + Error.generateV2Error({type: 'temporary_session_expired'}) + ).to.be.instanceOf(Error.TemporarySessionExpiredError); + + expect(Error.generateV2Error({code: 'invalid_fields'})).to.be.instanceOf( + Error.StripeInvalidRequestError + ); + }); + it('copies whitelisted properties', () => { const e = new Error.StripeError({ charge: 'foo', diff --git a/test/RequestSender.spec.ts b/test/RequestSender.spec.ts index 9ccf7b1ea3..6927803959 100644 --- a/test/RequestSender.spec.ts +++ b/test/RequestSender.spec.ts @@ -1,5 +1,7 @@ // @ts-nocheck import {expect} from 'chai'; +import nock = require('nock'); + import { StripeAuthenticationError, StripeConnectionError, @@ -7,15 +9,17 @@ import { StripeIdempotencyError, StripePermissionError, StripeRateLimitError, + StripeUnknownError, + TemporarySessionExpiredError, } from '../src/Error.js'; -import {HttpClientResponse} from '../src/net/HttpClient.js'; import {RequestSender} from '../src/RequestSender.js'; +import {ApiVersion} from '../src/apiVersion.js'; +import {HttpClientResponse} from '../src/net/HttpClient.js'; import { FAKE_API_KEY, getSpyableStripe, getTestServerStripe, } from './testUtils.js'; -import nock = require('nock'); const stripe = getSpyableStripe(); @@ -23,22 +27,46 @@ describe('RequestSender', () => { const sender = new RequestSender(stripe, 0); describe('_makeHeaders', () => { - it('sets the Authorization header with Bearer auth using the global API key', () => { - const headers = sender._makeHeaders(null, 0, null); - expect(headers.Authorization).to.equal(`Bearer ${FAKE_API_KEY}`); - }); - it('sets the Authorization header with Bearer auth using the specified API key', () => { - const headers = sender._makeHeaders('anotherFakeAuthToken', 0, null); - expect(headers.Authorization).to.equal('Bearer anotherFakeAuthToken'); - }); it('sets the Stripe-Version header if an API version is provided', () => { - const headers = sender._makeHeaders(null, 0, '1970-01-01'); + const headers = sender._makeHeaders({apiVersion: '1970-01-01'}); expect(headers['Stripe-Version']).to.equal('1970-01-01'); }); it('does not the set the Stripe-Version header if no API version is provided', () => { - const headers = sender._makeHeaders(null, 0, null); + const headers = sender._makeHeaders({}); expect(headers).to.not.include.keys('Stripe-Version'); }); + describe('idempotency keys', () => { + it('only creates creates an idempotency key if a v1 request wil retry', () => { + const headers = sender._makeHeaders({ + method: 'POST', + userSuppliedSettings: {maxNetworkRetries: 3}, + apiMode: 'v1', + }); + expect(headers['Idempotency-Key']).matches(/^stripe-node-retry/); + }); + // should probably always create an IK; until then, codify the behavior + it("skips idempotency genration for v1 reqeust if we're not retrying the request", () => { + const headers = sender._makeHeaders({ + method: 'POST', + userSuppliedSettings: {maxNetworkRetries: 0}, + apiMode: 'v1', + }); + expect(headers['Idempotency-Key']).equals(undefined); + }); + it('always creates an idempotency key for v2 POST requests', () => { + const headers = sender._makeHeaders({method: 'POST', apiMode: 'v2'}); + expect(headers['Idempotency-Key']).matches(/^stripe-node-retry/); + }); + it('always creates an idempotency key for v2 DELETE requests', () => { + const headers = sender._makeHeaders({method: 'DELETE', apiMode: 'v2'}); + expect(headers['Idempotency-Key']).matches(/^stripe-node-retry/); + }); + it('generates a new key every time', () => { + expect(sender._defaultIdempotencyKey('POST', {}, 'v2')).not.to.equal( + sender._defaultIdempotencyKey('POST', {}, 'v2') + ); + }); + }); }); describe('_shouldRetry', () => { @@ -90,7 +118,7 @@ describe('RequestSender', () => { describe('Parameter encoding', () => { // Use a real instance of stripe as we're mocking the http.request responses. - const realStripe = require('../src/stripe.cjs.node.js')('sk_test_xyz'); + const realStripe = require('../src/stripe.cjs.node.js')(FAKE_API_KEY); afterEach(() => { nock.cleanAll(); @@ -268,9 +296,12 @@ describe('RequestSender', () => { const scope = nock( `https://${options.host}`, - // Content-Length should be present for POST. + // Content-Length and Content-Type should be present for POST. { - reqheaders: {'Content-Length': options.body.length}, + reqheaders: { + 'Content-Length': options.body.length, + 'Content-Type': 'application/x-www-form-urlencoded', + }, } ) .post(options.path, options.body) @@ -286,6 +317,89 @@ describe('RequestSender', () => { ); }); + it('encodes the body in POST requests as JSON for v2', (done) => { + const options = { + host: stripe.getConstant('DEFAULT_HOST'), + path: '/v2/billing/meter_event_session', + data: { + name: 'llama', + }, + body: '{"name":"llama"}', + }; + + const scope = nock( + `https://${options.host}`, + // Content-Length and Content-Type should be present for POST. + { + reqheaders: { + 'Content-Length': options.body.length, + 'Content-Type': 'application/json', + }, + } + ) + .post(options.path, options.body) + .reply(200, '{}'); + + realStripe.v2.billing.meterEventSession.create( + options.data, + (err, response) => { + done(err); + scope.done(); + } + ); + }); + + it('encodes null values in the body in POST correctly for v2', (done) => { + const options = { + host: stripe.getConstant('DEFAULT_HOST'), + path: '/v2/billing/meter_event_session', + data: { + name: null, + }, + body: '{"name":null}', + }; + + const scope = nock( + `https://${options.host}`, + // Content-Length and Content-Type should be present for POST. + { + reqheaders: { + 'Content-Length': options.body.length, + 'Content-Type': 'application/json', + }, + } + ) + .post(options.path, options.body) + .reply(200, '{}'); + + realStripe.v2.billing.meterEventSession.create( + options.data, + (err, response) => { + done(err); + scope.done(); + } + ); + }); + + it('encodes data for GET requests as query params for v2', (done) => { + const host = stripe.getConstant('DEFAULT_HOST'); + const scope = nock(`https://${host}`) + .get( + `/v2/core/events/event_123?include=defaults&include=configuration`, + '' + ) + .reply(200, '{}'); + + realStripe.v2.core.events.retrieve( + 'event_123', + {include: ['defaults', 'configuration']}, + (err, response) => { + done(err); + scope.done(); + } + ); + }); + it('always includes Content-Length in POST requests even when empty', (done) => { const options = { host: stripe.getConstant('DEFAULT_HOST'), @@ -315,6 +429,38 @@ describe('RequestSender', () => { ); }); + it('encodes Date objects in POST requests as JSON for v2', (done) => { + const options = { + host: stripe.getConstant('DEFAULT_HOST'), + path: '/v2/billing/meter_event_session', + data: { + created: new Date('2009-02-13T23:31:30Z'), + }, + body: '{"created":"1234567890"}', + }; + + const scope = nock( + `https://${options.host}`, + // Content-Length and Content-Type should be present for POST. + { + reqheaders: { + 'Content-Length': options.body.length, + 'Content-Type': 'application/json', + }, + } + ) + .post(options.path, options.body) + .reply(200, '{}'); + + realStripe.v2.billing.meterEventSession.create( + options.data, + (err, response) => { + done(err); + scope.done(); + } + ); + }); + it('allows overriding host', (done) => { const scope = nock('https://myhost') .get('/v1/accounts/acct_123') @@ -332,6 +478,20 @@ describe('RequestSender', () => { } ); }); + + it('sends with APIVersion in header', (done) => { + const host = stripe.getConstant('DEFAULT_HOST'); + const scope = nock(`https://${host}`, { + reqheaders: {'Stripe-Version': ApiVersion}, + }) + .get('/v1/subscriptions') + .reply(200, '{}'); + + realStripe.subscriptions.list((err, response) => { + done(err); + scope.done(); + }); + }); }); }); @@ -509,6 +669,47 @@ describe('RequestSender', () => { }); }); + it('throws a v2 StripeError based on the underlying error "code" for v2 APIs', (done) => { + const error = { + type: 'temporary_session_expired', + message: 'you messed up', + }; + + nock(`https://${options.host}`) + .post('/v2/billing/meter_event_session', {}) + .reply(400, { + error, + }); + + realStripe.v2.billing.meterEventSession.create({}, (err) => { + expect(err).to.be.an.instanceOf(TemporarySessionExpiredError); + expect(err.message).to.equal('you messed up'); + done(); + }); + }); + + it('throws a v1 StripeError for v1 APIs', (done) => { + const error = { + type: 'temporary_session_expired', + message: 'you messed up', + }; + + nock(`https://${options.host}`) + .post('/v1/customers', {}) + .reply(400, { + error, + }); + + realStripe.customers.create({}, (err) => { + expect(err).to.be.an.instanceOf(StripeError); + expect(err).to.be.an.instanceOf(StripeUnknownError); + expect(err).not.to.be.an.instanceOf(TemporarySessionExpiredError); + expect(err.message).to.equal('you messed up'); + expect(err.raw.message).to.equal('you messed up'); + done(); + }); + }); + it('retries connection timeout errors', (done) => { let nRequestsReceived = 0; return getTestServerStripe( diff --git a/test/autoPagination.spec.ts b/test/autoPagination.spec.ts index 1c5f99db1e..063aad5211 100644 --- a/test/autoPagination.spec.ts +++ b/test/autoPagination.spec.ts @@ -5,6 +5,7 @@ import {expect} from 'chai'; import {makeAutoPaginationMethods} from '../src/autoPagination.js'; import {StripeResource} from '../src/StripeResource.js'; import {getMockStripe} from './testUtils.js'; +import {MethodSpec} from '../src/Types.js'; describe('auto pagination', () => { const testCase = (mockPaginationFn) => ({ @@ -36,7 +37,6 @@ describe('auto pagination', () => { method: 'GET', fullPath: '/v1/items', methodType: 'list', - apiMode: 'v1', }; const mockStripe = getMockStripe( @@ -544,7 +544,7 @@ describe('auto pagination', () => { }); }); - describe('foward pagination', () => { + describe('forward pagination', () => { it('paginates forwards through a page', () => { return testCaseV1List({ pages: [ @@ -647,7 +647,6 @@ describe('auto pagination', () => { const spec = { method: 'GET', methodType: 'search', - apiMode: 'v1', }; const addNextPage = (props) => { @@ -746,6 +745,103 @@ describe('auto pagination', () => { }); }); }); + describe('V2 list pagination', () => { + const mockPaginationV2List = (pages, initialArgs) => { + let i = 1; + const paramsLog = []; + const spec = { + method: 'GET', + fullPath: '/v2/items', + methodType: 'list', + }; + + const mockStripe = getMockStripe( + {}, + (_1, _2, path, _4, _5, _6, _7, callback) => { + paramsLog.push(path.slice(path.indexOf('?'))); + callback( + null, + Promise.resolve({ + data: pages[i].ids.map((id) => ({id})), + next_page_url: pages[i].next_page_url, + }) + ); + i += 1; + } + ); + const resource = new StripeResource(mockStripe); + + const paginator = makeAutoPaginationMethods( + resource, + initialArgs || {}, + spec, + Promise.resolve({ + data: pages[0].ids.map((id) => ({id})), + next_page_url: pages[0].next_page_url, + }) + ); + return {paginator, paramsLog}; + }; + + const testCaseV2List = testCase(mockPaginationV2List); + it('paginates forwards through a page', () => { + return testCaseV2List({ + pages: [ + {ids: [1, 2], next_page_url: '/v2/items?page=foo'}, + {ids: [3, 4]}, + ], + limit: 10, + expectedIds: [1, 2, 3, 4], + expectedParamsLog: ['?page=foo'], + }); + }); + + it('paginates forwards through uneven-sized pages', () => { + return testCaseV2List({ + pages: [ + {ids: [1, 2], next_page_url: '/v2/items?page=foo'}, + {ids: [3, 4], next_page_url: '/v2/items?page=bar'}, + {ids: [5]}, + ], + limit: 10, + expectedIds: [1, 2, 3, 4, 5], + expectedParamsLog: ['?page=foo', '?page=bar'], + }); + }); + + it('respects limit even when paginating', () => { + return testCaseV2List({ + pages: [ + {ids: [1, 2], next_page_url: '/v2/items?limit=5&page=a'}, + {ids: [3, 4], next_page_url: '/v2/items?limit=5&page=b'}, + {ids: [5, 6]}, + ], + limit: 5, + expectedIds: [1, 2, 3, 4, 5], + expectedParamsLog: ['?limit=5&page=a', '?limit=5&page=b'], + }); + }); + + it('paginates through multiple full pages', () => { + return testCaseV2List({ + pages: [ + {ids: [1, 2], next_page_url: '/v2/items?limit=10&page=wibble'}, + {ids: [3, 4], next_page_url: '/v2/items?limit=10&page=wobble'}, + {ids: [5, 6], next_page_url: '/v2/items?limit=10&page=weeble'}, + {ids: [7, 8], next_page_url: '/v2/items?limit=10&page=blubble'}, + {ids: [9, 10]}, + ], + limit: 10, + expectedIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + expectedParamsLog: [ + '?limit=10&page=wibble', + '?limit=10&page=wobble', + '?limit=10&page=weeble', + '?limit=10&page=blubble', + ], + }); + }); + }); }); export {}; diff --git a/test/crypto/helpers.ts b/test/crypto/helpers.ts index a056283c6c..3e5a905224 100644 --- a/test/crypto/helpers.ts +++ b/test/crypto/helpers.ts @@ -57,5 +57,16 @@ export const createCryptoProviderTestSuite = ( }); } }); + + describe('computeSHA256Async', () => { + it('computes the hash', async () => { + const signature = await cryptoProvider.computeSHA256Async( + new Uint8Array([1, 2, 3, 4, 5]) + ); + expect(Buffer.from(signature).toString('base64')).to.equal( + 'dPgf4WfZm0y0HW0MzagieMrunz4vJdXlo5Nv89zsYNA=' + ); + }); + }); }); }; diff --git a/test/net/helpers.ts b/test/net/helpers.ts index 6154b86473..cdec2b0e5f 100644 --- a/test/net/helpers.ts +++ b/test/net/helpers.ts @@ -101,7 +101,7 @@ export const createHttpClientTestSuite = (createHttpClientFn, extraTestsFn) => { }); it('sends request data (POST)', (done) => { - const expectedData = utils.stringifyRequestData({id: 'test'}); + const expectedData = utils.queryStringifyRequestData({id: 'test'}); nock('http://stripe.com') .post('/test') diff --git a/test/resources/generated_examples_test.spec.js b/test/resources/generated_examples_test.spec.js index 8a4e86cfaa..06c429bc1b 100644 --- a/test/resources/generated_examples_test.spec.js +++ b/test/resources/generated_examples_test.spec.js @@ -567,6 +567,19 @@ describe('Generated tests', function() { expect(session).not.to.be.null; }); + it('test_core_events_get', async function() { + const stripe = testUtils.createMockClient([ + { + method: 'GET', + path: '/v2/core/events/ll_123', + response: + '{"context":"context","created":"1970-01-12T21:42:34.472Z","id":"obj_123","livemode":true,"object":"v2.core.event","reason":{"type":"request","request":{"id":"obj_123","idempotency_key":"idempotency_key"}},"type":"type"}', + }, + ]); + const event = await stripe.v2.core.events.retrieve('ll_123'); + expect(event).not.to.be.null; + }); + it('test_country_specs_get', async function() { const countrySpecs = await stripe.countrySpecs.list({ limit: 3, diff --git a/test/stripe.spec.ts b/test/stripe.spec.ts index 79d702bf92..69c840e71e 100644 --- a/test/stripe.spec.ts +++ b/test/stripe.spec.ts @@ -4,14 +4,16 @@ 'use strict'; import {expect} from 'chai'; +import {StripeSignatureVerificationError} from '../src/Error.js'; import {ApiVersion} from '../src/apiVersion.js'; import {createStripe} from '../src/stripe.core.js'; +import {createApiKeyAuthenticator} from '../src/utils.js'; import { + FAKE_API_KEY, getMockPlatformFunctions, getRandomString, - getTestServerStripe, getStripeMockClient, - FAKE_API_KEY, + getTestServerStripe, } from './testUtils.js'; import Stripe = require('../src/stripe.cjs.node.js'); import crypto = require('crypto'); @@ -65,7 +67,7 @@ describe('Stripe Module', function() { }).to.not.throw(); }); - it('should perform a no-op if null, undefined or empty values are passed', () => { + it('API should use the default version when undefined or empty values are passed', () => { const cases = [null, undefined, '', {}]; cases.forEach((item) => { @@ -108,7 +110,32 @@ describe('Stripe Module', function() { describe('setApiKey', () => { it('uses Bearer auth', () => { - expect(stripe.getApiField('auth')).to.equal(`Bearer ${FAKE_API_KEY}`); + expect(stripe._authenticator._apiKey).to.equal(`${FAKE_API_KEY}`); + }); + + it('should throw if no api key or authenticator provided', () => { + expect(() => new Stripe(null)).to.throw( + 'Neither apiKey nor config.authenticator provided' + ); + }); + }); + + describe('authenticator', () => { + it('should throw an error when specifying both key and authenticator', () => { + expect(() => { + return new Stripe('key', { + authenticator: createApiKeyAuthenticator('...'), + }); + }).to.throw("Can't specify both apiKey and authenticator"); + }); + + it('can create client using authenticator', () => { + const authenticator = createApiKeyAuthenticator('...'); + const stripe = new Stripe(null, { + authenticator: authenticator, + }); + + expect(stripe._authenticator).to.equal(authenticator); }); }); @@ -529,6 +556,117 @@ describe('Stripe Module', function() { ); }); }); + describe('gets removed', () => { + let headers; + let stripeClient; + let closeServer; + beforeEach((callback) => { + getTestServerStripe( + {}, + (req, res) => { + headers = req.headers; + res.writeHeader(200); + res.write('{}'); + res.end(); + }, + (err, client, close) => { + if (err) { + return callback(err); + } + stripeClient = client; + closeServer = close; + return callback(); + } + ); + }); + afterEach(() => closeServer()); + + it('if explicitly undefined', (callback) => { + stripeClient.customers.create({stripeAccount: undefined}, (err) => { + closeServer(); + if (err) { + return callback(err); + } + expect(Object.keys(headers)).not.to.include('stripe-account'); + return callback(); + }); + }); + + it('if explicitly null', (callback) => { + stripeClient.customers.create({stripeAccount: null}, (err) => { + closeServer(); + if (err) { + return callback(err); + } + expect(Object.keys(headers)).not.to.include('stripe-account'); + return callback(); + }); + }); + }); + }); + + describe('context', () => { + describe('when passed in via the config object', () => { + let headers; + let stripeClient; + let closeServer; + beforeEach((callback) => { + getTestServerStripe( + { + stripeContext: 'ctx_123', + }, + (req, res) => { + headers = req.headers; + res.writeHeader(200); + res.write('{}'); + res.end(); + }, + (err, client, close) => { + if (err) { + return callback(err); + } + stripeClient = client; + closeServer = close; + return callback(); + } + ); + }); + afterEach(() => closeServer()); + it('is not sent on v1 call', (callback) => { + stripeClient.customers.create((err) => { + closeServer(); + if (err) { + return callback(err); + } + expect(headers['stripe-context']).to.equal(undefined); + return callback(); + }); + }); + it('is respected', (callback) => { + stripeClient.v2.billing.meterEventSession.create((err) => { + closeServer(); + if (err) { + return callback(err); + } + expect(headers['stripe-context']).to.equal('ctx_123'); + return callback(); + }); + }); + it('can still be overridden per-request', (callback) => { + stripeClient.v2.billing.meterEventSession.create( + {name: 'llama'}, + {stripeContext: 'ctx_456'}, + (err) => { + closeServer(); + if (err) { + return callback(err); + } + expect(headers['stripe-context']).to.equal('ctx_456'); + return callback(); + } + ); + }); + }); }); describe('setMaxNetworkRetries', () => { @@ -545,12 +683,12 @@ describe('Stripe Module', function() { }); describe('when passed in via the config object', () => { - it('should default to 1 if a non-integer is passed', () => { + it('should default to 2 if a non-integer is passed', () => { const newStripe = Stripe(FAKE_API_KEY, { maxNetworkRetries: 'foo', }); - expect(newStripe.getMaxNetworkRetries()).to.equal(1); + expect(newStripe.getMaxNetworkRetries()).to.equal(2); expect(() => { Stripe(FAKE_API_KEY, { @@ -572,7 +710,7 @@ describe('Stripe Module', function() { it('should use the default', () => { const newStripe = Stripe(FAKE_API_KEY); - expect(newStripe.getMaxNetworkRetries()).to.equal(1); + expect(newStripe.getMaxNetworkRetries()).to.equal(2); }); }); }); @@ -584,4 +722,243 @@ describe('Stripe Module', function() { expect(newStripe.VERSION).to.equal(Stripe.PACKAGE_VERSION); }); }); + + describe('parseThinEvent', () => { + const secret = 'whsec_test_secret'; + + it('can parse event from JSON payload', () => { + const jsonPayload = { + type: 'account.created', + data: 'hello', + related_object: {id: '123', url: 'hello_again'}, + }; + const payload = JSON.stringify(jsonPayload); + const header = stripe.webhooks.generateTestHeaderString({ + payload, + secret, + }); + const event = stripe.parseThinEvent(payload, header, secret); + + expect(event.type).to.equal(jsonPayload.type); + expect(event.data).to.equal(jsonPayload.data); + expect(event.related_object.id).to.equal(jsonPayload.related_object.id); + }); + + it('throws an error for invalid signatures', () => { + const payload = JSON.stringify({event_type: 'account.created'}); + + expect(() => { + stripe.parseThinEvent(payload, 'bad sigheader', secret); + }).to.throw(StripeSignatureVerificationError); + }); + }); + + describe('rawRequest', () => { + const returnedCustomer = { + id: 'cus_123', + }; + + it('should make request with specified encoding FORM', (done) => { + return getTestServerStripe( + {}, + (req, res) => { + expect(req.headers['content-type']).to.equal( + 'application/x-www-form-urlencoded' + ); + expect(req.headers['stripe-version']).to.equal(ApiVersion); + const requestBody = []; + req.on('data', (chunks) => { + requestBody.push(chunks); + }); + req.on('end', () => { + const body = Buffer.concat(requestBody).toString(); + expect(body).to.equal('description=test%20customer'); + }); + res.write(JSON.stringify(returnedCustomer)); + res.end(); + }, + async (err, stripe, closeServer) => { + if (err) return done(err); + try { + const result = await stripe.rawRequest( + 'POST', + '/v1/customers', + {description: 'test customer'}, + {} + ); + expect(result).to.deep.equal(returnedCustomer); + closeServer(); + done(); + } catch (err) { + return done(err); + } + } + ); + }); + + it('should make request with specified encoding JSON', (done) => { + return getTestServerStripe( + {}, + (req, res) => { + expect(req.headers['content-type']).to.equal('application/json'); + expect(req.headers['stripe-version']).to.equal(ApiVersion); + expect(req.headers.foo).to.equal('bar'); + const requestBody = []; + req.on('data', (chunks) => { + requestBody.push(chunks); + }); + req.on('end', () => { + const body = Buffer.concat(requestBody).toString(); + expect(body).to.equal( + '{"description":"test meter event","created":"1234567890"}' + ); + }); + res.write(JSON.stringify(returnedCustomer)); + res.end(); + }, + async (err, stripe, closeServer) => { + if (err) return done(err); + try { + const result = await stripe.rawRequest( + 'POST', + '/v2/billing/meter_events', + { + description: 'test meter event', + created: new Date('2009-02-13T23:31:30Z'), + }, + {additionalHeaders: {foo: 'bar'}} + ); + expect(result).to.deep.equal(returnedCustomer); + closeServer(); + done(); + } catch (err) { + return done(err); + } + } + ); + }); + + it('defaults to form encoding request if not specified', (done) => { + return getTestServerStripe( + {}, + (req, res) => { + expect(req.headers['content-type']).to.equal( + 'application/x-www-form-urlencoded' + ); + const requestBody = []; + req.on('data', (chunks) => { + requestBody.push(chunks); + }); + req.on('end', () => { + const body = Buffer.concat(requestBody).toString(); + expect(body).to.equal( + 'description=test%20customer&created=1234567890' + ); + }); + res.write(JSON.stringify(returnedCustomer)); + res.end(); + }, + async (err, stripe, closeServer) => { + if (err) return done(err); + try { + const result = await stripe.rawRequest('POST', '/v1/customers', { + description: 'test customer', + created: new Date('2009-02-13T23:31:30Z'), + }); + expect(result).to.deep.equal(returnedCustomer); + closeServer(); + done(); + } catch (err) { + return done(err); + } + } + ); + }); + + it('should make request with specified additional headers', (done) => { + return getTestServerStripe( + {}, + (req, res) => { + console.log(req.headers); + expect(req.headers.foo).to.equal('bar'); + res.write(JSON.stringify(returnedCustomer)); + res.end(); + }, + async (err, stripe, closeServer) => { + if (err) return done(err); + try { + const result = await stripe.rawRequest( + 'GET', + '/v1/customers/cus_123', + {}, + {additionalHeaders: {foo: 'bar'}} + ); + expect(result).to.deep.equal(returnedCustomer); + closeServer(); + done(); + } catch (err) { + return done(err); + } + } + ); + }); + + it('should make request successfully', async () => { + const response = await stripe.rawRequest('GET', '/v1/customers', {}); + + expect(response).to.have.property('object', 'list'); + }); + + it("should include 'raw_request' in usage telemetry", (done) => { + let telemetryHeader; + let shouldStayOpen = true; + return getTestServerStripe( + {}, + (req, res) => { + telemetryHeader = req.headers['x-stripe-client-telemetry']; + res.setHeader('Request-Id', `req_1`); + res.writeHeader(200); + res.write('{}'); + res.end(); + const ret = {shouldStayOpen}; + shouldStayOpen = false; + return ret; + }, + async (err, stripe, closeServer) => { + if (err) return done(err); + try { + await stripe.rawRequest( + 'POST', + '/v1/customers', + {description: 'test customer'}, + {} + ); + expect(telemetryHeader).to.equal(undefined); + await stripe.rawRequest( + 'POST', + '/v1/customers', + {description: 'test customer'}, + {} + ); + expect( + JSON.parse(telemetryHeader).last_request_metrics.usage + ).to.deep.equal(['raw_request']); + closeServer(); + done(); + } catch (err) { + return done(err); + } + } + ); + }); + + it('should throw error when passing in params to non-POST request', async () => { + await expect( + stripe.rawRequest('GET', '/v1/customers/cus_123', {foo: 'bar'}) + ).to.be.rejectedWith( + Error, + /rawRequest only supports params on POST requests. Please pass null and add your parameters to path./ + ); + }); + }); }); diff --git a/test/testUtils.ts b/test/testUtils.ts index 4d798ec6f7..454b273b46 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -8,6 +8,7 @@ import {NodePlatformFunctions} from '../src/platform/NodePlatformFunctions.js'; import {RequestSender} from '../src/RequestSender.js'; import {createStripe} from '../src/stripe.core.js'; import { + RequestAuthenticator, RequestCallback, RequestData, RequestDataProcessor, @@ -70,7 +71,7 @@ export const getStripeMockClient = (): StripeClient => { path: string, method: string, headers: RequestHeaders, - requestData: RequestData, + requestData: string, _protocol: string, timeout: number ): Promise { @@ -115,8 +116,8 @@ export const getMockStripe = ( host: string | null, path: string, data: RequestData, - auth: string | null, - options: RequestOptions = {}, + authenticator: RequestAuthenticator, + options: RequestOptions = {} as any, usage: Array, callback: RequestCallback, requestDataProcessor: RequestDataProcessor | null = null @@ -126,7 +127,7 @@ export const getMockStripe = ( host, path, data, - auth, + authenticator, options, usage, callback, @@ -170,8 +171,8 @@ export const getSpyableStripe = ( host: string | null, path: string, data: RequestData, - auth: string | null, - options: RequestOptions = {}, + authenticator: RequestAuthenticator, + options: RequestOptions = {} as any, usage: Array = [], callback: RequestCallback, requestDataProcessor: RequestDataProcessor | null = null @@ -183,6 +184,7 @@ export const getSpyableStripe = ( headers: RequestHeaders; settings: RequestSettings; auth?: string; + authenticator?: RequestAuthenticator; host?: string; usage?: Array; }; @@ -196,8 +198,13 @@ export const getSpyableStripe = ( if (usage && usage.length > 1) { req.usage = usage; } - if (auth) { - req.auth = auth; + if (authenticator) { + // Extract API key from the api-key authenticator + if ((authenticator as any)._apiKey) { + req.auth = (authenticator as any)._apiKey; + } else { + req.authenticator = authenticator; + } } if (host) { req.host = host; @@ -222,7 +229,7 @@ export const getSpyableStripe = ( host, path, data, - auth, + authenticator, options, usage, callback, diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 248d8ee92b..b2437d9c76 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -28,10 +28,10 @@ describe('utils', () => { }); }); - describe('stringifyRequestData', () => { + describe('queryStringifyRequestData', () => { it('Handles basic types', () => { expect( - utils.stringifyRequestData({ + utils.queryStringifyRequestData({ a: 1, b: 'foo', }) @@ -40,7 +40,7 @@ describe('utils', () => { it('Handles Dates', () => { expect( - utils.stringifyRequestData({ + utils.queryStringifyRequestData({ date: new Date('2009-02-13T23:31:30Z'), created: { gte: new Date('2009-02-13T23:31:30Z'), @@ -58,7 +58,7 @@ describe('utils', () => { it('Handles deeply nested object', () => { expect( - utils.stringifyRequestData({ + utils.queryStringifyRequestData({ a: { b: { c: { @@ -72,7 +72,7 @@ describe('utils', () => { it('Handles arrays of objects', () => { expect( - utils.stringifyRequestData({ + utils.queryStringifyRequestData({ a: [{b: 'c'}, {b: 'd'}], }) ).to.equal('a[0][b]=c&a[1][b]=d'); @@ -80,7 +80,7 @@ describe('utils', () => { it('Handles indexed arrays', () => { expect( - utils.stringifyRequestData({ + utils.queryStringifyRequestData({ a: { 0: {b: 'c'}, 1: {b: 'd'}, @@ -91,7 +91,7 @@ describe('utils', () => { it('Creates a string from an object, handling shallow nested objects', () => { expect( - utils.stringifyRequestData({ + utils.queryStringifyRequestData({ test: 1, foo: 'baz', somethingElse: '::""%&', @@ -110,6 +110,17 @@ describe('utils', () => { ].join('&') ); }); + + it('Handles v2 arrays', () => { + expect( + utils.queryStringifyRequestData( + { + include: ['a', 'b'], + }, + 'v2' + ) + ).to.equal('include=a&include=b'); + }); }); describe('protoExtend', () => { @@ -183,7 +194,6 @@ describe('utils', () => { describe('getOptsFromArgs', () => { it('handles an empty list', () => { expect(utils.getOptionsFromArgs([])).to.deep.equal({ - auth: null, host: null, headers: {}, settings: {}, @@ -193,7 +203,6 @@ describe('utils', () => { it('handles an list with no object', () => { const args = [1, 3]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: null, host: null, headers: {}, settings: {}, @@ -204,7 +213,6 @@ describe('utils', () => { it('ignores a non-options object', () => { const args = [{foo: 'bar'}]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: null, host: null, headers: {}, settings: {}, @@ -214,30 +222,33 @@ describe('utils', () => { it('parses an api key', () => { const args = ['sk_test_iiiiiiiiiiiiiiiiiiiiiiii']; - expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', + const options = utils.getOptionsFromArgs(args); + expect(options).to.deep.contain({ host: null, headers: {}, settings: {}, }); expect(args.length).to.equal(0); + expect(options.authenticator._apiKey).to.equal( + 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii' + ); }); it('assumes any string is an api key', () => { const args = ['yolo']; - expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: 'yolo', + const options = utils.getOptionsFromArgs(args); + expect(options).to.deep.contain({ host: null, headers: {}, settings: {}, }); expect(args.length).to.equal(0); + expect(options.authenticator._apiKey).to.equal('yolo'); }); it('parses an idempotency key', () => { const args = [{foo: 'bar'}, {idempotencyKey: 'foo'}]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: null, host: null, headers: {'Idempotency-Key': 'foo'}, settings: {}, @@ -248,7 +259,6 @@ describe('utils', () => { it('parses an api version', () => { const args = [{foo: 'bar'}, {apiVersion: '2003-03-30'}]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: null, host: null, headers: {'Stripe-Version': '2003-03-30'}, settings: {}, @@ -265,8 +275,8 @@ describe('utils', () => { apiVersion: '2010-01-10', }, ]; - expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', + const options = utils.getOptionsFromArgs(args); + expect(options).to.deep.contains({ host: null, headers: { 'Idempotency-Key': 'foo', @@ -275,6 +285,9 @@ describe('utils', () => { settings: {}, }); expect(args.length).to.equal(1); + expect(options.authenticator._apiKey).to.equal( + 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii' + ); }); it('parses an idempotency key and api key and api version', () => { @@ -285,8 +298,8 @@ describe('utils', () => { apiVersion: 'hunter2', }, ]; - expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii', + const options = utils.getOptionsFromArgs(args); + expect(options).to.deep.contains({ host: null, headers: { 'Idempotency-Key': 'foo', @@ -295,6 +308,9 @@ describe('utils', () => { settings: {}, }); expect(args.length).to.equal(0); + expect(options.authenticator._apiKey).to.equal( + 'sk_test_iiiiiiiiiiiiiiiiiiiiiiii' + ); }); it('parses additional per-request settings', () => { @@ -306,7 +322,6 @@ describe('utils', () => { ]; expect(utils.getOptionsFromArgs(args)).to.deep.equal({ - auth: null, host: null, headers: {}, settings: { diff --git a/testProjects/mjs/index.js b/testProjects/mjs/index.js index d5bc505f23..4271be4424 100644 --- a/testProjects/mjs/index.js +++ b/testProjects/mjs/index.js @@ -19,7 +19,8 @@ assert(Stripe.createNodeCryptoProvider); assert(Stripe.createSubtleCryptoProvider); assert(Stripe.errors); -assert(Stripe.errors.generate) +assert(Stripe.errors.generateV1Error); +assert(Stripe.errors.generateV2Error); assert(Stripe.errors.StripeError); assert(Stripe.errors.StripeCardError); assert(Stripe.errors.StripeInvalidRequestError); diff --git a/types/Errors.d.ts b/types/Errors.d.ts index 0b3a10d639..7f05e43539 100644 --- a/types/Errors.d.ts +++ b/types/Errors.d.ts @@ -1,5 +1,6 @@ declare module 'stripe' { namespace Stripe { + // rawErrorTypeEnum: The beginning of the section generated from our OpenAPI spec export type RawErrorType = | 'card_error' | 'invalid_request_error' @@ -7,10 +8,13 @@ declare module 'stripe' { | 'idempotency_error' | 'rate_limit_error' | 'authentication_error' - | 'invalid_grant'; + | 'invalid_grant' + | 'temporary_session_expired'; + // rawErrorTypeEnum: The end of the section generated from our OpenAPI spec export type StripeRawError = { message?: string; + userMessage?: string; type: RawErrorType; @@ -32,27 +36,35 @@ declare module 'stripe' { }; namespace errors { + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: 'card_error'} ): StripeCardError; + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: 'invalid_request_error'} ): StripeInvalidRequestError; + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: 'api_error'} ): StripeAPIError; + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: 'authentication_error'} ): StripeAuthenticationError; + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: 'rate_limit_error'} ): StripeRateLimitError; + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: 'idempotency_error'} ): StripeIdempotencyError; + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: 'invalid_grant'} ): StripeInvalidGrantError; + /** @deprecated Not for external use. */ function generate( rawError: StripeRawError & {type: RawErrorType} ): StripeError; @@ -60,27 +72,35 @@ declare module 'stripe' { class StripeError extends Error { constructor(rawError: StripeRawError); + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: 'card_error'} ): StripeCardError; + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: 'invalid_request_error'} ): StripeInvalidRequestError; + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: 'api_error'} ): StripeAPIError; + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: 'authentication_error'} ): StripeAuthenticationError; + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: 'rate_limit_error'} ): StripeRateLimitError; + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: 'idempotency_error'} ): StripeIdempotencyError; + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: 'invalid_grant'} ): StripeInvalidGrantError; + /** @deprecated Not for external use. */ static generate( rawError: StripeRawError & {type: RawErrorType} ): StripeError; @@ -91,6 +111,7 @@ declare module 'stripe' { */ readonly message: string; + // errorClassNameEnum: The beginning of the section generated from our OpenAPI spec readonly type: | 'StripeError' | 'StripeCardError' @@ -102,7 +123,9 @@ declare module 'stripe' { | 'StripeConnectionError' | 'StripeSignatureVerificationError' | 'StripeIdempotencyError' - | 'StripeInvalidGrantError'; + | 'StripeInvalidGrantError' + | 'TemporarySessionExpiredError'; + // errorClassNameEnum: The end of the section generated from our OpenAPI spec /** * See the "error types" section at https://stripe.com/docs/api/errors @@ -245,6 +268,13 @@ declare module 'stripe' { readonly type: 'StripeInvalidGrantError'; readonly rawType: 'invalid_grant'; } + + // errorClassDefinitions: The beginning of the section generated from our OpenAPI spec + export class TemporarySessionExpiredError extends StripeError { + readonly type: 'TemporarySessionExpiredError'; + readonly rawType: 'temporary_session_expired'; + } + // errorClassDefinitions: The end of the section generated from our OpenAPI spec } } } diff --git a/types/ThinEvent.d.ts b/types/ThinEvent.d.ts new file mode 100644 index 0000000000..d1feb07c34 --- /dev/null +++ b/types/ThinEvent.d.ts @@ -0,0 +1,38 @@ +// This is a manually maintained file + +declare module 'stripe' { + namespace Stripe { + namespace Event { + interface RelatedObject { + /** + * Object containing the reference to API resource relevant to the event. + */ + related_object: { + /** + * Unique identifier for the object relevant to the event. + */ + id: string; + + /** + * Type of the object relevant to the event. + */ + type: string; + + /** + * URL to retrieve the resource. + */ + url: string; + }; + } + } + /** + * The Event object as recieved from StripeClient.parseThinEvent. + */ + interface ThinEvent extends V2.EventBase { + /** + * Object containing the reference to API resource relevant to the event. + */ + related_object: Event.RelatedObject; + } + } +} diff --git a/types/V2/Billing/MeterEventAdjustments.d.ts b/types/V2/Billing/MeterEventAdjustments.d.ts new file mode 100644 index 0000000000..11b670631e --- /dev/null +++ b/types/V2/Billing/MeterEventAdjustments.d.ts @@ -0,0 +1,65 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Billing { + /** + * The MeterEventAdjustment object. + */ + interface MeterEventAdjustment { + /** + * The unique id of this meter event adjustment. + */ + id: string; + + /** + * String representing the object's type. Objects of the same type share the same value of the object field. + */ + object: 'billing.meter_event_adjustment'; + + /** + * Specifies which event to cancel. + */ + cancel: MeterEventAdjustment.Cancel; + + /** + * The time the adjustment was created. + */ + created: string; + + /** + * The name of the meter event. Corresponds with the `event_name` field on a meter. + */ + event_name: string; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + + /** + * Open Enum. The meter event adjustment's status. + */ + status: MeterEventAdjustment.Status; + + /** + * Open Enum. Specifies whether to cancel a single event or a range of events for a time period. Time period cancellation is not supported yet. + */ + type: 'cancel'; + } + + namespace MeterEventAdjustment { + interface Cancel { + /** + * Unique identifier for the event. You can only cancel events within 24 hours of Stripe receiving them. + */ + identifier: string; + } + + type Status = 'complete' | 'pending'; + } + } + } + } +} diff --git a/types/V2/Billing/MeterEventAdjustmentsResource.d.ts b/types/V2/Billing/MeterEventAdjustmentsResource.d.ts new file mode 100644 index 0000000000..310d13b7a5 --- /dev/null +++ b/types/V2/Billing/MeterEventAdjustmentsResource.d.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Billing { + interface MeterEventAdjustmentCreateParams { + /** + * Specifies which event to cancel. + */ + cancel: MeterEventAdjustmentCreateParams.Cancel; + + /** + * The name of the meter event. Corresponds with the `event_name` field on a meter. + */ + event_name: string; + + /** + * Specifies whether to cancel a single event or a range of events for a time period. Time period cancellation is not supported yet. + */ + type: 'cancel'; + } + + namespace MeterEventAdjustmentCreateParams { + interface Cancel { + /** + * Unique identifier for the event. You can only cancel events within 24 hours of Stripe receiving them. + */ + identifier: string; + } + } + } + + namespace Billing { + class MeterEventAdjustmentsResource { + /** + * Creates a meter event adjustment to cancel a previously sent meter event. + */ + create( + params: MeterEventAdjustmentCreateParams, + options?: RequestOptions + ): Promise>; + } + } + } + } +} diff --git a/types/V2/Billing/MeterEventSessionResource.d.ts b/types/V2/Billing/MeterEventSessionResource.d.ts new file mode 100644 index 0000000000..dadb405d8b --- /dev/null +++ b/types/V2/Billing/MeterEventSessionResource.d.ts @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Billing { + interface MeterEventSessionCreateParams {} + } + + namespace Billing { + class MeterEventSessionResource { + /** + * Creates a meter event session to send usage on the high-throughput meter event stream. Authentication tokens are only valid for 15 minutes, so you will need to create a new meter event session when your token expires. + */ + create( + params?: MeterEventSessionCreateParams, + options?: RequestOptions + ): Promise>; + create( + options?: RequestOptions + ): Promise>; + } + } + } + } +} diff --git a/types/V2/Billing/MeterEventSessions.d.ts b/types/V2/Billing/MeterEventSessions.d.ts new file mode 100644 index 0000000000..6259622644 --- /dev/null +++ b/types/V2/Billing/MeterEventSessions.d.ts @@ -0,0 +1,45 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Billing { + /** + * The MeterEventSession object. + */ + interface MeterEventSession { + /** + * The unique id of this auth session. + */ + id: string; + + /** + * String representing the object's type. Objects of the same type share the same value of the object field. + */ + object: 'billing.meter_event_session'; + + /** + * The authentication token for this session. Use this token when calling the + * high-throughput meter event API. + */ + authentication_token: string; + + /** + * The creation time of this session. + */ + created: string; + + /** + * The time at which this session will expire. + */ + expires_at: string; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + } + } + } + } +} diff --git a/types/V2/Billing/MeterEventStreamResource.d.ts b/types/V2/Billing/MeterEventStreamResource.d.ts new file mode 100644 index 0000000000..fc4e65e3d4 --- /dev/null +++ b/types/V2/Billing/MeterEventStreamResource.d.ts @@ -0,0 +1,62 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Billing { + interface MeterEventStreamCreateParams { + /** + * List of meter events to include in the request. + */ + events: Array; + } + + namespace MeterEventStreamCreateParams { + interface Event { + /** + * The name of the meter event. Corresponds with the `event_name` field on a meter. + */ + event_name: string; + + /** + * A unique identifier for the event. If not provided, one will be generated. + * We recommend using a globally unique identifier for this. We'll enforce + * uniqueness within a rolling 24 hour period. + */ + identifier?: string; + + /** + * The payload of the event. This must contain the fields corresponding to a meter's + * `customer_mapping.event_payload_key` (default is `stripe_customer_id`) and + * `value_settings.event_payload_key` (default is `value`). Read more about + * the + * [payload](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#payload-key-overrides). + */ + payload: { + [key: string]: string; + }; + + /** + * The time of the event. Must be within the past 35 calendar days or up to + * 5 minutes in the future. Defaults to current timestamp if not specified. + */ + timestamp?: string; + } + } + } + + namespace Billing { + class MeterEventStreamResource { + /** + * Creates meter events. Events are processed asynchronously, including validation. Requires a meter event session for authentication. Supports up to 10,000 requests per second in livemode. For even higher rate-limits, contact sales. + * @throws Stripe.TemporarySessionExpiredError + */ + create( + params: MeterEventStreamCreateParams, + options?: RequestOptions + ): Promise; + } + } + } + } +} diff --git a/types/V2/Billing/MeterEvents.d.ts b/types/V2/Billing/MeterEvents.d.ts new file mode 100644 index 0000000000..53aac66a23 --- /dev/null +++ b/types/V2/Billing/MeterEvents.d.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Billing { + /** + * Fix me empty_doc_string. + */ + interface MeterEvent { + /** + * String representing the object's type. Objects of the same type share the same value of the object field. + */ + object: 'billing.meter_event'; + + /** + * The creation time of this meter event. + */ + created: string; + + /** + * The name of the meter event. Corresponds with the `event_name` field on a meter. + */ + event_name: string; + + /** + * A unique identifier for the event. If not provided, one will be generated. We recommend using a globally unique identifier for this. We'll enforce uniqueness within a rolling 24 hour period. + */ + identifier: string; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + + /** + * The payload of the event. This must contain the fields corresponding to a meter's + * `customer_mapping.event_payload_key` (default is `stripe_customer_id`) and + * `value_settings.event_payload_key` (default is `value`). Read more about the payload. + */ + payload: { + [key: string]: string; + }; + + /** + * The time of the event. Must be within the past 35 calendar days or up to + * 5 minutes in the future. Defaults to current timestamp if not specified. + */ + timestamp: string; + } + } + } + } +} diff --git a/types/V2/Billing/MeterEventsResource.d.ts b/types/V2/Billing/MeterEventsResource.d.ts new file mode 100644 index 0000000000..00def8d19f --- /dev/null +++ b/types/V2/Billing/MeterEventsResource.d.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Billing { + interface MeterEventCreateParams { + /** + * The name of the meter event. Corresponds with the `event_name` field on a meter. + */ + event_name: string; + + /** + * The payload of the event. This must contain the fields corresponding to a meter's + * `customer_mapping.event_payload_key` (default is `stripe_customer_id`) and + * `value_settings.event_payload_key` (default is `value`). Read more about + * the + * [payload](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#payload-key-overrides). + */ + payload: { + [key: string]: string; + }; + + /** + * A unique identifier for the event. If not provided, one will be generated. + * We recommend using a globally unique identifier for this. We'll enforce + * uniqueness within a rolling 24 hour period. + */ + identifier?: string; + + /** + * The time of the event. Must be within the past 35 calendar days or up to + * 5 minutes in the future. Defaults to current timestamp if not specified. + */ + timestamp?: string; + } + } + + namespace Billing { + class MeterEventsResource { + /** + * Creates a meter event. Events are validated synchronously, but are processed asynchronously. Supports up to 1,000 events per second in livemode. For higher rate-limits, please use meter event streams instead. + */ + create( + params: MeterEventCreateParams, + options?: RequestOptions + ): Promise>; + } + } + } + } +} diff --git a/types/V2/BillingResource.d.ts b/types/V2/BillingResource.d.ts new file mode 100644 index 0000000000..a57eee05c7 --- /dev/null +++ b/types/V2/BillingResource.d.ts @@ -0,0 +1,14 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + class BillingResource { + meterEventSession: Stripe.V2.Billing.MeterEventSessionResource; + meterEventAdjustments: Stripe.V2.Billing.MeterEventAdjustmentsResource; + meterEventStream: Stripe.V2.Billing.MeterEventStreamResource; + meterEvents: Stripe.V2.Billing.MeterEventsResource; + } + } + } +} diff --git a/types/V2/Core/EventsResource.d.ts b/types/V2/Core/EventsResource.d.ts new file mode 100644 index 0000000000..e74b6bee42 --- /dev/null +++ b/types/V2/Core/EventsResource.d.ts @@ -0,0 +1,49 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Core { + interface EventRetrieveParams {} + } + + namespace Core { + interface EventListParams { + /** + * Primary object ID used to retrieve related events. + */ + object_id: string; + + limit?: number; + + page?: string; + } + } + + namespace Core { + class EventsResource { + /** + * Retrieves the details of an event. + */ + retrieve( + id: string, + params?: EventRetrieveParams, + options?: RequestOptions + ): Promise>; + retrieve( + id: string, + options?: RequestOptions + ): Promise>; + + /** + * List events, going back up to 30 days. + */ + list( + params: EventListParams, + options?: RequestOptions + ): ApiListPromise; + } + } + } + } +} diff --git a/types/V2/CoreResource.d.ts b/types/V2/CoreResource.d.ts new file mode 100644 index 0000000000..f65f8a4b98 --- /dev/null +++ b/types/V2/CoreResource.d.ts @@ -0,0 +1,11 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + class CoreResource { + events: Stripe.V2.Core.EventsResource; + } + } + } +} diff --git a/types/V2/EventTypes.d.ts b/types/V2/EventTypes.d.ts new file mode 100644 index 0000000000..6b436f6691 --- /dev/null +++ b/types/V2/EventTypes.d.ts @@ -0,0 +1,214 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe.V2 { + export type Event = + | Stripe.Events.V1BillingMeterErrorReportTriggeredEvent + | Stripe.Events.V1BillingMeterNoMeterFoundEvent; + } + + namespace Stripe.Events { + /** + * This event occurs when an async usage event error report is generated. + */ + export interface V1BillingMeterErrorReportTriggeredEvent + extends V2.EventBase { + type: 'v1.billing.meter.error_report_triggered'; + // Retrieves data specific to this event. + data: V1BillingMeterErrorReportTriggeredEvent.Data; + // Retrieves the object associated with the event. + related_object: Event.RelatedObject; + } + + namespace V1BillingMeterErrorReportTriggeredEvent { + export interface Data { + /** + * Extra field included in the event's `data` when fetched from /v2/events. + */ + developer_message_summary: string; + + /** + * This contains information about why meter error happens. + */ + reason: Data.Reason; + + /** + * The end of the window that is encapsulated by this summary. + */ + validation_end: string; + + /** + * The start of the window that is encapsulated by this summary. + */ + validation_start: string; + } + + namespace Data { + export interface Reason { + /** + * The total error count within this window. + */ + error_count: number; + + /** + * The error details. + */ + error_types: Array; + } + + namespace Reason { + export interface ErrorType { + /** + * Open Enum. + */ + code: ErrorType.Code; + + /** + * The number of errors of this type. + */ + error_count: number; + + /** + * A list of sample errors of this type. + */ + sample_errors: Array; + } + + namespace ErrorType { + export type Code = + | 'archived_meter' + | 'meter_event_customer_not_found' + | 'meter_event_dimension_count_too_high' + | 'meter_event_invalid_value' + | 'meter_event_no_customer_defined' + | 'missing_dimension_payload_keys' + | 'no_meter' + | 'timestamp_in_future' + | 'timestamp_too_far_in_past'; + + export interface SampleError { + /** + * The error message. + */ + error_message: string; + + /** + * The request causes the error. + */ + request: SampleError.Request; + } + + namespace SampleError { + export interface Request { + /** + * The request idempotency key. + */ + identifier: string; + } + } + } + } + } + } + + /** + * This event occurs when an async usage event is missing a meter. + */ + export interface V1BillingMeterNoMeterFoundEvent extends V2.EventBase { + type: 'v1.billing.meter.no_meter_found'; + // Retrieves data specific to this event. + data: V1BillingMeterNoMeterFoundEvent.Data; + } + + namespace V1BillingMeterNoMeterFoundEvent { + export interface Data { + /** + * Extra field included in the event's `data` when fetched from /v2/events. + */ + developer_message_summary: string; + + /** + * This contains information about why meter error happens. + */ + reason: Data.Reason; + + /** + * The end of the window that is encapsulated by this summary. + */ + validation_end: string; + + /** + * The start of the window that is encapsulated by this summary. + */ + validation_start: string; + } + + namespace Data { + export interface Reason { + /** + * The total error count within this window. + */ + error_count: number; + + /** + * The error details. + */ + error_types: Array; + } + + namespace Reason { + export interface ErrorType { + /** + * Open Enum. + */ + code: ErrorType.Code; + + /** + * The number of errors of this type. + */ + error_count: number; + + /** + * A list of sample errors of this type. + */ + sample_errors: Array; + } + + namespace ErrorType { + export type Code = + | 'archived_meter' + | 'meter_event_customer_not_found' + | 'meter_event_dimension_count_too_high' + | 'meter_event_invalid_value' + | 'meter_event_no_customer_defined' + | 'missing_dimension_payload_keys' + | 'no_meter' + | 'timestamp_in_future' + | 'timestamp_too_far_in_past'; + + export interface SampleError { + /** + * The error message. + */ + error_message: string; + + /** + * The request causes the error. + */ + request: SampleError.Request; + } + + namespace SampleError { + export interface Request { + /** + * The request idempotency key. + */ + identifier: string; + } + } + } + } + } + } + } +} diff --git a/types/V2/Events.d.ts b/types/V2/Events.d.ts new file mode 100644 index 0000000000..4a42091348 --- /dev/null +++ b/types/V2/Events.d.ts @@ -0,0 +1,75 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + namespace V2 { + namespace Event { + interface Reason { + /** + * Open Enum. Event reason type. + */ + type: 'request'; + + /** + * Information on the API request that instigated the event. + */ + request: Reason.Request | null; + } + + namespace Reason { + interface Request { + /** + * ID of the API request that caused the event. + */ + id: string; + + /** + * The idempotency key transmitted during the request. + */ + idempotency_key: string; + } + } + } + + /** + * The Event object. + */ + interface EventBase { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * String representing the object's type. Objects of the same type share the same value of the object field. + */ + object: 'v2.core.event'; + + /** + * Authentication context needed to fetch the event or related object. + */ + context: string | null; + + /** + * Time at which the object was created. + */ + created: string; + + /** + * Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + */ + livemode: boolean; + + /** + * Reason for the event. + */ + reason: Event.Reason | null; + + /** + * The type of the event. + */ + type: string; + } + } + } +} diff --git a/types/V2Resource.d.ts b/types/V2Resource.d.ts new file mode 100644 index 0000000000..b882341036 --- /dev/null +++ b/types/V2Resource.d.ts @@ -0,0 +1,10 @@ +// File generated from our OpenAPI spec + +declare module 'stripe' { + namespace Stripe { + class V2Resource { + billing: Stripe.V2.BillingResource; + core: Stripe.V2.CoreResource; + } + } +} diff --git a/types/index.d.ts b/types/index.d.ts index 74854c0daf..3d2b26258d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -8,6 +8,8 @@ /// /// /// +/// +/// // Imports: The beginning of the section generated from our OpenAPI spec /// /// @@ -127,6 +129,14 @@ /// /// /// +/// +/// +/// +/// +/// +/// +/// +/// /// /// /// @@ -269,6 +279,10 @@ /// /// /// +/// +/// +/// +/// /// // Imports: The end of the section generated from our OpenAPI spec @@ -340,6 +354,7 @@ declare module 'stripe' { tokens: Stripe.TokensResource; topups: Stripe.TopupsResource; transfers: Stripe.TransfersResource; + v2: Stripe.V2Resource; webhookEndpoints: Stripe.WebhookEndpointsResource; apps: { secrets: Stripe.Apps.SecretsResource; @@ -469,6 +484,109 @@ declare module 'stripe' { event: 'response', handler: (event: Stripe.ResponseEvent) => void ): void; + + /** + * Allows for sending "raw" requests to the Stripe API, which can be used for + * testing new API endpoints or performing requests that the library does + * not support yet. + * + * This is an experimental interface and is not yet stable. + * + * @param method - HTTP request method, 'GET', 'POST', or 'DELETE' + * @param path - The path of the request, e.g. '/v1/beta_endpoint' + * @param params - The parameters to include in the request body. + * @param options - Additional request options. + */ + rawRequest( + method: string, + path: string, + params?: {[key: string]: unknown}, + options?: Stripe.RawRequestOptions + ): Promise>; + + /** + * Parses webhook event payload into a ThinEvent and verifies webhook signature. + * To get more information on the event, pass the id from the returned object to + * `stripe.v2.core.events.retrieve()` + * + * @throws Stripe.errors.StripeSignatureVerificationError + */ + parseThinEvent: ( + /** + * Raw text body payload received from Stripe. + */ + payload: string | Buffer, + /** + * Value of the `stripe-signature` header from Stripe. + * Typically a string. + * + * Note that this is typed to accept an array of strings + * so that it works seamlessly with express's types, + * but will throw if an array is passed in practice + * since express should never return this header as an array, + * only a string. + */ + header: string | Buffer | Array, + /** + * Your Webhook Signing Secret for this endpoint (e.g., 'whsec_...'). + * You can get this [in your dashboard](https://dashboard.stripe.com/webhooks). + */ + secret: string, + /** + * Seconds of tolerance on timestamps. + */ + tolerance?: number, + /** + * Optional CryptoProvider to use for computing HMAC signatures. + */ + cryptoProvider?: Stripe.CryptoProvider, + + /** + * Optional: timestamp to use when checking signature validity. Defaults to Date.now(). + */ + receivedAt?: number + ) => Stripe.ThinEvent; + + /** + * Parses webhook event payload into a SnapshotEvent and verifies webhook signature. + * + * @throws Stripe.errors.StripeSignatureVerificationError + */ + parseSnapshotEvent: ( + /** + * Raw text body payload received from Stripe. + */ + payload: string | Buffer, + /** + * Value of the `stripe-signature` header from Stripe. + * Typically a string. + * + * Note that this is typed to accept an array of strings + * so that it works seamlessly with express's types, + * but will throw if an array is passed in practice + * since express should never return this header as an array, + * only a string. + */ + header: string | Buffer | Array, + /** + * Your Webhook Signing Secret for this endpoint (e.g., 'whsec_...'). + * You can get this [in your dashboard](https://dashboard.stripe.com/webhooks). + */ + secret: string, + /** + * Seconds of tolerance on timestamps. + */ + tolerance?: number, + /** + * Optional CryptoProvider to use for computing HMAC signatures. + */ + cryptoProvider?: Stripe.CryptoProvider, + + /** + * Optional: timestamp to use when checking signature validity. Defaults to Date.now(). + */ + receivedAt?: number + ) => Stripe.Event; } export default Stripe; diff --git a/types/lib.d.ts b/types/lib.d.ts index f23fdfea3c..66a33f2dc6 100644 --- a/types/lib.d.ts +++ b/types/lib.d.ts @@ -153,6 +153,13 @@ declare module 'stripe' { host?: string; } + export type RawRequestOptions = RequestOptions & { + /** + * Specify additional request headers. This is an experimental interface and is not yet stable. + */ + additionalHeaders?: {[headerName: string]: string}; + }; + export type Response = T & { lastResponse: { headers: {[key: string]: string}; From cb0551d18ddad7efcfe5e49b3d7ec921888716ea Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Fri, 27 Sep 2024 17:10:51 -0700 Subject: [PATCH 3/7] updated stripe_webhook_handler with related object fetching and improved output --- examples/snippets/stripe_webhook_handler.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/snippets/stripe_webhook_handler.js b/examples/snippets/stripe_webhook_handler.js index d4549a6d87..bc75e62683 100644 --- a/examples/snippets/stripe_webhook_handler.js +++ b/examples/snippets/stripe_webhook_handler.js @@ -20,8 +20,11 @@ app.post( // Fetch the event data to understand the failure const event = await client.v2.core.events.retrieve(thinEvent.id); if (event.type == 'v1.billing.meter.error_report_triggered') { - const meter = await event.fetchRelatedObject(); + const meter = await client.billing.meters.retrieve( + event.related_object.id + ); const meterId = meter.id; + console.log(`Success! ${meterId}`); // Record the failures and alert your team // Add your logic here } From cdcd0f66111d62a6dd7422f7c00071bb891c6d54 Mon Sep 17 00:00:00 2001 From: David Brownman <109395161+xavdid-stripe@users.noreply.github.com> Date: Sat, 28 Sep 2024 17:26:33 -0700 Subject: [PATCH 4/7] Fix typing of `Stripe.V2.Event` (#2193) --- types/ThinEvent.d.ts | 30 ++++++++++++++---------------- types/V2/Core/EventsResource.d.ts | 8 +++++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/types/ThinEvent.d.ts b/types/ThinEvent.d.ts index d1feb07c34..269ed2057a 100644 --- a/types/ThinEvent.d.ts +++ b/types/ThinEvent.d.ts @@ -3,26 +3,24 @@ declare module 'stripe' { namespace Stripe { namespace Event { + /** + * Object containing the reference to API resource relevant to the event. + */ interface RelatedObject { /** - * Object containing the reference to API resource relevant to the event. + * Unique identifier for the object relevant to the event. */ - related_object: { - /** - * Unique identifier for the object relevant to the event. - */ - id: string; + id: string; - /** - * Type of the object relevant to the event. - */ - type: string; + /** + * Type of the object relevant to the event. + */ + type: string; - /** - * URL to retrieve the resource. - */ - url: string; - }; + /** + * URL to retrieve the resource. + */ + url: string; } } /** @@ -32,7 +30,7 @@ declare module 'stripe' { /** * Object containing the reference to API resource relevant to the event. */ - related_object: Event.RelatedObject; + related_object: Event.RelatedObject | null; } } } diff --git a/types/V2/Core/EventsResource.d.ts b/types/V2/Core/EventsResource.d.ts index e74b6bee42..a37355274f 100644 --- a/types/V2/Core/EventsResource.d.ts +++ b/types/V2/Core/EventsResource.d.ts @@ -1,5 +1,7 @@ // File generated from our OpenAPI spec +/// + declare module 'stripe' { namespace Stripe { namespace V2 { @@ -29,11 +31,11 @@ declare module 'stripe' { id: string, params?: EventRetrieveParams, options?: RequestOptions - ): Promise>; + ): Promise>; retrieve( id: string, options?: RequestOptions - ): Promise>; + ): Promise>; /** * List events, going back up to 30 days. @@ -41,7 +43,7 @@ declare module 'stripe' { list( params: EventListParams, options?: RequestOptions - ): ApiListPromise; + ): ApiListPromise; } } } From 879563e4764efd40acb220a630f85a690d308116 Mon Sep 17 00:00:00 2001 From: Helen Ye Date: Mon, 30 Sep 2024 12:42:01 -0400 Subject: [PATCH 5/7] Update types from newest spec --- types/V2/Core/EventsResource.d.ts | 6 ++++++ types/V2/EventTypes.d.ts | 4 ++-- types/V2/Events.d.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/types/V2/Core/EventsResource.d.ts b/types/V2/Core/EventsResource.d.ts index a37355274f..21a09fa644 100644 --- a/types/V2/Core/EventsResource.d.ts +++ b/types/V2/Core/EventsResource.d.ts @@ -16,8 +16,14 @@ declare module 'stripe' { */ object_id: string; + /** + * The page size. + */ limit?: number; + /** + * The requested page number. + */ page?: string; } } diff --git a/types/V2/EventTypes.d.ts b/types/V2/EventTypes.d.ts index 6b436f6691..4fe79efa87 100644 --- a/types/V2/EventTypes.d.ts +++ b/types/V2/EventTypes.d.ts @@ -9,7 +9,7 @@ declare module 'stripe' { namespace Stripe.Events { /** - * This event occurs when an async usage event error report is generated. + * This event occurs when there are invalid async usage events for a given meter. */ export interface V1BillingMeterErrorReportTriggeredEvent extends V2.EventBase { @@ -112,7 +112,7 @@ declare module 'stripe' { } /** - * This event occurs when an async usage event is missing a meter. + * This event occurs when async usage events have missing or invalid meter ids. */ export interface V1BillingMeterNoMeterFoundEvent extends V2.EventBase { type: 'v1.billing.meter.no_meter_found'; diff --git a/types/V2/Events.d.ts b/types/V2/Events.d.ts index 4a42091348..6dac16c035 100644 --- a/types/V2/Events.d.ts +++ b/types/V2/Events.d.ts @@ -6,7 +6,7 @@ declare module 'stripe' { namespace Event { interface Reason { /** - * Open Enum. Event reason type. + * Event reason type. */ type: 'request'; From 0e2b9da5c988775f20cc075fd4bbeb633f7e8345 Mon Sep 17 00:00:00 2001 From: Helen Ye Date: Mon, 30 Sep 2024 13:48:53 -0400 Subject: [PATCH 6/7] try without params --- src/autoPagination.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/autoPagination.ts b/src/autoPagination.ts index 24dd990c35..95480f849d 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -176,7 +176,8 @@ class V2ListIterator implements AsyncIterator { if (!nextPageUrl) return null; this.spec.fullPath = nextPageUrl; const page = await this.stripeResource._makeRequest( - this.requestArgs, + // this.requestArgs, + [], this.spec, {} ); From 33f689912ff2a68c38f6c9146adc2b61f2bfa336 Mon Sep 17 00:00:00 2001 From: Helen Ye Date: Mon, 30 Sep 2024 16:16:23 -0400 Subject: [PATCH 7/7] do not send additional request params when turning --- src/autoPagination.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/autoPagination.ts b/src/autoPagination.ts index 95480f849d..c8e3704c86 100644 --- a/src/autoPagination.ts +++ b/src/autoPagination.ts @@ -175,12 +175,7 @@ class V2ListIterator implements AsyncIterator { const nextPageUrl = await this.nextPageUrl; if (!nextPageUrl) return null; this.spec.fullPath = nextPageUrl; - const page = await this.stripeResource._makeRequest( - // this.requestArgs, - [], - this.spec, - {} - ); + const page = await this.stripeResource._makeRequest([], this.spec, {}); this.nextPageUrl = Promise.resolve(page.next_page_url); this.currentPageIterator = Promise.resolve(page.data[Symbol.iterator]()); return this.currentPageIterator;