@fantastic.dev/better-stripe 0.11.0 → 0.13.0

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 (168) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +267 -2
  3. package/dist/client/billing/balanceFunding.d.ts +36 -0
  4. package/dist/client/billing/balanceFunding.d.ts.map +1 -0
  5. package/dist/client/billing/balanceFunding.js +107 -0
  6. package/dist/client/billing/balanceFunding.js.map +1 -0
  7. package/dist/client/billing/invoicePreview.d.ts +28 -0
  8. package/dist/client/billing/invoicePreview.d.ts.map +1 -0
  9. package/dist/client/billing/invoicePreview.js +56 -0
  10. package/dist/client/billing/invoicePreview.js.map +1 -0
  11. package/dist/client/charge-currency-format.d.ts +3 -0
  12. package/dist/client/charge-currency-format.d.ts.map +1 -0
  13. package/dist/client/charge-currency-format.js +22 -0
  14. package/dist/client/charge-currency-format.js.map +1 -0
  15. package/dist/client/charge-currency.d.ts +14 -0
  16. package/dist/client/charge-currency.d.ts.map +1 -0
  17. package/dist/client/charge-currency.js +85 -0
  18. package/dist/client/charge-currency.js.map +1 -0
  19. package/dist/client/costRecovery-invoices.d.ts +38 -0
  20. package/dist/client/costRecovery-invoices.d.ts.map +1 -0
  21. package/dist/client/costRecovery-invoices.js +105 -0
  22. package/dist/client/costRecovery-invoices.js.map +1 -0
  23. package/dist/client/costRecovery.d.ts +156 -0
  24. package/dist/client/costRecovery.d.ts.map +1 -0
  25. package/dist/client/costRecovery.js +117 -0
  26. package/dist/client/costRecovery.js.map +1 -0
  27. package/dist/client/index.d.ts +28 -66
  28. package/dist/client/index.d.ts.map +1 -1
  29. package/dist/client/index.js +25 -0
  30. package/dist/client/index.js.map +1 -1
  31. package/dist/client/products/prices.d.ts +3 -2
  32. package/dist/client/products/prices.d.ts.map +1 -1
  33. package/dist/client/products/prices.js +0 -3
  34. package/dist/client/products/prices.js.map +1 -1
  35. package/dist/client/products/products.d.ts +3 -2
  36. package/dist/client/products/products.d.ts.map +1 -1
  37. package/dist/client/products/products.js +0 -3
  38. package/dist/client/products/products.js.map +1 -1
  39. package/dist/client/settlement/processingCosts.d.ts +56 -0
  40. package/dist/client/settlement/processingCosts.d.ts.map +1 -0
  41. package/dist/client/settlement/processingCosts.js +173 -0
  42. package/dist/client/settlement/processingCosts.js.map +1 -0
  43. package/dist/client/utils/webhookEndpoints.d.ts +1 -1
  44. package/dist/client/utils/webhookEndpoints.d.ts.map +1 -1
  45. package/dist/client/utils/webhookEndpoints.js +1 -0
  46. package/dist/client/utils/webhookEndpoints.js.map +1 -1
  47. package/dist/client/webhooks/processors.d.ts.map +1 -1
  48. package/dist/client/webhooks/processors.js +1 -0
  49. package/dist/client/webhooks/processors.js.map +1 -1
  50. package/dist/component/_generated/component.d.ts +322 -0
  51. package/dist/component/_generated/component.d.ts.map +1 -1
  52. package/dist/component/costRecovery/claims.d.ts +68 -0
  53. package/dist/component/costRecovery/claims.d.ts.map +1 -0
  54. package/dist/component/costRecovery/claims.js +92 -0
  55. package/dist/component/costRecovery/claims.js.map +1 -0
  56. package/dist/component/costRecovery/completion.d.ts +39 -0
  57. package/dist/component/costRecovery/completion.d.ts.map +1 -0
  58. package/dist/component/costRecovery/completion.js +69 -0
  59. package/dist/component/costRecovery/completion.js.map +1 -0
  60. package/dist/component/costRecovery/helpers.d.ts +68 -0
  61. package/dist/component/costRecovery/helpers.d.ts.map +1 -0
  62. package/dist/component/costRecovery/helpers.js +71 -0
  63. package/dist/component/costRecovery/helpers.js.map +1 -0
  64. package/dist/component/costRecovery/mutations.d.ts +210 -0
  65. package/dist/component/costRecovery/mutations.d.ts.map +1 -0
  66. package/dist/component/costRecovery/mutations.js +93 -0
  67. package/dist/component/costRecovery/mutations.js.map +1 -0
  68. package/dist/component/costRecovery/queries.d.ts +108 -0
  69. package/dist/component/costRecovery/queries.d.ts.map +1 -0
  70. package/dist/component/costRecovery/queries.js +73 -0
  71. package/dist/component/costRecovery/queries.js.map +1 -0
  72. package/dist/component/costRecovery/reservations.d.ts +95 -0
  73. package/dist/component/costRecovery/reservations.d.ts.map +1 -0
  74. package/dist/component/costRecovery/reservations.js +134 -0
  75. package/dist/component/costRecovery/reservations.js.map +1 -0
  76. package/dist/component/costRecovery/schema.d.ts +382 -0
  77. package/dist/component/costRecovery/schema.d.ts.map +1 -0
  78. package/dist/component/costRecovery/schema.js +115 -0
  79. package/dist/component/costRecovery/schema.js.map +1 -0
  80. package/dist/component/schema.d.ts +135 -0
  81. package/dist/component/schema.js +5 -0
  82. package/dist/component/schema.js.map +1 -1
  83. package/dist/react/components/PriceCard.d.ts +3 -1
  84. package/dist/react/components/PriceCard.d.ts.map +1 -1
  85. package/dist/react/components/PriceCard.js +2 -2
  86. package/dist/react/components/PriceCard.js.map +1 -1
  87. package/dist/react/index.d.ts +1 -0
  88. package/dist/react/index.d.ts.map +1 -1
  89. package/dist/react/index.js +1 -0
  90. package/dist/react/index.js.map +1 -1
  91. package/dist/react/lib/price-helpers.d.ts +4 -6
  92. package/dist/react/lib/price-helpers.d.ts.map +1 -1
  93. package/dist/react/lib/price-helpers.js +16 -9
  94. package/dist/react/lib/price-helpers.js.map +1 -1
  95. package/dist/react/workflows/Collections.d.ts.map +1 -1
  96. package/dist/react/workflows/Collections.js +2 -1
  97. package/dist/react/workflows/Collections.js.map +1 -1
  98. package/dist/react/workflows/PlanPricingEditor.d.ts +4 -0
  99. package/dist/react/workflows/PlanPricingEditor.d.ts.map +1 -1
  100. package/dist/react/workflows/PlanPricingEditor.js +12 -9
  101. package/dist/react/workflows/PlanPricingEditor.js.map +1 -1
  102. package/dist/react/workflows/Refunds.d.ts +14 -3
  103. package/dist/react/workflows/Refunds.d.ts.map +1 -1
  104. package/dist/react/workflows/Refunds.js +58 -16
  105. package/dist/react/workflows/Refunds.js.map +1 -1
  106. package/dist/react/workflows/index.d.ts +3 -1
  107. package/dist/react/workflows/index.d.ts.map +1 -1
  108. package/dist/react/workflows/index.js +2 -1
  109. package/dist/react/workflows/index.js.map +1 -1
  110. package/dist/react/workflows/refund-preview.d.ts +16 -0
  111. package/dist/react/workflows/refund-preview.d.ts.map +1 -0
  112. package/dist/react/workflows/refund-preview.js +38 -0
  113. package/dist/react/workflows/refund-preview.js.map +1 -0
  114. package/docs/react-workflows.md +167 -1
  115. package/package.json +2 -2
  116. package/src/client/billing/balanceFunding.test.ts +257 -0
  117. package/src/client/billing/balanceFunding.ts +183 -0
  118. package/src/client/billing/balanceFundingTransport.test.ts +89 -0
  119. package/src/client/billing/invoicePreview.test.ts +123 -0
  120. package/src/client/billing/invoicePreview.ts +94 -0
  121. package/src/client/billing/invoicePreviewTransport.test.ts +70 -0
  122. package/src/client/charge-currency-format.ts +29 -0
  123. package/src/client/charge-currency.test.ts +113 -0
  124. package/src/client/charge-currency.ts +119 -0
  125. package/src/client/costRecovery-invoices.test.ts +401 -0
  126. package/src/client/costRecovery-invoices.transport.test.ts +107 -0
  127. package/src/client/costRecovery-invoices.ts +144 -0
  128. package/src/client/costRecovery.test.ts +76 -0
  129. package/src/client/costRecovery.ts +254 -0
  130. package/src/client/index.ts +76 -75
  131. package/src/client/products/prices.ts +13 -11
  132. package/src/client/products/products.ts +10 -10
  133. package/src/client/settlement/processingCosts.test.ts +349 -0
  134. package/src/client/settlement/processingCosts.ts +315 -0
  135. package/src/client/settlement/processingCostsTransport.test.ts +135 -0
  136. package/src/client/utils/webhookEndpoints.test.ts +5 -0
  137. package/src/client/utils/webhookEndpoints.ts +1 -0
  138. package/src/client/webhooks/hooks.test.ts +9 -0
  139. package/src/client/webhooks/payout-canceled.integration.test.ts +192 -0
  140. package/src/client/webhooks/processors.ts +1 -0
  141. package/src/component/_generated/api.ts +12 -0
  142. package/src/component/_generated/component.ts +407 -0
  143. package/src/component/cost-recovery.test.ts +344 -0
  144. package/src/component/costRecovery/claims.ts +119 -0
  145. package/src/component/costRecovery/completion.ts +92 -0
  146. package/src/component/costRecovery/helpers.ts +83 -0
  147. package/src/component/costRecovery/mutations.ts +115 -0
  148. package/src/component/costRecovery/queries.ts +88 -0
  149. package/src/component/costRecovery/reservations.ts +164 -0
  150. package/src/component/costRecovery/schema.ts +135 -0
  151. package/src/component/schema.ts +10 -0
  152. package/src/react/components/PayoutSchedule.render.test.tsx +15 -0
  153. package/src/react/components/PriceCard.render.test.tsx +37 -0
  154. package/src/react/components/PriceCard.tsx +9 -1
  155. package/src/react/index.test.ts +5 -0
  156. package/src/react/index.ts +7 -0
  157. package/src/react/lib/price-helpers.test.ts +15 -2
  158. package/src/react/lib/price-helpers.ts +25 -8
  159. package/src/react/workflows/Billing.test.tsx +1 -1
  160. package/src/react/workflows/Collections.test.tsx +36 -0
  161. package/src/react/workflows/Collections.tsx +3 -1
  162. package/src/react/workflows/PlanPricingEditor.currency.test.tsx +164 -0
  163. package/src/react/workflows/PlanPricingEditor.tsx +26 -10
  164. package/src/react/workflows/Refunds.currency.test.tsx +290 -0
  165. package/src/react/workflows/Refunds.tsx +115 -25
  166. package/src/react/workflows/index.ts +8 -1
  167. package/src/react/workflows/refund-preview.test.ts +65 -0
  168. package/src/react/workflows/refund-preview.ts +56 -0
@@ -0,0 +1,76 @@
1
+ import { componentsGeneric, type FunctionReference } from "convex/server";
2
+ import { expect, it } from "vitest";
3
+ import schema from "../component/schema.js";
4
+ import { initConvexTest } from "./setup.test.js";
5
+ import { BetterStripe } from "./index.js";
6
+ import type { Component, RunCtx } from "./helpers.js";
7
+ const modules = import.meta.glob("../component/**/*.{ts,js,tsx,jsx}");
8
+ it("uses the real component through the public recovery client without any provider key", async () => {
9
+ const t = initConvexTest();
10
+ t.registerComponent("betterStripe", schema, modules);
11
+ const stripe = new BetterStripe(
12
+ componentsGeneric().betterStripe as unknown as Component,
13
+ {},
14
+ );
15
+ const ctx = {
16
+ runQuery: (
17
+ ref: FunctionReference<"query">,
18
+ args: Record<string, unknown>,
19
+ ) => t.query(ref, args),
20
+ runMutation: (
21
+ ref: FunctionReference<"mutation">,
22
+ args: Record<string, unknown>,
23
+ ) => t.mutation(ref, args),
24
+ } as unknown as RunCtx;
25
+ const recovery = stripe.costRecovery;
26
+ const scope = { accountId: "acct_owner", scopeKey: "store", currency: "usd" };
27
+ await recovery.recordClaim(ctx, {
28
+ ...scope,
29
+ claimKey: "fee:ch_original",
30
+ sourceChargeId: "ch_original",
31
+ sourceGross: 10000,
32
+ sourceProcessingFee: 320,
33
+ amountDue: 320,
34
+ revision: 1,
35
+ });
36
+ expect(
37
+ await recovery.getClaim(ctx, { claimKey: "fee:ch_original" }),
38
+ ).toMatchObject({ revision: 1, amountDue: 320 });
39
+ const result = await recovery.reserve(ctx, {
40
+ ...scope,
41
+ groupKey: "invoice:in_1",
42
+ operationKey: "invoice:in_1:batch-1",
43
+ sourceKind: "invoice",
44
+ sourceId: "in_1",
45
+ maxDebit: 10000,
46
+ maxCredit: 10000,
47
+ });
48
+ expect(result.reservation.amount).toBe(320);
49
+ expect(
50
+ (await recovery.listClaims(ctx, { ...scope, limit: 10 })).page,
51
+ ).toHaveLength(1);
52
+ await recovery.seal(ctx, { groupKey: "invoice:in_1" });
53
+ expect(
54
+ (await recovery.getGroup(ctx, { groupKey: "invoice:in_1" }))?.status,
55
+ ).toBe("sealed");
56
+ expect(
57
+ (
58
+ await recovery.getReservation(ctx, {
59
+ operationKey: "invoice:in_1:batch-1",
60
+ })
61
+ )?.amount,
62
+ ).toBe(320);
63
+ expect(
64
+ (
65
+ await recovery.finish(ctx, {
66
+ groupKey: "invoice:in_1",
67
+ outcome: "collect",
68
+ })
69
+ ).isDone,
70
+ ).toBe(true);
71
+ expect(await recovery.balance(ctx, scope)).toMatchObject({
72
+ collectedAmount: 320,
73
+ outstanding: 0,
74
+ reservedAmount: 0,
75
+ });
76
+ });
@@ -0,0 +1,254 @@
1
+ import type Stripe from "stripe";
2
+ import {
3
+ readInvoiceFunding,
4
+ attachRecoveryInvoice,
5
+ } from "./costRecovery-invoices.js";
6
+ import type { Component, RunCtx } from "./helpers.js";
7
+ import { runMutationOrThrow } from "./helpers.js";
8
+ import { componentRef } from "./webhooks/helpers.js";
9
+ export type RecoveryScope = {
10
+ accountId: string;
11
+ scopeKey: string;
12
+ currency: string;
13
+ };
14
+ export type RecoveryObservation = RecoveryScope & {
15
+ claimKey: string;
16
+ sourceChargeId: string;
17
+ sourceGross: number;
18
+ sourceProcessingFee: number;
19
+ amountDue: number;
20
+ revision: number;
21
+ };
22
+ export type RecoveryClaim = RecoveryObservation & {
23
+ _id: string;
24
+ _creationTime: number;
25
+ collectedAmount: number;
26
+ reservedAmount: number;
27
+ outstanding: number;
28
+ sign: "positive" | "negative" | "zero";
29
+ };
30
+ export type RecoveryBalance = RecoveryScope & {
31
+ amountDue: number;
32
+ collectedAmount: number;
33
+ reservedAmount: number;
34
+ outstanding: number;
35
+ activeGroupKey?: string;
36
+ };
37
+ export type RecoveryGroupOptions = RecoveryScope & {
38
+ groupKey: string;
39
+ maxDebit: number;
40
+ maxCredit: number;
41
+ } & (
42
+ | {
43
+ sourceKind: "settlement";
44
+ sourceId: string;
45
+ sourceGross: number;
46
+ sourceProcessingFee: number;
47
+ }
48
+ | {
49
+ sourceKind: "invoice";
50
+ sourceId: string;
51
+ sourceGross?: number;
52
+ sourceProcessingFee?: number;
53
+ }
54
+ );
55
+ export type RecoveryGroup = RecoveryGroupOptions & {
56
+ _id: string;
57
+ _creationTime: number;
58
+ amount: number;
59
+ pendingOperations: number;
60
+ invoiceItemId?: string;
61
+ invoiceFunding?: {
62
+ sourceChargeId: string;
63
+ sourceGross: number;
64
+ sourceProcessingFee: number;
65
+ };
66
+ status:
67
+ "open" | "sealed" | "collecting" | "releasing" | "collected" | "released";
68
+ };
69
+ export type RecoveryReservation = {
70
+ _id: string;
71
+ _creationTime: number;
72
+ operationKey: string;
73
+ groupKey: string;
74
+ amount: number;
75
+ limit: number;
76
+ allocations: { claimId: string; claimKey: string; amount: number }[];
77
+ status: "reserved" | "collected" | "released" | "empty";
78
+ };
79
+ /** Host authorizes policy and scope. Provider methods verify invoice outcomes. */
80
+ export class CostRecoveryClient {
81
+ constructor(
82
+ private readonly component: Component,
83
+ private readonly provider?: () => Stripe,
84
+ ) {}
85
+ private async mutate<T>(
86
+ ctx: RunCtx,
87
+ path: string,
88
+ args: Record<string, unknown>,
89
+ ): Promise<T> {
90
+ return (await runMutationOrThrow(
91
+ ctx,
92
+ componentRef(this.component, `costRecovery/${path}`),
93
+ args,
94
+ )) as T;
95
+ }
96
+ private async query<T>(
97
+ ctx: RunCtx,
98
+ path: string,
99
+ args: Record<string, unknown>,
100
+ ): Promise<T> {
101
+ return (await ctx.runQuery(
102
+ componentRef(this.component, `costRecovery/queries/${path}`),
103
+ args,
104
+ )) as T;
105
+ }
106
+ /** Host authorizes the account, scope and invoice group; call from an action. */
107
+ async attachInvoice(ctx: RunCtx, opts: RecoveryScope & { groupKey: string }) {
108
+ if (!this.provider)
109
+ throw new Error("Invoice recovery requires a Stripe client.");
110
+ const group = await this.getGroup(ctx, { groupKey: opts.groupKey });
111
+ return attachRecoveryInvoice(
112
+ this.provider(),
113
+ group,
114
+ opts,
115
+ (invoiceItemId) =>
116
+ this.mutate<RecoveryGroup>(ctx, "mutations/bindInvoiceItem", {
117
+ groupKey: opts.groupKey,
118
+ invoiceItemId,
119
+ }),
120
+ );
121
+ }
122
+ /** Re-read Stripe; keep unpaid items reserved. Repeat until isDone after terminal outcomes. */
123
+ async reconcileInvoice(
124
+ ctx: RunCtx,
125
+ opts: RecoveryScope & { groupKey: string; releaseUncollectible?: boolean },
126
+ ) {
127
+ if (!this.provider)
128
+ throw new Error("Invoice recovery requires a Stripe client.");
129
+ const group = await this.getGroup(ctx, { groupKey: opts.groupKey });
130
+ const receipt = await attachRecoveryInvoice(
131
+ this.provider(),
132
+ group,
133
+ opts,
134
+ (invoiceItemId) =>
135
+ this.mutate<RecoveryGroup>(ctx, "mutations/bindInvoiceItem", {
136
+ groupKey: opts.groupKey,
137
+ invoiceItemId,
138
+ }),
139
+ false,
140
+ );
141
+ const outcome =
142
+ receipt.invoiceStatus === "paid"
143
+ ? "collect"
144
+ : receipt.invoiceStatus === "void" ||
145
+ (receipt.invoiceStatus === "uncollectible" &&
146
+ opts.releaseUncollectible === true)
147
+ ? "release"
148
+ : null;
149
+ if (!outcome) return { ...receipt, isDone: false };
150
+ return {
151
+ ...receipt,
152
+ ...(await this.finish(ctx, { groupKey: opts.groupKey, outcome })),
153
+ };
154
+ }
155
+ /** Reconcile a returned funding charge without changing the original cost claims. */
156
+ async rollbackInvoice(
157
+ ctx: RunCtx,
158
+ opts: RecoveryScope & { groupKey: string; revision: number },
159
+ ) {
160
+ if (!this.provider)
161
+ throw new Error("Invoice recovery requires a Stripe client.");
162
+ const stripe = this.provider();
163
+ const group = await this.getGroup(ctx, { groupKey: opts.groupKey });
164
+ const receipt = await attachRecoveryInvoice(
165
+ stripe,
166
+ group,
167
+ opts,
168
+ (invoiceItemId) =>
169
+ this.mutate<RecoveryGroup>(ctx, "mutations/bindInvoiceItem", {
170
+ groupKey: opts.groupKey,
171
+ invoiceItemId,
172
+ }),
173
+ false,
174
+ );
175
+ if (
176
+ receipt.group.status !== "collected" ||
177
+ receipt.invoiceStatus !== "paid"
178
+ )
179
+ throw new Error("Invoice rollback requires verified collection.");
180
+ const cost = await readInvoiceFunding(stripe, receipt.group);
181
+ if (cost.status === "not_ready") return cost;
182
+ await this.mutate<RecoveryGroup>(ctx, "mutations/bindInvoiceFunding", {
183
+ groupKey: opts.groupKey,
184
+ sourceChargeId: cost.chargeId,
185
+ sourceGross: cost.gross,
186
+ sourceProcessingFee: cost.processingFee,
187
+ });
188
+ return {
189
+ status: "ready" as const,
190
+ claim: await this.rollback(ctx, {
191
+ groupKey: opts.groupKey,
192
+ returnedPrincipal: cost.principalReturned,
193
+ revision: opts.revision,
194
+ }),
195
+ };
196
+ }
197
+ recordClaim(ctx: RunCtx, opts: RecoveryObservation) {
198
+ return this.mutate<RecoveryClaim>(ctx, "mutations/record", opts);
199
+ }
200
+ reserve(
201
+ ctx: RunCtx,
202
+ opts: RecoveryGroupOptions & { operationKey: string; limit?: number },
203
+ ) {
204
+ return this.mutate<{
205
+ reservation: RecoveryReservation;
206
+ group: RecoveryGroup;
207
+ }>(ctx, "mutations/reserve", opts);
208
+ }
209
+ seal(ctx: RunCtx, opts: { groupKey: string }) {
210
+ return this.mutate<RecoveryGroup>(ctx, "mutations/seal", opts);
211
+ }
212
+ finish(
213
+ ctx: RunCtx,
214
+ opts: { groupKey: string; outcome: "collect" | "release"; limit?: number },
215
+ ) {
216
+ return this.mutate<{ group: RecoveryGroup; isDone: boolean }>(
217
+ ctx,
218
+ "mutations/finish",
219
+ opts,
220
+ );
221
+ }
222
+ rollback(
223
+ ctx: RunCtx,
224
+ opts: { groupKey: string; returnedPrincipal: number; revision: number },
225
+ ) {
226
+ return this.mutate<RecoveryClaim>(ctx, "mutations/rollback", opts);
227
+ }
228
+ balance(ctx: RunCtx, opts: RecoveryScope) {
229
+ return this.query<RecoveryBalance>(ctx, "balance", opts);
230
+ }
231
+ listClaims(
232
+ ctx: RunCtx,
233
+ opts: RecoveryScope & { limit?: number; cursor?: string | null },
234
+ ) {
235
+ const { limit, cursor, ...scope } = opts;
236
+ return this.query<{
237
+ page: RecoveryClaim[];
238
+ continueCursor: string;
239
+ isDone: boolean;
240
+ }>(ctx, "listClaims", {
241
+ ...scope,
242
+ paginationOpts: { numItems: limit ?? 25, cursor: cursor ?? null },
243
+ });
244
+ }
245
+ getClaim(ctx: RunCtx, opts: { claimKey: string }) {
246
+ return this.query<RecoveryClaim | null>(ctx, "claim", opts);
247
+ }
248
+ getGroup(ctx: RunCtx, opts: { groupKey: string }) {
249
+ return this.query<RecoveryGroup | null>(ctx, "group", opts);
250
+ }
251
+ getReservation(ctx: RunCtx, opts: { operationKey: string }) {
252
+ return this.query<RecoveryReservation | null>(ctx, "reservation", opts);
253
+ }
254
+ }
@@ -1,3 +1,27 @@
1
+ import { CostRecoveryClient } from "./costRecovery.js";
2
+ export * from "./charge-currency.js";
3
+ export * from "./costRecovery.js";
4
+ import type { SubscriptionStatus } from "../component/billing/validators.js";
5
+ import * as processingCostsImpl from "./settlement/processingCosts.js";
6
+ export type {
7
+ ProcessingCostContext,
8
+ ProcessingCostOptions,
9
+ ProcessingCostTransaction,
10
+ ProcessingCostRecord,
11
+ ProcessingCostNotReadyReason,
12
+ } from "./settlement/processingCosts.js";
13
+ import * as balanceFundingImpl from "./billing/balanceFunding.js";
14
+ export type {
15
+ BalanceFundingCapability,
16
+ BalanceFundingPreparation,
17
+ PrepareBalanceFundingOptions,
18
+ SetBalanceFundingOptions,
19
+ } from "./billing/balanceFunding.js";
20
+ import * as invoicePreviewImpl from "./billing/invoicePreview.js";
21
+ export type {
22
+ UpcomingInvoicePreview,
23
+ UpcomingInvoicePreviewOptions,
24
+ } from "./billing/invoicePreview.js";
1
25
  import type { HttpRouter } from "convex/server";
2
26
  import { registerClientRoutes } from "./previewRoutes.js";
3
27
  import {
@@ -47,10 +71,6 @@ import { v } from "convex/values";
47
71
  import Stripe from "stripe";
48
72
 
49
73
  import type { ComponentApi } from "../component/_generated/component.js";
50
- import type {
51
- InvoiceStatus,
52
- SubscriptionStatus,
53
- } from "../component/billing/validators.js";
54
74
  import type {
55
75
  DisputeStatus,
56
76
  PayoutStatus,
@@ -495,17 +515,7 @@ export class BetterStripe {
495
515
 
496
516
  // PRODUCT METHODS
497
517
 
498
- async createProduct(
499
- ctx: RunCtx,
500
- opts: {
501
- name: string;
502
- description?: string;
503
- active?: boolean;
504
- accountId?: string;
505
- metadata?: Record<string, string>;
506
- idempotencyKey?: string;
507
- },
508
- ) {
518
+ async createProduct(ctx: RunCtx, opts: productsImpl.CreateProductOptions) {
509
519
  return productsImpl.createProduct(this.stripe(), this.component, ctx, {
510
520
  ...opts,
511
521
  // Stamp the per-store descriptor at create so it covers the first invoice
@@ -583,20 +593,7 @@ export class BetterStripe {
583
593
 
584
594
  // PRICE METHODS
585
595
 
586
- async createPrice(
587
- ctx: RunCtx,
588
- opts: {
589
- stripeProductId: string;
590
- unitAmount: number;
591
- currency?: string;
592
- type: "one_time" | "recurring";
593
- interval?: "day" | "week" | "month" | "year";
594
- intervalCount?: number;
595
- nickname?: string;
596
- metadata?: Record<string, string>;
597
- idempotencyKey?: string;
598
- },
599
- ) {
596
+ async createPrice(ctx: RunCtx, opts: pricesImpl.CreatePriceOptions) {
600
597
  return pricesImpl.createPrice(this.stripe(), this.component, ctx, opts);
601
598
  }
602
599
 
@@ -646,19 +643,7 @@ export class BetterStripe {
646
643
 
647
644
  async upsertPrice(
648
645
  ctx: RunCtx,
649
- opts: {
650
- stripePriceId: string;
651
- productId: string;
652
- stripeProductId: string;
653
- nickname?: string;
654
- unitAmount: number;
655
- currency: string;
656
- active: boolean;
657
- type: "one_time" | "recurring";
658
- interval?: "day" | "week" | "month" | "year";
659
- intervalCount?: number;
660
- metadata?: Record<string, unknown>;
661
- },
646
+ opts: Parameters<typeof pricesImpl.upsertPrice>[2],
662
647
  ) {
663
648
  return pricesImpl.upsertPrice(this.component, ctx, opts);
664
649
  }
@@ -956,23 +941,7 @@ export class BetterStripe {
956
941
 
957
942
  async upsertSubscription(
958
943
  ctx: RunCtx,
959
- opts: {
960
- stripeSubscriptionId: string;
961
- accountId?: string;
962
- userId: string;
963
- orgId?: string;
964
- status: SubscriptionStatus;
965
- priceId?: string;
966
- quantity?: number;
967
- currentPeriodStart?: string;
968
- currentPeriodEnd?: string;
969
- cancelAtPeriodEnd: boolean;
970
- canceledAt?: string;
971
- isTrialing: boolean;
972
- trialStart?: string;
973
- trialEnd?: string;
974
- metadata?: Record<string, unknown>;
975
- },
944
+ opts: Parameters<typeof subscriptionsImpl.upsertSubscription>[2],
976
945
  ) {
977
946
  return subscriptionsImpl.upsertSubscription(this.component, ctx, opts);
978
947
  }
@@ -1016,22 +985,7 @@ export class BetterStripe {
1016
985
 
1017
986
  async upsertInvoice(
1018
987
  ctx: RunCtx,
1019
- opts: {
1020
- stripeInvoiceId: string;
1021
- userId: string;
1022
- orgId?: string;
1023
- accountId?: string;
1024
- subscriptionId?: string;
1025
- status: InvoiceStatus;
1026
- currency: string;
1027
- amountDue: number;
1028
- amountPaid: number;
1029
- hostedInvoiceUrl?: string;
1030
- invoicePdf?: string;
1031
- periodStart?: string;
1032
- periodEnd?: string;
1033
- metadata?: Record<string, unknown>;
1034
- },
988
+ opts: Parameters<typeof invoicesImpl.upsertInvoice>[2],
1035
989
  ) {
1036
990
  return invoicesImpl.upsertInvoice(this.component, ctx, opts);
1037
991
  }
@@ -1044,6 +998,42 @@ export class BetterStripe {
1044
998
  return configImpl.getStripeMode(this.component, ctx);
1045
999
  }
1046
1000
 
1001
+ getBalanceFundingCapability() {
1002
+ return balanceFundingImpl.getBalanceFundingCapability(
1003
+ this.stripeApiVersion,
1004
+ );
1005
+ }
1006
+
1007
+ async prepareBalanceFunding(
1008
+ _ctx: RunCtx,
1009
+ opts: balanceFundingImpl.PrepareBalanceFundingOptions,
1010
+ ) {
1011
+ return balanceFundingImpl.prepareBalanceFunding(
1012
+ this.stripe(),
1013
+ this.stripeApiVersion,
1014
+ opts,
1015
+ );
1016
+ }
1017
+
1018
+ async setBalanceFunding(
1019
+ _ctx: RunCtx,
1020
+ opts: balanceFundingImpl.SetBalanceFundingOptions,
1021
+ ) {
1022
+ return balanceFundingImpl.setBalanceFunding(
1023
+ this.stripe(),
1024
+ this.stripeApiVersion,
1025
+ opts,
1026
+ );
1027
+ }
1028
+
1029
+ /** Authorize the customer in the host; validates subscription ownership. */
1030
+ async getUpcomingInvoicePreview(
1031
+ _ctx: RunCtx,
1032
+ opts: invoicePreviewImpl.UpcomingInvoicePreviewOptions,
1033
+ ) {
1034
+ return invoicePreviewImpl.getUpcomingInvoicePreview(this.stripe(), opts);
1035
+ }
1036
+
1047
1037
  async getInvoiceFromStripe(ctx: RunCtx, opts: { stripeInvoiceId: string }) {
1048
1038
  return invoicesImpl.getInvoiceFromStripe(this.stripe(), ctx, opts);
1049
1039
  }
@@ -1274,6 +1264,17 @@ export class BetterStripe {
1274
1264
  ) {
1275
1265
  return providerReads.getCouponFromStripe(this.stripe(), opts);
1276
1266
  }
1267
+ get costRecovery() {
1268
+ return new CostRecoveryClient(this.component, () => this.stripe());
1269
+ }
1270
+
1271
+ async getProcessingCostContext(
1272
+ _ctx: RunCtx,
1273
+ opts: processingCostsImpl.ProcessingCostOptions,
1274
+ ) {
1275
+ return processingCostsImpl.getProcessingCostContext(this.stripe(), opts);
1276
+ }
1277
+
1277
1278
  async getVerifiedSale(
1278
1279
  _ctx: RunCtx,
1279
1280
  opts: saleContextImpl.VerifiedSaleOptions,
@@ -11,21 +11,23 @@ import { componentRef } from "../webhooks/helpers.js";
11
11
  // Price methods
12
12
  // =============================================================================
13
13
 
14
+ export type CreatePriceOptions = {
15
+ stripeProductId: string;
16
+ unitAmount: number;
17
+ currency?: string;
18
+ type: "one_time" | "recurring";
19
+ interval?: "day" | "week" | "month" | "year";
20
+ intervalCount?: number;
21
+ nickname?: string;
22
+ metadata?: Record<string, string>;
23
+ idempotencyKey?: string;
24
+ };
25
+
14
26
  export async function createPrice(
15
27
  stripe: Stripe,
16
28
  component: Component,
17
29
  ctx: RunCtx,
18
- opts: {
19
- stripeProductId: string;
20
- unitAmount: number;
21
- currency?: string;
22
- type: "one_time" | "recurring";
23
- interval?: "day" | "week" | "month" | "year";
24
- intervalCount?: number;
25
- nickname?: string;
26
- metadata?: Record<string, string>;
27
- idempotencyKey?: string;
28
- },
30
+ opts: CreatePriceOptions,
29
31
  ) {
30
32
  // Validate the product exists in the component DB *before* writing to Stripe.
31
33
  // Creating the price first and throwing afterwards would orphan a price in
@@ -54,20 +54,20 @@ async function resolveProductStatementDescriptor(
54
54
  // Product methods
55
55
  // =============================================================================
56
56
 
57
+ export type CreateProductOptions = {
58
+ name: string;
59
+ description?: string;
60
+ active?: boolean;
61
+ accountId?: string;
62
+ metadata?: Record<string, string>;
63
+ idempotencyKey?: string;
64
+ };
65
+
57
66
  export async function createProduct(
58
67
  stripe: Stripe,
59
68
  component: Component,
60
69
  ctx: RunCtx,
61
- opts: {
62
- name: string;
63
- description?: string;
64
- active?: boolean;
65
- accountId?: string;
66
- metadata?: Record<string, string>;
67
- idempotencyKey?: string;
68
- /** Platform default statement-descriptor suffix (BetterStripe option). */
69
- defaultStatementDescriptorSuffix?: string;
70
- },
70
+ opts: CreateProductOptions & { defaultStatementDescriptorSuffix?: string },
71
71
  ) {
72
72
  const statementDescriptor = await resolveProductStatementDescriptor(
73
73
  component,