@fantastic.dev/better-stripe 0.18.0 → 0.20.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 -3
- package/dist/client/connect/transferReceipt.d.ts +48 -0
- package/dist/client/connect/transferReceipt.d.ts.map +1 -0
- package/dist/client/connect/transferReceipt.js +283 -0
- package/dist/client/connect/transferReceipt.js.map +1 -0
- package/dist/client/payoutPolicy.d.ts +3 -0
- package/dist/client/payoutPolicy.d.ts.map +1 -1
- package/dist/client/payoutPolicy.js +4 -0
- package/dist/client/payoutPolicy.js.map +1 -1
- package/dist/component/_generated/component.d.ts +297 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/earnings/triggers.d.ts +134 -0
- package/dist/component/earnings/triggers.d.ts.map +1 -1
- package/dist/component/payoutRelease/allocation.d.ts +48 -0
- package/dist/component/payoutRelease/allocation.d.ts.map +1 -0
- package/dist/component/payoutRelease/allocation.js +98 -0
- package/dist/component/payoutRelease/allocation.js.map +1 -0
- package/dist/component/payoutRelease/helpers.d.ts +106 -0
- package/dist/component/payoutRelease/helpers.d.ts.map +1 -0
- package/dist/component/payoutRelease/helpers.js +119 -0
- package/dist/component/payoutRelease/helpers.js.map +1 -0
- package/dist/component/payoutRelease/mutations.d.ts +218 -0
- package/dist/component/payoutRelease/mutations.d.ts.map +1 -0
- package/dist/component/payoutRelease/mutations.js +254 -0
- package/dist/component/payoutRelease/mutations.js.map +1 -0
- package/dist/component/payoutRelease/queries.d.ts +84 -0
- package/dist/component/payoutRelease/queries.d.ts.map +1 -0
- package/dist/component/payoutRelease/queries.js +51 -0
- package/dist/component/payoutRelease/queries.js.map +1 -0
- package/dist/component/payoutRelease/schema.d.ts +289 -0
- package/dist/component/payoutRelease/schema.d.ts.map +1 -0
- package/dist/component/payoutRelease/schema.js +56 -0
- package/dist/component/payoutRelease/schema.js.map +1 -0
- package/dist/component/schema.d.ts +110 -0
- package/dist/component/schema.js +3 -0
- package/dist/component/schema.js.map +1 -1
- package/docs/settlement.md +81 -0
- package/package.json +1 -1
- package/src/client/connect/payoutAllocation.test.ts +292 -0
- package/src/client/connect/transferReceipt.pagination.test.ts +209 -0
- package/src/client/connect/transferReceipt.test.ts +520 -0
- package/src/client/connect/transferReceipt.ts +409 -0
- package/src/client/payoutPolicy.ts +13 -0
- package/src/component/_generated/api.ts +8 -0
- package/src/component/_generated/component.ts +372 -0
- package/src/component/payout-release.test.ts +588 -0
- package/src/component/payoutRelease/allocation.ts +147 -0
- package/src/component/payoutRelease/helpers.ts +168 -0
- package/src/component/payoutRelease/mutations.ts +309 -0
- package/src/component/payoutRelease/queries.ts +56 -0
- package/src/component/payoutRelease/schema.ts +69 -0
- package/src/component/schema.ts +6 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { transferFingerprint as ledgerFingerprint } from "../../component/payoutRelease/helpers.js";
|
|
2
|
+
import type Stripe from "stripe";
|
|
3
|
+
import type { Infer } from "convex/values";
|
|
4
|
+
import type { transferDocValidator } from "../../component/connect/validators.js";
|
|
5
|
+
import type { Component, RunCtx } from "../helpers.js";
|
|
6
|
+
import { componentRef } from "../webhooks/helpers.js";
|
|
7
|
+
type LedgerTransfer = Infer<typeof transferDocValidator>;
|
|
8
|
+
export type TransferReceiptOptions = {
|
|
9
|
+
accountId: string;
|
|
10
|
+
stripeTransferId: string;
|
|
11
|
+
maxRequests?: number;
|
|
12
|
+
};
|
|
13
|
+
export type TransferReceiptTransaction = {
|
|
14
|
+
id: string;
|
|
15
|
+
sourceId: string;
|
|
16
|
+
amount: number;
|
|
17
|
+
fee: number;
|
|
18
|
+
net: number;
|
|
19
|
+
currency: string;
|
|
20
|
+
created: number;
|
|
21
|
+
/** Unix seconds, as returned by Stripe. */
|
|
22
|
+
availableOn: number;
|
|
23
|
+
status: "available" | "pending";
|
|
24
|
+
type: string;
|
|
25
|
+
};
|
|
26
|
+
export type TransferReceiptNotReadyReason =
|
|
27
|
+
| "not_in_ledger"
|
|
28
|
+
| "transfer_pending"
|
|
29
|
+
| "receipt_unavailable"
|
|
30
|
+
| "reversal_pending"
|
|
31
|
+
| "history_incomplete"
|
|
32
|
+
| "currency_allocation_required"
|
|
33
|
+
| "provider_changed"
|
|
34
|
+
| "ledger_changed";
|
|
35
|
+
export type TransferFundingReceipt =
|
|
36
|
+
| {
|
|
37
|
+
status: "not_ready";
|
|
38
|
+
accountId: string;
|
|
39
|
+
stripeTransferId: string;
|
|
40
|
+
reason: TransferReceiptNotReadyReason;
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
status: "ready";
|
|
44
|
+
accountId: string;
|
|
45
|
+
stripeTransferId: string;
|
|
46
|
+
destinationPaymentId: string;
|
|
47
|
+
sourceAmount: number;
|
|
48
|
+
sourceCurrency: string;
|
|
49
|
+
settlementCurrency: string;
|
|
50
|
+
providerReversedAmount: number;
|
|
51
|
+
totalNet: number;
|
|
52
|
+
/** Signed source contribution; previous payouts are not allocated here. */
|
|
53
|
+
matureNet: number;
|
|
54
|
+
pendingCredits: number;
|
|
55
|
+
role?: "store" | "affiliate" | "other";
|
|
56
|
+
reinstatement: boolean;
|
|
57
|
+
transactions: TransferReceiptTransaction[];
|
|
58
|
+
/** Snapshot observation time in milliseconds. */
|
|
59
|
+
observedAt: number;
|
|
60
|
+
};
|
|
61
|
+
class NotReady extends Error {
|
|
62
|
+
constructor(readonly reason: TransferReceiptNotReadyReason) {
|
|
63
|
+
super(reason);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const id = (value: string | { id: string } | null | undefined) =>
|
|
67
|
+
typeof value === "string" ? value : value?.id;
|
|
68
|
+
function integer(value: number) {
|
|
69
|
+
if (!Number.isSafeInteger(value))
|
|
70
|
+
throw new Error("Receipt amounts must be safe integers.");
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
const add = (a: number, b: number) => integer(a + b);
|
|
74
|
+
function fingerprint(t: Stripe.Transfer) {
|
|
75
|
+
return JSON.stringify([
|
|
76
|
+
t.id,
|
|
77
|
+
id(t.destination),
|
|
78
|
+
id(t.destination_payment),
|
|
79
|
+
id(t.source_transaction),
|
|
80
|
+
t.amount,
|
|
81
|
+
t.currency,
|
|
82
|
+
t.amount_reversed,
|
|
83
|
+
t.metadata?.bsReinstateOf,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
/** Recipient-scoped received credit. Previous payouts are deliberately not allocated here. */
|
|
87
|
+
export async function getTransferFundingReceipt(
|
|
88
|
+
stripe: Stripe,
|
|
89
|
+
component: Component,
|
|
90
|
+
ctx: RunCtx,
|
|
91
|
+
opts: TransferReceiptOptions,
|
|
92
|
+
): Promise<TransferFundingReceipt> {
|
|
93
|
+
const max = opts.maxRequests ?? 20;
|
|
94
|
+
if (!Number.isInteger(max) || max < 1 || max > 100)
|
|
95
|
+
throw new Error("Receipt request budget must be between 1 and 100.");
|
|
96
|
+
if (
|
|
97
|
+
!/^acct_\w+$/.test(opts.accountId) ||
|
|
98
|
+
!/^tr_\w+$/.test(opts.stripeTransferId)
|
|
99
|
+
)
|
|
100
|
+
throw new Error("Invalid recipient transfer identity.");
|
|
101
|
+
const scope = {
|
|
102
|
+
accountId: opts.accountId,
|
|
103
|
+
stripeTransferId: opts.stripeTransferId,
|
|
104
|
+
};
|
|
105
|
+
const read = () =>
|
|
106
|
+
ctx.runQuery(
|
|
107
|
+
componentRef(component, "connect/queries/getTransferByStripeId"),
|
|
108
|
+
{ stripeTransferId: opts.stripeTransferId },
|
|
109
|
+
) as Promise<LedgerTransfer | null>;
|
|
110
|
+
const ledger = await read();
|
|
111
|
+
if (!ledger)
|
|
112
|
+
return { status: "not_ready", ...scope, reason: "not_in_ledger" };
|
|
113
|
+
if (
|
|
114
|
+
ledger.destinationAccountId !== opts.accountId ||
|
|
115
|
+
ledger.stripeTransferId !== opts.stripeTransferId
|
|
116
|
+
)
|
|
117
|
+
throw new Error("Transfer ledger identity does not match the recipient.");
|
|
118
|
+
if (!["paid", "reversed"].includes(ledger.status))
|
|
119
|
+
return { status: "not_ready", ...scope, reason: "transfer_pending" };
|
|
120
|
+
let requests = 0;
|
|
121
|
+
async function request<T>(fn: () => Promise<T>): Promise<T> {
|
|
122
|
+
if (requests++ >= max) throw new NotReady("history_incomplete");
|
|
123
|
+
return fn();
|
|
124
|
+
}
|
|
125
|
+
async function scan<T extends { id: string }>(
|
|
126
|
+
fn: (cursor?: string) => Promise<{ data: T[]; has_more: boolean }>,
|
|
127
|
+
): Promise<T[]> {
|
|
128
|
+
const rows: T[] = [];
|
|
129
|
+
let cursor: string | undefined;
|
|
130
|
+
for (;;) {
|
|
131
|
+
const page = await request(() => fn(cursor));
|
|
132
|
+
rows.push(...page.data);
|
|
133
|
+
if (!page.has_more) return rows;
|
|
134
|
+
const next = page.data[page.data.length - 1]?.id;
|
|
135
|
+
if (!next || next === cursor) throw new NotReady("history_incomplete");
|
|
136
|
+
cursor = next;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const transfer = await request(() =>
|
|
141
|
+
stripe.transfers.retrieve(opts.stripeTransferId),
|
|
142
|
+
);
|
|
143
|
+
const destinationPaymentId = id(transfer.destination_payment);
|
|
144
|
+
if (
|
|
145
|
+
transfer.id !== ledger.stripeTransferId ||
|
|
146
|
+
id(transfer.destination) !== ledger.destinationAccountId ||
|
|
147
|
+
transfer.amount !== ledger.amount ||
|
|
148
|
+
transfer.currency !== ledger.currency ||
|
|
149
|
+
!destinationPaymentId
|
|
150
|
+
)
|
|
151
|
+
throw new Error(
|
|
152
|
+
"Provider transfer identity or provenance does not match the ledger.",
|
|
153
|
+
);
|
|
154
|
+
const source = id(transfer.source_transaction);
|
|
155
|
+
if (
|
|
156
|
+
ledger.sourceChargeId &&
|
|
157
|
+
source !== ledger.sourceChargeId &&
|
|
158
|
+
!(
|
|
159
|
+
ledger.reinstatement &&
|
|
160
|
+
!source &&
|
|
161
|
+
transfer.metadata?.bsReinstateOf === ledger.sourceChargeId
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
throw new Error("Transfer source provenance does not match the ledger.");
|
|
165
|
+
integer(transfer.amount);
|
|
166
|
+
integer(transfer.amount_reversed);
|
|
167
|
+
if (
|
|
168
|
+
transfer.amount <= 0 ||
|
|
169
|
+
transfer.amount_reversed < 0 ||
|
|
170
|
+
transfer.amount_reversed > transfer.amount
|
|
171
|
+
)
|
|
172
|
+
throw new Error("Invalid transfer receipt amount.");
|
|
173
|
+
for (const amount of [
|
|
174
|
+
ledger.reversedAmount ?? 0,
|
|
175
|
+
ledger.reversalClaimedAmount ?? 0,
|
|
176
|
+
]) {
|
|
177
|
+
integer(amount);
|
|
178
|
+
if (amount < 0 || amount > transfer.amount)
|
|
179
|
+
throw new Error("Invalid ledger reversal amount.");
|
|
180
|
+
}
|
|
181
|
+
if (
|
|
182
|
+
Math.max(ledger.reversedAmount ?? 0, ledger.reversalClaimedAmount ?? 0) >
|
|
183
|
+
transfer.amount_reversed
|
|
184
|
+
)
|
|
185
|
+
throw new NotReady("reversal_pending");
|
|
186
|
+
const payment = await request(() =>
|
|
187
|
+
stripe.charges.retrieve(
|
|
188
|
+
destinationPaymentId,
|
|
189
|
+
{},
|
|
190
|
+
{ stripeAccount: opts.accountId },
|
|
191
|
+
),
|
|
192
|
+
);
|
|
193
|
+
if (
|
|
194
|
+
payment.id !== destinationPaymentId ||
|
|
195
|
+
id(payment.source_transfer) !== transfer.id
|
|
196
|
+
)
|
|
197
|
+
throw new Error(
|
|
198
|
+
"Recipient payment identity does not match the transfer.",
|
|
199
|
+
);
|
|
200
|
+
if (payment.currency !== transfer.currency)
|
|
201
|
+
throw new NotReady("currency_allocation_required");
|
|
202
|
+
if (payment.amount !== transfer.amount)
|
|
203
|
+
throw new Error("Recipient payment amount does not match the transfer.");
|
|
204
|
+
if (
|
|
205
|
+
!payment.paid ||
|
|
206
|
+
payment.status !== "succeeded" ||
|
|
207
|
+
!id(payment.balance_transaction)
|
|
208
|
+
)
|
|
209
|
+
throw new NotReady("receipt_unavailable");
|
|
210
|
+
const records = new Map<string, TransferReceiptTransaction>();
|
|
211
|
+
let settlementCurrency: string | undefined;
|
|
212
|
+
async function receipts(
|
|
213
|
+
sourceId: string,
|
|
214
|
+
primary: { id: string; amount: number; currency: string; sign: 1 | -1 },
|
|
215
|
+
) {
|
|
216
|
+
const txs = await scan((cursor) =>
|
|
217
|
+
stripe.balanceTransactions.list(
|
|
218
|
+
{
|
|
219
|
+
source: sourceId,
|
|
220
|
+
limit: 100,
|
|
221
|
+
...(cursor ? { starting_after: cursor } : {}),
|
|
222
|
+
},
|
|
223
|
+
{ stripeAccount: opts.accountId },
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
if (!txs.length) throw new NotReady("receipt_unavailable");
|
|
227
|
+
for (const t of txs) {
|
|
228
|
+
if (id(t.source) !== sourceId)
|
|
229
|
+
throw new Error(
|
|
230
|
+
"Recipient receipt identity does not match its source.",
|
|
231
|
+
);
|
|
232
|
+
for (const amount of [
|
|
233
|
+
t.amount,
|
|
234
|
+
t.fee,
|
|
235
|
+
t.net,
|
|
236
|
+
t.created,
|
|
237
|
+
t.available_on,
|
|
238
|
+
])
|
|
239
|
+
integer(amount);
|
|
240
|
+
if (
|
|
241
|
+
t.fee < 0 ||
|
|
242
|
+
t.net !== integer(t.amount - t.fee) ||
|
|
243
|
+
t.created < 0 ||
|
|
244
|
+
t.available_on < 0
|
|
245
|
+
)
|
|
246
|
+
throw new Error("Invalid recipient receipt amount.");
|
|
247
|
+
if (t.status !== "available" && t.status !== "pending")
|
|
248
|
+
throw new NotReady("receipt_unavailable");
|
|
249
|
+
if (!/^[a-z]{3}$/.test(t.currency))
|
|
250
|
+
throw new Error("Invalid receipt currency.");
|
|
251
|
+
if ("balance_type" in t && t.balance_type !== "payments")
|
|
252
|
+
throw new NotReady("currency_allocation_required");
|
|
253
|
+
if (settlementCurrency && settlementCurrency !== t.currency)
|
|
254
|
+
throw new NotReady("currency_allocation_required");
|
|
255
|
+
settlementCurrency = t.currency;
|
|
256
|
+
if (t.id === primary.id) {
|
|
257
|
+
if (
|
|
258
|
+
t.currency === primary.currency &&
|
|
259
|
+
t.amount !== primary.sign * primary.amount
|
|
260
|
+
)
|
|
261
|
+
throw new Error(
|
|
262
|
+
"Original recipient receipt amount does not match the payment.",
|
|
263
|
+
);
|
|
264
|
+
if (
|
|
265
|
+
t.currency !== primary.currency &&
|
|
266
|
+
(!Number.isFinite(t.exchange_rate) ||
|
|
267
|
+
!t.exchange_rate ||
|
|
268
|
+
t.exchange_rate <= 0)
|
|
269
|
+
)
|
|
270
|
+
throw new NotReady("currency_allocation_required");
|
|
271
|
+
}
|
|
272
|
+
const record: TransferReceiptTransaction = {
|
|
273
|
+
id: t.id,
|
|
274
|
+
sourceId,
|
|
275
|
+
amount: t.amount,
|
|
276
|
+
fee: t.fee,
|
|
277
|
+
net: t.net,
|
|
278
|
+
currency: t.currency,
|
|
279
|
+
created: t.created,
|
|
280
|
+
availableOn: t.available_on,
|
|
281
|
+
status: t.status,
|
|
282
|
+
type: t.type,
|
|
283
|
+
};
|
|
284
|
+
const prior = records.get(t.id);
|
|
285
|
+
if (prior && JSON.stringify(prior) !== JSON.stringify(record))
|
|
286
|
+
throw new Error("Conflicting recipient receipt identity.");
|
|
287
|
+
records.set(t.id, record);
|
|
288
|
+
}
|
|
289
|
+
if (!records.has(primary.id)) throw new NotReady("receipt_unavailable");
|
|
290
|
+
}
|
|
291
|
+
await receipts(destinationPaymentId, {
|
|
292
|
+
id: id(payment.balance_transaction)!,
|
|
293
|
+
amount: payment.amount,
|
|
294
|
+
currency: payment.currency,
|
|
295
|
+
sign: 1,
|
|
296
|
+
});
|
|
297
|
+
const reversals = await scan((cursor) =>
|
|
298
|
+
stripe.transfers.listReversals(transfer.id, {
|
|
299
|
+
limit: 100,
|
|
300
|
+
...(cursor ? { starting_after: cursor } : {}),
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
const seenReversals = new Map<string, Stripe.TransferReversal>();
|
|
304
|
+
let reversed = 0;
|
|
305
|
+
for (const r of reversals) {
|
|
306
|
+
if (
|
|
307
|
+
id(r.transfer) !== transfer.id ||
|
|
308
|
+
r.currency !== transfer.currency ||
|
|
309
|
+
!Number.isSafeInteger(r.amount) ||
|
|
310
|
+
r.amount <= 0
|
|
311
|
+
)
|
|
312
|
+
throw new Error("Transfer reversal identity or amount does not match.");
|
|
313
|
+
const prior = seenReversals.get(r.id);
|
|
314
|
+
if (prior) {
|
|
315
|
+
if (
|
|
316
|
+
JSON.stringify([
|
|
317
|
+
prior.amount,
|
|
318
|
+
id(prior.destination_payment_refund),
|
|
319
|
+
]) !== JSON.stringify([r.amount, id(r.destination_payment_refund)])
|
|
320
|
+
)
|
|
321
|
+
throw new Error("Conflicting reversal receipt identity.");
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
seenReversals.set(r.id, r);
|
|
325
|
+
reversed = add(reversed, r.amount);
|
|
326
|
+
const refundId = id(r.destination_payment_refund);
|
|
327
|
+
if (!refundId) throw new NotReady("receipt_unavailable");
|
|
328
|
+
const refund = await request(() =>
|
|
329
|
+
stripe.refunds.retrieve(
|
|
330
|
+
refundId,
|
|
331
|
+
{},
|
|
332
|
+
{ stripeAccount: opts.accountId },
|
|
333
|
+
),
|
|
334
|
+
);
|
|
335
|
+
if (
|
|
336
|
+
refund.id !== refundId ||
|
|
337
|
+
id(refund.charge) !== destinationPaymentId ||
|
|
338
|
+
refund.amount !== r.amount ||
|
|
339
|
+
refund.currency !== transfer.currency
|
|
340
|
+
)
|
|
341
|
+
throw new Error(
|
|
342
|
+
"Recipient refund identity does not match its reversal.",
|
|
343
|
+
);
|
|
344
|
+
if (refund.status !== "succeeded") throw new NotReady("reversal_pending");
|
|
345
|
+
const refundTransactionId = id(refund.balance_transaction);
|
|
346
|
+
if (!refundTransactionId) throw new NotReady("receipt_unavailable");
|
|
347
|
+
await receipts(refundId, {
|
|
348
|
+
id: refundTransactionId,
|
|
349
|
+
amount: refund.amount,
|
|
350
|
+
currency: refund.currency,
|
|
351
|
+
sign: -1,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (reversed !== transfer.amount_reversed)
|
|
355
|
+
throw new NotReady("provider_changed");
|
|
356
|
+
const latest = await request(() => stripe.transfers.retrieve(transfer.id));
|
|
357
|
+
if (fingerprint(transfer) !== fingerprint(latest))
|
|
358
|
+
throw new NotReady("provider_changed");
|
|
359
|
+
const latestLedger = await read();
|
|
360
|
+
if (
|
|
361
|
+
!latestLedger ||
|
|
362
|
+
ledgerFingerprint(ledger) !== ledgerFingerprint(latestLedger)
|
|
363
|
+
)
|
|
364
|
+
throw new NotReady("ledger_changed");
|
|
365
|
+
let totalNet = 0,
|
|
366
|
+
availableNet = 0,
|
|
367
|
+
pendingCredits = 0,
|
|
368
|
+
pendingDebits = 0;
|
|
369
|
+
const now = Math.floor(Date.now() / 1000);
|
|
370
|
+
const primary = records.get(id(payment.balance_transaction)!)!;
|
|
371
|
+
const primaryPending =
|
|
372
|
+
primary.net > 0 &&
|
|
373
|
+
!(primary.status === "available" && primary.availableOn <= now)
|
|
374
|
+
? primary.net
|
|
375
|
+
: 0;
|
|
376
|
+
for (const t of records.values()) {
|
|
377
|
+
totalNet = add(totalNet, t.net);
|
|
378
|
+
if (t.status === "available" && t.availableOn <= now)
|
|
379
|
+
availableNet = add(availableNet, t.net);
|
|
380
|
+
else if (t.net > 0) pendingCredits = add(pendingCredits, t.net);
|
|
381
|
+
else pendingDebits = add(pendingDebits, -t.net);
|
|
382
|
+
}
|
|
383
|
+
// Cancel a pending original credit first. Uncovered pending debits reduce
|
|
384
|
+
// mature credit; later positive fee credits never qualify before maturity.
|
|
385
|
+
const canceledPending = Math.min(primaryPending, pendingDebits);
|
|
386
|
+
const matureNet = add(availableNet, -(pendingDebits - canceledPending));
|
|
387
|
+
pendingCredits = add(pendingCredits, -canceledPending);
|
|
388
|
+
return {
|
|
389
|
+
status: "ready",
|
|
390
|
+
...scope,
|
|
391
|
+
destinationPaymentId,
|
|
392
|
+
sourceAmount: transfer.amount,
|
|
393
|
+
sourceCurrency: transfer.currency,
|
|
394
|
+
settlementCurrency: settlementCurrency!,
|
|
395
|
+
providerReversedAmount: transfer.amount_reversed,
|
|
396
|
+
totalNet,
|
|
397
|
+
matureNet,
|
|
398
|
+
pendingCredits,
|
|
399
|
+
...(ledger.role ? { role: ledger.role } : {}),
|
|
400
|
+
reinstatement: ledger.reinstatement ?? false,
|
|
401
|
+
transactions: [...records.values()],
|
|
402
|
+
observedAt: Date.now(),
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (error instanceof NotReady)
|
|
406
|
+
return { status: "not_ready", ...scope, reason: error.reason };
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getTransferFundingReceipt,
|
|
3
|
+
type TransferReceiptOptions,
|
|
4
|
+
} from "./connect/transferReceipt.js";
|
|
5
|
+
export type * from "./connect/transferReceipt.js";
|
|
1
6
|
import type Stripe from "stripe";
|
|
2
7
|
import type { Infer } from "convex/values";
|
|
3
8
|
import type { Component, RunCtx } from "./helpers.js";
|
|
@@ -70,6 +75,14 @@ export class PayoutPolicyClient {
|
|
|
70
75
|
private readonly component: Component,
|
|
71
76
|
private readonly provider: () => Stripe,
|
|
72
77
|
) {}
|
|
78
|
+
transferReceipt(ctx: RunCtx, opts: TransferReceiptOptions) {
|
|
79
|
+
return getTransferFundingReceipt(
|
|
80
|
+
this.provider(),
|
|
81
|
+
this.component,
|
|
82
|
+
ctx,
|
|
83
|
+
opts,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
73
86
|
async get(
|
|
74
87
|
ctx: RunCtx,
|
|
75
88
|
scope: PayoutPolicyScope,
|
|
@@ -37,6 +37,10 @@ import type * as lib_stripeFactory from "../lib/stripeFactory.js";
|
|
|
37
37
|
import type * as payoutPolicy_helpers from "../payoutPolicy/helpers.js";
|
|
38
38
|
import type * as payoutPolicy_mutations from "../payoutPolicy/mutations.js";
|
|
39
39
|
import type * as payoutPolicy_queries from "../payoutPolicy/queries.js";
|
|
40
|
+
import type * as payoutRelease_allocation from "../payoutRelease/allocation.js";
|
|
41
|
+
import type * as payoutRelease_helpers from "../payoutRelease/helpers.js";
|
|
42
|
+
import type * as payoutRelease_mutations from "../payoutRelease/mutations.js";
|
|
43
|
+
import type * as payoutRelease_queries from "../payoutRelease/queries.js";
|
|
40
44
|
import type * as products_mutations from "../products/mutations.js";
|
|
41
45
|
import type * as products_queries from "../products/queries.js";
|
|
42
46
|
import type * as products_validators from "../products/validators.js";
|
|
@@ -84,6 +88,10 @@ const fullApi: ApiFromModules<{
|
|
|
84
88
|
"payoutPolicy/helpers": typeof payoutPolicy_helpers;
|
|
85
89
|
"payoutPolicy/mutations": typeof payoutPolicy_mutations;
|
|
86
90
|
"payoutPolicy/queries": typeof payoutPolicy_queries;
|
|
91
|
+
"payoutRelease/allocation": typeof payoutRelease_allocation;
|
|
92
|
+
"payoutRelease/helpers": typeof payoutRelease_helpers;
|
|
93
|
+
"payoutRelease/mutations": typeof payoutRelease_mutations;
|
|
94
|
+
"payoutRelease/queries": typeof payoutRelease_queries;
|
|
87
95
|
"products/mutations": typeof products_mutations;
|
|
88
96
|
"products/queries": typeof products_queries;
|
|
89
97
|
"products/validators": typeof products_validators;
|