@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.
- package/CHANGELOG.md +30 -0
- package/README.md +267 -2
- package/dist/client/billing/balanceFunding.d.ts +36 -0
- package/dist/client/billing/balanceFunding.d.ts.map +1 -0
- package/dist/client/billing/balanceFunding.js +107 -0
- package/dist/client/billing/balanceFunding.js.map +1 -0
- package/dist/client/billing/invoicePreview.d.ts +28 -0
- package/dist/client/billing/invoicePreview.d.ts.map +1 -0
- package/dist/client/billing/invoicePreview.js +56 -0
- package/dist/client/billing/invoicePreview.js.map +1 -0
- package/dist/client/charge-currency-format.d.ts +3 -0
- package/dist/client/charge-currency-format.d.ts.map +1 -0
- package/dist/client/charge-currency-format.js +22 -0
- package/dist/client/charge-currency-format.js.map +1 -0
- package/dist/client/charge-currency.d.ts +14 -0
- package/dist/client/charge-currency.d.ts.map +1 -0
- package/dist/client/charge-currency.js +85 -0
- package/dist/client/charge-currency.js.map +1 -0
- package/dist/client/costRecovery-invoices.d.ts +38 -0
- package/dist/client/costRecovery-invoices.d.ts.map +1 -0
- package/dist/client/costRecovery-invoices.js +105 -0
- package/dist/client/costRecovery-invoices.js.map +1 -0
- package/dist/client/costRecovery.d.ts +156 -0
- package/dist/client/costRecovery.d.ts.map +1 -0
- package/dist/client/costRecovery.js +117 -0
- package/dist/client/costRecovery.js.map +1 -0
- package/dist/client/index.d.ts +28 -66
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +25 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/products/prices.d.ts +3 -2
- package/dist/client/products/prices.d.ts.map +1 -1
- package/dist/client/products/prices.js +0 -3
- package/dist/client/products/prices.js.map +1 -1
- package/dist/client/products/products.d.ts +3 -2
- package/dist/client/products/products.d.ts.map +1 -1
- package/dist/client/products/products.js +0 -3
- package/dist/client/products/products.js.map +1 -1
- package/dist/client/settlement/processingCosts.d.ts +56 -0
- package/dist/client/settlement/processingCosts.d.ts.map +1 -0
- package/dist/client/settlement/processingCosts.js +173 -0
- package/dist/client/settlement/processingCosts.js.map +1 -0
- package/dist/client/utils/webhookEndpoints.d.ts +1 -1
- package/dist/client/utils/webhookEndpoints.d.ts.map +1 -1
- package/dist/client/utils/webhookEndpoints.js +1 -0
- package/dist/client/utils/webhookEndpoints.js.map +1 -1
- package/dist/client/webhooks/processors.d.ts.map +1 -1
- package/dist/client/webhooks/processors.js +1 -0
- package/dist/client/webhooks/processors.js.map +1 -1
- package/dist/component/_generated/component.d.ts +322 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/costRecovery/claims.d.ts +68 -0
- package/dist/component/costRecovery/claims.d.ts.map +1 -0
- package/dist/component/costRecovery/claims.js +92 -0
- package/dist/component/costRecovery/claims.js.map +1 -0
- package/dist/component/costRecovery/completion.d.ts +39 -0
- package/dist/component/costRecovery/completion.d.ts.map +1 -0
- package/dist/component/costRecovery/completion.js +69 -0
- package/dist/component/costRecovery/completion.js.map +1 -0
- package/dist/component/costRecovery/helpers.d.ts +68 -0
- package/dist/component/costRecovery/helpers.d.ts.map +1 -0
- package/dist/component/costRecovery/helpers.js +71 -0
- package/dist/component/costRecovery/helpers.js.map +1 -0
- package/dist/component/costRecovery/mutations.d.ts +210 -0
- package/dist/component/costRecovery/mutations.d.ts.map +1 -0
- package/dist/component/costRecovery/mutations.js +93 -0
- package/dist/component/costRecovery/mutations.js.map +1 -0
- package/dist/component/costRecovery/queries.d.ts +108 -0
- package/dist/component/costRecovery/queries.d.ts.map +1 -0
- package/dist/component/costRecovery/queries.js +73 -0
- package/dist/component/costRecovery/queries.js.map +1 -0
- package/dist/component/costRecovery/reservations.d.ts +95 -0
- package/dist/component/costRecovery/reservations.d.ts.map +1 -0
- package/dist/component/costRecovery/reservations.js +134 -0
- package/dist/component/costRecovery/reservations.js.map +1 -0
- package/dist/component/costRecovery/schema.d.ts +382 -0
- package/dist/component/costRecovery/schema.d.ts.map +1 -0
- package/dist/component/costRecovery/schema.js +115 -0
- package/dist/component/costRecovery/schema.js.map +1 -0
- package/dist/component/schema.d.ts +135 -0
- package/dist/component/schema.js +5 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/react/components/PriceCard.d.ts +3 -1
- package/dist/react/components/PriceCard.d.ts.map +1 -1
- package/dist/react/components/PriceCard.js +2 -2
- package/dist/react/components/PriceCard.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/lib/price-helpers.d.ts +4 -6
- package/dist/react/lib/price-helpers.d.ts.map +1 -1
- package/dist/react/lib/price-helpers.js +16 -9
- package/dist/react/lib/price-helpers.js.map +1 -1
- package/dist/react/workflows/Collections.d.ts.map +1 -1
- package/dist/react/workflows/Collections.js +2 -1
- package/dist/react/workflows/Collections.js.map +1 -1
- package/dist/react/workflows/PlanPricingEditor.d.ts +4 -0
- package/dist/react/workflows/PlanPricingEditor.d.ts.map +1 -1
- package/dist/react/workflows/PlanPricingEditor.js +12 -9
- package/dist/react/workflows/PlanPricingEditor.js.map +1 -1
- package/dist/react/workflows/Refunds.d.ts +14 -3
- package/dist/react/workflows/Refunds.d.ts.map +1 -1
- package/dist/react/workflows/Refunds.js +58 -16
- package/dist/react/workflows/Refunds.js.map +1 -1
- package/dist/react/workflows/index.d.ts +3 -1
- package/dist/react/workflows/index.d.ts.map +1 -1
- package/dist/react/workflows/index.js +2 -1
- package/dist/react/workflows/index.js.map +1 -1
- package/dist/react/workflows/refund-preview.d.ts +16 -0
- package/dist/react/workflows/refund-preview.d.ts.map +1 -0
- package/dist/react/workflows/refund-preview.js +38 -0
- package/dist/react/workflows/refund-preview.js.map +1 -0
- package/docs/react-workflows.md +167 -1
- package/package.json +2 -2
- package/src/client/billing/balanceFunding.test.ts +257 -0
- package/src/client/billing/balanceFunding.ts +183 -0
- package/src/client/billing/balanceFundingTransport.test.ts +89 -0
- package/src/client/billing/invoicePreview.test.ts +123 -0
- package/src/client/billing/invoicePreview.ts +94 -0
- package/src/client/billing/invoicePreviewTransport.test.ts +70 -0
- package/src/client/charge-currency-format.ts +29 -0
- package/src/client/charge-currency.test.ts +113 -0
- package/src/client/charge-currency.ts +119 -0
- package/src/client/costRecovery-invoices.test.ts +401 -0
- package/src/client/costRecovery-invoices.transport.test.ts +107 -0
- package/src/client/costRecovery-invoices.ts +144 -0
- package/src/client/costRecovery.test.ts +76 -0
- package/src/client/costRecovery.ts +254 -0
- package/src/client/index.ts +76 -75
- package/src/client/products/prices.ts +13 -11
- package/src/client/products/products.ts +10 -10
- package/src/client/settlement/processingCosts.test.ts +349 -0
- package/src/client/settlement/processingCosts.ts +315 -0
- package/src/client/settlement/processingCostsTransport.test.ts +135 -0
- package/src/client/utils/webhookEndpoints.test.ts +5 -0
- package/src/client/utils/webhookEndpoints.ts +1 -0
- package/src/client/webhooks/hooks.test.ts +9 -0
- package/src/client/webhooks/payout-canceled.integration.test.ts +192 -0
- package/src/client/webhooks/processors.ts +1 -0
- package/src/component/_generated/api.ts +12 -0
- package/src/component/_generated/component.ts +407 -0
- package/src/component/cost-recovery.test.ts +344 -0
- package/src/component/costRecovery/claims.ts +119 -0
- package/src/component/costRecovery/completion.ts +92 -0
- package/src/component/costRecovery/helpers.ts +83 -0
- package/src/component/costRecovery/mutations.ts +115 -0
- package/src/component/costRecovery/queries.ts +88 -0
- package/src/component/costRecovery/reservations.ts +164 -0
- package/src/component/costRecovery/schema.ts +135 -0
- package/src/component/schema.ts +10 -0
- package/src/react/components/PayoutSchedule.render.test.tsx +15 -0
- package/src/react/components/PriceCard.render.test.tsx +37 -0
- package/src/react/components/PriceCard.tsx +9 -1
- package/src/react/index.test.ts +5 -0
- package/src/react/index.ts +7 -0
- package/src/react/lib/price-helpers.test.ts +15 -2
- package/src/react/lib/price-helpers.ts +25 -8
- package/src/react/workflows/Billing.test.tsx +1 -1
- package/src/react/workflows/Collections.test.tsx +36 -0
- package/src/react/workflows/Collections.tsx +3 -1
- package/src/react/workflows/PlanPricingEditor.currency.test.tsx +164 -0
- package/src/react/workflows/PlanPricingEditor.tsx +26 -10
- package/src/react/workflows/Refunds.currency.test.tsx +290 -0
- package/src/react/workflows/Refunds.tsx +115 -25
- package/src/react/workflows/index.ts +8 -1
- package/src/react/workflows/refund-preview.test.ts +65 -0
- 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
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -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,
|