@fantastic.dev/better-stripe 0.28.2 → 0.28.3
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 +3 -1
- package/dist/client/connect/payoutExecutionTestHelpers.js +1 -1
- package/dist/client/connect/payoutExecutionTestHelpers.js.map +1 -1
- package/dist/component/_generated/component.d.ts +2 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/payoutRelease/deadline.d.ts +10 -0
- package/dist/component/payoutRelease/deadline.d.ts.map +1 -0
- package/dist/component/payoutRelease/deadline.js +20 -0
- package/dist/component/payoutRelease/deadline.js.map +1 -0
- package/dist/component/payoutRelease/planning.d.ts +1 -0
- package/dist/component/payoutRelease/planning.d.ts.map +1 -1
- package/dist/component/payoutRelease/planning.js +5 -2
- package/dist/component/payoutRelease/planning.js.map +1 -1
- package/dist/component/payoutRelease/preview.d.ts +7 -1
- package/dist/component/payoutRelease/preview.d.ts.map +1 -1
- package/dist/component/payoutRelease/preview.js +5 -1
- package/dist/component/payoutRelease/preview.js.map +1 -1
- package/dist/component/payoutRelease/queries.d.ts +2 -0
- package/dist/component/payoutRelease/queries.d.ts.map +1 -1
- package/dist/component/payoutWorker/mutations.d.ts.map +1 -1
- package/dist/component/payoutWorker/mutations.js +12 -4
- package/dist/component/payoutWorker/mutations.js.map +1 -1
- package/dist/react/workflows/PayoutSettings.d.ts.map +1 -1
- package/dist/react/workflows/PayoutSettings.js +1 -1
- package/dist/react/workflows/PayoutSettings.js.map +1 -1
- package/dist/react/workflows/payout-progress-state.d.ts.map +1 -1
- package/dist/react/workflows/payout-progress-state.js +16 -7
- package/dist/react/workflows/payout-progress-state.js.map +1 -1
- package/docs/react-workflows.md +3 -0
- package/docs/settlement.md +22 -0
- package/package.json +1 -1
- package/src/client/connect/payoutDeadline.test.ts +168 -0
- package/src/client/connect/payoutExecutionTestHelpers.ts +1 -1
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +2 -0
- package/src/component/payoutRelease/deadline.ts +24 -0
- package/src/component/payoutRelease/planning.ts +5 -2
- package/src/component/payoutRelease/preview.ts +5 -0
- package/src/component/payoutWorker/mutations.ts +20 -4
- package/src/react/workflows/PayoutProgress.test.tsx +23 -0
- package/src/react/workflows/PayoutSettings.tsx +4 -1
- package/src/react/workflows/payout-progress-state.ts +19 -7
|
@@ -3639,8 +3639,10 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
3639
3639
|
amount: number;
|
|
3640
3640
|
confirmedFees: number;
|
|
3641
3641
|
currency: string;
|
|
3642
|
+
holdDeadline?: number;
|
|
3642
3643
|
independentLiabilities: number;
|
|
3643
3644
|
matureUnpaid: number;
|
|
3645
|
+
minimumWaived?: boolean;
|
|
3644
3646
|
observedAt: number;
|
|
3645
3647
|
pendingCredits: number;
|
|
3646
3648
|
providerAvailable: number;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** One operational day to retry final payouts before the country holding deadline. */
|
|
2
|
+
export const finalAttemptWindowMs = 86400000;
|
|
3
|
+
type Policy = { holdDeadline?: number } | null;
|
|
4
|
+
export function finalAttemptStartsAt(policy: Policy): number | undefined {
|
|
5
|
+
return policy?.holdDeadline === undefined
|
|
6
|
+
? undefined
|
|
7
|
+
: policy.holdDeadline - finalAttemptWindowMs;
|
|
8
|
+
}
|
|
9
|
+
export function waiveApplicationMinimum(policy: Policy, now: number): boolean {
|
|
10
|
+
const start = finalAttemptStartsAt(policy);
|
|
11
|
+
return start !== undefined && now >= start && now < policy!.holdDeadline!;
|
|
12
|
+
}
|
|
13
|
+
export function nextPayoutAttempt(
|
|
14
|
+
policy: Policy,
|
|
15
|
+
now: number,
|
|
16
|
+
cadence: number,
|
|
17
|
+
) {
|
|
18
|
+
const start = finalAttemptStartsAt(policy);
|
|
19
|
+
if (waiveApplicationMinimum(policy, now))
|
|
20
|
+
return now + Math.min(cadence, 300000);
|
|
21
|
+
return start !== undefined && start > now
|
|
22
|
+
? Math.min(now + cadence, start)
|
|
23
|
+
: now + cadence;
|
|
24
|
+
}
|
|
@@ -5,6 +5,7 @@ import type { observation } from "./observation.js";
|
|
|
5
5
|
import { checkSnapshot, sourceById } from "./helpers.js";
|
|
6
6
|
import { readFees } from "./fees.js";
|
|
7
7
|
import { planRecipientPayout, type PayoutFundingSource } from "./allocation.js";
|
|
8
|
+
import { waiveApplicationMinimum } from "./deadline.js";
|
|
8
9
|
import { assertAllocationTotals } from "./allocationTotals.js";
|
|
9
10
|
export async function readPayoutPlan(
|
|
10
11
|
ctx: MutationCtx | QueryCtx,
|
|
@@ -45,13 +46,15 @@ export async function readPayoutPlan(
|
|
|
45
46
|
}
|
|
46
47
|
const fees = await readFees(ctx, args);
|
|
47
48
|
await assertAllocationTotals(ctx, args, sources);
|
|
49
|
+
const minimumWaived = waiveApplicationMinimum(policy, Date.now());
|
|
48
50
|
const plan = planRecipientPayout({
|
|
49
51
|
currency: args.currency,
|
|
50
|
-
|
|
52
|
+
// One minor unit is the positive-money guard, not an asserted provider floor.
|
|
53
|
+
threshold: minimumWaived ? 1 : minimum.amount,
|
|
51
54
|
providerAvailable: args.providerAvailable,
|
|
52
55
|
independentLiabilities: args.independentLiabilities,
|
|
53
56
|
confirmedFees: fees?.amount ?? 0,
|
|
54
57
|
sources,
|
|
55
58
|
});
|
|
56
|
-
return { plan, sources, fees, threshold: minimum.amount };
|
|
59
|
+
return { plan, sources, fees, threshold: minimum.amount, minimumWaived };
|
|
57
60
|
}
|
|
@@ -28,6 +28,8 @@ export const previewResult = v.union(
|
|
|
28
28
|
currency: v.string(),
|
|
29
29
|
amount: v.number(),
|
|
30
30
|
threshold: v.number(),
|
|
31
|
+
minimumWaived: v.optional(v.boolean()),
|
|
32
|
+
holdDeadline: v.optional(v.number()),
|
|
31
33
|
providerMinimum: v.null(),
|
|
32
34
|
matureUnpaid: v.number(),
|
|
33
35
|
pendingCredits: v.number(),
|
|
@@ -81,6 +83,7 @@ export async function previewPlan(
|
|
|
81
83
|
sources: funding,
|
|
82
84
|
fees,
|
|
83
85
|
threshold,
|
|
86
|
+
minimumWaived,
|
|
84
87
|
} = await readPayoutPlan(ctx, args, policy);
|
|
85
88
|
const progress = summarizePayoutProgress(
|
|
86
89
|
plan,
|
|
@@ -93,6 +96,8 @@ export async function previewPlan(
|
|
|
93
96
|
currency: plan.currency,
|
|
94
97
|
amount: plan.amount,
|
|
95
98
|
threshold,
|
|
99
|
+
minimumWaived,
|
|
100
|
+
holdDeadline: policy.holdDeadline,
|
|
96
101
|
providerMinimum: null,
|
|
97
102
|
matureUnpaid: plan.matureUnpaid,
|
|
98
103
|
pendingCredits: plan.pendingCredits,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { v } from "convex/values";
|
|
2
2
|
import { mutation } from "../_generated/server.js";
|
|
3
|
+
import {
|
|
4
|
+
finalAttemptStartsAt,
|
|
5
|
+
nextPayoutAttempt,
|
|
6
|
+
} from "../payoutRelease/deadline.js";
|
|
3
7
|
import { readPolicy } from "../payoutPolicy/helpers.js";
|
|
4
8
|
import { claimByKey, add } from "../payoutRelease/helpers.js";
|
|
5
9
|
import { balance as recoveryBalance } from "../affiliateRecovery/accounting.js";
|
|
@@ -38,7 +42,15 @@ export const begin = mutation({
|
|
|
38
42
|
reason: "worker_busy",
|
|
39
43
|
nextAttemptAt: job.leaseUntil,
|
|
40
44
|
};
|
|
41
|
-
|
|
45
|
+
const finalStart = finalAttemptStartsAt(policy);
|
|
46
|
+
const enteringFinalWindow =
|
|
47
|
+
policy?.mode === "custom" &&
|
|
48
|
+
policy.status === "ready" &&
|
|
49
|
+
finalStart !== undefined &&
|
|
50
|
+
finalStart <= now &&
|
|
51
|
+
policy.holdDeadline! > now &&
|
|
52
|
+
(job?.lastAttemptAt ?? 0) < finalStart;
|
|
53
|
+
if (job && job.nextAttemptAt > now && !enteringFinalWindow)
|
|
42
54
|
return {
|
|
43
55
|
status: "held" as const,
|
|
44
56
|
reason: "not_due",
|
|
@@ -65,7 +77,7 @@ export const begin = mutation({
|
|
|
65
77
|
hasOperation: false,
|
|
66
78
|
leaseUntil: undefined,
|
|
67
79
|
lastReason: "policy_inactive",
|
|
68
|
-
nextAttemptAt: now
|
|
80
|
+
nextAttemptAt: nextPayoutAttempt(policy, now, args.cadenceMs),
|
|
69
81
|
});
|
|
70
82
|
return { status: "held" as const, reason: "policy_inactive" };
|
|
71
83
|
}
|
|
@@ -95,7 +107,7 @@ export const begin = mutation({
|
|
|
95
107
|
recovery.reservedRestoration > 0 ||
|
|
96
108
|
recovery.activePayoutKey)
|
|
97
109
|
) {
|
|
98
|
-
const nextAttemptAt = now
|
|
110
|
+
const nextAttemptAt = nextPayoutAttempt(policy, now, args.cadenceMs);
|
|
99
111
|
await ctx.db.patch("payoutWorkerJobs", job._id, {
|
|
100
112
|
operationKey: undefined,
|
|
101
113
|
hasOperation: false,
|
|
@@ -171,7 +183,11 @@ export const finish = mutation({
|
|
|
171
183
|
operationKey: retain ? job.operationKey : undefined,
|
|
172
184
|
hasOperation: retain,
|
|
173
185
|
leaseUntil: undefined,
|
|
174
|
-
nextAttemptAt:
|
|
186
|
+
nextAttemptAt: nextPayoutAttempt(
|
|
187
|
+
await readPolicy(ctx, args),
|
|
188
|
+
Date.now(),
|
|
189
|
+
job.cadenceMs,
|
|
190
|
+
),
|
|
175
191
|
lastReason: args.reason,
|
|
176
192
|
completedGeneration: args.generation,
|
|
177
193
|
completedOperationKey: args.operationKey,
|
|
@@ -241,3 +241,26 @@ it("does not display a former custom holding deadline for Stripe-managed payouts
|
|
|
241
241
|
);
|
|
242
242
|
expect(screen.queryByText(/Custom holding deadline:/)).toBeNull();
|
|
243
243
|
});
|
|
244
|
+
|
|
245
|
+
it("explains the deadline waiver without a minimum progress bar", () => {
|
|
246
|
+
render(
|
|
247
|
+
<PayoutProgress
|
|
248
|
+
policy={policy}
|
|
249
|
+
currency="usd"
|
|
250
|
+
preview={{
|
|
251
|
+
...preview,
|
|
252
|
+
status: "qualified",
|
|
253
|
+
reason: undefined,
|
|
254
|
+
amount: 999,
|
|
255
|
+
minimumWaived: true,
|
|
256
|
+
holdDeadline: policy.holdDeadline,
|
|
257
|
+
}}
|
|
258
|
+
/>,
|
|
259
|
+
);
|
|
260
|
+
expect(screen.getByRole("status").textContent).toContain("holding deadline");
|
|
261
|
+
expect(screen.getByRole("status").textContent).toContain(
|
|
262
|
+
"application minimum is waived",
|
|
263
|
+
);
|
|
264
|
+
expect(screen.queryByRole("progressbar")).toBeNull();
|
|
265
|
+
expect(screen.getByText("Combined payout minimum")).toBeTruthy();
|
|
266
|
+
});
|
|
@@ -179,7 +179,10 @@ export function PayoutSettings(props: PayoutSettingsProps) {
|
|
|
179
179
|
</label>
|
|
180
180
|
<p>
|
|
181
181
|
Store and affiliate earnings combine per currency. Below-minimum
|
|
182
|
-
funds remain owed.
|
|
182
|
+
funds remain owed. During the final day before the holding deadline,
|
|
183
|
+
the application minimum is waived for automatic payout attempts.
|
|
184
|
+
Stripe payout limits and balance checks still apply. Other currency
|
|
185
|
+
minimums are preserved.
|
|
183
186
|
</p>
|
|
184
187
|
</>
|
|
185
188
|
)}
|
|
@@ -123,24 +123,36 @@ export function payoutProgressState({
|
|
|
123
123
|
if (
|
|
124
124
|
preview.currency !== currency ||
|
|
125
125
|
!configured ||
|
|
126
|
-
configured.amount !== preview.threshold
|
|
126
|
+
configured.amount !== preview.threshold ||
|
|
127
|
+
(preview.minimumWaived &&
|
|
128
|
+
preview.holdDeadline !== policy.holdDeadline)
|
|
127
129
|
)
|
|
128
130
|
throw new Error(
|
|
129
131
|
"Payout progress currency or policy does not match the assessment.",
|
|
130
132
|
);
|
|
131
133
|
state.status = "assessed";
|
|
132
134
|
state.reason = preview.reason ?? null;
|
|
133
|
-
state.message =
|
|
134
|
-
|
|
135
|
+
state.message = preview.minimumWaived
|
|
136
|
+
? "The holding deadline is approaching. The application minimum is waived for final automatic attempts; provider limits and all balance checks still apply. " +
|
|
137
|
+
(preview.status === "qualified"
|
|
138
|
+
? "The worker will verify eligibility again before any payout."
|
|
139
|
+
: (reasons[preview.reason ?? ""] ??
|
|
140
|
+
"Automatic payout remains on hold. Contact support before the deadline."))
|
|
141
|
+
: preview.status === "qualified"
|
|
135
142
|
? "The combined balance qualified at the assessment time. The automatic worker will verify it again before any payout."
|
|
136
143
|
: (reasons[preview.reason ?? ""] ??
|
|
137
144
|
"Automatic payout is on hold. Refresh or contact support.");
|
|
138
145
|
state.observedAt = preview.observedAt;
|
|
139
146
|
state.accounting = preview.accounting;
|
|
140
|
-
state.progress =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
state.progress = preview.minimumWaived
|
|
148
|
+
? null
|
|
149
|
+
: {
|
|
150
|
+
value: Math.max(
|
|
151
|
+
0,
|
|
152
|
+
Math.min(preview.threshold, preview.matureUnpaid),
|
|
153
|
+
),
|
|
154
|
+
max: preview.threshold,
|
|
155
|
+
};
|
|
144
156
|
const definitions = [
|
|
145
157
|
["qualifying", "Combined qualifying balance", preview.matureUnpaid],
|
|
146
158
|
["pending", "Pending settlement", preview.pendingCredits],
|