@fantastic.dev/better-stripe 0.10.2 → 0.12.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 +23 -0
- package/README.md +220 -4
- 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.d.ts +115 -0
- package/dist/client/costRecovery.d.ts.map +1 -0
- package/dist/client/costRecovery.js +50 -0
- package/dist/client/costRecovery.js.map +1 -0
- package/dist/client/index.d.ts +28 -64
- 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 +4 -2
- package/dist/client/products/prices.d.ts.map +1 -1
- package/dist/client/products/prices.js +5 -4
- package/dist/client/products/prices.js.map +1 -1
- package/dist/client/products/products.d.ts +4 -2
- package/dist/client/products/products.d.ts.map +1 -1
- package/dist/client/products/products.js +7 -5
- 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 +244 -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 +85 -0
- package/dist/component/costRecovery/claims.js.map +1 -0
- package/dist/component/costRecovery/completion.d.ts +33 -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 +62 -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 +138 -0
- package/dist/component/costRecovery/mutations.d.ts.map +1 -0
- package/dist/component/costRecovery/mutations.js +36 -0
- package/dist/component/costRecovery/mutations.js.map +1 -0
- package/dist/component/costRecovery/queries.d.ts +102 -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 +83 -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 +340 -0
- package/dist/component/costRecovery/schema.d.ts.map +1 -0
- package/dist/component/costRecovery/schema.js +109 -0
- package/dist/component/costRecovery/schema.js.map +1 -0
- package/dist/component/schema.d.ts +119 -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/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/docs/react-workflows.md +91 -0
- 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.test.ts +76 -0
- package/src/client/costRecovery.ts +149 -0
- package/src/client/index.ts +76 -73
- package/src/client/products/catalog-idempotency.test.ts +116 -0
- package/src/client/products/prices.ts +18 -11
- package/src/client/products/products.ts +17 -11
- 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 +308 -0
- package/src/component/cost-recovery.test.ts +344 -0
- package/src/component/costRecovery/claims.ts +109 -0
- package/src/component/costRecovery/completion.ts +92 -0
- package/src/component/costRecovery/helpers.ts +83 -0
- package/src/component/costRecovery/mutations.ts +43 -0
- package/src/component/costRecovery/queries.ts +88 -0
- package/src/component/costRecovery/reservations.ts +164 -0
- package/src/component/costRecovery/schema.ts +127 -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/PlanPricingEditor.currency.test.tsx +164 -0
- package/src/react/workflows/PlanPricingEditor.tsx +26 -10
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import { componentsGeneric } from "convex/server";
|
|
3
|
+
import { expect, it, vi } from "vitest";
|
|
4
|
+
import { BetterStripe } from "../index.js";
|
|
5
|
+
import { getStripeClient, type Component, type RunCtx } from "../helpers.js";
|
|
6
|
+
|
|
7
|
+
it.each([undefined, "2026-09-30.preview"] as const)(
|
|
8
|
+
"paginates using the configured version and connected namespace: %s",
|
|
9
|
+
async (version) => {
|
|
10
|
+
const key = `sk_test_cost_transport_${crypto.randomUUID()}`;
|
|
11
|
+
const bs = new BetterStripe(
|
|
12
|
+
componentsGeneric().betterStripe as unknown as Component,
|
|
13
|
+
{
|
|
14
|
+
STRIPE_SECRET_KEY: key,
|
|
15
|
+
stripeApiVersion: version,
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
const calls: {
|
|
19
|
+
path: string;
|
|
20
|
+
version: string;
|
|
21
|
+
account: string;
|
|
22
|
+
method: string;
|
|
23
|
+
}[] = [];
|
|
24
|
+
const sdk = getStripeClient(key, version);
|
|
25
|
+
const tx = (id: string, amount: number, fee = 0) => ({
|
|
26
|
+
id,
|
|
27
|
+
amount,
|
|
28
|
+
fee,
|
|
29
|
+
net: amount - fee,
|
|
30
|
+
currency: "usd",
|
|
31
|
+
created: 1800000000,
|
|
32
|
+
});
|
|
33
|
+
const refund = (id: string) => ({
|
|
34
|
+
id,
|
|
35
|
+
amount: 100,
|
|
36
|
+
status: "succeeded",
|
|
37
|
+
currency: "usd",
|
|
38
|
+
charge: "ch_test",
|
|
39
|
+
payment_intent: "pi_test",
|
|
40
|
+
balance_transaction: tx(`txn_${id}`, -100),
|
|
41
|
+
});
|
|
42
|
+
(
|
|
43
|
+
sdk as unknown as { _setApiField: (key: string, value: unknown) => void }
|
|
44
|
+
)._setApiField("httpClient", {
|
|
45
|
+
getClientName: () => "cost-context-contract",
|
|
46
|
+
makeRequest: async (
|
|
47
|
+
...[, , path, method, headers]: Parameters<
|
|
48
|
+
ReturnType<typeof Stripe.createNodeHttpClient>["makeRequest"]
|
|
49
|
+
>
|
|
50
|
+
) => {
|
|
51
|
+
calls.push({
|
|
52
|
+
path,
|
|
53
|
+
method,
|
|
54
|
+
version: String(headers["Stripe-Version"]),
|
|
55
|
+
account: String(headers["Stripe-Account"]),
|
|
56
|
+
});
|
|
57
|
+
const next = path.includes("starting_after");
|
|
58
|
+
const data = path.includes("/payment_intents/")
|
|
59
|
+
? {
|
|
60
|
+
id: "pi_test",
|
|
61
|
+
status: "succeeded",
|
|
62
|
+
currency: "usd",
|
|
63
|
+
amount_received: 10000,
|
|
64
|
+
metadata: {},
|
|
65
|
+
latest_charge: {
|
|
66
|
+
id: "ch_test",
|
|
67
|
+
payment_intent: "pi_test",
|
|
68
|
+
currency: "usd",
|
|
69
|
+
balance_transaction: tx("txn_charge", 10000, 320),
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
: path.includes("/refunds")
|
|
73
|
+
? {
|
|
74
|
+
object: "list",
|
|
75
|
+
url: "/v1/refunds",
|
|
76
|
+
has_more: !next,
|
|
77
|
+
data: [refund(next ? "re_second" : "re_first")],
|
|
78
|
+
}
|
|
79
|
+
: {
|
|
80
|
+
object: "list",
|
|
81
|
+
url: "/v1/disputes",
|
|
82
|
+
has_more: !next,
|
|
83
|
+
data: [
|
|
84
|
+
{
|
|
85
|
+
id: next ? "dp_second" : "dp_first",
|
|
86
|
+
status: "warning_closed",
|
|
87
|
+
amount: 100,
|
|
88
|
+
currency: "usd",
|
|
89
|
+
charge: "ch_test",
|
|
90
|
+
payment_intent: "pi_test",
|
|
91
|
+
balance_transactions: [],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
getStatusCode: () => 200,
|
|
97
|
+
getHeaders: () => ({}),
|
|
98
|
+
getRawResponse: () => ({}),
|
|
99
|
+
toJSON: async () => data,
|
|
100
|
+
toStream: () => {
|
|
101
|
+
throw new Error("Unexpected stream");
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const ctx = {
|
|
107
|
+
runQuery: vi.fn(),
|
|
108
|
+
runMutation: vi.fn(),
|
|
109
|
+
} as unknown as RunCtx;
|
|
110
|
+
expect(
|
|
111
|
+
await bs.getProcessingCostContext(ctx, {
|
|
112
|
+
paymentIntentId: "pi_test",
|
|
113
|
+
stripeAccountId: "acct_connected",
|
|
114
|
+
}),
|
|
115
|
+
).toMatchObject({
|
|
116
|
+
status: "ready",
|
|
117
|
+
refundedPrincipal: 200,
|
|
118
|
+
refunds: [{ id: "re_first" }, { id: "re_second" }],
|
|
119
|
+
disputes: [{ id: "dp_first" }, { id: "dp_second" }],
|
|
120
|
+
});
|
|
121
|
+
expect(calls).toHaveLength(5);
|
|
122
|
+
expect(
|
|
123
|
+
calls.every(
|
|
124
|
+
(call) =>
|
|
125
|
+
call.method === "GET" &&
|
|
126
|
+
call.version === (version ?? "2026-09-30.endive") &&
|
|
127
|
+
call.account === "acct_connected",
|
|
128
|
+
),
|
|
129
|
+
).toBe(true);
|
|
130
|
+
expect(
|
|
131
|
+
calls.filter((call) => call.path.includes("starting_after")),
|
|
132
|
+
).toHaveLength(2);
|
|
133
|
+
expect(ctx.runMutation).not.toHaveBeenCalled();
|
|
134
|
+
},
|
|
135
|
+
);
|
|
@@ -51,6 +51,11 @@ const asStripe = (s: ReturnType<typeof makeStripe>) =>
|
|
|
51
51
|
const ctx = {} as RunCtx;
|
|
52
52
|
|
|
53
53
|
describe("event constant exports", () => {
|
|
54
|
+
it("selects dedicated payout cancellations by default", () => {
|
|
55
|
+
expect(BETTER_STRIPE_WEBHOOK_EVENTS).toContain("payout.canceled");
|
|
56
|
+
expect(ALL_BETTER_STRIPE_EVENTS).toContain("payout.canceled");
|
|
57
|
+
});
|
|
58
|
+
|
|
54
59
|
it("ALL_BETTER_STRIPE_EVENTS is the concatenation of V1 then V2 events", () => {
|
|
55
60
|
expect(ALL_BETTER_STRIPE_EVENTS).toEqual([
|
|
56
61
|
...BETTER_STRIPE_WEBHOOK_EVENTS,
|
|
@@ -108,3 +108,12 @@ describe("scheduleAsyncHook — invoice.payment_failed (BTS-33)", () => {
|
|
|
108
108
|
);
|
|
109
109
|
});
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
describe("scheduleAsyncHook — payout cancellation", () => {
|
|
113
|
+
it("does not schedule the paid convenience hook for cancellation", async () => {
|
|
114
|
+
const whCtx = makeWhCtx({ doc: { status: "canceled" } });
|
|
115
|
+
await scheduleAsyncHook(whCtx, "payout.canceled", "po_canceled");
|
|
116
|
+
expect(whCtx.ctx.runQuery).not.toHaveBeenCalled();
|
|
117
|
+
expect(whCtx.ctx.scheduler?.runAfter).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// @vitest-environment edge-runtime
|
|
2
|
+
import Stripe from "stripe";
|
|
3
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import schema from "../../component/schema.js";
|
|
6
|
+
import { getStripeClient } from "../helpers.js";
|
|
7
|
+
import { components, initConvexTest } from "../setup.test.js";
|
|
8
|
+
import type { RegisterRoutesConfig, StripeWebhookEvent } from "../types.js";
|
|
9
|
+
import { handleWebhookRequest } from "./handler.js";
|
|
10
|
+
|
|
11
|
+
const modules = import.meta.glob("../../component/**/*.{ts,js,tsx,jsx}");
|
|
12
|
+
const key = "sk_test_payout_cancellation_regression";
|
|
13
|
+
const signingSecret = "whsec_payout_cancellation_regression";
|
|
14
|
+
const payoutRef = components.betterStripe.connect.queries.getPayoutByStripeId;
|
|
15
|
+
const ledgerRef = components.betterStripe.webhooks.queries.getWebhookEvent;
|
|
16
|
+
const transport = vi.fn(async () => {
|
|
17
|
+
throw new Error("Unexpected provider request in payout regression");
|
|
18
|
+
});
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
transport.mockClear();
|
|
21
|
+
vi.spyOn(
|
|
22
|
+
getStripeClient(key).getApiField("httpClient"),
|
|
23
|
+
"makeRequest",
|
|
24
|
+
).mockImplementation(transport);
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => vi.restoreAllMocks());
|
|
27
|
+
|
|
28
|
+
type PayoutEvent = Extract<
|
|
29
|
+
StripeWebhookEvent,
|
|
30
|
+
{
|
|
31
|
+
type:
|
|
32
|
+
| "payout.created"
|
|
33
|
+
| "payout.updated"
|
|
34
|
+
| "payout.paid"
|
|
35
|
+
| "payout.failed"
|
|
36
|
+
| "payout.canceled";
|
|
37
|
+
}
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
function fixture(
|
|
41
|
+
type: PayoutEvent["type"] = "payout.created",
|
|
42
|
+
status: PayoutEvent["data"]["object"]["status"] = "pending",
|
|
43
|
+
) {
|
|
44
|
+
const t = initConvexTest();
|
|
45
|
+
t.registerComponent("betterStripe", schema, modules);
|
|
46
|
+
const event: PayoutEvent = {
|
|
47
|
+
id: `evt_qa_${type.replace(".", "_")}`,
|
|
48
|
+
object: "event" as const,
|
|
49
|
+
api_version: null,
|
|
50
|
+
created: 1792010000,
|
|
51
|
+
pending_webhooks: 1,
|
|
52
|
+
request: null,
|
|
53
|
+
type,
|
|
54
|
+
account: "acct_qa_recipient",
|
|
55
|
+
livemode: false,
|
|
56
|
+
data: {
|
|
57
|
+
object: {
|
|
58
|
+
id: "po_qa_audit",
|
|
59
|
+
object: "payout" as const,
|
|
60
|
+
amount: 1200,
|
|
61
|
+
application_fee: null,
|
|
62
|
+
application_fee_amount: null,
|
|
63
|
+
automatic: true,
|
|
64
|
+
balance_transaction: null,
|
|
65
|
+
created: 1792010000,
|
|
66
|
+
description: null,
|
|
67
|
+
failure_balance_transaction: null,
|
|
68
|
+
failure_code: null,
|
|
69
|
+
failure_message: null,
|
|
70
|
+
livemode: false,
|
|
71
|
+
original_payout: null,
|
|
72
|
+
payout_method: null,
|
|
73
|
+
reconciliation_status: "not_applicable",
|
|
74
|
+
reversed_by: null,
|
|
75
|
+
source_type: "card",
|
|
76
|
+
statement_descriptor: null,
|
|
77
|
+
trace_id: null,
|
|
78
|
+
type: "bank_account",
|
|
79
|
+
currency: "usd",
|
|
80
|
+
status,
|
|
81
|
+
arrival_date: 1792108800,
|
|
82
|
+
method: "standard",
|
|
83
|
+
destination: "ba_qa_destination",
|
|
84
|
+
metadata: { accountId: "acct_untrusted_metadata" },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const payout = () => t.query(payoutRef, { stripePayoutId: "po_qa_audit" });
|
|
89
|
+
const deliver = async (config: RegisterRoutesConfig = {}, tamper = false) => {
|
|
90
|
+
const body = JSON.stringify(event);
|
|
91
|
+
const signature = await Stripe.webhooks.generateTestHeaderStringAsync({
|
|
92
|
+
payload: body,
|
|
93
|
+
secret: signingSecret,
|
|
94
|
+
});
|
|
95
|
+
return t.action(async (ctx) => {
|
|
96
|
+
const response = await handleWebhookRequest(
|
|
97
|
+
ctx,
|
|
98
|
+
new Request("https://example.test/stripe/webhook", {
|
|
99
|
+
method: "POST",
|
|
100
|
+
body: tamper ? body.replace("po_qa_audit", "po_tampered") : body,
|
|
101
|
+
headers: { "stripe-signature": signature },
|
|
102
|
+
}),
|
|
103
|
+
components.betterStripe,
|
|
104
|
+
{ stripeSecretKey: key, webhookSecret: signingSecret, ...config },
|
|
105
|
+
);
|
|
106
|
+
return { status: response.status, body: await response.json() };
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
return { t, event, payout, deliver };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
it("reconciles signed cancellation before callbacks and deduplicates replay", async () => {
|
|
113
|
+
const f = fixture("payout.created", "pending");
|
|
114
|
+
expect((await f.deliver()).status).toBe(200);
|
|
115
|
+
const created = await f.payout();
|
|
116
|
+
expect(created?.status).toBe("pending");
|
|
117
|
+
f.event.id = "evt_canceled_regression";
|
|
118
|
+
f.event.type = "payout.canceled";
|
|
119
|
+
f.event.data.object.status = "canceled";
|
|
120
|
+
const observed: string[] = [];
|
|
121
|
+
const paid = vi.fn();
|
|
122
|
+
const config: RegisterRoutesConfig = {
|
|
123
|
+
onEvent: async (ctx) => {
|
|
124
|
+
const row = await ctx.runQuery(payoutRef, {
|
|
125
|
+
stripePayoutId: "po_qa_audit",
|
|
126
|
+
});
|
|
127
|
+
observed.push(`all:${row?.status}`);
|
|
128
|
+
},
|
|
129
|
+
events: {
|
|
130
|
+
"payout.canceled": async (ctx) => {
|
|
131
|
+
const row = await ctx.runQuery(payoutRef, {
|
|
132
|
+
stripePayoutId: "po_qa_audit",
|
|
133
|
+
});
|
|
134
|
+
observed.push(`canceled:${row?.status}`);
|
|
135
|
+
},
|
|
136
|
+
"payout.paid": paid,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
expect((await f.deliver(config)).status).toBe(200);
|
|
140
|
+
const canceled = await f.payout();
|
|
141
|
+
expect(canceled).toMatchObject({
|
|
142
|
+
_id: created?._id,
|
|
143
|
+
stripePayoutId: "po_qa_audit",
|
|
144
|
+
accountId: "acct_qa_recipient",
|
|
145
|
+
amount: 1200,
|
|
146
|
+
currency: "usd",
|
|
147
|
+
status: "canceled",
|
|
148
|
+
});
|
|
149
|
+
expect(observed).toEqual(["all:canceled", "canceled:canceled"]);
|
|
150
|
+
expect(await f.deliver(config)).toMatchObject({
|
|
151
|
+
status: 200,
|
|
152
|
+
body: { deduplicated: true },
|
|
153
|
+
});
|
|
154
|
+
expect(await f.payout()).toEqual(canceled);
|
|
155
|
+
expect(observed).toEqual(["all:canceled", "canceled:canceled"]);
|
|
156
|
+
expect(paid).not.toHaveBeenCalled();
|
|
157
|
+
expect(
|
|
158
|
+
await f.t.query(ledgerRef, { stripeEventId: f.event.id }),
|
|
159
|
+
).toMatchObject({ status: "processed" });
|
|
160
|
+
expect(transport).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("rejects a tampered cancellation before storage or callbacks", async () => {
|
|
164
|
+
const f = fixture("payout.canceled", "canceled");
|
|
165
|
+
const onEvent = vi.fn();
|
|
166
|
+
expect((await f.deliver({ onEvent }, true)).status).toBe(400);
|
|
167
|
+
expect(await f.payout()).toBeNull();
|
|
168
|
+
expect(await f.t.query(ledgerRef, { stripeEventId: f.event.id })).toBeNull();
|
|
169
|
+
expect(onEvent).not.toHaveBeenCalled();
|
|
170
|
+
expect(transport).not.toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it.each([
|
|
174
|
+
["payout.created", "pending"],
|
|
175
|
+
["payout.updated", "in_transit"],
|
|
176
|
+
["payout.paid", "paid"],
|
|
177
|
+
["payout.failed", "failed"],
|
|
178
|
+
] as const)(
|
|
179
|
+
"preserves signed %s processing and replay",
|
|
180
|
+
async (type, status) => {
|
|
181
|
+
const f = fixture(type, status);
|
|
182
|
+
expect((await f.deliver()).status).toBe(200);
|
|
183
|
+
const first = await f.payout();
|
|
184
|
+
expect(first).toMatchObject({ accountId: "acct_qa_recipient", status });
|
|
185
|
+
expect(await f.deliver()).toMatchObject({
|
|
186
|
+
status: 200,
|
|
187
|
+
body: { deduplicated: true },
|
|
188
|
+
});
|
|
189
|
+
expect(await f.payout()).toEqual(first);
|
|
190
|
+
expect(transport).not.toHaveBeenCalled();
|
|
191
|
+
},
|
|
192
|
+
);
|
|
@@ -17,6 +17,12 @@ import type * as connect_validators from "../connect/validators.js";
|
|
|
17
17
|
import type * as core_mutations from "../core/mutations.js";
|
|
18
18
|
import type * as core_queries from "../core/queries.js";
|
|
19
19
|
import type * as core_validators from "../core/validators.js";
|
|
20
|
+
import type * as costRecovery_claims from "../costRecovery/claims.js";
|
|
21
|
+
import type * as costRecovery_completion from "../costRecovery/completion.js";
|
|
22
|
+
import type * as costRecovery_helpers from "../costRecovery/helpers.js";
|
|
23
|
+
import type * as costRecovery_mutations from "../costRecovery/mutations.js";
|
|
24
|
+
import type * as costRecovery_queries from "../costRecovery/queries.js";
|
|
25
|
+
import type * as costRecovery_reservations from "../costRecovery/reservations.js";
|
|
20
26
|
import type * as lib_fees from "../lib/fees.js";
|
|
21
27
|
import type * as lib_idempotency from "../lib/idempotency.js";
|
|
22
28
|
import type * as lib_paymentSeller from "../lib/paymentSeller.js";
|
|
@@ -48,6 +54,12 @@ const fullApi: ApiFromModules<{
|
|
|
48
54
|
"core/mutations": typeof core_mutations;
|
|
49
55
|
"core/queries": typeof core_queries;
|
|
50
56
|
"core/validators": typeof core_validators;
|
|
57
|
+
"costRecovery/claims": typeof costRecovery_claims;
|
|
58
|
+
"costRecovery/completion": typeof costRecovery_completion;
|
|
59
|
+
"costRecovery/helpers": typeof costRecovery_helpers;
|
|
60
|
+
"costRecovery/mutations": typeof costRecovery_mutations;
|
|
61
|
+
"costRecovery/queries": typeof costRecovery_queries;
|
|
62
|
+
"costRecovery/reservations": typeof costRecovery_reservations;
|
|
51
63
|
"lib/fees": typeof lib_fees;
|
|
52
64
|
"lib/idempotency": typeof lib_idempotency;
|
|
53
65
|
"lib/paymentSeller": typeof lib_paymentSeller;
|