From 62f7bf7e7e9a8f0393f2e3459b10696fac80bdc8 Mon Sep 17 00:00:00 2001 From: leonardo Date: Thu, 9 Apr 2026 10:27:18 -0300 Subject: [PATCH] Bump stripe to v22 --- .changeset/honest-bears-marry.md | 5 + docs/content/docs/plugins/stripe.mdx | 904 +++++++++++++-------------- packages/stripe/package.json | 4 +- packages/stripe/src/routes.ts | 7 +- packages/stripe/src/types.ts | 8 +- pnpm-lock.yaml | 190 +++--- 6 files changed, 570 insertions(+), 548 deletions(-) create mode 100644 .changeset/honest-bears-marry.md diff --git a/.changeset/honest-bears-marry.md b/.changeset/honest-bears-marry.md new file mode 100644 index 0000000000..cf0322fb10 --- /dev/null +++ b/.changeset/honest-bears-marry.md @@ -0,0 +1,5 @@ +--- +"@better-auth/stripe": minor +--- + +Bump Stripe version to v22 diff --git a/docs/content/docs/plugins/stripe.mdx b/docs/content/docs/plugins/stripe.mdx index 5f4e70aea3..fb79beb332 100644 --- a/docs/content/docs/plugins/stripe.mdx +++ b/docs/content/docs/plugins/stripe.mdx @@ -1,128 +1,139 @@ ---- +*** + title: Stripe description: Stripe plugin for Better Auth to manage subscriptions and payments. ---- +-------------------------------------------------------------------------------- The Stripe plugin integrates Stripe's payment and subscription functionality with Better Auth. Since payment and authentication are often tightly coupled, this plugin simplifies the integration of Stripe into your application, handling customer creation, subscription management, and webhook processing. ## Features -- Create Stripe Customers automatically when users sign up -- Manage subscription plans and pricing -- Process subscription lifecycle events (creation, updates, cancellations) -- Handle Stripe webhooks securely with signature verification -- Expose subscription data to your application -- Support for trial periods and subscription upgrades -- **Automatic trial abuse prevention** - Users can only get one trial per account across all plans -- Flexible reference system to associate subscriptions with users or organizations -- Team subscription support with seats management +* Create Stripe Customers automatically when users sign up +* Manage subscription plans and pricing +* Process subscription lifecycle events (creation, updates, cancellations) +* Handle Stripe webhooks securely with signature verification +* Expose subscription data to your application +* Support for trial periods and subscription upgrades +* **Automatic trial abuse prevention** - Users can only get one trial per account across all plans +* Flexible reference system to associate subscriptions with users or organizations +* Team subscription support with seats management ## Installation - - ### Install the plugin + + ### Install the plugin - First, install the plugin: + First, install the plugin: + ```package-install + @better-auth/stripe + ``` + + + If you're using a separate client and server setup, make sure to install the plugin in both parts of your project. + + + + + ### Install the Stripe SDK + + Next, install the Stripe SDK on your server: + + ```package-install + stripe@^22.0.1 + ``` + + + + ### Add the plugin to your auth config + + ```ts title="auth.ts" + import { betterAuth } from "better-auth" + import { stripe } from "@better-auth/stripe" + import Stripe from "stripe" + + const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: "2026-03-25.dahlia", // Latest API version as of Stripe SDK v22.0.0 + }) + + export const auth = betterAuth({ + // ... your existing config + plugins: [ + stripe({ + stripeClient, + stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!, + createCustomerOnSignUp: true, + }) + ] + }) + ``` + + + **Upgrading from an older Stripe SDK?** v19+ uses async webhook signature verification (`constructEventAsync`), v22 requires `new Stripe()` constructor syntax (no longer callable as a plain function). Both are handled — no plugin code changes needed on your end! + + + + + ### Add the client plugin + + ```ts title="auth-client.ts" + import { createAuthClient } from "better-auth/client" + import { stripeClient } from "@better-auth/stripe/client" + + export const authClient = createAuthClient({ + // ... your existing config + plugins: [ + stripeClient({ + subscription: true //if you want to enable subscription management + }) + ] + }) + ``` + + + + ### Migrate the database + + Run the migration or generate the schema to add the necessary tables to the database. + + + ```package-install - @better-auth/stripe + npx auth migrate ``` - - If you're using a separate client and server setup, make sure to install the plugin in both parts of your project. - - - - ### Install the Stripe SDK - - Next, install the Stripe SDK on your server: + + ```package-install - stripe@^20.0.0 + npx auth generate ``` - - - ### Add the plugin to your auth config + + - ```ts title="auth.ts" - import { betterAuth } from "better-auth" - import { stripe } from "@better-auth/stripe" - import Stripe from "stripe" + See the [Schema](#schema) section to add the tables manually. + - const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: "2025-11-17.clover", // Latest API version as of Stripe SDK v20.0.0 - }) + + ### Set up Stripe webhooks - export const auth = betterAuth({ - // ... your existing config - plugins: [ - stripe({ - stripeClient, - stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!, - createCustomerOnSignUp: true, - }) - ] - }) - ``` + Create a webhook endpoint in your Stripe dashboard pointing to: - - **Upgrading from Stripe v18?** Version 19 uses async webhook signature verification (`constructEventAsync`) which is handled internally by the plugin. No code changes required on your end! - - - - ### Add the client plugin + ``` + https://your-domain.com/api/auth/stripe/webhook + ``` - ```ts title="auth-client.ts" - import { createAuthClient } from "better-auth/client" - import { stripeClient } from "@better-auth/stripe/client" + `/api/auth` is the default path for the auth server. - export const authClient = createAuthClient({ - // ... your existing config - plugins: [ - stripeClient({ - subscription: true //if you want to enable subscription management - }) - ] - }) - ``` - - - ### Migrate the database + Make sure to select at least these events: - Run the migration or generate the schema to add the necessary tables to the database. + * `checkout.session.completed` + * `customer.subscription.created` + * `customer.subscription.updated` + * `customer.subscription.deleted` - - - ```package-install - npx auth migrate - ``` - - - ```package-install - npx auth generate - ``` - - - See the [Schema](#schema) section to add the tables manually. - - - ### Set up Stripe webhooks - - Create a webhook endpoint in your Stripe dashboard pointing to: - - ``` - https://your-domain.com/api/auth/stripe/webhook - ``` - `/api/auth` is the default path for the auth server. - - Make sure to select at least these events: - - `checkout.session.completed` - - `customer.subscription.created` - - `customer.subscription.updated` - - `customer.subscription.deleted` - - Save the webhook signing secret provided by Stripe and add it to your environment variables as `STRIPE_WEBHOOK_SECRET`. - + Save the webhook signing secret provided by Stripe and add it to your environment variables as `STRIPE_WEBHOOK_SECRET`. + ## Usage @@ -207,69 +218,65 @@ see [plan configuration](#plan-configuration) for more. To create a subscription, use the `subscription.upgrade` method: - -```ts -type upgradeSubscription = { - /** - * The name of the plan to upgrade to. - */ - plan: string = "pro" - /** - * Whether to upgrade to an annual plan. - */ - annual?: boolean = true - /** - * Reference id of the subscription. Defaults based on customerType. - */ - referenceId?: string = "123" - /** - * The id of the subscription to upgrade. - */ - subscriptionId?: string = "sub_123" - /** - * Additional metadata to store with the subscription. - */ - metadata?: Record - /** - * The type of customer for billing. (Default: "user") - */ - customerType?: "user" | "organization" - /** - * Number of seats to upgrade to (if applicable). - */ - seats?: number = 1 - /** - * The IETF language tag of the locale Checkout is displayed in. - * If not provided or set to `auto`, the browser's locale is used. - */ - locale?: string - /** - * The URL to which Stripe should send customers when payment or setup is complete. - */ - successUrl: string - /** - * If set, checkout shows a back button and customers will be directed here if they cancel payment. - */ - cancelUrl: string - /** - * The URL to return to from the Billing Portal (used when upgrading existing subscriptions) - */ - returnUrl?: string - /** - * Disable redirect after successful subscription. - */ - disableRedirect: boolean = false - /** - * Schedule the plan change at the end of the current billing period - * instead of applying it immediately. - */ - scheduleAtPeriodEnd?: boolean = false -} -``` + + ```ts + type upgradeSubscription = { + /** + * The name of the plan to upgrade to. + */ + plan: string = "pro" + /** + * Whether to upgrade to an annual plan. + */ + annual?: boolean = true + /** + * Reference id of the subscription. Defaults based on customerType. + */ + referenceId?: string = "123" + /** + * The id of the subscription to upgrade. + */ + subscriptionId?: string = "sub_123" + /** + * Additional metadata to store with the subscription. + */ + metadata?: Record + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + /** + * Number of seats to upgrade to (if applicable). + */ + seats?: number = 1 + /** + * The IETF language tag of the locale Checkout is displayed in. + * If not provided or set to `auto`, the browser's locale is used. + */ + locale?: string + /** + * The URL to which Stripe should send customers when payment or setup is complete. + */ + successUrl: string + /** + * If set, checkout shows a back button and customers will be directed here if they cancel payment. + */ + cancelUrl: string + /** + * The URL to return to from the Billing Portal (used when upgrading existing subscriptions) + */ + returnUrl?: string + /** + * Disable redirect after successful subscription. + */ + disableRedirect: boolean = false + /** + * Schedule the plan change at the end of the current billing period + * instead of applying it immediately. + */ + scheduleAtPeriodEnd?: boolean = false + } + ``` **Simple Example:** @@ -289,9 +296,9 @@ await authClient.subscription.upgrade({ This will create a Checkout Session and redirect the user to the Stripe Checkout page. -The plugin only supports one active or trialing subscription per reference ID (user or organization) at a time. Multiple concurrent subscriptions for the same reference ID are not supported. + The plugin only supports one active or trialing subscription per reference ID (user or organization) at a time. Multiple concurrent subscriptions for the same reference ID are not supported. -If the user already has an active subscription, you **must** provide the `subscriptionId` parameter when upgrading. Otherwise, a new subscription may be created alongside the existing one, resulting in duplicate billing. + If the user already has an active subscription, you **must** provide the `subscriptionId` parameter when upgrading. Otherwise, a new subscription may be created alongside the existing one, resulting in duplicate billing. > **Important:** The `successUrl` parameter will be internally modified to handle race conditions between checkout completion and webhook processing. The plugin creates an intermediate redirect that ensures subscription status is properly updated before redirecting to your success page. @@ -310,6 +317,7 @@ if(error) { #### Switching Plans To switch a subscription to a different plan, use the `subscription.upgrade` method: + ```ts title="client.ts" await authClient.subscription.upgrade({ plan: "pro", @@ -318,6 +326,7 @@ await authClient.subscription.upgrade({ subscriptionId: "sub_123", // the Stripe subscription ID of the user's current plan }); ``` + This ensures that the user only pays for the new plan, and not both. #### Scheduling Plan Changes at Period End @@ -337,43 +346,39 @@ await authClient.subscription.upgrade({ This uses the [Stripe Subscription Schedules API](https://docs.stripe.com/billing/subscriptions/subscription-schedules) to create a two-phase schedule: the current plan continues until the billing period ends, then the new plan starts automatically with no proration. -When `scheduleAtPeriodEnd` is `true`: -- The subscription plan is **not changed** until the billing period ends — only `stripeScheduleId` is stored so clients can detect the pending change -- No redirect to Stripe Checkout or Billing Portal occurs, the change is applied server-side -- At the end of the billing period, Stripe fires a `customer.subscription.updated` webhook which updates the subscription record automatically -- If a new upgrade or schedule is requested before the period ends, the existing pending schedule is released first + When `scheduleAtPeriodEnd` is `true`: + + * The subscription plan is **not changed** until the billing period ends — only `stripeScheduleId` is stored so clients can detect the pending change + * No redirect to Stripe Checkout or Billing Portal occurs, the change is applied server-side + * At the end of the billing period, Stripe fires a `customer.subscription.updated` webhook which updates the subscription record automatically + * If a new upgrade or schedule is requested before the period ends, the existing pending schedule is released first #### Listing Active Subscriptions To get the user's active subscriptions: - -```ts -type listActiveSubscriptions = { - /** - * Reference id of the subscription to list. - */ - referenceId?: string = '123' - /** - * The type of customer for billing. (Default: "user") - */ - customerType?: "user" | "organization" -} + + ```ts + type listActiveSubscriptions = { + /** + * Reference id of the subscription to list. + */ + referenceId?: string = '123' + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + } -// get the active subscription -const activeSubscription = subscriptions.find( - sub => sub.status === "active" || sub.status === "trialing" -); + // get the active subscription + const activeSubscription = subscriptions.find( + sub => sub.status === "active" || sub.status === "trialing" + ); -// Check subscription limits -const projectLimit = subscriptions?.limits?.projects || 0; -``` + // Check subscription limits + const projectLimit = subscriptions?.limits?.projects || 0; + ``` Make sure to provide `authorizeReference` in your plugin config to authorize the reference ID @@ -399,131 +404,119 @@ stripe({ } }) ``` + #### Canceling a Subscription To cancel a subscription: - -```ts -type cancelSubscription = { - /** - * Reference id of the subscription to cancel. Defaults based on customerType. - */ - referenceId?: string = 'org_123' - /** - * The type of customer for billing. (Default: "user") - */ - customerType?: "user" | "organization" - /** - * The id of the subscription to cancel. - */ - subscriptionId?: string = 'sub_123' - /** - * URL to take customers to when they click on the billing portal's link to return to your website. - */ - returnUrl: string = '/account' -} -``` + + ```ts + type cancelSubscription = { + /** + * Reference id of the subscription to cancel. Defaults based on customerType. + */ + referenceId?: string = 'org_123' + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + /** + * The id of the subscription to cancel. + */ + subscriptionId?: string = 'sub_123' + /** + * URL to take customers to when they click on the billing portal's link to return to your website. + */ + returnUrl: string = '/account' + } + ``` This will redirect the user to the Stripe Billing Portal where they can cancel their subscription. -**Understanding Cancellation States** + **Understanding Cancellation States** -Stripe supports different types of cancellation, and the plugin tracks all of them: + Stripe supports different types of cancellation, and the plugin tracks all of them: -| Field | Description | -|---------------------|----------------------------------------------------------------------------------------------------------------------------------| -| `cancelAtPeriodEnd` | Whether this subscription will (if status=active) or did (if status=canceled) cancel at the end of the current billing period. | -| `cancelAt` | If the subscription is scheduled to be canceled, this is the time at which the cancellation will take effect. | -| `canceledAt` | If the subscription has been canceled, this is the time when it was canceled. | -| `endedAt` | If the subscription has ended, the date the subscription ended. | -| `status` | Changes to "canceled" only after the subscription has actually ended. | + | Field | Description | + | ------------------- | ------------------------------------------------------------------------------------------------------------------------------ | + | `cancelAtPeriodEnd` | Whether this subscription will (if status=active) or did (if status=canceled) cancel at the end of the current billing period. | + | `cancelAt` | If the subscription is scheduled to be canceled, this is the time at which the cancellation will take effect. | + | `canceledAt` | If the subscription has been canceled, this is the time when it was canceled. | + | `endedAt` | If the subscription has ended, the date the subscription ended. | + | `status` | Changes to "canceled" only after the subscription has actually ended. | #### Restoring a Subscription -> **Note:** This only works for subscriptions that are still active but have a pending cancellation or a scheduled plan change. It cannot restore subscriptions that have already ended (`status: "canceled"` with `endedAt` set). +> **Note:** This only works for subscriptions that are still active but have a pending cancellation or a scheduled plan change. It cannot restore subscriptions that have already ended (`status: "canceled"` with `endedAt` set). If a user changes their mind after canceling a subscription or scheduling a plan change, you can restore the subscription: - - -```ts -type restoreSubscription = { - /** - * Reference id of the subscription to restore. Defaults based on customerType. - */ - referenceId?: string = '123' - /** - * The type of customer for billing. (Default: "user") - */ - customerType?: "user" | "organization" - /** - * The id of the subscription to restore. - */ - subscriptionId?: string = 'sub_123' -} -``` + + ```ts + type restoreSubscription = { + /** + * Reference id of the subscription to restore. Defaults based on customerType. + */ + referenceId?: string = '123' + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + /** + * The id of the subscription to restore. + */ + subscriptionId?: string = 'sub_123' + } + ``` -This endpoint handles two cases: + This endpoint handles two cases: -- **Pending cancellation**: Sets `cancelAtPeriodEnd` to `false` and clears `cancelAt` / `canceledAt`, so the subscription continues to renew. -- **Pending plan change** (via `scheduleAtPeriodEnd`): Releases the Stripe subscription schedule and clears `stripeScheduleId`, so the current plan remains unchanged. + * **Pending cancellation**: Sets `cancelAtPeriodEnd` to `false` and clears `cancelAt` / `canceledAt`, so the subscription continues to renew. + * **Pending plan change** (via `scheduleAtPeriodEnd`): Releases the Stripe subscription schedule and clears `stripeScheduleId`, so the current plan remains unchanged. - #### Creating Billing Portal Sessions To create a [Stripe billing portal session](https://docs.stripe.com/api/customer_portal/sessions/create) where customers can manage their subscriptions, update payment methods, and view billing history: - -```ts -type createBillingPortal = { - /** - * The IETF language tag of the locale Customer Portal is displayed in. - * If not provided or set to `auto`, the browser's locale is used. - */ - locale?: string - /** - * Reference id of the subscription. - */ - referenceId?: string = "123" - /** - * The type of customer for billing. (Default: "user") - */ - customerType?: "user" | "organization" - /** - * Return URL to redirect back after exiting the billing portal. - */ - returnUrl?: string - /** - * Disable the automatic redirect to the billing page. - * @default false - */ - disableRedirect?: boolean = false -} -``` + + ```ts + type createBillingPortal = { + /** + * The IETF language tag of the locale Customer Portal is displayed in. + * If not provided or set to `auto`, the browser's locale is used. + */ + locale?: string + /** + * Reference id of the subscription. + */ + referenceId?: string = "123" + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + /** + * Return URL to redirect back after exiting the billing portal. + */ + returnUrl?: string + /** + * Disable the automatic redirect to the billing page. + * @default false + */ + disableRedirect?: boolean = false + } + ``` - -For supported locales, see the [IETF language tag documentation](https://docs.stripe.com/js/appendix/supported_locales). + + + For supported locales, see the [IETF language tag documentation](https://docs.stripe.com/js/appendix/supported_locales). - + This endpoint creates a Stripe billing portal session and returns a URL in the response as `data.url`. You can redirect users to this URL to allow them to manage their subscription, payment methods, and billing history. ### Reference System @@ -589,10 +582,10 @@ subscription: { The plugin automatically handles common webhook events: -- `checkout.session.completed`: Updates subscription status after checkout -- `customer.subscription.created`: Creates a subscription when created outside the checkout flow -- `customer.subscription.updated`: Updates subscription details when changed -- `customer.subscription.deleted`: Marks subscription as canceled +* `checkout.session.completed`: Updates subscription status after checkout +* `customer.subscription.created`: Creates a subscription when created outside the checkout flow +* `customer.subscription.updated`: Updates subscription details when changed +* `customer.subscription.deleted`: Marks subscription as canceled You can also handle custom events: @@ -673,35 +666,34 @@ You can configure trial periods for your plans: The Stripe plugin adds the following tables to your database: - ### User Table Name: `user` ### Organization -Table Name: `organization` (only when `organization.enabled` is `true`) +Table Name: `organization` (only when `organization.enabled` is `true`) ### Subscription @@ -710,109 +702,109 @@ Table Name: `subscription` ### Customizing the Schema @@ -849,55 +841,55 @@ stripe({ ### Subscription Options -| Option | Type | Description | -| -------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| `enabled` | `boolean` | Whether to enable subscription functionality. **Required.** | -| `plans` | `StripePlan[]` or `function` | An array of subscription plans or an async function that returns plans. **Required** if enabled. | -| `requireEmailVerification` | `boolean` | Whether to require email verification before allowing subscription upgrades. Default: `false`. | -| `authorizeReference` | `function` | Authorize reference IDs. Receives `{ user, session, referenceId, action }` and context. | -| `getCheckoutSessionParams` | `function` | Customize Stripe Checkout session parameters. Receives `{ user, session, plan, subscription }`, request, and context. | +| Option | Type | Description | +| -------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `enabled` | `boolean` | Whether to enable subscription functionality. **Required.** | +| `plans` | `StripePlan[]` or `function` | An array of subscription plans or an async function that returns plans. **Required** if enabled. | +| `requireEmailVerification` | `boolean` | Whether to require email verification before allowing subscription upgrades. Default: `false`. | +| `authorizeReference` | `function` | Authorize reference IDs. Receives `{ user, session, referenceId, action }` and context. | +| `getCheckoutSessionParams` | `function` | Customize Stripe Checkout session parameters. Receives `{ user, session, plan, subscription }`, request, and context. | | `onSubscriptionComplete` | `function` | Called when a subscription is created via checkout. Receives `{ event, stripeSubscription, subscription, plan }` and context. | -| `onSubscriptionCreated` | `function` | Called when a subscription is created outside checkout. Receives `{ event, stripeSubscription, subscription, plan }`. | -| `onSubscriptionUpdate` | `function` | Called when a subscription is updated. Receives `{ event, subscription }`. | -| `onSubscriptionCancel` | `function` | Called when a subscription is canceled. Receives `{ event, subscription, stripeSubscription, cancellationDetails }`. | -| `onSubscriptionDeleted` | `function` | Called when a subscription is deleted. Receives `{ event, stripeSubscription, subscription }`. | +| `onSubscriptionCreated` | `function` | Called when a subscription is created outside checkout. Receives `{ event, stripeSubscription, subscription, plan }`. | +| `onSubscriptionUpdate` | `function` | Called when a subscription is updated. Receives `{ event, subscription }`. | +| `onSubscriptionCancel` | `function` | Called when a subscription is canceled. Receives `{ event, subscription, stripeSubscription, cancellationDetails }`. | +| `onSubscriptionDeleted` | `function` | Called when a subscription is deleted. Receives `{ event, stripeSubscription, subscription }`. | #### Plan Configuration -| Option | Type | Description | -| ------------------------- | ---------- | ------------------------------------------------------------ | -| `name` | `string` | The name of the plan. **Required.** | -| `priceId` | `string` | The Stripe price ID. **Required** unless using `lookupKey`. | -| `lookupKey` | `string` | The Stripe price lookup key. Alternative to `priceId`. | -| `annualDiscountPriceId` | `string` | A price ID for annual billing. | -| `annualDiscountLookupKey` | `string` | The Stripe price lookup key for annual billing. | -| `limits` | `object` | Limits for plan (e.g. `{ projects: 10, storage: 5 }`). | -| `group` | `string` | A group name for categorizing plans. | -| `seatPriceId` | `string` | Per-seat billing price ID. Requires the `organization` plugin. | -| `prorationBehavior` | `string` | Proration behavior on subscription updates: `"create_prorations"` (default), `"always_invoice"`, or `"none"`. | -| `lineItems` | `LineItem[]` | Additional line items to include in the checkout session. | -| `freeTrial` | `object` | Trial configuration. See [below](#free-trial-configuration). | +| Option | Type | Description | +| ------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | The name of the plan. **Required.** | +| `priceId` | `string` | The Stripe price ID. **Required** unless using `lookupKey`. | +| `lookupKey` | `string` | The Stripe price lookup key. Alternative to `priceId`. | +| `annualDiscountPriceId` | `string` | A price ID for annual billing. | +| `annualDiscountLookupKey` | `string` | The Stripe price lookup key for annual billing. | +| `limits` | `object` | Limits for plan (e.g. `{ projects: 10, storage: 5 }`). | +| `group` | `string` | A group name for categorizing plans. | +| `seatPriceId` | `string` | Per-seat billing price ID. Requires the `organization` plugin. | +| `prorationBehavior` | `string` | Proration behavior on subscription updates: `"create_prorations"` (default), `"always_invoice"`, or `"none"`. | +| `lineItems` | `LineItem[]` | Additional line items to include in the checkout session. | +| `freeTrial` | `object` | Trial configuration. See [below](#free-trial-configuration). | -Stripe does not support [mixed-interval subscriptions](https://docs.stripe.com/billing/subscriptions/mixed-interval) via Checkout Sessions. All line items in a checkout should use the **same billing interval** (e.g. all monthly or all yearly). If intervals differ, the Stripe API will reject the request. + Stripe does not support [mixed-interval subscriptions](https://docs.stripe.com/billing/subscriptions/mixed-interval) via Checkout Sessions. All line items in a checkout should use the **same billing interval** (e.g. all monthly or all yearly). If intervals differ, the Stripe API will reject the request. #### Free Trial Configuration -| Option | Type | Description | -| ---------------- | ---------- | ---------------------------------------------------------------------------------------- | -| `days` | `number` | Number of trial days. **Required.** | -| `onTrialStart` | `function` | Called when a trial starts. Receives `subscription`. | -| `onTrialEnd` | `function` | Called when a trial ends. Receives `{ subscription }` and context. | -| `onTrialExpired` | `function` | Called when a trial expires without conversion. Receives `subscription` and context. | +| Option | Type | Description | +| ---------------- | ---------- | ------------------------------------------------------------------------------------ | +| `days` | `number` | Number of trial days. **Required.** | +| `onTrialStart` | `function` | Called when a trial starts. Receives `subscription`. | +| `onTrialEnd` | `function` | Called when a trial ends. Receives `{ subscription }` and context. | +| `onTrialExpired` | `function` | Called when a trial expires without conversion. Receives `subscription` and context. | ### Organization Options -| Option | Type | Description | -| ------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------ | -| `enabled` | `boolean` | Enable Organization Customer support. **Required.** | -| `getCustomerCreateParams` | `function` | Customize Stripe customer creation parameters for organizations. Receives `organization` and context. | -| `onCustomerCreate` | `function` | Called after an organization customer is created. Receives `{ stripeCustomer, organization }` and context. | +| Option | Type | Description | +| ------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | +| `enabled` | `boolean` | Enable Organization Customer support. **Required.** | +| `getCustomerCreateParams` | `function` | Customize Stripe customer creation parameters for organizations. Receives `organization` and context. | +| `onCustomerCreate` | `function` | Called after an organization customer is created. Receives `{ stripeCustomer, organization }` and context. | ## Advanced Usage @@ -906,11 +898,11 @@ Stripe does not support [mixed-interval subscriptions](https://docs.stripe.com/b The Stripe plugin integrates with the [organization plugin](/docs/plugins/organization) to enable organizations as Stripe Customers. Instead of individual users, organizations become the billing entity for subscriptions. This is useful for B2B services where billing is tied to the organization rather than individual user. -**When Organization Customer is enabled:** + **When Organization Customer is enabled:** -- A Stripe Customer is automatically created when an organization first subscribes -- Organization name changes are synced to the Stripe Customer -- Organizations with active subscriptions cannot be deleted + * A Stripe Customer is automatically created when an organization first subscribes + * Organization name changes are synced to the Stripe Customer + * Organizations with active subscriptions cannot be deleted #### Enabling Organization Customer @@ -1052,12 +1044,14 @@ subscription: { The Stripe plugin automatically prevents users from getting multiple free trials. Once a user has used a trial period (regardless of which plan), they will not be eligible for additional trials on any plan. **How it works:** -- The system tracks trial usage across all plans for each user -- When a user subscribes to a plan with a trial, the system checks their subscription history -- If the user has ever had a trial (indicated by `trialStart`/`trialEnd` fields or `trialing` status), no new trial will be offered -- This prevents abuse where users cancel subscriptions and resubscribe to get multiple free trials + +* The system tracks trial usage across all plans for each user +* When a user subscribes to a plan with a trial, the system checks their subscription history +* If the user has ever had a trial (indicated by `trialStart`/`trialEnd` fields or `trialing` status), no new trial will be offered +* This prevents abuse where users cancel subscriptions and resubscribe to get multiple free trials **Example scenario:** + 1. User subscribes to "Starter" plan with 7-day trial 2. User cancels the subscription after the trial 3. User tries to subscribe to "Premium" plan - no trial will be offered diff --git a/packages/stripe/package.json b/packages/stripe/package.json index 976424ffe4..b3ef1f69ad 100644 --- a/packages/stripe/package.json +++ b/packages/stripe/package.json @@ -67,13 +67,13 @@ "@better-auth/core": "workspace:*", "better-auth": "workspace:*", "better-call": "catalog:", - "stripe": "^20.4.0", + "stripe": "^22.0.1", "tsdown": "catalog:" }, "peerDependencies": { "@better-auth/core": "workspace:^", "better-auth": "workspace:^", "better-call": "catalog:", - "stripe": "^18 || ^19 || ^20" + "stripe": "^18 || ^19 || ^20 || ^21 || ^22" } } diff --git a/packages/stripe/src/routes.ts b/packages/stripe/src/routes.ts index d7c0db48d8..e71eb5917d 100644 --- a/packages/stripe/src/routes.ts +++ b/packages/stripe/src/routes.ts @@ -18,6 +18,7 @@ import { import { customerMetadata, subscriptionMetadata } from "./metadata"; import { referenceMiddleware, stripeSessionMiddleware } from "./middleware"; import type { + CheckoutSessionLocale, CustomerType, StripeCtxSession, StripeOptions, @@ -167,7 +168,7 @@ const upgradeSubscriptionBodySchema = z.object({ * If not provided or set to `auto`, the browser's locale is used. */ locale: z - .custom((localization) => { + .custom((localization) => { return typeof localization === "string"; }) .meta({ @@ -1124,7 +1125,7 @@ export const upgradeSubscription = (options: StripeOptions) => { }); }); return ctx.json({ - ...checkoutSession, + url: checkoutSession.url, redirect: !ctx.body.disableRedirect, }); }, @@ -1804,7 +1805,7 @@ const createBillingPortalBodySchema = z.object({ * If not provided or set to `auto`, the browser's locale is used. */ locale: z - .custom((localization) => { + .custom((localization) => { return typeof localization === "string"; }) .meta({ diff --git a/packages/stripe/src/types.ts b/packages/stripe/src/types.ts index 562e543a08..45e777cc8a 100644 --- a/packages/stripe/src/types.ts +++ b/packages/stripe/src/types.ts @@ -31,6 +31,10 @@ export type StripeCtxSession = { user: User & WithStripeCustomerId; }; +export type CheckoutSessionLocale = NonNullable< + Stripe.Checkout.SessionCreateParams["locale"] +>; + export type StripePlan = { /** * Monthly price id @@ -103,7 +107,9 @@ export type StripePlan = { * * @see https://docs.stripe.com/billing/subscriptions/mixed-interval#limitations */ - lineItems?: Stripe.Checkout.SessionCreateParams.LineItem[] | undefined; + lineItems?: + | NonNullable + | undefined; /** * Free trial days */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1531294cfb..1441a05ef8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,7 +53,7 @@ catalogs: version: 4.0.18 vitest: specifier: ^4.0.18 - version: 4.0.18 + version: 4.1.3 overrides: axios: <1.14.0 @@ -95,10 +95,10 @@ importers: version: 25.3.2 '@vitest/coverage-istanbul': specifier: catalog:vitest - version: 4.0.18(vitest@4.0.18) + version: 4.0.18(vitest@4.1.3) '@vitest/ui': specifier: catalog:vitest - version: 4.0.18(vitest@4.0.18) + version: 4.0.18(vitest@4.1.3) cspell: specifier: ^9.7.0 version: 9.7.0 @@ -122,7 +122,7 @@ importers: version: 5.9.3 vitest: specifier: catalog:vitest - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) docs: dependencies: @@ -693,7 +693,7 @@ importers: devDependencies: '@cloudflare/vitest-pool-workers': specifier: ^0.12.18 - version: 0.12.18(@cloudflare/workers-types@4.20260226.1)(@vitest/runner@4.1.3)(@vitest/snapshot@4.1.3)(vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.0.18))(@vitest/ui@4.0.18)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 0.12.18(@cloudflare/workers-types@4.20260226.1)(@vitest/runner@4.1.3)(@vitest/snapshot@4.1.3)(vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@cloudflare/workers-types': specifier: ^4.20260226.1 version: 4.20260226.1 @@ -1367,7 +1367,7 @@ importers: version: 5.9.3 vitest: specifier: catalog:vitest - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/scim: dependencies: @@ -1465,8 +1465,8 @@ importers: specifier: 'catalog:' version: 1.3.5(zod@4.3.6) stripe: - specifier: ^20.4.0 - version: 20.4.0(@types/node@25.5.2) + specifier: ^22.0.1 + version: 22.0.1(@types/node@25.5.2) tsdown: specifier: 'catalog:' version: 0.21.1(@arethetypeswrong/core@0.18.2)(oxc-resolver@11.19.0)(publint@0.3.17)(synckit@0.11.11)(typescript@5.9.3) @@ -1503,7 +1503,7 @@ importers: version: 0.21.1(@arethetypeswrong/core@0.18.2)(oxc-resolver@11.19.0)(publint@0.3.17)(synckit@0.11.11)(typescript@5.9.3) vitest: specifier: catalog:vitest - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) test: devDependencies: @@ -1524,7 +1524,7 @@ importers: version: 7.24.1 vitest: specifier: catalog:vitest - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages: @@ -13915,6 +13915,15 @@ packages: '@types/node': optional: true + stripe@22.0.1: + resolution: {integrity: sha512-Yw764pZ6s8Xu4CtUZdD5uWOkw6gc9xzO9OKylCuj1gMhMDLbyGbDtaPNNSFE4mB6njYSHESYIVbF1iIzUfAl2g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + strnum@1.1.2: resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} @@ -15462,7 +15471,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 - tinyexec: 1.0.4 + tinyexec: 1.1.1 '@arethetypeswrong/cli@0.18.2': dependencies: @@ -16544,14 +16553,14 @@ snapshots: optionalDependencies: workerd: 1.20260305.0 - '@cloudflare/vitest-pool-workers@0.12.18(@cloudflare/workers-types@4.20260226.1)(@vitest/runner@4.1.3)(@vitest/snapshot@4.1.3)(vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.0.18))(@vitest/ui@4.0.18)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': + '@cloudflare/vitest-pool-workers@0.12.18(@cloudflare/workers-types@4.20260226.1)(@vitest/runner@4.1.3)(@vitest/snapshot@4.1.3)(vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: '@vitest/runner': 4.1.3 '@vitest/snapshot': 4.1.3 cjs-module-lexer: 1.4.3 esbuild: 0.27.3 miniflare: 4.20260305.0 - vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.0.18))(@vitest/ui@4.0.18)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) wrangler: 4.69.0(@cloudflare/workers-types@4.20260226.1) transitivePeerDependencies: - '@cloudflare/workers-types' @@ -21063,7 +21072,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 - postcss: 8.5.6 + postcss: 8.5.9 tailwindcss: 4.2.1 '@tanstack/directive-functions-plugin@1.121.21(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': @@ -21376,7 +21385,7 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 @@ -21388,7 +21397,7 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': @@ -21853,7 +21862,7 @@ snapshots: recast: 0.23.11 vinxi: 0.5.11(a5ab8d302db831b8b20037f6725d6951) - '@vitest/coverage-istanbul@4.0.18(vitest@4.0.18)': + '@vitest/coverage-istanbul@4.0.18(vitest@4.1.3)': dependencies: '@istanbuljs/schema': 0.1.3 '@jridgewell/gen-mapping': 0.3.13 @@ -21864,8 +21873,8 @@ snapshots: istanbul-reports: 3.2.0 magicast: 0.5.2 obug: 2.1.1 - tinyrainbow: 3.0.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + tinyrainbow: 3.1.0 + vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -21876,7 +21885,7 @@ snapshots: '@vitest/spy': 4.0.18 '@vitest/utils': 4.0.18 chai: 6.2.2 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 '@vitest/expect@4.1.3': dependencies: @@ -21887,15 +21896,6 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.0.18(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.12.10(@types/node@25.3.2)(typescript@5.9.3) - vite: 7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@4.0.18(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.0.18 @@ -21905,6 +21905,15 @@ snapshots: msw: 2.12.10(@types/node@25.5.2)(typescript@5.9.3) vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@vitest/mocker@4.1.3(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.1.3 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.12.10(@types/node@25.3.2)(typescript@5.9.3) + vite: 7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@vitest/mocker@4.1.3(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.3 @@ -21916,7 +21925,7 @@ snapshots: '@vitest/pretty-format@4.0.18': dependencies: - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 '@vitest/pretty-format@4.1.3': dependencies: @@ -21956,14 +21965,26 @@ snapshots: flatted: 3.3.3 pathe: 2.0.3 sirv: 3.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + optional: true + + '@vitest/ui@4.0.18(vitest@4.1.3)': + dependencies: + '@vitest/utils': 4.0.18 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils@4.0.18': dependencies: '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 '@vitest/utils@4.1.3': dependencies: @@ -21976,7 +21997,7 @@ snapshots: '@vue/compiler-core@3.5.29': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@vue/shared': 3.5.29 entities: 7.0.1 estree-walker: 2.0.2 @@ -22324,7 +22345,7 @@ snapshots: babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 transitivePeerDependencies: @@ -25595,7 +25616,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -26230,7 +26251,7 @@ snapshots: magicast@0.2.11: dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 recast: 0.23.11 @@ -26243,7 +26264,7 @@ snapshots: magicast@0.5.2: dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 source-map-js: 1.2.1 @@ -26922,7 +26943,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 metro: 0.83.4 @@ -27011,7 +27032,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -27938,7 +27959,7 @@ snapshots: dependencies: citty: 0.2.1 pathe: 2.0.3 - tinyexec: 1.0.4 + tinyexec: 1.1.1 oauth2-mock-server@8.2.2: dependencies: @@ -30190,6 +30211,10 @@ snapshots: optionalDependencies: '@types/node': 25.5.2 + stripe@22.0.1(@types/node@25.5.2): + optionalDependencies: + '@types/node': 25.5.2 + strnum@1.1.2: optional: true @@ -30533,12 +30558,12 @@ snapshots: hookable: 6.0.1 import-without-cache: 0.2.5 obug: 2.1.1 - picomatch: 4.0.3 + picomatch: 4.0.4 rolldown: 1.0.0-rc.8 rolldown-plugin-dts: 0.22.4(oxc-resolver@11.19.0)(rolldown@1.0.0-rc.8)(typescript@5.9.3) semver: 7.7.4 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 tree-kill: 1.2.2 unconfig-core: 7.5.0 unrun: 0.2.31(synckit@0.11.11) @@ -31220,46 +31245,6 @@ snapshots: optionalDependencies: vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): - dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.0 - '@types/node': 25.3.2 - '@vitest/ui': 4.0.18(vitest@4.0.18) - happy-dom: 20.8.9 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.0.18)(happy-dom@20.8.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: '@vitest/expect': 4.0.18 @@ -31300,7 +31285,38 @@ snapshots: - tsx - yaml - vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.0.18))(@vitest/ui@4.0.18)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + '@vitest/expect': 4.1.3 + '@vitest/mocker': 4.1.3(msw@2.12.10(@types/node@25.3.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.3 + '@vitest/runner': 4.1.3 + '@vitest/snapshot': 4.1.3 + '@vitest/spy': 4.1.3 + '@vitest/utils': 4.1.3 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 7.3.2(@types/node@25.3.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 25.3.2 + '@vitest/coverage-istanbul': 4.0.18(vitest@4.1.3) + '@vitest/ui': 4.0.18(vitest@4.1.3) + happy-dom: 20.8.9 + transitivePeerDependencies: + - msw + + vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/coverage-istanbul@4.0.18(vitest@4.1.3))(@vitest/ui@4.0.18(vitest@4.1.3))(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.3 '@vitest/mocker': 4.1.3(msw@2.12.10(@types/node@25.5.2)(typescript@5.9.3))(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.32.0)(sass@1.97.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) @@ -31325,8 +31341,8 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 25.5.2 - '@vitest/coverage-istanbul': 4.0.18(vitest@4.0.18) - '@vitest/ui': 4.0.18(vitest@4.0.18) + '@vitest/coverage-istanbul': 4.0.18(vitest@4.1.3) + '@vitest/ui': 4.0.18(vitest@4.1.3) happy-dom: 20.8.9 transitivePeerDependencies: - msw