@fantastic.dev/better-stripe 0.22.0 → 0.23.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 +6 -0
- package/README.md +5 -3
- package/dist/client/connect/payoutActivationProof.d.ts +16 -0
- package/dist/client/connect/payoutActivationProof.d.ts.map +1 -0
- package/dist/client/connect/payoutActivationProof.js +72 -0
- package/dist/client/connect/payoutActivationProof.js.map +1 -0
- package/dist/client/connect/payoutAdoption.d.ts +35 -0
- package/dist/client/connect/payoutAdoption.d.ts.map +1 -0
- package/dist/client/connect/payoutAdoption.js +230 -0
- package/dist/client/connect/payoutAdoption.js.map +1 -0
- package/dist/client/connect/payoutAdoptionAge.d.ts +19 -0
- package/dist/client/connect/payoutAdoptionAge.d.ts.map +1 -0
- package/dist/client/connect/payoutAdoptionAge.js +56 -0
- package/dist/client/connect/payoutAdoptionAge.js.map +1 -0
- package/dist/client/connect/payoutAdoptionAllocation.d.ts +24 -0
- package/dist/client/connect/payoutAdoptionAllocation.d.ts.map +1 -0
- package/dist/client/connect/payoutAdoptionAllocation.js +78 -0
- package/dist/client/connect/payoutAdoptionAllocation.js.map +1 -0
- package/dist/client/connect/payoutReconciliation.d.ts.map +1 -1
- package/dist/client/connect/payoutReconciliation.js +4 -2
- package/dist/client/connect/payoutReconciliation.js.map +1 -1
- package/dist/client/payoutPolicy.d.ts +7 -1
- package/dist/client/payoutPolicy.d.ts.map +1 -1
- package/dist/client/payoutPolicy.js +78 -7
- package/dist/client/payoutPolicy.js.map +1 -1
- package/dist/component/_generated/component.d.ts +196 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/earnings/triggers.d.ts +38 -4
- package/dist/component/earnings/triggers.d.ts.map +1 -1
- package/dist/component/payoutPolicy/adoption.d.ts +9 -0
- package/dist/component/payoutPolicy/adoption.d.ts.map +1 -0
- package/dist/component/payoutPolicy/adoption.js +45 -0
- package/dist/component/payoutPolicy/adoption.js.map +1 -0
- package/dist/component/payoutPolicy/mutations.d.ts +96 -0
- package/dist/component/payoutPolicy/mutations.d.ts.map +1 -1
- package/dist/component/payoutPolicy/mutations.js +38 -5
- package/dist/component/payoutPolicy/mutations.js.map +1 -1
- package/dist/component/payoutPolicy/queries.d.ts +16 -0
- package/dist/component/payoutPolicy/queries.d.ts.map +1 -1
- package/dist/component/payoutPolicy/schema.d.ts +299 -2
- package/dist/component/payoutPolicy/schema.d.ts.map +1 -1
- package/dist/component/payoutPolicy/schema.js +7 -0
- package/dist/component/payoutPolicy/schema.js.map +1 -1
- package/dist/component/payoutRelease/adoption.d.ts +269 -0
- package/dist/component/payoutRelease/adoption.d.ts.map +1 -0
- package/dist/component/payoutRelease/adoption.js +197 -0
- package/dist/component/payoutRelease/adoption.js.map +1 -0
- package/dist/component/payoutRelease/helpers.d.ts +3 -0
- package/dist/component/payoutRelease/helpers.d.ts.map +1 -1
- package/dist/component/payoutRelease/helpers.js +20 -0
- package/dist/component/payoutRelease/helpers.js.map +1 -1
- package/dist/component/payoutRelease/mutations.d.ts +81 -0
- package/dist/component/payoutRelease/mutations.d.ts.map +1 -1
- package/dist/component/payoutRelease/mutations.js +6 -0
- package/dist/component/payoutRelease/mutations.js.map +1 -1
- package/dist/component/payoutRelease/observation.d.ts +18 -0
- package/dist/component/payoutRelease/observation.d.ts.map +1 -0
- package/dist/component/payoutRelease/observation.js +11 -0
- package/dist/component/payoutRelease/observation.js.map +1 -0
- package/dist/component/payoutRelease/queries.d.ts +3 -0
- package/dist/component/payoutRelease/queries.d.ts.map +1 -1
- package/dist/component/payoutRelease/schema.d.ts +8 -19
- package/dist/component/payoutRelease/schema.d.ts.map +1 -1
- package/dist/component/payoutRelease/schema.js +3 -9
- package/dist/component/payoutRelease/schema.js.map +1 -1
- package/dist/component/schema.d.ts +73 -2
- package/docs/settlement.md +121 -40
- package/package.json +1 -1
- package/src/client/connect/payoutActivationProof.ts +101 -0
- package/src/client/connect/payoutAdoption.test.ts +421 -0
- package/src/client/connect/payoutAdoption.ts +330 -0
- package/src/client/connect/payoutAdoptionAge.test.ts +89 -0
- package/src/client/connect/payoutAdoptionAge.ts +74 -0
- package/src/client/connect/payoutAdoptionAllocation.test.ts +132 -0
- package/src/client/connect/payoutAdoptionAllocation.ts +106 -0
- package/src/client/connect/payoutReconciliation.test.ts +16 -0
- package/src/client/connect/payoutReconciliation.ts +6 -2
- package/src/client/payoutPolicy.test.ts +16 -0
- package/src/client/payoutPolicy.ts +107 -5
- package/src/component/_generated/api.ts +6 -0
- package/src/component/_generated/component.ts +192 -1
- package/src/component/payout-adoption.test.ts +272 -0
- package/src/component/payout-policy-adoption.test.ts +131 -0
- package/src/component/payoutPolicy/adoption.ts +76 -0
- package/src/component/payoutPolicy/mutations.ts +46 -3
- package/src/component/payoutPolicy/schema.ts +9 -0
- package/src/component/payoutRelease/adoption.ts +262 -0
- package/src/component/payoutRelease/helpers.ts +21 -0
- package/src/component/payoutRelease/mutations.ts +10 -0
- package/src/component/payoutRelease/observation.ts +10 -0
- package/src/component/payoutRelease/schema.ts +3 -9
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { addSummaryAmounts as add } from "../../component/earnings/arithmetic.js";
|
|
2
|
+
export type HistoricalFundingSource = {
|
|
3
|
+
stripeTransferId: string;
|
|
4
|
+
currency: string;
|
|
5
|
+
transactions: {
|
|
6
|
+
net: number;
|
|
7
|
+
created: number;
|
|
8
|
+
availableOn: number;
|
|
9
|
+
status: string;
|
|
10
|
+
}[];
|
|
11
|
+
};
|
|
12
|
+
export type HistoricalFundingPayout = {
|
|
13
|
+
stripePayoutId: string;
|
|
14
|
+
currency: string;
|
|
15
|
+
amount: number;
|
|
16
|
+
created: number;
|
|
17
|
+
returnedAt?: number;
|
|
18
|
+
allocations: { stripeTransferId: string; amount: number }[];
|
|
19
|
+
};
|
|
20
|
+
function integer(n: number) {
|
|
21
|
+
if (!Number.isSafeInteger(n))
|
|
22
|
+
throw new Error("Invalid historical funding arithmetic.");
|
|
23
|
+
return n;
|
|
24
|
+
}
|
|
25
|
+
function timestamp(n: number) {
|
|
26
|
+
if (integer(n) < 0) throw new Error("Invalid historical funding timestamp.");
|
|
27
|
+
}
|
|
28
|
+
/** Validate explicit historical cash provenance, not today's still-unpaid entitlement. */
|
|
29
|
+
export function validateHistoricalFunding(
|
|
30
|
+
sources: HistoricalFundingSource[],
|
|
31
|
+
payouts: HistoricalFundingPayout[],
|
|
32
|
+
): void {
|
|
33
|
+
const byId = new Map<string, HistoricalFundingSource>();
|
|
34
|
+
for (const s of sources) {
|
|
35
|
+
if (byId.has(s.stripeTransferId))
|
|
36
|
+
throw new Error("Duplicate historical funding source.");
|
|
37
|
+
if (!/^tr_\w+$/.test(s.stripeTransferId) || !/^[a-z]{3}$/.test(s.currency))
|
|
38
|
+
throw new Error("Invalid historical funding source.");
|
|
39
|
+
for (const t of s.transactions) {
|
|
40
|
+
integer(t.net);
|
|
41
|
+
timestamp(t.created);
|
|
42
|
+
timestamp(t.availableOn);
|
|
43
|
+
if (!["available", "pending"].includes(t.status))
|
|
44
|
+
throw new Error("Invalid historical funding status.");
|
|
45
|
+
}
|
|
46
|
+
byId.set(s.stripeTransferId, s);
|
|
47
|
+
}
|
|
48
|
+
const ids = new Set<string>();
|
|
49
|
+
for (const p of payouts) {
|
|
50
|
+
if (ids.has(p.stripePayoutId))
|
|
51
|
+
throw new Error("Duplicate historical payout.");
|
|
52
|
+
ids.add(p.stripePayoutId);
|
|
53
|
+
if (
|
|
54
|
+
!/^po_\w+$/.test(p.stripePayoutId) ||
|
|
55
|
+
!/^[a-z]{3}$/.test(p.currency) ||
|
|
56
|
+
integer(p.amount) <= 0
|
|
57
|
+
)
|
|
58
|
+
throw new Error("Invalid historical payout principal.");
|
|
59
|
+
timestamp(p.created);
|
|
60
|
+
if (p.returnedAt !== undefined) {
|
|
61
|
+
timestamp(p.returnedAt);
|
|
62
|
+
if (p.returnedAt < p.created)
|
|
63
|
+
throw new Error("Historical payout return predates the payout.");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const prior: HistoricalFundingPayout[] = [];
|
|
67
|
+
for (const p of [...payouts].sort(
|
|
68
|
+
(a, b) =>
|
|
69
|
+
a.created - b.created || a.stripePayoutId.localeCompare(b.stripePayoutId),
|
|
70
|
+
)) {
|
|
71
|
+
const seen = new Set<string>();
|
|
72
|
+
let principal = 0;
|
|
73
|
+
for (const a of p.allocations) {
|
|
74
|
+
if (seen.has(a.stripeTransferId))
|
|
75
|
+
throw new Error("Duplicate historical source allocation.");
|
|
76
|
+
seen.add(a.stripeTransferId);
|
|
77
|
+
if (integer(a.amount) <= 0)
|
|
78
|
+
throw new Error("Invalid historical principal allocation.");
|
|
79
|
+
principal = add(principal, a.amount);
|
|
80
|
+
const s = byId.get(a.stripeTransferId);
|
|
81
|
+
if (!s)
|
|
82
|
+
throw new Error("Historical payout funding source is unavailable.");
|
|
83
|
+
if (s.currency !== p.currency)
|
|
84
|
+
throw new Error("Historical payout currency allocation mismatch.");
|
|
85
|
+
let capacity = 0;
|
|
86
|
+
for (const t of s.transactions)
|
|
87
|
+
if (
|
|
88
|
+
t.status === "available" &&
|
|
89
|
+
Math.max(t.created, t.availableOn) <= p.created
|
|
90
|
+
)
|
|
91
|
+
capacity = add(capacity, t.net);
|
|
92
|
+
for (const old of prior)
|
|
93
|
+
if (old.returnedAt === undefined || old.returnedAt > p.created)
|
|
94
|
+
for (const allocation of old.allocations)
|
|
95
|
+
if (allocation.stripeTransferId === s.stripeTransferId)
|
|
96
|
+
capacity = add(capacity, -allocation.amount);
|
|
97
|
+
if (a.amount > capacity)
|
|
98
|
+
throw new Error(
|
|
99
|
+
"Historical payout exceeds verified mature source funding.",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (principal !== p.amount)
|
|
103
|
+
throw new Error("Historical allocations do not equal payout principal.");
|
|
104
|
+
prior.push(p);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -396,3 +396,19 @@ it("rejects a payout sent to a different bank than the immutable claim", async (
|
|
|
396
396
|
await s.t.run((ctx) => ctx.db.query("payoutSourceAllocations").collect()),
|
|
397
397
|
).toMatchObject([{ paidAmount: 0, reservedAmount: 1000 }]);
|
|
398
398
|
});
|
|
399
|
+
it("reconciles an explicitly adopted automatic payout without a package claim marker", async () => {
|
|
400
|
+
const s = await setup();
|
|
401
|
+
await s.t.run(async (ctx) => {
|
|
402
|
+
const c = await ctx.db.query("payoutReleaseClaims").first();
|
|
403
|
+
await ctx.db.patch("payoutReleaseClaims", c!._id, {
|
|
404
|
+
historical: true,
|
|
405
|
+
stripePayoutId: "po_one",
|
|
406
|
+
destinationId: "ba_one",
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
s.setPayout({ automatic: true, metadata: {} as never });
|
|
410
|
+
expect(await s.reconcile()).toMatchObject({
|
|
411
|
+
status: "updated",
|
|
412
|
+
claim: { status: "paid", historical: true },
|
|
413
|
+
});
|
|
414
|
+
});
|
|
@@ -132,7 +132,7 @@ export async function reconcileRecipientPayout(
|
|
|
132
132
|
const receipt = await getRecipientPayoutReceipt(stripe, {
|
|
133
133
|
accountId: options.accountId,
|
|
134
134
|
stripePayoutId: payoutId,
|
|
135
|
-
operationKey: options.operationKey,
|
|
135
|
+
...(claim.historical ? {} : { operationKey: options.operationKey }),
|
|
136
136
|
});
|
|
137
137
|
if (receipt.status !== "ready")
|
|
138
138
|
return { status: "held", reason: receipt.reason };
|
|
@@ -151,7 +151,11 @@ export async function reconcileRecipientPayout(
|
|
|
151
151
|
);
|
|
152
152
|
// Current claims use the default card source balance. Other rails need a
|
|
153
153
|
// persisted allocation contract before a provider receipt can be adopted.
|
|
154
|
-
if (
|
|
154
|
+
if (
|
|
155
|
+
receipt.sourceType !== "card" ||
|
|
156
|
+
(claim.historical &&
|
|
157
|
+
(receipt.method !== "standard" || receipt.type !== "bank_account"))
|
|
158
|
+
)
|
|
155
159
|
return { status: "held", reason: "source_allocation_required" };
|
|
156
160
|
const fee = addSummaryAmounts(receipt.debit.fee, receipt.credit?.fee ?? 0);
|
|
157
161
|
if (fee < 0) return { status: "held", reason: "fee_allocation_required" };
|
|
@@ -318,3 +318,19 @@ it("rechecks delayed activation and cancels safely when funds arrive after prepa
|
|
|
318
318
|
status: "ready",
|
|
319
319
|
});
|
|
320
320
|
});
|
|
321
|
+
it("requires adoption for historical payouts even when today's balance is zero", async () => {
|
|
322
|
+
const { client, ctx, provider } = await setup();
|
|
323
|
+
provider.payouts.list.mockImplementation(
|
|
324
|
+
async (params?: { status?: string }) =>
|
|
325
|
+
({
|
|
326
|
+
data: params?.status ? [] : [{ id: "po_old" }],
|
|
327
|
+
has_more: false,
|
|
328
|
+
}) as never,
|
|
329
|
+
);
|
|
330
|
+
expect(await client.prepare(ctx, options)).toMatchObject({
|
|
331
|
+
status: "migration_required",
|
|
332
|
+
reason: "existing_payout_history",
|
|
333
|
+
});
|
|
334
|
+
expect(provider.balanceSettings.update).not.toHaveBeenCalled();
|
|
335
|
+
expect(await client.get(ctx, scope)).toBeNull();
|
|
336
|
+
});
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { collectAdoptionProof } from "./connect/payoutActivationProof.js";
|
|
2
|
+
import {
|
|
3
|
+
adoptRecipientPayouts,
|
|
4
|
+
type PayoutAdoptionOptions,
|
|
5
|
+
} from "./connect/payoutAdoption.js";
|
|
6
|
+
export type * from "./connect/payoutAdoption.js";
|
|
1
7
|
import {
|
|
2
8
|
releaseRecipientPayout,
|
|
3
9
|
type PayoutReleaseOptions,
|
|
@@ -32,6 +38,7 @@ import {
|
|
|
32
38
|
canonicalSchedule,
|
|
33
39
|
canonicalThresholds,
|
|
34
40
|
validateScope,
|
|
41
|
+
holdingDays,
|
|
35
42
|
} from "../component/payoutPolicy/helpers.js";
|
|
36
43
|
export type PayoutPolicy = Infer<typeof policyDoc>;
|
|
37
44
|
export type PayoutPolicyOperation = Infer<typeof operationDoc>;
|
|
@@ -42,14 +49,18 @@ export type PayoutPolicyOptions = PayoutPolicyScope & {
|
|
|
42
49
|
mode: PayoutPolicy["mode"];
|
|
43
50
|
thresholds: PayoutPolicy["thresholds"];
|
|
44
51
|
consent?: true;
|
|
52
|
+
/** Explicit funded-account adoption; historical payout mappings must already be imported. */
|
|
53
|
+
adoptExistingBalance?: true;
|
|
45
54
|
};
|
|
46
55
|
export type PayoutPolicyLimitation = {
|
|
47
56
|
status: "unsupported" | "migration_required";
|
|
57
|
+
adoptionReason?: string;
|
|
48
58
|
reason:
|
|
49
59
|
| "provider_permission_denied"
|
|
50
60
|
| "payout_control_unverified"
|
|
51
61
|
| "country_unavailable"
|
|
52
62
|
| "payouts_unavailable"
|
|
63
|
+
| "existing_payout_history"
|
|
53
64
|
| "existing_balance"
|
|
54
65
|
| "in_flight_payouts"
|
|
55
66
|
| "existing_manual_schedule";
|
|
@@ -90,6 +101,9 @@ export class PayoutPolicyClient {
|
|
|
90
101
|
private readonly component: Component,
|
|
91
102
|
private readonly provider: () => Stripe,
|
|
92
103
|
) {}
|
|
104
|
+
adoptPayouts(ctx: RunCtx, opts: PayoutAdoptionOptions) {
|
|
105
|
+
return adoptRecipientPayouts(this.provider(), this.component, ctx, opts);
|
|
106
|
+
}
|
|
93
107
|
release(ctx: RunCtx, opts: PayoutReleaseOptions) {
|
|
94
108
|
return releaseRecipientPayout(
|
|
95
109
|
this.provider(),
|
|
@@ -212,6 +226,15 @@ export class PayoutPolicyClient {
|
|
|
212
226
|
if (payouts.has_more || payouts.data.length)
|
|
213
227
|
return { status: "migration_required", reason: "in_flight_payouts" };
|
|
214
228
|
}
|
|
229
|
+
const history = await stripe.payouts.list(
|
|
230
|
+
{ limit: 1 },
|
|
231
|
+
{ stripeAccount: scope.accountId },
|
|
232
|
+
);
|
|
233
|
+
if (history.has_more || history.data.length)
|
|
234
|
+
return {
|
|
235
|
+
status: "migration_required",
|
|
236
|
+
reason: "existing_payout_history",
|
|
237
|
+
};
|
|
215
238
|
return null;
|
|
216
239
|
}
|
|
217
240
|
/** Persist restoration intent first. Existing mixed funds require explicit adoption; never infer ownership. */
|
|
@@ -250,7 +273,8 @@ export class PayoutPolicyClient {
|
|
|
250
273
|
if (
|
|
251
274
|
prior.mode !== opts.mode ||
|
|
252
275
|
prior.expectedRevision !== opts.expectedRevision ||
|
|
253
|
-
!equal(prior.thresholds, thresholds)
|
|
276
|
+
!equal(prior.thresholds, thresholds) ||
|
|
277
|
+
Boolean(prior.adoption) !== Boolean(opts.adoptExistingBalance)
|
|
254
278
|
)
|
|
255
279
|
throw new Error("Payout policy operation identity cannot be reused.");
|
|
256
280
|
return { status: "prepared", operation: prior };
|
|
@@ -266,14 +290,48 @@ export class PayoutPolicyClient {
|
|
|
266
290
|
policy?.country,
|
|
267
291
|
);
|
|
268
292
|
if ("status" in controls) return controls;
|
|
293
|
+
let adoption: PayoutPolicyOperation["adoption"];
|
|
294
|
+
if (
|
|
295
|
+
opts.adoptExistingBalance &&
|
|
296
|
+
(opts.mode !== "custom" || policy?.mode !== "stripe_managed")
|
|
297
|
+
)
|
|
298
|
+
throw new Error("Funded adoption requires an owned automatic policy.");
|
|
269
299
|
if (opts.mode === "custom" && policy?.mode !== "custom") {
|
|
270
300
|
if (controls.schedule.interval === "manual")
|
|
271
301
|
return {
|
|
272
302
|
status: "migration_required",
|
|
273
303
|
reason: "existing_manual_schedule",
|
|
274
304
|
};
|
|
275
|
-
|
|
276
|
-
|
|
305
|
+
if (opts.adoptExistingBalance) {
|
|
306
|
+
const proof = await collectAdoptionProof(
|
|
307
|
+
this.provider(),
|
|
308
|
+
this.component,
|
|
309
|
+
ctx,
|
|
310
|
+
opts,
|
|
311
|
+
policy!.revision,
|
|
312
|
+
thresholds.map((t) => t.currency),
|
|
313
|
+
);
|
|
314
|
+
if (proof.status !== "verified")
|
|
315
|
+
return {
|
|
316
|
+
status: "migration_required",
|
|
317
|
+
reason: "existing_balance",
|
|
318
|
+
adoptionReason: proof.reason,
|
|
319
|
+
};
|
|
320
|
+
adoption = proof.proof;
|
|
321
|
+
if (
|
|
322
|
+
adoption.oldestHeldAt !== undefined &&
|
|
323
|
+
adoption.oldestHeldAt + holdingDays(controls.country) * 86400000 <=
|
|
324
|
+
Date.now()
|
|
325
|
+
)
|
|
326
|
+
return {
|
|
327
|
+
status: "migration_required",
|
|
328
|
+
reason: "existing_balance",
|
|
329
|
+
adoptionReason: "holding_deadline_expired",
|
|
330
|
+
};
|
|
331
|
+
} else {
|
|
332
|
+
const migration = await this.migrationPreflight(opts);
|
|
333
|
+
if (migration) return migration;
|
|
334
|
+
}
|
|
277
335
|
}
|
|
278
336
|
const operation = (await runMutationOrThrow(
|
|
279
337
|
ctx,
|
|
@@ -288,6 +346,7 @@ export class PayoutPolicyClient {
|
|
|
288
346
|
country: controls.country,
|
|
289
347
|
observedSchedule: controls.schedule,
|
|
290
348
|
observedAt,
|
|
349
|
+
...(adoption ? { adoption } : {}),
|
|
291
350
|
},
|
|
292
351
|
)) as PayoutPolicyOperation;
|
|
293
352
|
return { status: "prepared", operation };
|
|
@@ -302,6 +361,11 @@ export class PayoutPolicyClient {
|
|
|
302
361
|
| { status: "canceled"; operation: PayoutPolicyOperation }
|
|
303
362
|
| PayoutPolicyLimitation
|
|
304
363
|
> {
|
|
364
|
+
args = {
|
|
365
|
+
accountId: args.accountId,
|
|
366
|
+
ownerKey: args.ownerKey,
|
|
367
|
+
operationKey: args.operationKey,
|
|
368
|
+
};
|
|
305
369
|
const operation = await this.operation(ctx, args);
|
|
306
370
|
if (!operation) throw new Error("Payout policy operation is unavailable.");
|
|
307
371
|
if (operation.status === "complete") return { status: "ready", operation };
|
|
@@ -334,6 +398,13 @@ export class PayoutPolicyClient {
|
|
|
334
398
|
if ("status" in controls) return controls;
|
|
335
399
|
if (operation.mode === "custom" && controls.country !== operation.country)
|
|
336
400
|
return { status: "schedule_drift" };
|
|
401
|
+
if (
|
|
402
|
+
operation.adoption &&
|
|
403
|
+
!operation.attempted &&
|
|
404
|
+
equal(controls.schedule, operation.targetSchedule)
|
|
405
|
+
)
|
|
406
|
+
return { status: "schedule_drift" };
|
|
407
|
+
let freshAdoption: PayoutPolicyOperation["adoption"];
|
|
337
408
|
if (!equal(controls.schedule, operation.targetSchedule)) {
|
|
338
409
|
if (!equal(controls.schedule, operation.observedSchedule))
|
|
339
410
|
return { status: "schedule_drift" };
|
|
@@ -342,7 +413,38 @@ export class PayoutPolicyClient {
|
|
|
342
413
|
!operation.attempted &&
|
|
343
414
|
policy?.mode === "stripe_managed"
|
|
344
415
|
) {
|
|
345
|
-
const
|
|
416
|
+
const result = operation.adoption
|
|
417
|
+
? await collectAdoptionProof(
|
|
418
|
+
this.provider(),
|
|
419
|
+
this.component,
|
|
420
|
+
ctx,
|
|
421
|
+
args,
|
|
422
|
+
policy.revision,
|
|
423
|
+
operation.thresholds.map((t) => t.currency),
|
|
424
|
+
)
|
|
425
|
+
: null;
|
|
426
|
+
if (result?.status === "verified") freshAdoption = result.proof;
|
|
427
|
+
const ageExpired =
|
|
428
|
+
result?.status === "verified" &&
|
|
429
|
+
result.proof.oldestHeldAt !== undefined &&
|
|
430
|
+
result.proof.oldestHeldAt +
|
|
431
|
+
holdingDays(operation.country) * 86400000 <=
|
|
432
|
+
Date.now();
|
|
433
|
+
const migration: PayoutPolicyLimitation | null = ageExpired
|
|
434
|
+
? {
|
|
435
|
+
status: "migration_required",
|
|
436
|
+
reason: "existing_balance",
|
|
437
|
+
adoptionReason: "holding_deadline_expired",
|
|
438
|
+
}
|
|
439
|
+
: result?.status === "held"
|
|
440
|
+
? {
|
|
441
|
+
status: "migration_required",
|
|
442
|
+
reason: "existing_balance",
|
|
443
|
+
adoptionReason: result.reason,
|
|
444
|
+
}
|
|
445
|
+
: result
|
|
446
|
+
? null
|
|
447
|
+
: await this.migrationPreflight(args);
|
|
346
448
|
if (migration) {
|
|
347
449
|
await runMutationOrThrow(
|
|
348
450
|
ctx,
|
|
@@ -358,7 +460,7 @@ export class PayoutPolicyClient {
|
|
|
358
460
|
await runMutationOrThrow(
|
|
359
461
|
ctx,
|
|
360
462
|
componentRef(this.component, "payoutPolicy/mutations/beginAttempt"),
|
|
361
|
-
args,
|
|
463
|
+
{ ...args, ...(freshAdoption ? { adoption: freshAdoption } : {}) },
|
|
362
464
|
);
|
|
363
465
|
try {
|
|
364
466
|
await this.provider().balanceSettings.update(
|
|
@@ -34,13 +34,16 @@ import type * as lib_idempotency from "../lib/idempotency.js";
|
|
|
34
34
|
import type * as lib_paymentSeller from "../lib/paymentSeller.js";
|
|
35
35
|
import type * as lib_reversals from "../lib/reversals.js";
|
|
36
36
|
import type * as lib_stripeFactory from "../lib/stripeFactory.js";
|
|
37
|
+
import type * as payoutPolicy_adoption from "../payoutPolicy/adoption.js";
|
|
37
38
|
import type * as payoutPolicy_helpers from "../payoutPolicy/helpers.js";
|
|
38
39
|
import type * as payoutPolicy_mutations from "../payoutPolicy/mutations.js";
|
|
39
40
|
import type * as payoutPolicy_queries from "../payoutPolicy/queries.js";
|
|
41
|
+
import type * as payoutRelease_adoption from "../payoutRelease/adoption.js";
|
|
40
42
|
import type * as payoutRelease_allocation from "../payoutRelease/allocation.js";
|
|
41
43
|
import type * as payoutRelease_fees from "../payoutRelease/fees.js";
|
|
42
44
|
import type * as payoutRelease_helpers from "../payoutRelease/helpers.js";
|
|
43
45
|
import type * as payoutRelease_mutations from "../payoutRelease/mutations.js";
|
|
46
|
+
import type * as payoutRelease_observation from "../payoutRelease/observation.js";
|
|
44
47
|
import type * as payoutRelease_queries from "../payoutRelease/queries.js";
|
|
45
48
|
import type * as products_mutations from "../products/mutations.js";
|
|
46
49
|
import type * as products_queries from "../products/queries.js";
|
|
@@ -86,13 +89,16 @@ const fullApi: ApiFromModules<{
|
|
|
86
89
|
"lib/paymentSeller": typeof lib_paymentSeller;
|
|
87
90
|
"lib/reversals": typeof lib_reversals;
|
|
88
91
|
"lib/stripeFactory": typeof lib_stripeFactory;
|
|
92
|
+
"payoutPolicy/adoption": typeof payoutPolicy_adoption;
|
|
89
93
|
"payoutPolicy/helpers": typeof payoutPolicy_helpers;
|
|
90
94
|
"payoutPolicy/mutations": typeof payoutPolicy_mutations;
|
|
91
95
|
"payoutPolicy/queries": typeof payoutPolicy_queries;
|
|
96
|
+
"payoutRelease/adoption": typeof payoutRelease_adoption;
|
|
92
97
|
"payoutRelease/allocation": typeof payoutRelease_allocation;
|
|
93
98
|
"payoutRelease/fees": typeof payoutRelease_fees;
|
|
94
99
|
"payoutRelease/helpers": typeof payoutRelease_helpers;
|
|
95
100
|
"payoutRelease/mutations": typeof payoutRelease_mutations;
|
|
101
|
+
"payoutRelease/observation": typeof payoutRelease_observation;
|
|
96
102
|
"payoutRelease/queries": typeof payoutRelease_queries;
|
|
97
103
|
"products/mutations": typeof products_mutations;
|
|
98
104
|
"products/queries": typeof products_queries;
|