@fantastic.dev/better-stripe 0.21.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 +12 -0
- package/README.md +8 -5
- 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/payoutExecution.d.ts +34 -0
- package/dist/client/connect/payoutExecution.d.ts.map +1 -0
- package/dist/client/connect/payoutExecution.js +189 -0
- package/dist/client/connect/payoutExecution.js.map +1 -0
- package/dist/client/connect/payoutHistory.d.ts +22 -0
- package/dist/client/connect/payoutHistory.d.ts.map +1 -0
- package/dist/client/connect/payoutHistory.js +122 -0
- package/dist/client/connect/payoutHistory.js.map +1 -0
- package/dist/client/connect/payoutReconciliation.d.ts.map +1 -1
- package/dist/client/connect/payoutReconciliation.js +6 -2
- package/dist/client/connect/payoutReconciliation.js.map +1 -1
- package/dist/client/payoutPolicy.d.ts +10 -1
- package/dist/client/payoutPolicy.d.ts.map +1 -1
- package/dist/client/payoutPolicy.js +82 -7
- package/dist/client/payoutPolicy.js.map +1 -1
- package/dist/component/_generated/component.d.ts +269 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/earnings/triggers.d.ts +40 -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 +4 -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 +87 -0
- package/dist/component/payoutRelease/mutations.d.ts.map +1 -1
- package/dist/component/payoutRelease/mutations.js +13 -1
- 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 +74 -0
- package/dist/component/payoutRelease/queries.d.ts.map +1 -1
- package/dist/component/payoutRelease/queries.js +29 -0
- package/dist/component/payoutRelease/queries.js.map +1 -1
- package/dist/component/payoutRelease/schema.d.ts +13 -19
- package/dist/component/payoutRelease/schema.d.ts.map +1 -1
- package/dist/component/payoutRelease/schema.js +4 -9
- package/dist/component/payoutRelease/schema.js.map +1 -1
- package/dist/component/schema.d.ts +75 -2
- package/docs/settlement.md +181 -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/payoutExecution.test.ts +504 -0
- package/src/client/connect/payoutExecution.ts +265 -0
- package/src/client/connect/payoutHistory.test.ts +145 -0
- package/src/client/connect/payoutHistory.ts +149 -0
- package/src/client/connect/payoutReconciliation.test.ts +29 -0
- package/src/client/connect/payoutReconciliation.ts +10 -2
- package/src/client/payoutPolicy.test.ts +16 -0
- package/src/client/payoutPolicy.ts +121 -5
- package/src/component/_generated/api.ts +6 -0
- package/src/component/_generated/component.ts +272 -1
- package/src/component/payout-adoption.test.ts +272 -0
- package/src/component/payout-policy-adoption.test.ts +131 -0
- package/src/component/payout-release-snapshot.test.ts +57 -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 +19 -1
- package/src/component/payoutRelease/observation.ts +10 -0
- package/src/component/payoutRelease/queries.ts +35 -0
- package/src/component/payoutRelease/schema.ts +4 -9
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { v, type Infer } from "convex/values";
|
|
2
|
+
import type { MutationCtx } from "../_generated/server.js";
|
|
3
|
+
import { scopeFields } from "../payoutPolicy/schema.js";
|
|
4
|
+
import { readPolicy } from "../payoutPolicy/helpers.js";
|
|
5
|
+
import { observation, allocation } from "./schema.js";
|
|
6
|
+
import {
|
|
7
|
+
add,
|
|
8
|
+
checkSnapshot,
|
|
9
|
+
sourceById,
|
|
10
|
+
payoutClaimFingerprint,
|
|
11
|
+
} from "./helpers.js";
|
|
12
|
+
import { recordFees } from "./fees.js";
|
|
13
|
+
export const historicalArgs = {
|
|
14
|
+
...scopeFields,
|
|
15
|
+
expectedRevision: v.number(),
|
|
16
|
+
payout: v.object({
|
|
17
|
+
stripePayoutId: v.string(),
|
|
18
|
+
amount: v.number(),
|
|
19
|
+
currency: v.string(),
|
|
20
|
+
created: v.number(),
|
|
21
|
+
destinationId: v.string(),
|
|
22
|
+
status: v.union(v.literal("paid"), v.literal("returned")),
|
|
23
|
+
debitTransactionId: v.string(),
|
|
24
|
+
debitFee: v.number(),
|
|
25
|
+
returnTransactionId: v.optional(v.string()),
|
|
26
|
+
returnFee: v.optional(v.number()),
|
|
27
|
+
}),
|
|
28
|
+
allocations: v.array(allocation),
|
|
29
|
+
observations: v.array(observation),
|
|
30
|
+
};
|
|
31
|
+
type Args = {
|
|
32
|
+
[K in keyof typeof historicalArgs]: Infer<(typeof historicalArgs)[K]>;
|
|
33
|
+
};
|
|
34
|
+
/** Trusted package observations only. Provider history and historical maturity are verified upstream. */
|
|
35
|
+
export async function adoptHistoricalPayout(ctx: MutationCtx, args: Args) {
|
|
36
|
+
const policy = await readPolicy(ctx, args);
|
|
37
|
+
if (!policy || policy.revision !== args.expectedRevision)
|
|
38
|
+
throw new Error("Historical payout policy revision changed.");
|
|
39
|
+
if (policy.mode !== "stripe_managed" || policy.status !== "ready")
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Historical adoption requires an inactive automatic policy.",
|
|
42
|
+
);
|
|
43
|
+
const p = args.payout;
|
|
44
|
+
if (
|
|
45
|
+
!/^po_\w+$/.test(p.stripePayoutId) ||
|
|
46
|
+
!/^ba_\w+$/.test(p.destinationId) ||
|
|
47
|
+
!/^[a-z]{3}$/.test(p.currency) ||
|
|
48
|
+
!Number.isSafeInteger(p.amount) ||
|
|
49
|
+
p.amount <= 0 ||
|
|
50
|
+
!Number.isSafeInteger(p.created) ||
|
|
51
|
+
p.created < 0 ||
|
|
52
|
+
p.created * 1000 > Date.now() ||
|
|
53
|
+
!Number.isSafeInteger(p.created * 1000) ||
|
|
54
|
+
!/^txn_\w+$/.test(p.debitTransactionId) ||
|
|
55
|
+
!Number.isSafeInteger(p.debitFee) ||
|
|
56
|
+
p.debitFee < 0 ||
|
|
57
|
+
(p.status === "returned"
|
|
58
|
+
? !p.returnTransactionId ||
|
|
59
|
+
!/^txn_\w+$/.test(p.returnTransactionId) ||
|
|
60
|
+
!Number.isSafeInteger(p.returnFee)
|
|
61
|
+
: p.returnTransactionId !== undefined || p.returnFee !== undefined)
|
|
62
|
+
)
|
|
63
|
+
throw new Error("Invalid historical payout receipt.");
|
|
64
|
+
const rows = await checkSnapshot(
|
|
65
|
+
ctx,
|
|
66
|
+
args.accountId,
|
|
67
|
+
args.observations,
|
|
68
|
+
true,
|
|
69
|
+
);
|
|
70
|
+
const byObservation = new Map(
|
|
71
|
+
args.observations.map((o) => [o.stripeTransferId, o]),
|
|
72
|
+
);
|
|
73
|
+
const seen = new Set<string>();
|
|
74
|
+
let total = 0;
|
|
75
|
+
const allocations = args.allocations
|
|
76
|
+
.map((a) => {
|
|
77
|
+
if (
|
|
78
|
+
!Number.isSafeInteger(a.amount) ||
|
|
79
|
+
a.amount <= 0 ||
|
|
80
|
+
seen.has(a.stripeTransferId) ||
|
|
81
|
+
!rows.has(a.stripeTransferId) ||
|
|
82
|
+
byObservation.get(a.stripeTransferId)?.currency !== p.currency
|
|
83
|
+
)
|
|
84
|
+
throw new Error("Invalid historical source allocation.");
|
|
85
|
+
seen.add(a.stripeTransferId);
|
|
86
|
+
total = add(total, a.amount);
|
|
87
|
+
const role = rows.get(a.stripeTransferId)!.role;
|
|
88
|
+
return {
|
|
89
|
+
stripeTransferId: a.stripeTransferId,
|
|
90
|
+
amount: a.amount,
|
|
91
|
+
...(role ? { role } : {}),
|
|
92
|
+
};
|
|
93
|
+
})
|
|
94
|
+
.sort((a, b) => a.stripeTransferId.localeCompare(b.stripeTransferId));
|
|
95
|
+
if (total !== p.amount)
|
|
96
|
+
throw new Error(
|
|
97
|
+
"Historical source allocations do not equal payout principal.",
|
|
98
|
+
);
|
|
99
|
+
const operationKey = `bs:historical:${p.stripePayoutId}`;
|
|
100
|
+
const old = await ctx.db
|
|
101
|
+
.query("payoutReleaseClaims")
|
|
102
|
+
.withIndex("by_provider_payout", (q) =>
|
|
103
|
+
q.eq("stripePayoutId", p.stripePayoutId),
|
|
104
|
+
)
|
|
105
|
+
.unique();
|
|
106
|
+
if (old) {
|
|
107
|
+
if (
|
|
108
|
+
!old.historical ||
|
|
109
|
+
old.accountId !== args.accountId ||
|
|
110
|
+
old.ownerKey !== args.ownerKey ||
|
|
111
|
+
old.operationKey !== operationKey ||
|
|
112
|
+
old.currency !== p.currency ||
|
|
113
|
+
old.amount !== p.amount ||
|
|
114
|
+
old.destinationId !== p.destinationId ||
|
|
115
|
+
old.attemptedAt !== p.created * 1000 ||
|
|
116
|
+
old.status !== p.status ||
|
|
117
|
+
old.debitTransactionId !== p.debitTransactionId ||
|
|
118
|
+
old.debitFee !== p.debitFee ||
|
|
119
|
+
old.returnTransactionId !== p.returnTransactionId ||
|
|
120
|
+
old.providerFee !== add(p.debitFee, p.returnFee ?? 0) ||
|
|
121
|
+
JSON.stringify(
|
|
122
|
+
old.allocations.map((a) => [a.stripeTransferId, a.amount, a.role]),
|
|
123
|
+
) !==
|
|
124
|
+
JSON.stringify(
|
|
125
|
+
allocations.map((a) => [a.stripeTransferId, a.amount, a.role]),
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
throw new Error(
|
|
129
|
+
"Historical payout identity or source allocation changed; reconcile existing outcomes.",
|
|
130
|
+
);
|
|
131
|
+
return old;
|
|
132
|
+
}
|
|
133
|
+
for (const status of ["reserved", "submitted"] as const) {
|
|
134
|
+
const unresolved = await ctx.db
|
|
135
|
+
.query("payoutReleaseClaims")
|
|
136
|
+
.withIndex("by_account_status", (q) =>
|
|
137
|
+
q.eq("accountId", args.accountId).eq("status", status),
|
|
138
|
+
)
|
|
139
|
+
.first();
|
|
140
|
+
if (unresolved)
|
|
141
|
+
throw new Error(
|
|
142
|
+
"Resolve existing payout attempts before historical adoption.",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const history = await ctx.db
|
|
146
|
+
.query("payoutReleaseClaims")
|
|
147
|
+
.withIndex("by_account_status", (q) => q.eq("accountId", args.accountId))
|
|
148
|
+
.take(100);
|
|
149
|
+
if (history.length >= 100)
|
|
150
|
+
throw new Error("Historical payout snapshot limit exceeded.");
|
|
151
|
+
for (const a of allocations) {
|
|
152
|
+
const source = await sourceById(ctx, a.stripeTransferId);
|
|
153
|
+
if (
|
|
154
|
+
source &&
|
|
155
|
+
(source.accountId !== args.accountId ||
|
|
156
|
+
source.ownerKey !== args.ownerKey ||
|
|
157
|
+
source.currency !== p.currency ||
|
|
158
|
+
source.reservedAmount !== 0)
|
|
159
|
+
)
|
|
160
|
+
throw new Error(
|
|
161
|
+
"Historical source allocation identity or reservations changed.",
|
|
162
|
+
);
|
|
163
|
+
const paid = p.status === "paid" ? a.amount : 0;
|
|
164
|
+
if (source)
|
|
165
|
+
await ctx.db.patch("payoutSourceAllocations", source._id, {
|
|
166
|
+
paidAmount: add(source.paidAmount, paid),
|
|
167
|
+
});
|
|
168
|
+
else
|
|
169
|
+
await ctx.db.insert("payoutSourceAllocations", {
|
|
170
|
+
accountId: args.accountId,
|
|
171
|
+
ownerKey: args.ownerKey,
|
|
172
|
+
stripeTransferId: a.stripeTransferId,
|
|
173
|
+
currency: p.currency,
|
|
174
|
+
paidAmount: paid,
|
|
175
|
+
reservedAmount: 0,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const id = await ctx.db.insert("payoutReleaseClaims", {
|
|
179
|
+
accountId: args.accountId,
|
|
180
|
+
ownerKey: args.ownerKey,
|
|
181
|
+
operationKey,
|
|
182
|
+
expectedRevision: args.expectedRevision,
|
|
183
|
+
historical: true,
|
|
184
|
+
currency: p.currency,
|
|
185
|
+
amount: p.amount,
|
|
186
|
+
destinationId: p.destinationId,
|
|
187
|
+
allocations,
|
|
188
|
+
observations: args.observations,
|
|
189
|
+
status: p.status,
|
|
190
|
+
attemptedAt: p.created * 1000,
|
|
191
|
+
stripePayoutId: p.stripePayoutId,
|
|
192
|
+
...(p.returnTransactionId
|
|
193
|
+
? { returnTransactionId: p.returnTransactionId }
|
|
194
|
+
: {}),
|
|
195
|
+
});
|
|
196
|
+
const claim = (await ctx.db.get("payoutReleaseClaims", id))!;
|
|
197
|
+
await recordFees(
|
|
198
|
+
ctx,
|
|
199
|
+
claim,
|
|
200
|
+
{
|
|
201
|
+
debitTransactionId: p.debitTransactionId,
|
|
202
|
+
debitFee: p.debitFee,
|
|
203
|
+
...(p.returnFee !== undefined ? { returnFee: p.returnFee } : {}),
|
|
204
|
+
},
|
|
205
|
+
p.status === "returned",
|
|
206
|
+
);
|
|
207
|
+
return (await ctx.db.get("payoutReleaseClaims", id))!;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const historicalBatchArgs = {
|
|
211
|
+
...scopeFields,
|
|
212
|
+
expectedRevision: v.number(),
|
|
213
|
+
observations: v.array(observation),
|
|
214
|
+
expectedClaims: v.array(
|
|
215
|
+
v.object({ operationKey: v.string(), fingerprint: v.string() }),
|
|
216
|
+
),
|
|
217
|
+
payouts: v.array(
|
|
218
|
+
v.object({
|
|
219
|
+
payout: historicalArgs.payout,
|
|
220
|
+
allocations: historicalArgs.allocations,
|
|
221
|
+
}),
|
|
222
|
+
),
|
|
223
|
+
};
|
|
224
|
+
type BatchArgs = {
|
|
225
|
+
[K in keyof typeof historicalBatchArgs]: Infer<
|
|
226
|
+
(typeof historicalBatchArgs)[K]
|
|
227
|
+
>;
|
|
228
|
+
};
|
|
229
|
+
export async function adoptHistoricalBatch(ctx: MutationCtx, args: BatchArgs) {
|
|
230
|
+
if (!args.payouts.length || args.payouts.length > 100)
|
|
231
|
+
throw new Error("Invalid historical payout batch size.");
|
|
232
|
+
const rows = await ctx.db
|
|
233
|
+
.query("payoutReleaseClaims")
|
|
234
|
+
.withIndex("by_account_status", (q) => q.eq("accountId", args.accountId))
|
|
235
|
+
.take(101);
|
|
236
|
+
const expected = new Map(
|
|
237
|
+
args.expectedClaims.map((c) => [c.operationKey, c.fingerprint]),
|
|
238
|
+
);
|
|
239
|
+
if (
|
|
240
|
+
rows.length > 100 ||
|
|
241
|
+
expected.size !== args.expectedClaims.length ||
|
|
242
|
+
rows.length !== expected.size ||
|
|
243
|
+
rows.some(
|
|
244
|
+
(c) =>
|
|
245
|
+
c.ownerKey !== args.ownerKey ||
|
|
246
|
+
expected.get(c.operationKey) !== payoutClaimFingerprint(c),
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
throw new Error("Historical payout history snapshot changed.");
|
|
250
|
+
const claims = [];
|
|
251
|
+
for (const p of args.payouts)
|
|
252
|
+
claims.push(
|
|
253
|
+
await adoptHistoricalPayout(ctx, {
|
|
254
|
+
accountId: args.accountId,
|
|
255
|
+
ownerKey: args.ownerKey,
|
|
256
|
+
expectedRevision: args.expectedRevision,
|
|
257
|
+
observations: args.observations,
|
|
258
|
+
...p,
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
261
|
+
return { claims };
|
|
262
|
+
}
|
|
@@ -166,3 +166,24 @@ export async function moveAllocations(
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
|
|
170
|
+
/** Stable financial identity for historical adoption and activation snapshots. */
|
|
171
|
+
export function payoutClaimFingerprint(c: Doc<"payoutReleaseClaims">) {
|
|
172
|
+
return JSON.stringify([
|
|
173
|
+
c.accountId,
|
|
174
|
+
c.ownerKey,
|
|
175
|
+
c.operationKey,
|
|
176
|
+
c.historical ?? false,
|
|
177
|
+
c.currency,
|
|
178
|
+
c.amount,
|
|
179
|
+
c.status,
|
|
180
|
+
c.attemptedAt,
|
|
181
|
+
c.stripePayoutId,
|
|
182
|
+
c.destinationId,
|
|
183
|
+
c.debitTransactionId,
|
|
184
|
+
c.debitFee,
|
|
185
|
+
c.providerFee,
|
|
186
|
+
c.returnTransactionId,
|
|
187
|
+
c.allocations.map((a) => [a.stripeTransferId, a.amount, a.role]),
|
|
188
|
+
]);
|
|
189
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
historicalBatchArgs,
|
|
3
|
+
adoptHistoricalBatch as importHistoricalBatch,
|
|
4
|
+
} from "./adoption.js";
|
|
1
5
|
import { readFees, recordFees } from "./fees.js";
|
|
2
6
|
import { v } from "convex/values";
|
|
3
7
|
import { mutation } from "../_generated/server.js";
|
|
@@ -31,6 +35,7 @@ export const claim = mutation({
|
|
|
31
35
|
...identity,
|
|
32
36
|
expectedRevision: v.number(),
|
|
33
37
|
currency: v.string(),
|
|
38
|
+
destinationId: v.optional(v.string()),
|
|
34
39
|
providerAvailable: v.number(),
|
|
35
40
|
independentLiabilities: v.number(),
|
|
36
41
|
observations: v.array(observation),
|
|
@@ -38,11 +43,17 @@ export const claim = mutation({
|
|
|
38
43
|
returns: v.union(claimDoc, held),
|
|
39
44
|
handler: async (ctx, args) => {
|
|
40
45
|
validateIdentity(args);
|
|
46
|
+
if (
|
|
47
|
+
args.destinationId !== undefined &&
|
|
48
|
+
!/^ba_\w+$/.test(args.destinationId)
|
|
49
|
+
)
|
|
50
|
+
throw new Error("Invalid payout bank destination.");
|
|
41
51
|
const existing = await claimByKey(ctx, args);
|
|
42
52
|
if (existing) {
|
|
43
53
|
if (
|
|
44
54
|
existing.currency !== args.currency ||
|
|
45
|
-
existing.expectedRevision !== args.expectedRevision
|
|
55
|
+
existing.expectedRevision !== args.expectedRevision ||
|
|
56
|
+
existing.destinationId !== args.destinationId
|
|
46
57
|
)
|
|
47
58
|
throw new Error("Payout claim identity cannot be reused.");
|
|
48
59
|
return existing;
|
|
@@ -122,6 +133,7 @@ export const claim = mutation({
|
|
|
122
133
|
expectedRevision: args.expectedRevision,
|
|
123
134
|
currency: args.currency,
|
|
124
135
|
amount: plan.amount,
|
|
136
|
+
...(args.destinationId ? { destinationId: args.destinationId } : {}),
|
|
125
137
|
allocations: plan.allocations,
|
|
126
138
|
observations: args.observations,
|
|
127
139
|
feeRevision: fees?.revision ?? 0,
|
|
@@ -324,3 +336,9 @@ export const recordRejection = mutation({
|
|
|
324
336
|
return (await ctx.db.get("payoutReleaseClaims", claim._id))!;
|
|
325
337
|
},
|
|
326
338
|
});
|
|
339
|
+
|
|
340
|
+
export const adoptHistoricalBatch = mutation({
|
|
341
|
+
args: historicalBatchArgs,
|
|
342
|
+
returns: v.object({ claims: v.array(claimDoc) }),
|
|
343
|
+
handler: importHistoricalBatch,
|
|
344
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
export const observation = v.object({
|
|
3
|
+
stripeTransferId: v.string(),
|
|
4
|
+
currency: v.string(),
|
|
5
|
+
matureNet: v.number(),
|
|
6
|
+
pendingCredits: v.number(),
|
|
7
|
+
ledgerFingerprint: v.string(),
|
|
8
|
+
observedAt: v.number(),
|
|
9
|
+
transactionIds: v.array(v.string()),
|
|
10
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { transferDocValidator } from "../connect/validators.js";
|
|
1
2
|
import { readFees } from "./fees.js";
|
|
2
3
|
import { paginationOptsValidator } from "convex/server";
|
|
3
4
|
import { v } from "convex/values";
|
|
@@ -65,3 +66,37 @@ export const fees = query({
|
|
|
65
66
|
return { amount: balance?.amount ?? 0, revision: balance?.revision ?? 0 };
|
|
66
67
|
},
|
|
67
68
|
});
|
|
69
|
+
|
|
70
|
+
/** Complete bounded input for package qualification. Partial membership never qualifies. */
|
|
71
|
+
export const snapshot = query({
|
|
72
|
+
args: { accountId: v.string(), ownerKey: v.string() },
|
|
73
|
+
returns: v.union(
|
|
74
|
+
v.object({
|
|
75
|
+
status: v.literal("held"),
|
|
76
|
+
reason: v.literal("snapshot_limit"),
|
|
77
|
+
}),
|
|
78
|
+
v.object({
|
|
79
|
+
status: v.literal("ready"),
|
|
80
|
+
transfers: v.array(transferDocValidator),
|
|
81
|
+
claims: v.array(claimDoc),
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
handler: async (ctx, args) => {
|
|
85
|
+
await readPolicy(ctx, args);
|
|
86
|
+
const transfers = await ctx.db
|
|
87
|
+
.query("transfers")
|
|
88
|
+
.withIndex("by_destination_account_id", (q) =>
|
|
89
|
+
q.eq("destinationAccountId", args.accountId),
|
|
90
|
+
)
|
|
91
|
+
.take(101);
|
|
92
|
+
const claims = await ctx.db
|
|
93
|
+
.query("payoutReleaseClaims")
|
|
94
|
+
.withIndex("by_account_status", (q) => q.eq("accountId", args.accountId))
|
|
95
|
+
.take(101);
|
|
96
|
+
if (transfers.length > 100 || claims.length > 100)
|
|
97
|
+
return { status: "held" as const, reason: "snapshot_limit" as const };
|
|
98
|
+
if (claims.some((c) => c.ownerKey !== args.ownerKey))
|
|
99
|
+
throw new Error("Payout history owner does not match.");
|
|
100
|
+
return { status: "ready" as const, transfers, claims };
|
|
101
|
+
},
|
|
102
|
+
});
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { defineTable } from "convex/server";
|
|
2
2
|
import { v } from "convex/values";
|
|
3
3
|
import { scopeFields } from "../payoutPolicy/schema.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
currency: v.string(),
|
|
7
|
-
matureNet: v.number(),
|
|
8
|
-
pendingCredits: v.number(),
|
|
9
|
-
ledgerFingerprint: v.string(),
|
|
10
|
-
observedAt: v.number(),
|
|
11
|
-
transactionIds: v.array(v.string()),
|
|
12
|
-
});
|
|
4
|
+
import { observation } from "./observation.js";
|
|
5
|
+
export { observation } from "./observation.js";
|
|
13
6
|
export const allocation = v.object({
|
|
14
7
|
stripeTransferId: v.string(),
|
|
15
8
|
amount: v.number(),
|
|
@@ -19,10 +12,12 @@ export const allocation = v.object({
|
|
|
19
12
|
});
|
|
20
13
|
export const claimFields = {
|
|
21
14
|
...scopeFields,
|
|
15
|
+
historical: v.optional(v.boolean()),
|
|
22
16
|
operationKey: v.string(),
|
|
23
17
|
expectedRevision: v.number(),
|
|
24
18
|
currency: v.string(),
|
|
25
19
|
amount: v.number(),
|
|
20
|
+
destinationId: v.optional(v.string()),
|
|
26
21
|
allocations: v.array(allocation),
|
|
27
22
|
observations: v.array(observation),
|
|
28
23
|
status: v.union(
|