@fantastic.dev/better-stripe 0.15.0 → 0.17.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 +16 -0
- package/MIGRATION.md +6 -0
- package/README.md +15 -1
- package/dist/client/connect/payoutSnapshot.d.ts +2 -0
- package/dist/client/connect/payoutSnapshot.d.ts.map +1 -1
- package/dist/client/connect/payoutSnapshot.js +2 -0
- package/dist/client/connect/payoutSnapshot.js.map +1 -1
- package/dist/client/connect/refunds.d.ts +12 -0
- package/dist/client/connect/refunds.d.ts.map +1 -1
- package/dist/client/connect/refunds.js +8 -0
- package/dist/client/connect/refunds.js.map +1 -1
- package/dist/client/index.d.ts +8 -10
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +6 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/readContracts.d.ts +50 -0
- package/dist/client/readContracts.d.ts.map +1 -0
- package/dist/client/readContracts.js +24 -0
- package/dist/client/readContracts.js.map +1 -0
- package/dist/client/types/documents.d.ts +2 -0
- package/dist/client/types/documents.d.ts.map +1 -1
- package/dist/client/webhooks/processors.js +2 -0
- package/dist/client/webhooks/processors.js.map +1 -1
- package/dist/component/_generated/component.d.ts +213 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/connect/mutations.d.ts +2 -0
- package/dist/component/connect/mutations.d.ts.map +1 -1
- package/dist/component/connect/mutations.js +8 -8
- package/dist/component/connect/mutations.js.map +1 -1
- package/dist/component/connect/queries.d.ts +102 -2
- package/dist/component/connect/queries.d.ts.map +1 -1
- package/dist/component/connect/queries.js +88 -45
- package/dist/component/connect/queries.js.map +1 -1
- package/dist/component/connect/schema.d.ts +14 -2
- package/dist/component/connect/schema.d.ts.map +1 -1
- package/dist/component/connect/schema.js +19 -1
- package/dist/component/connect/schema.js.map +1 -1
- package/dist/component/connect/validators.d.ts +13 -2
- package/dist/component/connect/validators.d.ts.map +1 -1
- package/dist/component/connect/validators.js +4 -0
- package/dist/component/connect/validators.js.map +1 -1
- package/dist/component/earnings/legacyReads.d.ts +51 -0
- package/dist/component/earnings/legacyReads.d.ts.map +1 -0
- package/dist/component/earnings/legacyReads.js +43 -0
- package/dist/component/earnings/legacyReads.js.map +1 -0
- package/dist/component/earnings/mutations.d.ts +9 -0
- package/dist/component/earnings/mutations.d.ts.map +1 -0
- package/dist/component/earnings/mutations.js +23 -0
- package/dist/component/earnings/mutations.js.map +1 -0
- package/dist/component/earnings/reads.d.ts +14 -0
- package/dist/component/earnings/reads.d.ts.map +1 -0
- package/dist/component/earnings/reads.js +28 -0
- package/dist/component/earnings/reads.js.map +1 -0
- package/dist/component/earnings/schema.d.ts +20 -0
- package/dist/component/earnings/schema.d.ts.map +1 -0
- package/dist/component/earnings/schema.js +12 -0
- package/dist/component/earnings/schema.js.map +1 -0
- package/dist/component/earnings/triggers.d.ts +1294 -0
- package/dist/component/earnings/triggers.d.ts.map +1 -0
- package/dist/component/earnings/triggers.js +68 -0
- package/dist/component/earnings/triggers.js.map +1 -0
- package/dist/component/products/queries.d.ts +47 -0
- package/dist/component/products/queries.d.ts.map +1 -1
- package/dist/component/products/queries.js +59 -0
- package/dist/component/products/queries.js.map +1 -1
- package/dist/component/refundHistory/queries.d.ts +28 -0
- package/dist/component/refundHistory/queries.d.ts.map +1 -0
- package/dist/component/refundHistory/queries.js +59 -0
- package/dist/component/refundHistory/queries.js.map +1 -0
- package/dist/component/schema.d.ts +33 -2
- package/dist/component/schema.js +2 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/react/components/EarningsSummary.d.ts +3 -1
- package/dist/react/components/EarningsSummary.d.ts.map +1 -1
- package/dist/react/components/EarningsSummary.js +3 -3
- package/dist/react/components/EarningsSummary.js.map +1 -1
- package/dist/react/hooks/useCurrencyEarnings.d.ts +31 -0
- package/dist/react/hooks/useCurrencyEarnings.d.ts.map +1 -0
- package/dist/react/hooks/useCurrencyEarnings.js +30 -0
- package/dist/react/hooks/useCurrencyEarnings.js.map +1 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/workflows/Costs.d.ts +1 -0
- package/dist/react/workflows/Costs.d.ts.map +1 -1
- package/dist/react/workflows/Costs.js +7 -3
- package/dist/react/workflows/Costs.js.map +1 -1
- package/docs/react-workflows.md +40 -0
- package/docs/read-contracts.md +136 -0
- package/package.json +3 -2
- package/src/client/connect/payoutSnapshot.test.ts +27 -0
- package/src/client/connect/payoutSnapshot.ts +4 -0
- package/src/client/connect/refunds.ts +28 -0
- package/src/client/index.ts +10 -11
- package/src/client/readContracts.ts +90 -0
- package/src/client/readPagination.test.ts +62 -0
- package/src/client/refund-history.test.ts +67 -0
- package/src/client/types/documents.ts +2 -0
- package/src/client/webhooks/payout-canceled.integration.test.ts +25 -0
- package/src/client/webhooks/processors.ts +2 -0
- package/src/component/_generated/api.ts +10 -0
- package/src/component/_generated/component.ts +257 -0
- package/src/component/connect/mutations.ts +8 -8
- package/src/component/connect/queries.ts +110 -48
- package/src/component/connect/schema.ts +19 -1
- package/src/component/connect/validators.ts +4 -0
- package/src/component/currency-migration.test.ts +119 -0
- package/src/component/currency-totals.test.ts +95 -0
- package/src/component/earnings/legacyReads.ts +59 -0
- package/src/component/earnings/mutations.ts +21 -0
- package/src/component/earnings/reads.ts +41 -0
- package/src/component/earnings/schema.ts +11 -0
- package/src/component/earnings/triggers.ts +91 -0
- package/src/component/products/queries.ts +74 -0
- package/src/component/public.test.ts +1 -1
- package/src/component/read-pagination.test.ts +193 -0
- package/src/component/refund-history.test.ts +198 -0
- package/src/component/refundHistory/queries.ts +99 -0
- package/src/component/schema.ts +2 -0
- package/src/react/components/EarningsSummary.tsx +5 -2
- package/src/react/hooks/useCurrencyEarnings.test.ts +59 -0
- package/src/react/hooks/useCurrencyEarnings.ts +73 -0
- package/src/react/index.ts +12 -0
- package/src/react/read-contracts.render.test.tsx +20 -0
- package/src/react/workflows/Costs.test.tsx +21 -0
- package/src/react/workflows/Costs.tsx +9 -3
|
@@ -46,6 +46,8 @@ export type EarningsSummaryProps = {
|
|
|
46
46
|
net?: string;
|
|
47
47
|
paidOut?: string;
|
|
48
48
|
};
|
|
49
|
+
/** Override formatting for observed currencies outside the charge formatter. */
|
|
50
|
+
formatAmount?: (amount: number, currency: string, locale?: string) => string;
|
|
49
51
|
locale?: string;
|
|
50
52
|
className?: string;
|
|
51
53
|
children?: (props: EarningsSummaryRenderProps) => ReactNode;
|
|
@@ -70,6 +72,7 @@ export function EarningsSummary({
|
|
|
70
72
|
noPayoutsLabel = "No payouts yet",
|
|
71
73
|
labels,
|
|
72
74
|
locale,
|
|
75
|
+
formatAmount = formatPrice,
|
|
73
76
|
className,
|
|
74
77
|
children,
|
|
75
78
|
}: EarningsSummaryProps) {
|
|
@@ -93,12 +96,12 @@ export function EarningsSummary({
|
|
|
93
96
|
? latestPayoutLabel
|
|
94
97
|
.replace(
|
|
95
98
|
"{amount}",
|
|
96
|
-
|
|
99
|
+
formatAmount(latestPayout.amount, latestPayout.currency, locale),
|
|
97
100
|
)
|
|
98
101
|
.replace("{status}", latestPayout.status)
|
|
99
102
|
: noPayoutsLabel;
|
|
100
103
|
|
|
101
|
-
const money = (amount: number) =>
|
|
104
|
+
const money = (amount: number) => formatAmount(amount, currency, locale);
|
|
102
105
|
const renderProps: EarningsSummaryRenderProps = {
|
|
103
106
|
grossLabel: money(gross),
|
|
104
107
|
reversedLabel: money(reversed),
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import { createUseCurrencyEarnings } from "./useCurrencyEarnings.js";
|
|
3
|
+
it("joins exact server totals by currency without summing preview rows", () => {
|
|
4
|
+
const earnings = {
|
|
5
|
+
totals: [
|
|
6
|
+
{
|
|
7
|
+
currency: "jpy",
|
|
8
|
+
gross: 1000,
|
|
9
|
+
reversed: 100,
|
|
10
|
+
net: 900,
|
|
11
|
+
transferCount: 99,
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
transfers: [],
|
|
15
|
+
};
|
|
16
|
+
const payouts = {
|
|
17
|
+
totals: [{ currency: "usd", paidOut: 50, payoutCount: 20 }],
|
|
18
|
+
payouts: [],
|
|
19
|
+
};
|
|
20
|
+
const useEarnings = createUseCurrencyEarnings(
|
|
21
|
+
(ref) => (ref === "earnings" ? earnings : payouts),
|
|
22
|
+
"earnings",
|
|
23
|
+
"payouts",
|
|
24
|
+
);
|
|
25
|
+
expect(useEarnings("acct_a").totals).toEqual([
|
|
26
|
+
{
|
|
27
|
+
currency: "jpy",
|
|
28
|
+
gross: 1000,
|
|
29
|
+
reversed: 100,
|
|
30
|
+
net: 900,
|
|
31
|
+
transferCount: 99,
|
|
32
|
+
paidOut: 0,
|
|
33
|
+
payoutCount: 0,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
currency: "usd",
|
|
37
|
+
gross: 0,
|
|
38
|
+
reversed: 0,
|
|
39
|
+
net: 0,
|
|
40
|
+
transferCount: 0,
|
|
41
|
+
paidOut: 50,
|
|
42
|
+
payoutCount: 20,
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
it("skips unresolved accounts and distinguishes loading from empty totals", () => {
|
|
47
|
+
const args: unknown[] = [];
|
|
48
|
+
const hook = createUseCurrencyEarnings(
|
|
49
|
+
(_ref, value) => {
|
|
50
|
+
args.push(value);
|
|
51
|
+
return undefined;
|
|
52
|
+
},
|
|
53
|
+
"earnings",
|
|
54
|
+
"payouts",
|
|
55
|
+
);
|
|
56
|
+
expect(hook(undefined)).toMatchObject({ totals: [], isLoading: false });
|
|
57
|
+
expect(args).toEqual(["skip", "skip"]);
|
|
58
|
+
expect(hook("acct_a").isLoading).toBe(true);
|
|
59
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import type { EarningsTransfer } from "./useEarnings.js";
|
|
3
|
+
import type { StripeComponentPayout } from "../types.js";
|
|
4
|
+
export type CurrencyEarningsTotal = {
|
|
5
|
+
currency: string;
|
|
6
|
+
gross: number;
|
|
7
|
+
reversed: number;
|
|
8
|
+
net: number;
|
|
9
|
+
transferCount: number;
|
|
10
|
+
};
|
|
11
|
+
export type CurrencyPayoutTotal = {
|
|
12
|
+
currency: string;
|
|
13
|
+
paidOut: number;
|
|
14
|
+
payoutCount: number;
|
|
15
|
+
};
|
|
16
|
+
export type AccountCurrencyEarnings = {
|
|
17
|
+
totals: CurrencyEarningsTotal[];
|
|
18
|
+
transfers: EarningsTransfer[];
|
|
19
|
+
};
|
|
20
|
+
export type AccountCurrencyPayouts = {
|
|
21
|
+
totals: CurrencyPayoutTotal[];
|
|
22
|
+
payouts: StripeComponentPayout[];
|
|
23
|
+
};
|
|
24
|
+
export type UseCurrencyEarningsResult = {
|
|
25
|
+
totals: (CurrencyEarningsTotal & CurrencyPayoutTotal)[];
|
|
26
|
+
transfers: EarningsTransfer[];
|
|
27
|
+
payouts: StripeComponentPayout[];
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** Bind authorized host queries to the maintained component currency summaries. */
|
|
31
|
+
export function createUseCurrencyEarnings(
|
|
32
|
+
useQuery: (ref: any, args: Record<string, unknown> | "skip") => any,
|
|
33
|
+
earningsRef: any,
|
|
34
|
+
payoutsRef: any,
|
|
35
|
+
) {
|
|
36
|
+
return function useCurrencyEarnings(
|
|
37
|
+
accountId: string | undefined,
|
|
38
|
+
): UseCurrencyEarningsResult {
|
|
39
|
+
const earnings = useQuery(
|
|
40
|
+
earningsRef,
|
|
41
|
+
accountId ? { destinationAccountId: accountId } : "skip",
|
|
42
|
+
) as AccountCurrencyEarnings | undefined;
|
|
43
|
+
const payouts = useQuery(payoutsRef, accountId ? { accountId } : "skip") as
|
|
44
|
+
AccountCurrencyPayouts | undefined;
|
|
45
|
+
const byCurrency = new Map<
|
|
46
|
+
string,
|
|
47
|
+
CurrencyEarningsTotal & CurrencyPayoutTotal
|
|
48
|
+
>();
|
|
49
|
+
for (const row of earnings?.totals ?? [])
|
|
50
|
+
byCurrency.set(row.currency, { ...row, paidOut: 0, payoutCount: 0 });
|
|
51
|
+
for (const row of payouts?.totals ?? [])
|
|
52
|
+
byCurrency.set(row.currency, {
|
|
53
|
+
...(byCurrency.get(row.currency) ?? {
|
|
54
|
+
currency: row.currency,
|
|
55
|
+
gross: 0,
|
|
56
|
+
reversed: 0,
|
|
57
|
+
net: 0,
|
|
58
|
+
transferCount: 0,
|
|
59
|
+
}),
|
|
60
|
+
...row,
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
totals: [...byCurrency.values()].sort((a, b) =>
|
|
64
|
+
a.currency.localeCompare(b.currency),
|
|
65
|
+
),
|
|
66
|
+
transfers: earnings?.transfers ?? [],
|
|
67
|
+
payouts: payouts?.payouts ?? [],
|
|
68
|
+
isLoading:
|
|
69
|
+
accountId !== undefined &&
|
|
70
|
+
(earnings === undefined || payouts === undefined),
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -293,3 +293,15 @@ export {
|
|
|
293
293
|
|
|
294
294
|
export { SaveCard } from "./workflows/SaveCard.js";
|
|
295
295
|
export type { SaveCardProps, SaveCardFormState } from "./workflows/SaveCard.js";
|
|
296
|
+
|
|
297
|
+
export { createUseCurrencyEarnings } from "./hooks/useCurrencyEarnings.js";
|
|
298
|
+
export type {
|
|
299
|
+
CurrencyEarningsTotal,
|
|
300
|
+
CurrencyPayoutTotal,
|
|
301
|
+
AccountCurrencyEarnings,
|
|
302
|
+
AccountCurrencyPayouts,
|
|
303
|
+
UseCurrencyEarningsResult,
|
|
304
|
+
} from "./hooks/useCurrencyEarnings.js";
|
|
305
|
+
|
|
306
|
+
// Pins end cursors for the component paginator, including reactive page splits.
|
|
307
|
+
export { usePaginatedQuery as useComponentPaginatedQuery } from "convex-helpers/react";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
import { EarningsSummary } from "./components/EarningsSummary.js";
|
|
5
|
+
it("lets currency-aware earnings consumers format three-decimal observed amounts", () => {
|
|
6
|
+
const html = renderToStaticMarkup(
|
|
7
|
+
createElement(EarningsSummary, {
|
|
8
|
+
gross: 1234,
|
|
9
|
+
reversed: 100,
|
|
10
|
+
net: 1134,
|
|
11
|
+
paidOut: 1000,
|
|
12
|
+
currency: "kwd",
|
|
13
|
+
payouts: [],
|
|
14
|
+
formatAmount: (amount: number, currency: string) =>
|
|
15
|
+
`${currency.toUpperCase()} ${(amount / 1000).toFixed(3)}`,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
expect(html).toContain("KWD 1.234");
|
|
19
|
+
expect(html).toContain("KWD 1.134");
|
|
20
|
+
});
|
|
@@ -39,3 +39,24 @@ it("renders chargeback economics and explicitly handles missing charge links", (
|
|
|
39
39
|
expect(screen.getByText("$80.00")).toBeTruthy();
|
|
40
40
|
expect(screen.getByText("No transfer rows found.")).toBeTruthy();
|
|
41
41
|
});
|
|
42
|
+
it("distinguishes reserved recovery from money already collected", () => {
|
|
43
|
+
render(
|
|
44
|
+
<CostRecovery
|
|
45
|
+
rows={[
|
|
46
|
+
{
|
|
47
|
+
paymentIntentId: "pi_reserved",
|
|
48
|
+
amountDue: 1000,
|
|
49
|
+
collectedAmount: 400,
|
|
50
|
+
invoicedAmount: 999,
|
|
51
|
+
reservedAmount: 300,
|
|
52
|
+
outstanding: 300,
|
|
53
|
+
currency: "usd",
|
|
54
|
+
},
|
|
55
|
+
]}
|
|
56
|
+
/>,
|
|
57
|
+
);
|
|
58
|
+
expect(screen.getByText("Collected")).toBeTruthy();
|
|
59
|
+
expect(screen.getByText("Reserved")).toBeTruthy();
|
|
60
|
+
expect(screen.queryByText("$9.99")).toBeNull();
|
|
61
|
+
expect(screen.getAllByText("$3.00")).toHaveLength(2);
|
|
62
|
+
});
|
|
@@ -10,24 +10,30 @@ export type CostRecoveryRow = {
|
|
|
10
10
|
amountDue: number;
|
|
11
11
|
collectedAmount: number;
|
|
12
12
|
invoicedAmount: number;
|
|
13
|
+
reservedAmount?: number;
|
|
13
14
|
outstanding: number;
|
|
14
15
|
currency: string;
|
|
15
16
|
};
|
|
16
17
|
export function CostRecovery(props: ResourceCollectionProps<CostRecoveryRow>) {
|
|
18
|
+
const reserved = props.rows?.some((row) => row.reservedAmount !== undefined);
|
|
17
19
|
const columns: WorkflowColumn<CostRecoveryRow>[] = [
|
|
18
20
|
{ id: "payment", label: "Payment", cell: (row) => row.paymentIntentId },
|
|
19
21
|
...(
|
|
20
22
|
[
|
|
21
23
|
["amountDue", "Total cost"],
|
|
22
|
-
["collectedAmount", "Deducted from earnings"],
|
|
23
|
-
[
|
|
24
|
+
["collectedAmount", reserved ? "Collected" : "Deducted from earnings"],
|
|
25
|
+
[
|
|
26
|
+
reserved ? "reservedAmount" : "invoicedAmount",
|
|
27
|
+
reserved ? "Reserved" : "Added to invoice",
|
|
28
|
+
],
|
|
24
29
|
["outstanding", "Outstanding"],
|
|
25
30
|
] as const
|
|
26
31
|
).map(([field, label]) => ({
|
|
27
32
|
id: field,
|
|
28
33
|
label,
|
|
29
34
|
align: "end" as const,
|
|
30
|
-
cell: (row: CostRecoveryRow) =>
|
|
35
|
+
cell: (row: CostRecoveryRow) =>
|
|
36
|
+
formatPrice(row[field] ?? row.invoicedAmount, row.currency),
|
|
31
37
|
})),
|
|
32
38
|
];
|
|
33
39
|
return (
|