@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
|
@@ -26,8 +26,10 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
26
26
|
currency: string;
|
|
27
27
|
debitFee?: number | undefined;
|
|
28
28
|
debitTransactionId?: string | undefined;
|
|
29
|
+
destinationId?: string | undefined;
|
|
29
30
|
expectedRevision: number;
|
|
30
31
|
feeRevision?: number | undefined;
|
|
32
|
+
historical?: boolean | undefined;
|
|
31
33
|
observations: {
|
|
32
34
|
currency: string;
|
|
33
35
|
ledgerFingerprint: string;
|
|
@@ -50,10 +52,12 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
50
52
|
}, {
|
|
51
53
|
accountId: import("convex/values").VString<string, "required">;
|
|
52
54
|
ownerKey: import("convex/values").VString<string, "required">;
|
|
55
|
+
historical: import("convex/values").VBoolean<boolean | undefined, "optional">;
|
|
53
56
|
operationKey: import("convex/values").VString<string, "required">;
|
|
54
57
|
expectedRevision: import("convex/values").VFloat64<number, "required">;
|
|
55
58
|
currency: import("convex/values").VString<string, "required">;
|
|
56
59
|
amount: import("convex/values").VFloat64<number, "required">;
|
|
60
|
+
destinationId: import("convex/values").VString<string | undefined, "optional">;
|
|
57
61
|
allocations: import("convex/values").VArray<{
|
|
58
62
|
amount: number;
|
|
59
63
|
role?: "affiliate" | "other" | "store" | undefined;
|
|
@@ -107,7 +111,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
107
111
|
requestId: import("convex/values").VString<string, "required">;
|
|
108
112
|
code: import("convex/values").VUnion<"amount_too_small" | "balance_insufficient", [import("convex/values").VLiteral<"balance_insufficient", "required">, import("convex/values").VLiteral<"amount_too_small", "required">], "required", never>;
|
|
109
113
|
}, "optional", "code" | "requestId">;
|
|
110
|
-
}, "required", "accountId" | "allocations" | "amount" | "attemptedAt" | "currency" | "debitFee" | "debitTransactionId" | "expectedRevision" | "feeRevision" | "observations" | "operationKey" | "ownerKey" | "providerFee" | "rejection" | "rejection.code" | "rejection.requestId" | "returnTransactionId" | "status" | "stripePayoutId">, {
|
|
114
|
+
}, "required", "accountId" | "allocations" | "amount" | "attemptedAt" | "currency" | "debitFee" | "debitTransactionId" | "destinationId" | "expectedRevision" | "feeRevision" | "historical" | "observations" | "operationKey" | "ownerKey" | "providerFee" | "rejection" | "rejection.code" | "rejection.requestId" | "returnTransactionId" | "status" | "stripePayoutId">, {
|
|
111
115
|
by_account_status: ["accountId", "status", "_creationTime"];
|
|
112
116
|
by_debit_receipt: ["debitTransactionId", "_creationTime"];
|
|
113
117
|
by_operation: ["operationKey", "_creationTime"];
|
|
@@ -185,6 +189,22 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
185
189
|
}, {}, {}>;
|
|
186
190
|
payoutPolicyOperations: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
187
191
|
accountId: string;
|
|
192
|
+
adoption?: {
|
|
193
|
+
claims: {
|
|
194
|
+
fingerprint: string;
|
|
195
|
+
operationKey: string;
|
|
196
|
+
}[];
|
|
197
|
+
observations: {
|
|
198
|
+
currency: string;
|
|
199
|
+
ledgerFingerprint: string;
|
|
200
|
+
matureNet: number;
|
|
201
|
+
observedAt: number;
|
|
202
|
+
pendingCredits: number;
|
|
203
|
+
stripeTransferId: string;
|
|
204
|
+
transactionIds: string[];
|
|
205
|
+
}[];
|
|
206
|
+
oldestHeldAt?: number | undefined;
|
|
207
|
+
} | undefined;
|
|
188
208
|
attempted?: boolean | undefined;
|
|
189
209
|
country: string;
|
|
190
210
|
expectedRevision: number;
|
|
@@ -216,6 +236,59 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
216
236
|
}, {
|
|
217
237
|
accountId: import("convex/values").VString<string, "required">;
|
|
218
238
|
ownerKey: import("convex/values").VString<string, "required">;
|
|
239
|
+
adoption: import("convex/values").VObject<{
|
|
240
|
+
claims: {
|
|
241
|
+
fingerprint: string;
|
|
242
|
+
operationKey: string;
|
|
243
|
+
}[];
|
|
244
|
+
observations: {
|
|
245
|
+
currency: string;
|
|
246
|
+
ledgerFingerprint: string;
|
|
247
|
+
matureNet: number;
|
|
248
|
+
observedAt: number;
|
|
249
|
+
pendingCredits: number;
|
|
250
|
+
stripeTransferId: string;
|
|
251
|
+
transactionIds: string[];
|
|
252
|
+
}[];
|
|
253
|
+
oldestHeldAt?: number | undefined;
|
|
254
|
+
} | undefined, {
|
|
255
|
+
observations: import("convex/values").VArray<{
|
|
256
|
+
currency: string;
|
|
257
|
+
ledgerFingerprint: string;
|
|
258
|
+
matureNet: number;
|
|
259
|
+
observedAt: number;
|
|
260
|
+
pendingCredits: number;
|
|
261
|
+
stripeTransferId: string;
|
|
262
|
+
transactionIds: string[];
|
|
263
|
+
}[], import("convex/values").VObject<{
|
|
264
|
+
currency: string;
|
|
265
|
+
ledgerFingerprint: string;
|
|
266
|
+
matureNet: number;
|
|
267
|
+
observedAt: number;
|
|
268
|
+
pendingCredits: number;
|
|
269
|
+
stripeTransferId: string;
|
|
270
|
+
transactionIds: string[];
|
|
271
|
+
}, {
|
|
272
|
+
stripeTransferId: import("convex/values").VString<string, "required">;
|
|
273
|
+
currency: import("convex/values").VString<string, "required">;
|
|
274
|
+
matureNet: import("convex/values").VFloat64<number, "required">;
|
|
275
|
+
pendingCredits: import("convex/values").VFloat64<number, "required">;
|
|
276
|
+
ledgerFingerprint: import("convex/values").VString<string, "required">;
|
|
277
|
+
observedAt: import("convex/values").VFloat64<number, "required">;
|
|
278
|
+
transactionIds: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
|
|
279
|
+
}, "required", "currency" | "ledgerFingerprint" | "matureNet" | "observedAt" | "pendingCredits" | "stripeTransferId" | "transactionIds">, "required">;
|
|
280
|
+
claims: import("convex/values").VArray<{
|
|
281
|
+
fingerprint: string;
|
|
282
|
+
operationKey: string;
|
|
283
|
+
}[], import("convex/values").VObject<{
|
|
284
|
+
fingerprint: string;
|
|
285
|
+
operationKey: string;
|
|
286
|
+
}, {
|
|
287
|
+
operationKey: import("convex/values").VString<string, "required">;
|
|
288
|
+
fingerprint: import("convex/values").VString<string, "required">;
|
|
289
|
+
}, "required", "fingerprint" | "operationKey">, "required">;
|
|
290
|
+
oldestHeldAt: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
291
|
+
}, "optional", "claims" | "observations" | "oldestHeldAt">;
|
|
219
292
|
operationKey: import("convex/values").VString<string, "required">;
|
|
220
293
|
expectedRevision: import("convex/values").VFloat64<number, "required">;
|
|
221
294
|
mode: import("convex/values").VUnion<"custom" | "stripe_managed", [import("convex/values").VLiteral<"stripe_managed", "required">, import("convex/values").VLiteral<"custom", "required">], "required", never>;
|
|
@@ -261,7 +334,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
261
334
|
}, "required", "interval" | "monthlyDays" | "weeklyDays">;
|
|
262
335
|
status: import("convex/values").VUnion<"canceled" | "complete" | "pending", [import("convex/values").VLiteral<"pending", "required">, import("convex/values").VLiteral<"complete", "required">, import("convex/values").VLiteral<"canceled", "required">], "required", never>;
|
|
263
336
|
attempted: import("convex/values").VBoolean<boolean | undefined, "optional">;
|
|
264
|
-
}, "required", "accountId" | "attempted" | "country" | "expectedRevision" | "mode" | "observedAt" | "observedSchedule" | "observedSchedule.interval" | "observedSchedule.monthlyDays" | "observedSchedule.weeklyDays" | "operationKey" | "ownerKey" | "previousSchedule" | "previousSchedule.interval" | "previousSchedule.monthlyDays" | "previousSchedule.weeklyDays" | "revision" | "status" | "targetSchedule" | "targetSchedule.interval" | "targetSchedule.monthlyDays" | "targetSchedule.weeklyDays" | "thresholds">, {
|
|
337
|
+
}, "required", "accountId" | "adoption" | "adoption.claims" | "adoption.observations" | "adoption.oldestHeldAt" | "attempted" | "country" | "expectedRevision" | "mode" | "observedAt" | "observedSchedule" | "observedSchedule.interval" | "observedSchedule.monthlyDays" | "observedSchedule.weeklyDays" | "operationKey" | "ownerKey" | "previousSchedule" | "previousSchedule.interval" | "previousSchedule.monthlyDays" | "previousSchedule.weeklyDays" | "revision" | "status" | "targetSchedule" | "targetSchedule.interval" | "targetSchedule.monthlyDays" | "targetSchedule.weeklyDays" | "thresholds">, {
|
|
265
338
|
by_operation: ["operationKey", "_creationTime"];
|
|
266
339
|
}, {}, {}>;
|
|
267
340
|
currencyTotals: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
package/docs/settlement.md
CHANGED
|
@@ -135,14 +135,10 @@ Balance Settings API does not expose a numeric payout-floor field; this package
|
|
|
135
135
|
does not invent one. `minimum_balance_by_currency` is a retained reserve after a
|
|
136
136
|
payout, **not a minimum payout trigger**. [Stripe reserve documentation](https://docs.stripe.com/payouts/minimum-balances-for-automatic-payouts).
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
and must account for existing transferred balances.
|
|
143
|
-
- Before bank payout: use a manual payout schedule where the platform has
|
|
144
|
-
permission, and run an automatic application job. Otherwise provider automatic
|
|
145
|
-
sweeps can bypass the application threshold. A retained reserve cannot fix that.
|
|
138
|
+
For a custom threshold, BetterStripe holds funds in the recipient's Stripe
|
|
139
|
+
balance and controls the bank-payout schedule. Verified manual controls and a
|
|
140
|
+
host automatic job prevent provider sweeps from bypassing the threshold. A
|
|
141
|
+
retained reserve cannot serve as the trigger.
|
|
146
142
|
|
|
147
143
|
Manual holding limits depend on country: Thailand 10 days, US 2 years, other
|
|
148
144
|
countries generally 90 days. Schedule permissions depend on account controls and
|
|
@@ -152,7 +148,7 @@ See [payout schedule permissions](https://docs.stripe.com/connect/manage-payout-
|
|
|
152
148
|
and [manual payout restrictions](https://docs.stripe.com/connect/manual-payouts).
|
|
153
149
|
|
|
154
150
|
Custom threshold enforcement is not provided by the snapshot or settlement APIs.
|
|
155
|
-
|
|
151
|
+
Use the recipient-held policy, adoption and release APIs below. Applications must
|
|
156
152
|
not describe a displayed threshold as enforced while Stripe controls automatic
|
|
157
153
|
payouts. A numeric below/exact/above provider-floor demonstration is unavailable
|
|
158
154
|
without a supported provider floor read; surface this capability limitation.
|
|
@@ -183,15 +179,15 @@ obtain control. Stripe also validates update permissions. Before each subsequent
|
|
|
183
179
|
payout attempt, `verify` must confirm live manual settings, unchanged country,
|
|
184
180
|
and an unexpired policy; stored `ready` state alone is insufficient.
|
|
185
181
|
|
|
186
|
-
|
|
187
|
-
in-flight payouts, and
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
182
|
+
Ordinary initial activation requires zero available/pending balances, no past or
|
|
183
|
+
in-flight payouts, and an automatic schedule. Existing balances, payout history
|
|
184
|
+
or a manual schedule return `migration_required`. The ordinary preflight repeats
|
|
185
|
+
before a delayed initial write. For an owned funded account, use the explicit
|
|
186
|
+
[historical import and funded activation flow](#historical-import-and-funded-activation)
|
|
187
|
+
below. Unknown money is never classified as earnings merely because it is
|
|
188
|
+
available. Existing manual schedules still require an authorized migration to a
|
|
189
|
+
known automatic schedule before custom activation; the package does not guess
|
|
190
|
+
which schedule should be restored later.
|
|
195
191
|
|
|
196
192
|
The host must schedule a recurring server job that pages `due(ctx,
|
|
197
193
|
{ cursor: null, numItems: 100 })`, follows its cursor, and calls `expire` for each
|
|
@@ -208,10 +204,9 @@ For opt-out, prepare a new operation with the latest revision,
|
|
|
208
204
|
available after Dashboard control is lost, subject to Stripe permissions. It
|
|
209
205
|
restores only the captured schedule; other balance settings remain untouched.
|
|
210
206
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
enforced until those boundaries and the recurring expiry job are installed.
|
|
207
|
+
The policy/control APIs compose with historical adoption and combined release.
|
|
208
|
+
Install recurring release and expiry jobs before exposing custom scheduling.
|
|
209
|
+
React/example job integration and larger-account collection remain separate work.
|
|
215
210
|
`verify.providerMinimum` is `null`: the Balance Settings API does not expose a
|
|
216
211
|
numeric payout-floor field, and retained balance reserves are not payout triggers.
|
|
217
212
|
|
|
@@ -219,14 +214,13 @@ Sources: [payout schedules](https://docs.stripe.com/connect/manage-payout-schedu
|
|
|
219
214
|
[manual payout holding limits](https://docs.stripe.com/connect/manual-payouts), and
|
|
220
215
|
[Dashboard control limits](https://docs.stripe.com/connect/platform-controls-for-stripe-dashboard-accounts#control-payout-timing).
|
|
221
216
|
|
|
222
|
-
### Transactional
|
|
217
|
+
### Transactional source allocations
|
|
223
218
|
|
|
224
|
-
The
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
prior payouts, independent liabilities, and live payout controls first.
|
|
219
|
+
The package release method owns provider verification and payout creation over
|
|
220
|
+
`payoutRelease` claim and source-allocation tables. Use that method rather than
|
|
221
|
+
calling trusted component mutations with UI totals or an isolated transfer
|
|
222
|
+
receipt. Complete provider history, still-unpaid ownership, prior payouts,
|
|
223
|
+
independent liabilities and live controls must be established together.
|
|
230
224
|
|
|
231
225
|
A claim combines mature creator and affiliate entitlement within one currency.
|
|
232
226
|
It deducts persisted paid and reserved allocations, keeps negative entitlement
|
|
@@ -242,13 +236,11 @@ release an unaccepted claim within a conservative 23-hour window. It requires
|
|
|
242
236
|
its own unique Stripe request receipt; ambiguous errors and older attempts stay
|
|
243
237
|
held. This window stays below Stripe's [24-hour v1 idempotency retention minimum](https://docs.stripe.com/api/idempotent_requests).
|
|
244
238
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
minimums can be presented as an operational feature. In-memory claim tests are
|
|
251
|
-
not evidence of a completed Stripe bank payout.
|
|
239
|
+
Current atomic snapshots support 100 account transfers, 100 historical claims and
|
|
240
|
+
1,000 receipt IDs per source. Larger/incomplete histories hold the whole operation
|
|
241
|
+
without partial totals. Resumable larger-account collection, other provider-debit
|
|
242
|
+
allocation and recurring/UI integration remain required. The package does not
|
|
243
|
+
claim that component tests alone demonstrate a Stripe bank payout.
|
|
252
244
|
|
|
253
245
|
## Verified recipient funding receipts
|
|
254
246
|
|
|
@@ -366,7 +358,156 @@ require explicit allocation. Original debit receipts and their fees remain
|
|
|
366
358
|
immutable. Older returned claims can receive verified fee evidence without
|
|
367
359
|
releasing their principal again.
|
|
368
360
|
|
|
369
|
-
These APIs reconcile existing claims
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
361
|
+
These receipt APIs reconcile existing claims. Use the release API below for
|
|
362
|
+
qualification and creation; receipt reconciliation by itself does not enforce a
|
|
363
|
+
minimum.
|
|
364
|
+
|
|
365
|
+
## Combined recipient release
|
|
366
|
+
|
|
367
|
+
After authorized custom policy activation, a host scheduled action can call:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
const result = await stripe.payoutPolicy.release(ctx, {
|
|
371
|
+
accountId,
|
|
372
|
+
ownerKey,
|
|
373
|
+
operationKey, // stable unique identity for this scheduled operation
|
|
374
|
+
currency: "usd",
|
|
375
|
+
independentLiabilities, // authorized outstanding off-provider debts/reserves
|
|
376
|
+
maxPages: 10,
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Authenticate the recipient and policy, and read all known off-provider debts and
|
|
381
|
+
reserves before invoking this server-only method. Never pass an untrusted client
|
|
382
|
+
amount or assume that an omitted debt is zero. Provider source refunds and payout
|
|
383
|
+
fees are already reconciled by the package and must not be counted again in
|
|
384
|
+
`independentLiabilities`. The host must install the recurring release and expiry
|
|
385
|
+
jobs before offering custom scheduling. This method does not install a cron.
|
|
386
|
+
|
|
387
|
+
The package verifies live manual schedule controls, reads every account source,
|
|
388
|
+
retrieves transfer and payout receipts, and compares the complete settlement
|
|
389
|
+
currency balance history with those verified receipts. Unknown credits, refunds,
|
|
390
|
+
fees and prior payouts return `held/unallocated_history`. A current balance is
|
|
391
|
+
never treated as proof of unpaid ownership. History must reconcile to the live
|
|
392
|
+
available and pending balance; changing or incomplete snapshots stay held.
|
|
393
|
+
|
|
394
|
+
Mature unpaid creator and affiliate sources qualify together in integer minor
|
|
395
|
+
units. Pending credits do not qualify. Outstanding source reservations, prior
|
|
396
|
+
paid allocations, actual refunds, payout fees and supplied independent liabilities
|
|
397
|
+
reduce eligibility. The cap uses the available card-source balance, since the
|
|
398
|
+
immutable provider request explicitly selects `source_type: "card"` and a
|
|
399
|
+
standard bank payout. The complete bank-account listing must identify one
|
|
400
|
+
default bank for the settlement currency. Its ID is persisted with the claim,
|
|
401
|
+
passed explicitly to Stripe, and checked again in the payout receipt. Other rails and currencies cannot increase that cap.
|
|
402
|
+
|
|
403
|
+
A qualifying operation reserves its exact source allocations in a component
|
|
404
|
+
transaction, verifies live controls again, and records the attempted marker before
|
|
405
|
+
calling Stripe. The provider idempotency key and request are fixed by the durable
|
|
406
|
+
claim. Concurrent callers therefore share one financial operation. Subsequent
|
|
407
|
+
calls to an attempted operation only reconcile provider receipts, including when
|
|
408
|
+
the original call timed out. They never blindly recreate an unidentified payout.
|
|
409
|
+
A verified no-object `balance_insufficient` or `amount_too_small` rejection releases
|
|
410
|
+
reservations; unknown errors keep them held. An interrupted unattempted operation
|
|
411
|
+
is canceled on replay, and a new scheduled operation can collect fresh receipts.
|
|
412
|
+
Below-threshold money remains owed. The numeric provider floor remains `null`
|
|
413
|
+
unless a supported provider contract exposes it.
|
|
414
|
+
|
|
415
|
+
Current atomic collection accepts up to 100 account transfers and 100 historical
|
|
416
|
+
claims. Larger accounts return `held/snapshot_limit`, without paying a partial
|
|
417
|
+
page. Provider history uses a 1–100 page budget, default 10; exhaustion stays held.
|
|
418
|
+
Use the historical import and funded activation flow below for known old funds.
|
|
419
|
+
Resumable larger-account collection, independent provider-debit allocation,
|
|
420
|
+
recurring example jobs and React controls remain integration work. Unknown funds
|
|
421
|
+
and unsupported accounts keep explicit migration holds.
|
|
422
|
+
|
|
423
|
+
Sources: [payout creation and source balance selection](https://docs.stripe.com/api/payouts/create),
|
|
424
|
+
[complete balance history and automatic-only payout filtering](https://docs.stripe.com/api/balance_transactions/list).
|
|
425
|
+
|
|
426
|
+
## Historical import and funded activation
|
|
427
|
+
|
|
428
|
+
The host authenticates one recipient account and owner, authorizes historical
|
|
429
|
+
source mappings, and records consent before custom scheduling. Start with an
|
|
430
|
+
owned `stripe_managed` policy at its current revision. If no policy exists,
|
|
431
|
+
prepare/apply a default policy with `expectedRevision: -1`,
|
|
432
|
+
`mode: "stripe_managed"`, and `thresholds: []`. This records ownership and keeps
|
|
433
|
+
the current automatic schedule.
|
|
434
|
+
|
|
435
|
+
Import past payouts whose allocations are not already recorded by BetterStripe:
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
const imported = await stripe.payoutPolicy.adoptPayouts(ctx, {
|
|
439
|
+
accountId,
|
|
440
|
+
ownerKey,
|
|
441
|
+
expectedRevision: policy.revision,
|
|
442
|
+
currency: "usd",
|
|
443
|
+
payouts: [
|
|
444
|
+
{
|
|
445
|
+
stripePayoutId: historicalPayoutId,
|
|
446
|
+
allocations: [
|
|
447
|
+
{ stripeTransferId: creatorTransferId, amount: 600 },
|
|
448
|
+
{ stripeTransferId: affiliateTransferId, amount: 400 },
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Amounts use integer settlement-currency minor units. The package does not infer
|
|
456
|
+
manual-payout ownership from a present balance or Stripe's automatic-only payout
|
|
457
|
+
filter. Supply authorized accounting for every unrecorded historical payout in
|
|
458
|
+
the currency. Existing package claims supply their immutable allocations on
|
|
459
|
+
subsequent calls; use `payouts: []` to verify/reconcile already imported history.
|
|
460
|
+
The same flow supports explicit accounting for automatic historical payouts.
|
|
461
|
+
|
|
462
|
+
The package retrieves actual recipient funding and payout receipts, verifies
|
|
463
|
+
full history against live available/pending cash, and validates each allocation
|
|
464
|
+
against source maturity at the historical payout date. Future credits and
|
|
465
|
+
not-yet-mature bank returns cannot fund an earlier payout. Terminal paid and
|
|
466
|
+
verified returned payouts are supported; pending or ambiguous outcomes stay
|
|
467
|
+
held. Unknown credits, debits, fees or unallocated payouts require further
|
|
468
|
+
reconciliation. All unknown historical payouts import as one component
|
|
469
|
+
transaction, including fees and source allocations. Changed mappings and stale
|
|
470
|
+
competing snapshots reject without mixing their writes. Replays never create a
|
|
471
|
+
payout. Historical paid principal remains paid after later refunds, so debt
|
|
472
|
+
reduces later qualifying funds rather than making the old commission payable
|
|
473
|
+
again. An adopted payout can reconcile a subsequent bank failure without
|
|
474
|
+
requiring a package marker that was absent from its original Stripe object.
|
|
475
|
+
|
|
476
|
+
After successful import, explicitly request funded-account adoption during
|
|
477
|
+
preparation, using the latest owned policy revision:
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
const prepared = await stripe.payoutPolicy.prepare(ctx, {
|
|
481
|
+
accountId,
|
|
482
|
+
ownerKey,
|
|
483
|
+
operationKey,
|
|
484
|
+
expectedRevision: policy.revision,
|
|
485
|
+
mode: "custom",
|
|
486
|
+
thresholds: [{ currency: "usd", amount: 1000 }],
|
|
487
|
+
consent: true,
|
|
488
|
+
adoptExistingBalance: true,
|
|
489
|
+
});
|
|
490
|
+
if (prepared.status === "prepared") {
|
|
491
|
+
await stripe.payoutPolicy.apply(ctx, { accountId, ownerKey, operationKey });
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Preparation verifies all settlement currencies, full source membership and prior
|
|
496
|
+
payouts. It repeats provider-history and balance checks before a delayed initial
|
|
497
|
+
attempt, then checks local financial fingerprints under OCC before recording
|
|
498
|
+
the attempt marker. New unallocated history cancels an unattempted activation
|
|
499
|
+
while preserving the automatic schedule. Refresh the policy revision and use a
|
|
500
|
+
new operation key after cancellation. If a manual schedule appears without the
|
|
501
|
+
package's attempt marker, the operation reports schedule drift rather than
|
|
502
|
+
pretending the package performed the change. Provider timeouts retain the
|
|
503
|
+
persisted attempt and immutable schedule request for reconciliation.
|
|
504
|
+
|
|
505
|
+
Holding age follows the unpaid funds. Paid original credits do not age newer
|
|
506
|
+
funds; source debt and verified payout fees consume older ownership first.
|
|
507
|
+
Verified bank-return cash starts a new holding age when it becomes available.
|
|
508
|
+
Preparation cannot restart an expired holding period, and later checks can only
|
|
509
|
+
shorten the pending operation's deadline. `migration_required.adoptionReason`
|
|
510
|
+
explains unknown history or an expired holding period. This flow does not enable
|
|
511
|
+
Dashboard accounts with withdrawal bypasses, Global Payouts, unsupported source
|
|
512
|
+
rails, unknown numeric provider floors or accounts beyond the current atomic
|
|
513
|
+
snapshot bounds. It installs no host cron or UI.
|
package/package.json
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { Infer } from "convex/values";
|
|
3
|
+
import type { Component, RunCtx } from "../helpers.js";
|
|
4
|
+
import type { adoptionProof } from "../../component/payoutPolicy/schema.js";
|
|
5
|
+
import type { claimDoc } from "../../component/payoutRelease/schema.js";
|
|
6
|
+
import { payoutClaimFingerprint } from "../../component/payoutRelease/helpers.js";
|
|
7
|
+
import { componentRef } from "../webhooks/helpers.js";
|
|
8
|
+
import { adoptRecipientPayouts } from "./payoutAdoption.js";
|
|
9
|
+
function balanceIdentity(b: Stripe.Balance) {
|
|
10
|
+
return JSON.stringify(
|
|
11
|
+
[b.available, b.pending].map((rows) =>
|
|
12
|
+
rows
|
|
13
|
+
.map((t) => [
|
|
14
|
+
t.currency,
|
|
15
|
+
t.amount,
|
|
16
|
+
Object.entries(t.source_types ?? {}).sort(),
|
|
17
|
+
])
|
|
18
|
+
.sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function transactionIdentity(t?: Stripe.BalanceTransaction) {
|
|
23
|
+
return t
|
|
24
|
+
? JSON.stringify([
|
|
25
|
+
t.id,
|
|
26
|
+
t.source,
|
|
27
|
+
t.amount,
|
|
28
|
+
t.net,
|
|
29
|
+
t.currency,
|
|
30
|
+
t.status,
|
|
31
|
+
t.available_on,
|
|
32
|
+
])
|
|
33
|
+
: "none";
|
|
34
|
+
}
|
|
35
|
+
/** Complete read/reconcile proof. Unknown historical payouts require explicit import first. */
|
|
36
|
+
export async function collectAdoptionProof(
|
|
37
|
+
stripe: Stripe,
|
|
38
|
+
component: Component,
|
|
39
|
+
ctx: RunCtx,
|
|
40
|
+
scope: { accountId: string; ownerKey: string },
|
|
41
|
+
expectedRevision: number,
|
|
42
|
+
configuredCurrencies: string[],
|
|
43
|
+
): Promise<
|
|
44
|
+
| { status: "verified"; proof: Infer<typeof adoptionProof> }
|
|
45
|
+
| { status: "held"; reason: string }
|
|
46
|
+
> {
|
|
47
|
+
const request = { stripeAccount: scope.accountId };
|
|
48
|
+
const initial = await stripe.balance.retrieve(undefined, request);
|
|
49
|
+
const first = await stripe.balanceTransactions.list({ limit: 1 }, request);
|
|
50
|
+
const queue = new Set([
|
|
51
|
+
...configuredCurrencies,
|
|
52
|
+
...initial.available.map((t) => t.currency),
|
|
53
|
+
...initial.pending.map((t) => t.currency),
|
|
54
|
+
...(first.data[0] ? [first.data[0].currency] : []),
|
|
55
|
+
]);
|
|
56
|
+
const completed = new Set<string>();
|
|
57
|
+
let observations: Infer<typeof adoptionProof>["observations"] = [],
|
|
58
|
+
oldestHeldAt: number | undefined;
|
|
59
|
+
for (const currency of queue) {
|
|
60
|
+
if (completed.size >= 50)
|
|
61
|
+
return { status: "held", reason: "currency_history_incomplete" };
|
|
62
|
+
const result = await adoptRecipientPayouts(
|
|
63
|
+
stripe,
|
|
64
|
+
component,
|
|
65
|
+
ctx,
|
|
66
|
+
{ ...scope, currency, expectedRevision, payouts: [] },
|
|
67
|
+
true,
|
|
68
|
+
);
|
|
69
|
+
if (result.status !== "adopted") return result;
|
|
70
|
+
completed.add(currency);
|
|
71
|
+
for (const c of result.historyCurrencies) queue.add(c);
|
|
72
|
+
observations = result.observations;
|
|
73
|
+
if (result.oldestHeldAt !== undefined)
|
|
74
|
+
oldestHeldAt = Math.min(oldestHeldAt ?? Infinity, result.oldestHeldAt);
|
|
75
|
+
}
|
|
76
|
+
const balance = await stripe.balance.retrieve(undefined, request);
|
|
77
|
+
const latest = await stripe.balanceTransactions.list({ limit: 1 }, request);
|
|
78
|
+
if (
|
|
79
|
+
balanceIdentity(initial) !== balanceIdentity(balance) ||
|
|
80
|
+
transactionIdentity(first.data[0]) !== transactionIdentity(latest.data[0])
|
|
81
|
+
)
|
|
82
|
+
return { status: "held", reason: "provider_changed" };
|
|
83
|
+
const snapshot = (await ctx.runQuery(
|
|
84
|
+
componentRef(component, "payoutRelease/queries/snapshot"),
|
|
85
|
+
{ accountId: scope.accountId, ownerKey: scope.ownerKey },
|
|
86
|
+
)) as
|
|
87
|
+
| { status: "ready"; claims: Infer<typeof claimDoc>[] }
|
|
88
|
+
| { status: "held"; reason: string };
|
|
89
|
+
if (snapshot.status !== "ready") return snapshot;
|
|
90
|
+
return {
|
|
91
|
+
status: "verified",
|
|
92
|
+
proof: {
|
|
93
|
+
observations,
|
|
94
|
+
claims: snapshot.claims.map((c) => ({
|
|
95
|
+
operationKey: c.operationKey,
|
|
96
|
+
fingerprint: payoutClaimFingerprint(c),
|
|
97
|
+
})),
|
|
98
|
+
...(oldestHeldAt !== undefined ? { oldestHeldAt } : {}),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|