@fantastic.dev/better-stripe 0.20.1 → 0.22.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 +16 -5
- 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/payoutReceipt.d.ts +53 -0
- package/dist/client/connect/payoutReceipt.d.ts.map +1 -0
- package/dist/client/connect/payoutReceipt.js +185 -0
- package/dist/client/connect/payoutReceipt.js.map +1 -0
- package/dist/client/connect/payoutReconciliation.d.ts +38 -0
- package/dist/client/connect/payoutReconciliation.d.ts.map +1 -0
- package/dist/client/connect/payoutReconciliation.js +112 -0
- package/dist/client/connect/payoutReconciliation.js.map +1 -0
- package/dist/client/payoutPolicy.d.ts +9 -0
- package/dist/client/payoutPolicy.d.ts.map +1 -1
- package/dist/client/payoutPolicy.js +12 -0
- package/dist/client/payoutPolicy.js.map +1 -1
- package/dist/component/_generated/component.d.ts +112 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/earnings/triggers.d.ts +52 -2
- package/dist/component/earnings/triggers.d.ts.map +1 -1
- package/dist/component/payoutRelease/allocation.d.ts +1 -0
- package/dist/component/payoutRelease/allocation.d.ts.map +1 -1
- package/dist/component/payoutRelease/allocation.js +3 -0
- package/dist/component/payoutRelease/allocation.js.map +1 -1
- package/dist/component/payoutRelease/fees.d.ts +22 -0
- package/dist/component/payoutRelease/fees.d.ts.map +1 -0
- package/dist/component/payoutRelease/fees.js +73 -0
- package/dist/component/payoutRelease/fees.js.map +1 -0
- package/dist/component/payoutRelease/helpers.d.ts +5 -0
- package/dist/component/payoutRelease/helpers.d.ts.map +1 -1
- package/dist/component/payoutRelease/mutations.d.ts +29 -0
- package/dist/component/payoutRelease/mutations.d.ts.map +1 -1
- package/dist/component/payoutRelease/mutations.js +24 -1
- package/dist/component/payoutRelease/mutations.js.map +1 -1
- package/dist/component/payoutRelease/queries.d.ts +87 -0
- package/dist/component/payoutRelease/queries.d.ts.map +1 -1
- package/dist/component/payoutRelease/queries.js +39 -0
- package/dist/component/payoutRelease/queries.js.map +1 -1
- package/dist/component/payoutRelease/schema.d.ts +43 -2
- package/dist/component/payoutRelease/schema.d.ts.map +1 -1
- package/dist/component/payoutRelease/schema.js +12 -0
- package/dist/component/payoutRelease/schema.js.map +1 -1
- package/dist/component/schema.d.ts +27 -1
- package/dist/component/schema.js +2 -1
- package/dist/component/schema.js.map +1 -1
- package/docs/settlement.md +134 -3
- package/package.json +1 -1
- package/src/client/connect/payoutAllocation.test.ts +36 -3
- 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/payoutReceipt.test.ts +312 -0
- package/src/client/connect/payoutReceipt.ts +287 -0
- package/src/client/connect/payoutReconciliation.test.ts +398 -0
- package/src/client/connect/payoutReconciliation.ts +178 -0
- package/src/client/payoutPolicy.ts +30 -0
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +118 -0
- package/src/component/payout-fees.test.ts +224 -0
- package/src/component/payout-release-snapshot.test.ts +57 -0
- package/src/component/payoutRelease/allocation.ts +6 -0
- package/src/component/payoutRelease/fees.ts +93 -0
- package/src/component/payoutRelease/mutations.ts +26 -1
- package/src/component/payoutRelease/queries.ts +46 -0
- package/src/component/payoutRelease/schema.ts +13 -0
- package/src/component/schema.ts +2 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFees, recordFees } from "./fees.js";
|
|
1
2
|
import { v } from "convex/values";
|
|
2
3
|
import { mutation } from "../_generated/server.js";
|
|
3
4
|
import { identity, observation, claimDoc } from "./schema.js";
|
|
@@ -30,6 +31,7 @@ export const claim = mutation({
|
|
|
30
31
|
...identity,
|
|
31
32
|
expectedRevision: v.number(),
|
|
32
33
|
currency: v.string(),
|
|
34
|
+
destinationId: v.optional(v.string()),
|
|
33
35
|
providerAvailable: v.number(),
|
|
34
36
|
independentLiabilities: v.number(),
|
|
35
37
|
observations: v.array(observation),
|
|
@@ -37,11 +39,17 @@ export const claim = mutation({
|
|
|
37
39
|
returns: v.union(claimDoc, held),
|
|
38
40
|
handler: async (ctx, args) => {
|
|
39
41
|
validateIdentity(args);
|
|
42
|
+
if (
|
|
43
|
+
args.destinationId !== undefined &&
|
|
44
|
+
!/^ba_\w+$/.test(args.destinationId)
|
|
45
|
+
)
|
|
46
|
+
throw new Error("Invalid payout bank destination.");
|
|
40
47
|
const existing = await claimByKey(ctx, args);
|
|
41
48
|
if (existing) {
|
|
42
49
|
if (
|
|
43
50
|
existing.currency !== args.currency ||
|
|
44
|
-
existing.expectedRevision !== args.expectedRevision
|
|
51
|
+
existing.expectedRevision !== args.expectedRevision ||
|
|
52
|
+
existing.destinationId !== args.destinationId
|
|
45
53
|
)
|
|
46
54
|
throw new Error("Payout claim identity cannot be reused.");
|
|
47
55
|
return existing;
|
|
@@ -88,11 +96,13 @@ export const claim = mutation({
|
|
|
88
96
|
reservedAmount: prior?.reservedAmount ?? 0,
|
|
89
97
|
});
|
|
90
98
|
}
|
|
99
|
+
const fees = await readFees(ctx, args);
|
|
91
100
|
const plan = planRecipientPayout({
|
|
92
101
|
currency: args.currency,
|
|
93
102
|
threshold: minimum.amount,
|
|
94
103
|
providerAvailable: args.providerAvailable,
|
|
95
104
|
independentLiabilities: args.independentLiabilities,
|
|
105
|
+
confirmedFees: fees?.amount ?? 0,
|
|
96
106
|
sources,
|
|
97
107
|
});
|
|
98
108
|
if (plan.status === "held") return plan;
|
|
@@ -119,8 +129,10 @@ export const claim = mutation({
|
|
|
119
129
|
expectedRevision: args.expectedRevision,
|
|
120
130
|
currency: args.currency,
|
|
121
131
|
amount: plan.amount,
|
|
132
|
+
...(args.destinationId ? { destinationId: args.destinationId } : {}),
|
|
122
133
|
allocations: plan.allocations,
|
|
123
134
|
observations: args.observations,
|
|
135
|
+
feeRevision: fees?.revision ?? 0,
|
|
124
136
|
status: "reserved",
|
|
125
137
|
});
|
|
126
138
|
return (await ctx.db.get("payoutReleaseClaims", id))!;
|
|
@@ -135,6 +147,9 @@ export const beginAttempt = mutation({
|
|
|
135
147
|
throw new Error("Payout claim is not reserved.");
|
|
136
148
|
if (claim.attemptedAt !== undefined) return claim;
|
|
137
149
|
await readyPolicy(ctx, claim);
|
|
150
|
+
const feeRevision = (await readFees(ctx, claim))?.revision ?? 0;
|
|
151
|
+
if (feeRevision !== (claim.feeRevision ?? 0))
|
|
152
|
+
throw new Error("Payout fee revision changed after reservation.");
|
|
138
153
|
await checkSnapshot(ctx, claim.accountId, claim.observations, true);
|
|
139
154
|
await ctx.db.patch("payoutReleaseClaims", claim._id, {
|
|
140
155
|
attemptedAt: Date.now(),
|
|
@@ -175,6 +190,9 @@ export const recordOutcome = mutation({
|
|
|
175
190
|
v.literal("canceled"),
|
|
176
191
|
),
|
|
177
192
|
returnTransactionId: v.optional(v.string()),
|
|
193
|
+
debitTransactionId: v.optional(v.string()),
|
|
194
|
+
debitFee: v.optional(v.number()),
|
|
195
|
+
returnFee: v.optional(v.number()),
|
|
178
196
|
},
|
|
179
197
|
returns: claimDoc,
|
|
180
198
|
handler: async (ctx, args) => {
|
|
@@ -226,8 +244,15 @@ export const recordOutcome = mutation({
|
|
|
226
244
|
if (claim.status === "returned") {
|
|
227
245
|
if (returned && args.returnTransactionId !== claim.returnTransactionId)
|
|
228
246
|
throw new Error("Payout return receipt identity changed.");
|
|
247
|
+
if (returned) {
|
|
248
|
+
await recordFees(ctx, claim, args, true);
|
|
249
|
+
return (await ctx.db.get("payoutReleaseClaims", claim._id))!;
|
|
250
|
+
}
|
|
229
251
|
return claim;
|
|
230
252
|
}
|
|
253
|
+
if (claim.status === "paid" && args.providerStatus !== "paid" && !returned)
|
|
254
|
+
return claim;
|
|
255
|
+
await recordFees(ctx, claim, args, returned);
|
|
231
256
|
if (returned) {
|
|
232
257
|
await moveAllocations(
|
|
233
258
|
ctx,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { transferDocValidator } from "../connect/validators.js";
|
|
2
|
+
import { readFees } from "./fees.js";
|
|
1
3
|
import { paginationOptsValidator } from "convex/server";
|
|
2
4
|
import { v } from "convex/values";
|
|
3
5
|
import { query } from "../_generated/server.js";
|
|
@@ -54,3 +56,47 @@ export const pending = query({
|
|
|
54
56
|
};
|
|
55
57
|
},
|
|
56
58
|
});
|
|
59
|
+
|
|
60
|
+
export const fees = query({
|
|
61
|
+
args: { accountId: v.string(), ownerKey: v.string(), currency: v.string() },
|
|
62
|
+
returns: v.object({ amount: v.number(), revision: v.number() }),
|
|
63
|
+
handler: async (ctx, args) => {
|
|
64
|
+
await readPolicy(ctx, args);
|
|
65
|
+
const balance = await readFees(ctx, args);
|
|
66
|
+
return { amount: balance?.amount ?? 0, revision: balance?.revision ?? 0 };
|
|
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
|
+
});
|
|
@@ -23,6 +23,7 @@ export const claimFields = {
|
|
|
23
23
|
expectedRevision: v.number(),
|
|
24
24
|
currency: v.string(),
|
|
25
25
|
amount: v.number(),
|
|
26
|
+
destinationId: v.optional(v.string()),
|
|
26
27
|
allocations: v.array(allocation),
|
|
27
28
|
observations: v.array(observation),
|
|
28
29
|
status: v.union(
|
|
@@ -36,6 +37,10 @@ export const claimFields = {
|
|
|
36
37
|
attemptedAt: v.optional(v.number()),
|
|
37
38
|
stripePayoutId: v.optional(v.string()),
|
|
38
39
|
returnTransactionId: v.optional(v.string()),
|
|
40
|
+
feeRevision: v.optional(v.number()),
|
|
41
|
+
debitTransactionId: v.optional(v.string()),
|
|
42
|
+
debitFee: v.optional(v.number()),
|
|
43
|
+
providerFee: v.optional(v.number()),
|
|
39
44
|
rejection: v.optional(
|
|
40
45
|
v.object({
|
|
41
46
|
requestId: v.string(),
|
|
@@ -55,6 +60,7 @@ export const claimsTable = defineTable(claimFields)
|
|
|
55
60
|
.index("by_operation", ["operationKey"])
|
|
56
61
|
.index("by_account_status", ["accountId", "status"])
|
|
57
62
|
.index("by_provider_payout", ["stripePayoutId"])
|
|
63
|
+
.index("by_debit_receipt", ["debitTransactionId"])
|
|
58
64
|
.index("by_return_receipt", ["returnTransactionId"])
|
|
59
65
|
.index("by_rejection_request", ["rejection.requestId"]);
|
|
60
66
|
export const sourcesTable = defineTable({
|
|
@@ -67,3 +73,10 @@ export const sourcesTable = defineTable({
|
|
|
67
73
|
.index("by_transfer", ["stripeTransferId"])
|
|
68
74
|
.index("by_account", ["accountId"]);
|
|
69
75
|
export const identity = { ...scopeFields, operationKey: v.string() };
|
|
76
|
+
|
|
77
|
+
export const feeBalancesTable = defineTable({
|
|
78
|
+
...scopeFields,
|
|
79
|
+
currency: v.string(),
|
|
80
|
+
amount: v.number(),
|
|
81
|
+
revision: v.number(),
|
|
82
|
+
}).index("by_account_currency", ["accountId", "currency"]);
|
package/src/component/schema.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
feeBalancesTable as payoutFeeBalancesTable,
|
|
2
3
|
claimsTable as payoutReleaseClaimsTable,
|
|
3
4
|
sourcesTable as payoutSourceAllocationsTable,
|
|
4
5
|
} from "./payoutRelease/schema.js";
|
|
@@ -36,6 +37,7 @@ import { pricesTable, productsTable } from "./products/schema";
|
|
|
36
37
|
import { webhookEventsTable } from "./webhooks/schema";
|
|
37
38
|
|
|
38
39
|
export default defineSchema({
|
|
40
|
+
payoutFeeBalances: payoutFeeBalancesTable,
|
|
39
41
|
payoutReleaseClaims: payoutReleaseClaimsTable,
|
|
40
42
|
payoutSourceAllocations: payoutSourceAllocationsTable,
|
|
41
43
|
payoutPolicies: policiesTable,
|