@fantastic.dev/better-stripe 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/MIGRATION.md +14 -0
  3. package/README.md +10 -1
  4. package/dist/client/apiVersion.d.ts +6 -0
  5. package/dist/client/apiVersion.d.ts.map +1 -0
  6. package/dist/client/apiVersion.js +13 -0
  7. package/dist/client/apiVersion.js.map +1 -0
  8. package/dist/client/index.d.ts +8 -12
  9. package/dist/client/index.d.ts.map +1 -1
  10. package/dist/client/index.js +18 -4
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/client/previewRoutes.d.ts +9 -0
  13. package/dist/client/previewRoutes.d.ts.map +1 -0
  14. package/dist/client/previewRoutes.js +15 -0
  15. package/dist/client/previewRoutes.js.map +1 -0
  16. package/dist/client/types/options.d.ts +3 -0
  17. package/dist/client/types/options.d.ts.map +1 -1
  18. package/dist/client/utils/webhookEndpoints.d.ts +12 -8
  19. package/dist/client/utils/webhookEndpoints.d.ts.map +1 -1
  20. package/dist/client/utils/webhookEndpoints.js +41 -2
  21. package/dist/client/utils/webhookEndpoints.js.map +1 -1
  22. package/dist/client/webhooks/v2.d.ts.map +1 -1
  23. package/dist/client/webhooks/v2.js +10 -7
  24. package/dist/client/webhooks/v2.js.map +1 -1
  25. package/dist/component/lib/stripeFactory.d.ts +3 -2
  26. package/dist/component/lib/stripeFactory.d.ts.map +1 -1
  27. package/dist/component/lib/stripeFactory.js +6 -5
  28. package/dist/component/lib/stripeFactory.js.map +1 -1
  29. package/docs/accounts-v2-preview.md +132 -0
  30. package/package.json +2 -1
  31. package/src/client/apiVersion.ts +20 -0
  32. package/src/client/customer-preview-thin.test.ts +41 -0
  33. package/src/client/customer-preview-webhooks.test.ts +150 -0
  34. package/src/client/customer-preview.test.ts +185 -0
  35. package/src/client/index.ts +31 -16
  36. package/src/client/previewRoutes.ts +26 -0
  37. package/src/client/stripe-version.test.ts +10 -4
  38. package/src/client/types/options.ts +3 -0
  39. package/src/client/utils/webhookEndpoints.ts +70 -15
  40. package/src/client/webhooks/v2.ts +14 -10
  41. package/src/component/lib/stripeFactory.ts +13 -5
@@ -0,0 +1,185 @@
1
+ import Stripe from "stripe";
2
+ import { componentsGeneric } from "convex/server";
3
+ import { expect, it, vi } from "vitest";
4
+ import { BetterStripe, type BetterStripeOptions } from "./index.js";
5
+ import { getStripeClient, type Component, type RunCtx } from "./helpers.js";
6
+
7
+ const preview = "2026-09-30.preview";
8
+ const component = componentsGeneric().betterStripe as unknown as Component;
9
+ const ctx = {
10
+ runQuery: vi.fn().mockImplementation(async (_ref, args) =>
11
+ args?.stripeAccountId
12
+ ? {
13
+ _id: "row",
14
+ userId: "buyer",
15
+ stripeAccountId: "acct_buyer",
16
+ appliedConfigurations: [],
17
+ }
18
+ : null,
19
+ ),
20
+ runMutation: vi.fn().mockResolvedValue("row"),
21
+ } as unknown as RunCtx;
22
+ function fixture(version: string | undefined) {
23
+ const key = `sk_test_preview_transport_${crypto.randomUUID()}`;
24
+ const bs = new BetterStripe(component, {
25
+ STRIPE_SECRET_KEY: key,
26
+ ...(version === undefined ? {} : { stripeApiVersion: version }),
27
+ } as BetterStripeOptions);
28
+ const calls: {
29
+ path: string;
30
+ version: string;
31
+ idempotency: string | undefined;
32
+ body: string;
33
+ }[] = [];
34
+ for (const selected of new Set([version, "2026-09-30.endive"])) {
35
+ const sdk = getStripeClient(key, selected);
36
+ (
37
+ sdk as unknown as { _setApiField: (name: string, value: unknown) => void }
38
+ )._setApiField("httpClient", {
39
+ getClientName: () => "preview-contract-test",
40
+ makeRequest: async (
41
+ ...[, , path, , headers, body]: Parameters<
42
+ ReturnType<typeof Stripe.createNodeHttpClient>["makeRequest"]
43
+ >
44
+ ) => {
45
+ calls.push({
46
+ path,
47
+ version: String(headers["Stripe-Version"]),
48
+ idempotency: headers["Idempotency-Key"] as string | undefined,
49
+ body: String(body),
50
+ });
51
+ const data = path.includes("/checkout/sessions")
52
+ ? {
53
+ id: "cs_preview",
54
+ object: "checkout.session",
55
+ client_secret: "test_secret",
56
+ status: "open",
57
+ }
58
+ : path.includes("/v2/core/accounts")
59
+ ? {
60
+ id: "acct_buyer",
61
+ object: "v2.core.account",
62
+ applied_configurations: ["customer"],
63
+ configuration: { customer: {} },
64
+ metadata: { userId: "buyer" },
65
+ }
66
+ : path.includes("/prices/")
67
+ ? {
68
+ id: "price_preview",
69
+ active: true,
70
+ unit_amount: 1000,
71
+ currency: "usd",
72
+ type: "one_time",
73
+ product: "prod_preview",
74
+ }
75
+ : path.includes("/products/")
76
+ ? { id: "prod_preview", active: true }
77
+ : path.includes("/billing_portal/sessions")
78
+ ? {
79
+ id: "bps_preview",
80
+ url: "https://billing.stripe.com/test",
81
+ }
82
+ : path.includes("/refunds")
83
+ ? { id: "re_preview", status: "succeeded" }
84
+ : path.includes("/payment_intents/")
85
+ ? {
86
+ id: "pi_preview",
87
+ latest_charge: {
88
+ id: "ch_preview",
89
+ amount: 1000,
90
+ currency: "usd",
91
+ },
92
+ }
93
+ : path.includes("/charges/")
94
+ ? { id: "ch_preview", amount: 1000, currency: "usd" }
95
+ : path.includes("/coupons/")
96
+ ? { id: "coupon_preview", valid: true }
97
+ : {
98
+ id: "sub_preview",
99
+ status: "active",
100
+ customer_account: "acct_buyer",
101
+ items: { data: [] },
102
+ metadata: {},
103
+ };
104
+ return {
105
+ getStatusCode: () => 200,
106
+ getHeaders: () => ({}),
107
+ getRawResponse: () => ({}),
108
+ toJSON: async () => data,
109
+ toStream: () => {
110
+ throw new Error("Unexpected stream");
111
+ },
112
+ };
113
+ },
114
+ });
115
+ }
116
+ return { bs, calls };
117
+ }
118
+ it.each([undefined, preview])(
119
+ "uses one selected version for account, Checkout, billing, refunds and authoritative reads (%s)",
120
+ async (version) => {
121
+ const { bs, calls } = fixture(version);
122
+ await bs.ensureCustomerAccount(ctx, {
123
+ userId: "buyer",
124
+ email: "buyer@example.com",
125
+ idempotencyKey: "account:buyer",
126
+ });
127
+ await bs.createCheckoutSession(ctx, {
128
+ userId: "buyer",
129
+ accountId: "acct_buyer",
130
+ stripePriceId: "price_preview",
131
+ mode: "payment",
132
+ returnUrl: "https://example.com/receipt",
133
+ idempotencyKey: "order:one",
134
+ });
135
+ await bs.getCheckoutSessionFromStripe(ctx, {
136
+ stripeSessionId: "cs_preview",
137
+ });
138
+ await bs.getSubscriptionFromStripe(ctx, {
139
+ stripeSubscriptionId: "sub_preview",
140
+ });
141
+ await bs.getCouponFromStripe(ctx, { stripeCouponId: "coupon_preview" });
142
+ await bs.cancelSubscription(ctx, { stripeSubscriptionId: "sub_preview" });
143
+ await bs.reactivateSubscription(ctx, {
144
+ stripeSubscriptionId: "sub_preview",
145
+ });
146
+ await bs.createBillingPortalSession(ctx, {
147
+ stripeAccountId: "acct_buyer",
148
+ returnUrl: "https://example.com/billing",
149
+ });
150
+ await bs.createRefund(ctx, {
151
+ stripeChargeId: "ch_preview",
152
+ idempotencyKey: "refund:one",
153
+ });
154
+ expect(calls.length).toBeGreaterThanOrEqual(8);
155
+ expect(new Set(calls.map((c) => c.version))).toEqual(
156
+ new Set([version ?? "2026-09-30.endive"]),
157
+ );
158
+ expect(calls.find((c) => c.path === "/v2/core/accounts")?.idempotency).toBe(
159
+ "account:buyer",
160
+ );
161
+ expect(
162
+ calls.find((c) => c.path === "/v1/checkout/sessions" && c.idempotency)
163
+ ?.idempotency,
164
+ ).toBe("order:one");
165
+ expect(
166
+ calls.find((c) => c.path === "/v1/checkout/sessions" && c.idempotency)
167
+ ?.body,
168
+ ).toContain("customer_account=acct_buyer");
169
+ expect(
170
+ calls.find((c) => c.path === "/v2/core/accounts")?.body,
171
+ ).not.toContain("merchant");
172
+ expect(bs.stripeApiVersion).toBe(version ?? "2026-09-30.endive");
173
+ },
174
+ );
175
+ it.each(["", "2020-08-27", "2026-09-30.preview; unsafe", "not-a-version"])(
176
+ "rejects unsupported client versions clearly (%s)",
177
+ (version) => {
178
+ expect(
179
+ () =>
180
+ new BetterStripe(component, {
181
+ stripeApiVersion: version,
182
+ } as unknown as BetterStripeOptions),
183
+ ).toThrow("Unsupported Stripe API version");
184
+ },
185
+ );
@@ -1,3 +1,13 @@
1
+ import type { HttpRouter } from "convex/server";
2
+ import { registerClientRoutes } from "./previewRoutes.js";
3
+ import {
4
+ resolveStripeApiVersion,
5
+ type SupportedStripeApiVersion,
6
+ } from "./apiVersion.js";
7
+ export {
8
+ STRIPE_CUSTOMER_PREVIEW_API_VERSION,
9
+ type SupportedStripeApiVersion,
10
+ } from "./apiVersion.js";
1
11
  export type {
2
12
  CheckoutSessionReadOptions,
3
13
  SubscriptionReadOptions,
@@ -184,6 +194,7 @@ function clonePlatformFee(config: PlatformFeeConfig): PlatformFeeConfig {
184
194
 
185
195
  export class BetterStripe {
186
196
  public component: Component;
197
+ readonly stripeApiVersion: SupportedStripeApiVersion;
187
198
  private _apiKey?: string;
188
199
  private _triggers?: SyncTriggers;
189
200
  private _hooks?: AsyncHooks;
@@ -192,6 +203,7 @@ export class BetterStripe {
192
203
 
193
204
  constructor(component: BetterStripeComponent, options?: BetterStripeOptions) {
194
205
  this.component = component;
206
+ this.stripeApiVersion = resolveStripeApiVersion(options?.stripeApiVersion);
195
207
  this._apiKey = options?.STRIPE_SECRET_KEY;
196
208
  this._triggers = options?.triggers;
197
209
  this._hooks = options?.hooks;
@@ -240,8 +252,15 @@ export class BetterStripe {
240
252
  return key;
241
253
  }
242
254
 
243
- private stripe(apiVersion?: string): Stripe {
244
- return getStripeClient(this.apiKey, apiVersion);
255
+ registerRoutes(
256
+ http: HttpRouter,
257
+ config?: Parameters<typeof registerClientRoutes>[2],
258
+ ) {
259
+ return registerClientRoutes(this, http, config);
260
+ }
261
+
262
+ private stripe(): Stripe {
263
+ return getStripeClient(this.apiKey, this.stripeApiVersion);
245
264
  }
246
265
 
247
266
  // Static defaults (preserved for backwards compatibility)
@@ -1427,13 +1446,12 @@ export class BetterStripe {
1427
1446
 
1428
1447
  async createWebhookEndpoint(
1429
1448
  ctx: RunCtx,
1430
- opts: {
1431
- url: string;
1432
- description?: string;
1433
- enabledEvents?: string[];
1434
- },
1449
+ opts: webhookEndpointsImpl.WebhookEndpointOptions,
1435
1450
  ): Promise<{ id: string; secret: string; url: string }> {
1436
- return webhookEndpointsImpl.createWebhookEndpoint(this.stripe(), ctx, opts);
1451
+ return webhookEndpointsImpl.createWebhookEndpoint(this.stripe(), ctx, {
1452
+ ...opts,
1453
+ apiVersion: this.stripeApiVersion,
1454
+ });
1437
1455
  }
1438
1456
 
1439
1457
  async listEventDestinations(ctx: RunCtx, opts?: { limit?: number }) {
@@ -1442,13 +1460,7 @@ export class BetterStripe {
1442
1460
 
1443
1461
  async setupEventDestination(
1444
1462
  ctx: RunCtx,
1445
- opts: {
1446
- url: string;
1447
- name?: string;
1448
- description?: string;
1449
- enabledEvents?: string[];
1450
- eventPayload?: "snapshot" | "thin";
1451
- },
1463
+ opts: webhookEndpointsImpl.EventDestinationOptions,
1452
1464
  ): Promise<{
1453
1465
  id: string;
1454
1466
  secret: string;
@@ -1456,7 +1468,10 @@ export class BetterStripe {
1456
1468
  enabledEvents: string[];
1457
1469
  created: boolean;
1458
1470
  }> {
1459
- return webhookEndpointsImpl.setupEventDestination(this.stripe(), ctx, opts);
1471
+ return webhookEndpointsImpl.setupEventDestination(this.stripe(), ctx, {
1472
+ ...opts,
1473
+ apiVersion: this.stripeApiVersion,
1474
+ });
1460
1475
  }
1461
1476
 
1462
1477
  // WEBHOOK EVENT QUERIES
@@ -0,0 +1,26 @@
1
+ import type { HttpRouter } from "convex/server";
2
+ import type { Component } from "./helpers.js";
3
+ import type { RegisterRoutesConfig } from "./types.js";
4
+ import { registerRoutes } from "./webhooks/index.js";
5
+ import { throwStripeError } from "./errors.js";
6
+ export function registerClientRoutes(
7
+ client: { component: Component; apiKey: string; stripeApiVersion: string },
8
+ http: HttpRouter,
9
+ config?: RegisterRoutesConfig,
10
+ ) {
11
+ if (
12
+ (config?.stripeApiVersion !== undefined &&
13
+ config.stripeApiVersion !== client.stripeApiVersion) ||
14
+ (config?.stripeSecretKey !== undefined &&
15
+ config.stripeSecretKey !== client.apiKey)
16
+ )
17
+ throwStripeError(
18
+ "INVALID_CONFIGURATION",
19
+ "Webhook API version and secret key must match the BetterStripe client.",
20
+ );
21
+ registerRoutes(http, client.component, {
22
+ ...config,
23
+ stripeApiVersion: client.stripeApiVersion,
24
+ stripeSecretKey: client.apiKey,
25
+ });
26
+ }
@@ -11,10 +11,16 @@ describe("Stripe version pin", () => {
11
11
  expect(Stripe.API_VERSION).toBe(STRIPE_API_VERSION);
12
12
  });
13
13
 
14
- it.each([getStripeClient, getStripe])(
14
+ it.each(
15
+ [getStripeClient, getStripe].flatMap((factory) =>
16
+ [undefined, "2026-09-30.preview" as const].map(
17
+ (version) => [factory, version] as const,
18
+ ),
19
+ ),
20
+ )(
15
21
  "pins outgoing API requests from each factory",
16
- async (factory) => {
17
- const stripe = factory("sk_test_version_transport_only");
22
+ async (factory, selectedVersion) => {
23
+ const stripe = factory("sk_test_version_transport_only", selectedVersion);
18
24
  let version: string | undefined;
19
25
  // Factories deliberately expose no transport override. Replace only the
20
26
  // SDK's transport slot, leaving its request serializer/headers untouched.
@@ -41,7 +47,7 @@ describe("Stripe version pin", () => {
41
47
  },
42
48
  });
43
49
  await stripe.products.retrieve("prod_local");
44
- expect(version).toBe(STRIPE_API_VERSION);
50
+ expect(version).toBe(selectedVersion ?? STRIPE_API_VERSION);
45
51
  },
46
52
  );
47
53
  });
@@ -1,3 +1,4 @@
1
+ import type { SupportedStripeApiVersion } from "../apiVersion.js";
1
2
  import type {
2
3
  StripeEventHandler,
3
4
  StripeEventHandlers,
@@ -51,6 +52,8 @@ export type FeeOverride = PlatformFeeConfig;
51
52
  // ---------------------------------------------------------------------------
52
53
 
53
54
  export type BetterStripeOptions = {
55
+ /** Defaults to GA. Non-Connect Accounts v2 customers require explicit preview opt-in. */
56
+ stripeApiVersion?: SupportedStripeApiVersion;
54
57
  STRIPE_SECRET_KEY?: string;
55
58
  /** Sync triggers — run in the same transaction as component DB writes */
56
59
  triggers?: SyncTriggers;
@@ -1,3 +1,5 @@
1
+ import { STRIPE_CUSTOMER_PREVIEW_API_VERSION } from "../apiVersion.js";
2
+ import { throwStripeError } from "../errors.js";
1
3
  import type Stripe from "stripe";
2
4
 
3
5
  import type { RunCtx } from "../helpers.js";
@@ -73,6 +75,13 @@ export const BETTER_STRIPE_V2_WEBHOOK_EVENTS = [
73
75
  "v2.core.account_link.returned",
74
76
  ] as const;
75
77
 
78
+ /** Minimal customer lifecycle subscription for non-Connect preview merchants. */
79
+ export const BETTER_STRIPE_CUSTOMER_ACCOUNT_EVENTS = [
80
+ "v2.core.account.created",
81
+ "v2.core.account[configuration.customer].updated",
82
+ "v2.core.account[identity].updated",
83
+ ] as const;
84
+
76
85
  export type BetterStripeV2WebhookEvent =
77
86
  (typeof BETTER_STRIPE_V2_WEBHOOK_EVENTS)[number];
78
87
 
@@ -103,14 +112,19 @@ export async function listWebhookEndpoints(
103
112
  }));
104
113
  }
105
114
 
115
+ export type WebhookEndpointOptions = {
116
+ url: string;
117
+ description?: string;
118
+ enabledEvents?: string[];
119
+ };
120
+ export type EventDestinationOptions = WebhookEndpointOptions & {
121
+ name?: string;
122
+ eventPayload?: "snapshot" | "thin";
123
+ };
106
124
  export async function createWebhookEndpoint(
107
125
  stripe: Stripe,
108
126
  _ctx: RunCtx,
109
- opts: {
110
- url: string;
111
- description?: string;
112
- enabledEvents?: string[];
113
- },
127
+ opts: WebhookEndpointOptions & { apiVersion?: string },
114
128
  ): Promise<{ id: string; secret: string; url: string }> {
115
129
  // Remove existing endpoint with same URL (secret is only available on create)
116
130
  const existing = await stripe.webhookEndpoints.list({ limit: 100 });
@@ -121,6 +135,12 @@ export async function createWebhookEndpoint(
121
135
 
122
136
  const endpoint = await stripe.webhookEndpoints.create({
123
137
  url: opts.url,
138
+ ...(opts.apiVersion
139
+ ? {
140
+ api_version:
141
+ opts.apiVersion as Stripe.WebhookEndpointCreateParams.ApiVersion,
142
+ }
143
+ : {}),
124
144
  enabled_events: (opts.enabledEvents ??
125
145
  BETTER_STRIPE_WEBHOOK_EVENTS) as Stripe.WebhookEndpointCreateParams.EnabledEvent[],
126
146
  description:
@@ -189,14 +209,7 @@ export async function listEventDestinations(
189
209
  export async function setupEventDestination(
190
210
  stripe: Stripe,
191
211
  _ctx: RunCtx,
192
- opts: {
193
- url: string;
194
- name?: string;
195
- description?: string;
196
- enabledEvents?: string[];
197
- /** 'snapshot' for V1 events, 'thin' for V2 events. Defaults to 'thin'. */
198
- eventPayload?: "snapshot" | "thin";
199
- },
212
+ opts: EventDestinationOptions & { apiVersion?: string },
200
213
  ): Promise<{
201
214
  id: string;
202
215
  secret: string;
@@ -208,7 +221,9 @@ export async function setupEventDestination(
208
221
  const events =
209
222
  opts.enabledEvents ??
210
223
  (payloadType === "thin"
211
- ? [...BETTER_STRIPE_V2_WEBHOOK_EVENTS]
224
+ ? opts.apiVersion === STRIPE_CUSTOMER_PREVIEW_API_VERSION
225
+ ? [...BETTER_STRIPE_CUSTOMER_ACCOUNT_EVENTS]
226
+ : [...BETTER_STRIPE_V2_WEBHOOK_EVENTS]
212
227
  : [...BETTER_STRIPE_WEBHOOK_EVENTS]);
213
228
  const name =
214
229
  opts.name ??
@@ -228,6 +243,36 @@ export async function setupEventDestination(
228
243
  d.webhook_endpoint?.url === opts.url && d.event_payload === payloadType,
229
244
  );
230
245
 
246
+ if (
247
+ match &&
248
+ payloadType === "snapshot" &&
249
+ opts.apiVersion &&
250
+ match.snapshot_api_version !== opts.apiVersion
251
+ )
252
+ throwStripeError(
253
+ "INVALID_CONFIGURATION",
254
+ "Existing snapshot destination API version differs. Create a replacement destination with the selected version and configure its signing secret before retiring the old destination.",
255
+ );
256
+
257
+ if (match && opts.apiVersion === STRIPE_CUSTOMER_PREVIEW_API_VERSION) {
258
+ const scopes = new Set(
259
+ (match.events_from ?? []).map((scope) =>
260
+ scope === "self"
261
+ ? "@self"
262
+ : scope === "other_accounts"
263
+ ? "@accounts"
264
+ : scope,
265
+ ),
266
+ );
267
+ const required =
268
+ payloadType === "thin" ? ["@self", "@accounts"] : ["@self"];
269
+ if (required.some((scope) => !scopes.has(scope)))
270
+ throwStripeError(
271
+ "INVALID_CONFIGURATION",
272
+ "Existing destination event scope differs. Create a replacement with the customer preview scopes before retiring the old destination.",
273
+ );
274
+ }
275
+
231
276
  if (match) {
232
277
  const updated = await stripe.v2.core.eventDestinations.update(match.id, {
233
278
  name,
@@ -256,7 +301,17 @@ export async function setupEventDestination(
256
301
  type: "webhook_endpoint",
257
302
  event_payload: payloadType,
258
303
  enabled_events: events,
259
- events_from: payloadType === "thin" ? ["self", "other_accounts"] : ["self"],
304
+ ...(payloadType === "snapshot" && opts.apiVersion
305
+ ? { snapshot_api_version: opts.apiVersion }
306
+ : {}),
307
+ events_from:
308
+ opts.apiVersion === STRIPE_CUSTOMER_PREVIEW_API_VERSION
309
+ ? payloadType === "thin"
310
+ ? ["@self", "@accounts"]
311
+ : ["@self"]
312
+ : payloadType === "thin"
313
+ ? ["self", "other_accounts"]
314
+ : ["self"],
260
315
  webhook_endpoint: { url: opts.url },
261
316
  include: ["webhook_endpoint.signing_secret", "webhook_endpoint.url"],
262
317
  });
@@ -1,3 +1,4 @@
1
+ import { STRIPE_CUSTOMER_PREVIEW_API_VERSION } from "../apiVersion.js";
1
2
  import type Stripe from "stripe";
2
3
 
3
4
  import { deriveAccountStatus } from "../core/accountStatus.js";
@@ -74,16 +75,19 @@ export async function handleV2Event(
74
75
  return null;
75
76
  }
76
77
 
77
- const retrieveParams = {
78
- include: [
79
- "configuration.merchant",
80
- "configuration.recipient",
81
- "configuration.customer",
82
- "identity",
83
- "requirements",
84
- ] satisfies NonNullable<
85
- Parameters<Stripe["v2"]["core"]["accounts"]["retrieve"]>[1]
86
- >["include"],
78
+ const retrieveParams: NonNullable<
79
+ Parameters<Stripe["v2"]["core"]["accounts"]["retrieve"]>[1]
80
+ > = {
81
+ include:
82
+ whCtx.config?.stripeApiVersion === STRIPE_CUSTOMER_PREVIEW_API_VERSION
83
+ ? ["configuration.customer", "identity"]
84
+ : [
85
+ "configuration.merchant",
86
+ "configuration.recipient",
87
+ "configuration.customer",
88
+ "identity",
89
+ "requirements",
90
+ ],
87
91
  };
88
92
  // Preserve the verified notification's account/organization context when
89
93
  // fetching the latest resource; otherwise organization keys can fetch from
@@ -1,6 +1,10 @@
1
1
  import Stripe from "stripe";
2
2
 
3
- import { STRIPE_API_VERSION } from "../../client/constants.js";
3
+ import {
4
+ resolveStripeApiVersion,
5
+ type SupportedStripeApiVersion,
6
+ } from "../../client/apiVersion.js";
7
+ import type { StripeApiVersion } from "../../client/stripe-types.js";
4
8
 
5
9
  /**
6
10
  * Keyed cache for Stripe SDK instances.
@@ -14,14 +18,18 @@ const _clients = new Map<string, Stripe>();
14
18
 
15
19
  /**
16
20
  * Get or create a Stripe SDK instance for the given API key.
17
- * Uses the component's pinned API version.
21
+ * Defaults to GA; an explicitly selected supported preview gets its own cache entry.
18
22
  */
19
- export function getStripe(apiKey: string): Stripe {
20
- const cacheKey = `${apiKey}:${STRIPE_API_VERSION}`;
23
+ export function getStripe(
24
+ apiKey: string,
25
+ apiVersion?: SupportedStripeApiVersion,
26
+ ): Stripe {
27
+ const version = resolveStripeApiVersion(apiVersion);
28
+ const cacheKey = `${apiKey}:${version}`;
21
29
  let client = _clients.get(cacheKey);
22
30
  if (!client) {
23
31
  client = new Stripe(apiKey, {
24
- apiVersion: STRIPE_API_VERSION,
32
+ apiVersion: version as StripeApiVersion,
25
33
  typescript: true,
26
34
  });
27
35
  _clients.set(cacheKey, client);