@fantastic.dev/better-stripe 0.9.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 (160) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/MIGRATION.md +26 -0
  3. package/README.md +38 -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/billing/checkout.d.ts +1 -0
  9. package/dist/client/billing/checkout.d.ts.map +1 -1
  10. package/dist/client/billing/checkout.js +5 -1
  11. package/dist/client/billing/checkout.js.map +1 -1
  12. package/dist/client/billing/providerReads.d.ts +18 -0
  13. package/dist/client/billing/providerReads.d.ts.map +1 -0
  14. package/dist/client/billing/providerReads.js +15 -0
  15. package/dist/client/billing/providerReads.js.map +1 -0
  16. package/dist/client/billing/subscriptions.d.ts +1 -1
  17. package/dist/client/billing/subscriptions.d.ts.map +1 -1
  18. package/dist/client/billing/subscriptions.js.map +1 -1
  19. package/dist/client/connect/payoutSnapshot.d.ts +28 -0
  20. package/dist/client/connect/payoutSnapshot.d.ts.map +1 -0
  21. package/dist/client/connect/payoutSnapshot.js +81 -0
  22. package/dist/client/connect/payoutSnapshot.js.map +1 -0
  23. package/dist/client/connect/transfers.d.ts.map +1 -1
  24. package/dist/client/connect/transfers.js +13 -32
  25. package/dist/client/connect/transfers.js.map +1 -1
  26. package/dist/client/core/accounts.d.ts +12 -23
  27. package/dist/client/core/accounts.d.ts.map +1 -1
  28. package/dist/client/core/accounts.js +5 -1
  29. package/dist/client/core/accounts.js.map +1 -1
  30. package/dist/client/core/idempotency.d.ts +5 -0
  31. package/dist/client/core/idempotency.d.ts.map +1 -0
  32. package/dist/client/core/idempotency.js +10 -0
  33. package/dist/client/core/idempotency.js.map +1 -0
  34. package/dist/client/index.d.ts +39 -37
  35. package/dist/client/index.d.ts.map +1 -1
  36. package/dist/client/index.js +47 -56
  37. package/dist/client/index.js.map +1 -1
  38. package/dist/client/previewRoutes.d.ts +9 -0
  39. package/dist/client/previewRoutes.d.ts.map +1 -0
  40. package/dist/client/previewRoutes.js +15 -0
  41. package/dist/client/previewRoutes.js.map +1 -0
  42. package/dist/client/settlement/allocation.d.ts +30 -0
  43. package/dist/client/settlement/allocation.d.ts.map +1 -0
  44. package/dist/client/settlement/allocation.js +71 -0
  45. package/dist/client/settlement/allocation.js.map +1 -0
  46. package/dist/client/settlement/commissions.d.ts +27 -0
  47. package/dist/client/settlement/commissions.d.ts.map +1 -0
  48. package/dist/client/settlement/commissions.js +69 -0
  49. package/dist/client/settlement/commissions.js.map +1 -0
  50. package/dist/client/settlement/execution.d.ts +27 -0
  51. package/dist/client/settlement/execution.d.ts.map +1 -0
  52. package/dist/client/settlement/execution.js +70 -0
  53. package/dist/client/settlement/execution.js.map +1 -0
  54. package/dist/client/settlement/reconciliation.d.ts +10 -0
  55. package/dist/client/settlement/reconciliation.d.ts.map +1 -0
  56. package/dist/client/settlement/reconciliation.js +73 -0
  57. package/dist/client/settlement/reconciliation.js.map +1 -0
  58. package/dist/client/settlement/settleSale.d.ts +21 -0
  59. package/dist/client/settlement/settleSale.d.ts.map +1 -0
  60. package/dist/client/settlement/settleSale.js +27 -0
  61. package/dist/client/settlement/settleSale.js.map +1 -0
  62. package/dist/client/settlement/verifiedSale.d.ts +25 -0
  63. package/dist/client/settlement/verifiedSale.d.ts.map +1 -0
  64. package/dist/client/settlement/verifiedSale.js +100 -0
  65. package/dist/client/settlement/verifiedSale.js.map +1 -0
  66. package/dist/client/types/options.d.ts +3 -0
  67. package/dist/client/types/options.d.ts.map +1 -1
  68. package/dist/client/utils/webhookEndpoints.d.ts +12 -8
  69. package/dist/client/utils/webhookEndpoints.d.ts.map +1 -1
  70. package/dist/client/utils/webhookEndpoints.js +41 -2
  71. package/dist/client/utils/webhookEndpoints.js.map +1 -1
  72. package/dist/client/webhooks/asyncHooks.d.ts +5 -0
  73. package/dist/client/webhooks/asyncHooks.d.ts.map +1 -0
  74. package/dist/client/webhooks/asyncHooks.js +20 -0
  75. package/dist/client/webhooks/asyncHooks.js.map +1 -0
  76. package/dist/client/webhooks/v2.d.ts.map +1 -1
  77. package/dist/client/webhooks/v2.js +10 -7
  78. package/dist/client/webhooks/v2.js.map +1 -1
  79. package/dist/component/_generated/component.d.ts +80 -0
  80. package/dist/component/_generated/component.d.ts.map +1 -1
  81. package/dist/component/lib/stripeFactory.d.ts +3 -2
  82. package/dist/component/lib/stripeFactory.d.ts.map +1 -1
  83. package/dist/component/lib/stripeFactory.js +6 -5
  84. package/dist/component/lib/stripeFactory.js.map +1 -1
  85. package/dist/component/schema.d.ts +67 -0
  86. package/dist/component/schema.js +4 -0
  87. package/dist/component/schema.js.map +1 -1
  88. package/dist/component/settlement/mutations.d.ts +47 -0
  89. package/dist/component/settlement/mutations.d.ts.map +1 -0
  90. package/dist/component/settlement/mutations.js +104 -0
  91. package/dist/component/settlement/mutations.js.map +1 -0
  92. package/dist/component/settlement/queries.d.ts +29 -0
  93. package/dist/component/settlement/queries.d.ts.map +1 -0
  94. package/dist/component/settlement/queries.js +35 -0
  95. package/dist/component/settlement/queries.js.map +1 -0
  96. package/dist/component/settlement/schema.d.ts +99 -0
  97. package/dist/component/settlement/schema.d.ts.map +1 -0
  98. package/dist/component/settlement/schema.js +35 -0
  99. package/dist/component/settlement/schema.js.map +1 -0
  100. package/dist/react/workflows/Checkout.d.ts +3 -1
  101. package/dist/react/workflows/Checkout.d.ts.map +1 -1
  102. package/dist/react/workflows/Checkout.js +3 -2
  103. package/dist/react/workflows/Checkout.js.map +1 -1
  104. package/dist/react/workflows/Payouts.d.ts +3 -18
  105. package/dist/react/workflows/Payouts.d.ts.map +1 -1
  106. package/dist/react/workflows/Payouts.js +2 -2
  107. package/dist/react/workflows/Payouts.js.map +1 -1
  108. package/docs/accounts-v2-preview.md +132 -0
  109. package/docs/react-workflows.md +5 -2
  110. package/docs/settlement.md +158 -0
  111. package/package.json +3 -1
  112. package/src/client/apiVersion.ts +20 -0
  113. package/src/client/billing/checkout-idempotency.test.ts +82 -0
  114. package/src/client/billing/checkout.ts +6 -1
  115. package/src/client/billing/providerReads.test.ts +60 -0
  116. package/src/client/billing/providerReads.ts +30 -0
  117. package/src/client/billing/subscriptions.ts +1 -8
  118. package/src/client/connect/payoutSnapshot.test.ts +91 -0
  119. package/src/client/connect/payoutSnapshot.ts +130 -0
  120. package/src/client/connect/transfers.test.ts +15 -4
  121. package/src/client/connect/transfers.ts +13 -42
  122. package/src/client/core/account-idempotency.test.ts +78 -0
  123. package/src/client/core/accounts.ts +18 -25
  124. package/src/client/core/idempotency.test.ts +10 -0
  125. package/src/client/core/idempotency.ts +11 -0
  126. package/src/client/customer-preview-thin.test.ts +41 -0
  127. package/src/client/customer-preview-webhooks.test.ts +150 -0
  128. package/src/client/customer-preview.test.ts +185 -0
  129. package/src/client/index.ts +115 -119
  130. package/src/client/previewRoutes.ts +26 -0
  131. package/src/client/settlement/allocation.test.ts +98 -0
  132. package/src/client/settlement/allocation.ts +110 -0
  133. package/src/client/settlement/commissions.test.ts +103 -0
  134. package/src/client/settlement/commissions.ts +125 -0
  135. package/src/client/settlement/execution.test.ts +186 -0
  136. package/src/client/settlement/execution.ts +134 -0
  137. package/src/client/settlement/reconciliation.ts +119 -0
  138. package/src/client/settlement/settleSale.ts +59 -0
  139. package/src/client/settlement/verifiedSale.test.ts +139 -0
  140. package/src/client/settlement/verifiedSale.ts +159 -0
  141. package/src/client/stripe-version.test.ts +10 -4
  142. package/src/client/types/options.ts +3 -0
  143. package/src/client/utils/webhookEndpoints.ts +70 -15
  144. package/src/client/webhooks/asyncHooks.ts +38 -0
  145. package/src/client/webhooks/processors.test.ts +9 -9
  146. package/src/client/webhooks/v2.ts +14 -10
  147. package/src/component/_generated/api.ts +4 -0
  148. package/src/component/_generated/component.ts +100 -0
  149. package/src/component/lib/stripeFactory.ts +13 -5
  150. package/src/component/schema.ts +8 -0
  151. package/src/component/settlement/mutations.ts +119 -0
  152. package/src/component/settlement/queries.ts +38 -0
  153. package/src/component/settlement/schema.ts +45 -0
  154. package/src/component/settlement-claims.test.ts +77 -0
  155. package/src/react/components/EmbeddedDisputes.render.test.tsx +2 -2
  156. package/src/react/index.payment-element.test.ts +22 -37
  157. package/src/react/workflows/Checkout.test.tsx +30 -0
  158. package/src/react/workflows/Checkout.tsx +10 -3
  159. package/src/react/workflows/Payouts.test.tsx +33 -0
  160. package/src/react/workflows/Payouts.tsx +21 -15
@@ -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,47 @@
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";
11
+ export type {
12
+ CheckoutSessionReadOptions,
13
+ SubscriptionReadOptions,
14
+ CouponReadOptions,
15
+ } from "./billing/providerReads.js";
16
+ import * as providerReads from "./billing/providerReads.js";
17
+ export type { CreateAccountOptions } from "./core/accounts.js";
18
+ import { buildAsyncHookRoutes } from "./webhooks/asyncHooks.js";
19
+ import * as saleContextImpl from "./settlement/verifiedSale.js";
20
+ import * as settlementImpl from "./settlement/settleSale.js";
21
+ import * as commissionImpl from "./settlement/commissions.js";
22
+ import * as payoutSnapshotImpl from "./connect/payoutSnapshot.js";
23
+ export { allocateSale } from "./settlement/allocation.js";
24
+ export type {
25
+ SaleAllocation,
26
+ SaleAllocationOptions,
27
+ } from "./settlement/allocation.js";
28
+ export type {
29
+ VerifiedSale,
30
+ VerifiedSaleOptions,
31
+ } from "./settlement/verifiedSale.js";
32
+ export type { SettleSaleOptions } from "./settlement/settleSale.js";
33
+ export type { SettlementResult } from "./settlement/execution.js";
34
+ export type {
35
+ CommissionTerm,
36
+ CommissionSelectionOptions,
37
+ CommissionSelection,
38
+ CommissionDecision,
39
+ } from "./settlement/commissions.js";
40
+ export type {
41
+ RecipientPayoutSnapshot,
42
+ RecipientPayoutRow,
43
+ PayoutSnapshotOptions,
44
+ } from "./connect/payoutSnapshot.js";
1
45
  import { internalActionGeneric, internalMutationGeneric } from "convex/server";
2
46
  import { v } from "convex/values";
3
47
  import Stripe from "stripe";
@@ -146,12 +190,11 @@ function clonePlatformFee(config: PlatformFeeConfig): PlatformFeeConfig {
146
190
  };
147
191
  }
148
192
 
149
- // =============================================================================
150
193
  // BETTER STRIPE CLIENT
151
- // =============================================================================
152
194
 
153
195
  export class BetterStripe {
154
196
  public component: Component;
197
+ readonly stripeApiVersion: SupportedStripeApiVersion;
155
198
  private _apiKey?: string;
156
199
  private _triggers?: SyncTriggers;
157
200
  private _hooks?: AsyncHooks;
@@ -160,6 +203,7 @@ export class BetterStripe {
160
203
 
161
204
  constructor(component: BetterStripeComponent, options?: BetterStripeOptions) {
162
205
  this.component = component;
206
+ this.stripeApiVersion = resolveStripeApiVersion(options?.stripeApiVersion);
163
207
  this._apiKey = options?.STRIPE_SECRET_KEY;
164
208
  this._triggers = options?.triggers;
165
209
  this._hooks = options?.hooks;
@@ -208,13 +252,18 @@ export class BetterStripe {
208
252
  return key;
209
253
  }
210
254
 
211
- private stripe(apiVersion?: string): Stripe {
212
- 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);
213
264
  }
214
265
 
215
- // ============================================================================
216
266
  // Static defaults (preserved for backwards compatibility)
217
- // ============================================================================
218
267
 
219
268
  static DEFAULT_ACCOUNT_CONFIGURATION =
220
269
  accountsImpl.DEFAULT_ACCOUNT_CONFIGURATION;
@@ -222,21 +271,9 @@ export class BetterStripe {
222
271
  static DEFAULT_CUSTOMER_CONFIGURATION =
223
272
  accountsImpl.DEFAULT_CUSTOMER_CONFIGURATION;
224
273
 
225
- // ============================================================================
226
274
  // ACCOUNT METHODS
227
- // ============================================================================
228
275
 
229
- async createAccount(
230
- ctx: RunCtx,
231
- opts: {
232
- userId: string;
233
- email?: string;
234
- name?: string;
235
- country?: string;
236
- orgId?: string;
237
- metadata?: Record<string, string>;
238
- },
239
- ) {
276
+ async createAccount(ctx: RunCtx, opts: accountsImpl.CreateAccountOptions) {
240
277
  return accountsImpl.createAccount(this.stripe(), this.component, ctx, opts);
241
278
  }
242
279
 
@@ -276,14 +313,7 @@ export class BetterStripe {
276
313
 
277
314
  async getOrCreateAccount(
278
315
  ctx: RunCtx,
279
- opts: {
280
- userId: string;
281
- email?: string;
282
- name?: string;
283
- country?: string;
284
- orgId?: string;
285
- metadata?: Record<string, string>;
286
- },
316
+ opts: accountsImpl.CreateAccountOptions,
287
317
  ) {
288
318
  return accountsImpl.getOrCreateAccount(
289
319
  this.stripe(),
@@ -453,14 +483,7 @@ export class BetterStripe {
453
483
  */
454
484
  async ensureCustomerAccount(
455
485
  ctx: RunCtx,
456
- opts: {
457
- userId: string;
458
- email?: string;
459
- name?: string;
460
- country?: string;
461
- orgId?: string;
462
- metadata?: Record<string, string>;
463
- },
486
+ opts: accountsImpl.CreateAccountOptions,
464
487
  ) {
465
488
  return accountsImpl.ensureCustomerAccount(
466
489
  this.stripe(),
@@ -470,9 +493,7 @@ export class BetterStripe {
470
493
  );
471
494
  }
472
495
 
473
- // ============================================================================
474
496
  // PRODUCT METHODS
475
- // ============================================================================
476
497
 
477
498
  async createProduct(
478
499
  ctx: RunCtx,
@@ -559,9 +580,7 @@ export class BetterStripe {
559
580
  return productsImpl.deleteProduct(this.stripe(), ctx, opts);
560
581
  }
561
582
 
562
- // ============================================================================
563
583
  // PRICE METHODS
564
- // ============================================================================
565
584
 
566
585
  async createPrice(
567
586
  ctx: RunCtx,
@@ -649,13 +668,12 @@ export class BetterStripe {
649
668
  return pricesImpl.listStripePrices(this.stripe(), ctx, opts);
650
669
  }
651
670
 
652
- // ============================================================================
653
671
  // CHECKOUT METHODS
654
- // ============================================================================
655
672
 
656
673
  async createCheckoutSession(
657
674
  ctx: RunCtx,
658
675
  opts: {
676
+ idempotencyKey?: string;
659
677
  userId: string;
660
678
  orgId?: string;
661
679
  stripePriceId: string;
@@ -744,9 +762,7 @@ export class BetterStripe {
744
762
  return checkoutImpl.updateCheckoutSession(this.stripe(), ctx, opts);
745
763
  }
746
764
 
747
- // ============================================================================
748
765
  // SUBSCRIPTION METHODS
749
- // ============================================================================
750
766
 
751
767
  /**
752
768
  * Create a subscription directly for a V2 buyer (`customer_account`), off the
@@ -943,14 +959,7 @@ export class BetterStripe {
943
959
  accountId?: string;
944
960
  userId: string;
945
961
  orgId?: string;
946
- status:
947
- | "active"
948
- | "trialing"
949
- | "past_due"
950
- | "canceled"
951
- | "incomplete"
952
- | "unpaid"
953
- | "paused";
962
+ status: SubscriptionStatus;
954
963
  priceId?: string;
955
964
  quantity?: number;
956
965
  currentPeriodStart?: string;
@@ -970,9 +979,7 @@ export class BetterStripe {
970
979
  return subscriptionsImpl.getTrialStatus(this.component, ctx, opts);
971
980
  }
972
981
 
973
- // ============================================================================
974
982
  // INVOICE METHODS
975
- // ============================================================================
976
983
 
977
984
  async listInvoices(
978
985
  ctx: RunCtx,
@@ -1039,9 +1046,7 @@ export class BetterStripe {
1039
1046
  return invoicesImpl.getInvoiceFromStripe(this.stripe(), ctx, opts);
1040
1047
  }
1041
1048
 
1042
- // ============================================================================
1043
1049
  // PAYMENT METHOD METHODS
1044
- // ============================================================================
1045
1050
 
1046
1051
  async listPaymentMethods(
1047
1052
  ctx: RunCtx,
@@ -1072,9 +1077,7 @@ export class BetterStripe {
1072
1077
  return paymentMethodsImpl.setDefaultPaymentMethod(this.stripe(), ctx, opts);
1073
1078
  }
1074
1079
 
1075
- // ============================================================================
1076
1080
  // ACCOUNT LINK METHODS
1077
- // ============================================================================
1078
1081
 
1079
1082
  async createAccountLink(
1080
1083
  ctx: RunCtx,
@@ -1150,9 +1153,7 @@ export class BetterStripe {
1150
1153
  return accountLinksImpl.getCountrySpecs(this.stripe(), ctx, opts);
1151
1154
  }
1152
1155
 
1153
- // ============================================================================
1154
1156
  // PAYOUT METHODS
1155
- // ============================================================================
1156
1157
 
1157
1158
  async createPayout(
1158
1159
  ctx: RunCtx,
@@ -1253,6 +1254,50 @@ export class BetterStripe {
1253
1254
  * (`{ available, pending, currency }`, minor units) per currency — each entry
1254
1255
  * feeds the headless `PayoutSchedule` component's `balance` prop directly.
1255
1256
  */
1257
+ async getCheckoutSessionFromStripe(
1258
+ _ctx: RunCtx,
1259
+ opts: providerReads.CheckoutSessionReadOptions,
1260
+ ) {
1261
+ return providerReads.getCheckoutSessionFromStripe(this.stripe(), opts);
1262
+ }
1263
+ async getSubscriptionFromStripe(
1264
+ _ctx: RunCtx,
1265
+ opts: providerReads.SubscriptionReadOptions,
1266
+ ) {
1267
+ return providerReads.getSubscriptionFromStripe(this.stripe(), opts);
1268
+ }
1269
+ async getCouponFromStripe(
1270
+ _ctx: RunCtx,
1271
+ opts: providerReads.CouponReadOptions,
1272
+ ) {
1273
+ return providerReads.getCouponFromStripe(this.stripe(), opts);
1274
+ }
1275
+ async getVerifiedSale(
1276
+ _ctx: RunCtx,
1277
+ opts: saleContextImpl.VerifiedSaleOptions,
1278
+ ) {
1279
+ return saleContextImpl.getVerifiedSale(this.stripe(), opts);
1280
+ }
1281
+ async settleSale(ctx: RunCtx, opts: settlementImpl.SettleSaleOptions) {
1282
+ return settlementImpl.settleSale(this.stripe(), this.component, ctx, opts);
1283
+ }
1284
+ async selectCommissionInvoice(
1285
+ ctx: RunCtx,
1286
+ opts: commissionImpl.CommissionSelectionOptions,
1287
+ ) {
1288
+ return commissionImpl.selectCommissionInvoice(
1289
+ this.stripe(),
1290
+ this.component,
1291
+ ctx,
1292
+ opts,
1293
+ );
1294
+ }
1295
+ async getPayoutSnapshot(
1296
+ _ctx: RunCtx,
1297
+ opts: payoutSnapshotImpl.PayoutSnapshotOptions,
1298
+ ) {
1299
+ return payoutSnapshotImpl.getPayoutSnapshot(this.stripe(), opts);
1300
+ }
1256
1301
  async getAccountBalance(_ctx: RunCtx, opts: { stripeAccountId: string }) {
1257
1302
  return payoutsImpl.getAccountBalance(this.stripe(), opts);
1258
1303
  }
@@ -1268,9 +1313,7 @@ export class BetterStripe {
1268
1313
  return payoutsImpl.listPayouts(this.component, ctx, opts);
1269
1314
  }
1270
1315
 
1271
- // ============================================================================
1272
1316
  // REFUND METHODS
1273
- // ============================================================================
1274
1317
 
1275
1318
  /**
1276
1319
  * Issue a refund (BTS-34). `refundApplicationFee` and `reverseTransfer`
@@ -1325,9 +1368,7 @@ export class BetterStripe {
1325
1368
  return refundsImpl.listRefunds(this.component, ctx, opts);
1326
1369
  }
1327
1370
 
1328
- // ============================================================================
1329
1371
  // DISPUTE METHODS
1330
- // ============================================================================
1331
1372
 
1332
1373
  async getDisputeByStripeId(
1333
1374
  ctx: RunCtx,
@@ -1397,9 +1438,7 @@ export class BetterStripe {
1397
1438
  return disputesImpl.closeDispute(this.stripe(), this.component, ctx, opts);
1398
1439
  }
1399
1440
 
1400
- // ============================================================================
1401
1441
  // WEBHOOK ENDPOINT MANAGEMENT
1402
- // ============================================================================
1403
1442
 
1404
1443
  async listWebhookEndpoints(ctx: RunCtx, opts?: { limit?: number }) {
1405
1444
  return webhookEndpointsImpl.listWebhookEndpoints(this.stripe(), ctx, opts);
@@ -1407,13 +1446,12 @@ export class BetterStripe {
1407
1446
 
1408
1447
  async createWebhookEndpoint(
1409
1448
  ctx: RunCtx,
1410
- opts: {
1411
- url: string;
1412
- description?: string;
1413
- enabledEvents?: string[];
1414
- },
1449
+ opts: webhookEndpointsImpl.WebhookEndpointOptions,
1415
1450
  ): Promise<{ id: string; secret: string; url: string }> {
1416
- return webhookEndpointsImpl.createWebhookEndpoint(this.stripe(), ctx, opts);
1451
+ return webhookEndpointsImpl.createWebhookEndpoint(this.stripe(), ctx, {
1452
+ ...opts,
1453
+ apiVersion: this.stripeApiVersion,
1454
+ });
1417
1455
  }
1418
1456
 
1419
1457
  async listEventDestinations(ctx: RunCtx, opts?: { limit?: number }) {
@@ -1422,13 +1460,7 @@ export class BetterStripe {
1422
1460
 
1423
1461
  async setupEventDestination(
1424
1462
  ctx: RunCtx,
1425
- opts: {
1426
- url: string;
1427
- name?: string;
1428
- description?: string;
1429
- enabledEvents?: string[];
1430
- eventPayload?: "snapshot" | "thin";
1431
- },
1463
+ opts: webhookEndpointsImpl.EventDestinationOptions,
1432
1464
  ): Promise<{
1433
1465
  id: string;
1434
1466
  secret: string;
@@ -1436,12 +1468,13 @@ export class BetterStripe {
1436
1468
  enabledEvents: string[];
1437
1469
  created: boolean;
1438
1470
  }> {
1439
- return webhookEndpointsImpl.setupEventDestination(this.stripe(), ctx, opts);
1471
+ return webhookEndpointsImpl.setupEventDestination(this.stripe(), ctx, {
1472
+ ...opts,
1473
+ apiVersion: this.stripeApiVersion,
1474
+ });
1440
1475
  }
1441
1476
 
1442
- // ============================================================================
1443
1477
  // WEBHOOK EVENT QUERIES
1444
- // ============================================================================
1445
1478
 
1446
1479
  async listWebhookEvents(
1447
1480
  ctx: RunCtx,
@@ -1462,17 +1495,13 @@ export class BetterStripe {
1462
1495
  }>;
1463
1496
  }
1464
1497
 
1465
- // ============================================================================
1466
1498
  // DATA MANAGEMENT
1467
- // ============================================================================
1468
1499
 
1469
1500
  async clearAll(ctx: RunCtx): Promise<{ cleared: number; tables: string[] }> {
1470
1501
  return dataImpl.clearAll(this.component, ctx);
1471
1502
  }
1472
1503
 
1473
- // ============================================================================
1474
1504
  // SYNC METHODS
1475
- // ============================================================================
1476
1505
 
1477
1506
  async syncAllAccounts(ctx: RunCtx) {
1478
1507
  return accountsImpl.syncAllAccounts(this.stripe(), this.component, ctx);
@@ -1490,9 +1519,7 @@ export class BetterStripe {
1490
1519
  );
1491
1520
  }
1492
1521
 
1493
- // ============================================================================
1494
1522
  // WEBHOOK HANDLER EXPORT (BetterAuth-style)
1495
- // ============================================================================
1496
1523
 
1497
1524
  /**
1498
1525
  * Build the two webhook handler function definitions for the app to export
@@ -1811,38 +1838,7 @@ export class BetterStripe {
1811
1838
  ),
1812
1839
  };
1813
1840
 
1814
- /** One routing entry per async hook — identical mapping to the old API. */
1815
- const ASYNC: Record<
1816
- AsyncHookName,
1817
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined
1818
- > = {
1819
- afterAccountUpdated: hooks?.onAccountUpdated as
1820
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1821
- afterCheckoutCompleted: hooks?.onCheckoutCompleted as
1822
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1823
- afterSubscriptionUpdated: hooks?.onSubscriptionUpdated as
1824
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1825
- afterSubscriptionCanceled: hooks?.onSubscriptionCanceled as
1826
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1827
- afterTrialEnding: hooks?.onTrialEnding as
1828
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1829
- afterInvoicePaid: hooks?.onInvoicePaid as
1830
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1831
- afterInvoicePaymentFailed: hooks?.onInvoicePaymentFailed as
1832
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1833
- afterPaymentSucceeded: hooks?.onPaymentSucceeded as
1834
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1835
- afterPaymentFailed: hooks?.onPaymentFailed as
1836
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1837
- afterPayoutCompleted: hooks?.onPayoutCompleted as
1838
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1839
- afterRefundCreated: hooks?.onRefundCreated as
1840
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1841
- afterDisputeCreated: hooks?.onDisputeCreated as
1842
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1843
- afterDisputeClosed: hooks?.onDisputeClosed as
1844
- ((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
1845
- };
1841
+ const ASYNC = buildAsyncHookRoutes(hooks);
1846
1842
 
1847
1843
  const syncWebhook = internalMutationGeneric({
1848
1844
  args: { dispatcher: v.string(), data: v.any() },
@@ -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
+ }