@fantastic.dev/better-stripe 0.23.1 → 0.24.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 +26 -2
- package/README.md +40 -5
- package/dist/client/affiliateRecovery/disputes.d.ts +18 -0
- package/dist/client/affiliateRecovery/disputes.d.ts.map +1 -0
- package/dist/client/affiliateRecovery/disputes.js +33 -0
- package/dist/client/affiliateRecovery/disputes.js.map +1 -0
- package/dist/client/affiliateRecovery/engine.d.ts +29 -0
- package/dist/client/affiliateRecovery/engine.d.ts.map +1 -0
- package/dist/client/affiliateRecovery/engine.js +116 -0
- package/dist/client/affiliateRecovery/engine.js.map +1 -0
- package/dist/client/affiliateRecovery/payoutGate.d.ts +24 -0
- package/dist/client/affiliateRecovery/payoutGate.d.ts.map +1 -0
- package/dist/client/affiliateRecovery/payoutGate.js +30 -0
- package/dist/client/affiliateRecovery/payoutGate.js.map +1 -0
- package/dist/client/affiliateRecovery/provider.d.ts +14 -0
- package/dist/client/affiliateRecovery/provider.d.ts.map +1 -0
- package/dist/client/affiliateRecovery/provider.js +96 -0
- package/dist/client/affiliateRecovery/provider.js.map +1 -0
- package/dist/client/affiliateRecovery/review.d.ts +28 -0
- package/dist/client/affiliateRecovery/review.d.ts.map +1 -0
- package/dist/client/affiliateRecovery/review.js +86 -0
- package/dist/client/affiliateRecovery/review.js.map +1 -0
- package/dist/client/affiliateRecovery/validators.d.ts +402 -0
- package/dist/client/affiliateRecovery/validators.d.ts.map +1 -0
- package/dist/client/affiliateRecovery/validators.js +27 -0
- package/dist/client/affiliateRecovery/validators.js.map +1 -0
- package/dist/client/affiliateRecovery.d.ts +131 -0
- package/dist/client/affiliateRecovery.d.ts.map +1 -0
- package/dist/client/affiliateRecovery.js +69 -0
- package/dist/client/affiliateRecovery.js.map +1 -0
- package/dist/client/connect/commissionReceipts.d.ts +4 -0
- package/dist/client/connect/commissionReceipts.d.ts.map +1 -0
- package/dist/client/connect/commissionReceipts.js +17 -0
- package/dist/client/connect/commissionReceipts.js.map +1 -0
- package/dist/client/connect/payoutAdoptionTestHelpers.d.ts +536 -2
- package/dist/client/connect/payoutAdoptionTestHelpers.d.ts.map +1 -1
- package/dist/client/connect/payoutSync.d.ts +32 -0
- package/dist/client/connect/payoutSync.d.ts.map +1 -0
- package/dist/client/connect/payoutSync.js +48 -0
- package/dist/client/connect/payoutSync.js.map +1 -0
- package/dist/client/connect/payoutSyncTestHelpers.d.ts +1509 -0
- package/dist/client/connect/payoutSyncTestHelpers.d.ts.map +1 -0
- package/dist/client/connect/payoutSyncTestHelpers.js +62 -0
- package/dist/client/connect/payoutSyncTestHelpers.js.map +1 -0
- package/dist/client/connect/payoutSyncValidation.d.ts +16 -0
- package/dist/client/connect/payoutSyncValidation.d.ts.map +1 -0
- package/dist/client/connect/payoutSyncValidation.js +54 -0
- package/dist/client/connect/payoutSyncValidation.js.map +1 -0
- package/dist/client/connect/payouts.d.ts +3 -1
- package/dist/client/connect/payouts.d.ts.map +1 -1
- package/dist/client/connect/payouts.js +45 -12
- package/dist/client/connect/payouts.js.map +1 -1
- package/dist/client/connect/refundPrincipal.d.ts +4 -0
- package/dist/client/connect/refundPrincipal.d.ts.map +1 -0
- package/dist/client/connect/refundPrincipal.js +21 -0
- package/dist/client/connect/refundPrincipal.js.map +1 -0
- package/dist/client/connect/reversalExecution.d.ts +47 -0
- package/dist/client/connect/reversalExecution.d.ts.map +1 -0
- package/dist/client/connect/reversalExecution.js +84 -0
- package/dist/client/connect/reversalExecution.js.map +1 -0
- package/dist/client/connect/transfers.d.ts +8 -4
- package/dist/client/connect/transfers.d.ts.map +1 -1
- package/dist/client/connect/transfers.js +52 -57
- package/dist/client/connect/transfers.js.map +1 -1
- package/dist/client/index.d.ts +29 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +9 -6
- package/dist/client/index.js.map +1 -1
- package/dist/client/readContracts.d.ts +8 -0
- package/dist/client/readContracts.d.ts.map +1 -1
- package/dist/client/readContracts.js +7 -0
- package/dist/client/readContracts.js.map +1 -1
- package/dist/client/settlement/execution.d.ts.map +1 -1
- package/dist/client/settlement/execution.js +30 -3
- package/dist/client/settlement/execution.js.map +1 -1
- package/dist/client/settlement/reconciliation.d.ts.map +1 -1
- package/dist/client/settlement/reconciliation.js +16 -2
- package/dist/client/settlement/reconciliation.js.map +1 -1
- package/dist/client/webhooks/disputeRecovery.d.ts +4 -0
- package/dist/client/webhooks/disputeRecovery.d.ts.map +1 -0
- package/dist/client/webhooks/disputeRecovery.js +40 -0
- package/dist/client/webhooks/disputeRecovery.js.map +1 -0
- package/dist/client/webhooks/processors.d.ts.map +1 -1
- package/dist/client/webhooks/processors.js +14 -29
- package/dist/client/webhooks/processors.js.map +1 -1
- package/dist/component/_generated/component.d.ts +560 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/affiliateEarnings/mutations.d.ts +22 -0
- package/dist/component/affiliateEarnings/mutations.d.ts.map +1 -0
- package/dist/component/affiliateEarnings/mutations.js +160 -0
- package/dist/component/affiliateEarnings/mutations.js.map +1 -0
- package/dist/component/affiliatePayouts/mutations.d.ts +25 -0
- package/dist/component/affiliatePayouts/mutations.d.ts.map +1 -0
- package/dist/component/affiliatePayouts/mutations.js +104 -0
- package/dist/component/affiliatePayouts/mutations.js.map +1 -0
- package/dist/component/affiliatePayouts/queries.d.ts +23 -0
- package/dist/component/affiliatePayouts/queries.d.ts.map +1 -0
- package/dist/component/affiliatePayouts/queries.js +23 -0
- package/dist/component/affiliatePayouts/queries.js.map +1 -0
- package/dist/component/affiliatePayouts/recipientGuard.d.ts +12 -0
- package/dist/component/affiliatePayouts/recipientGuard.d.ts.map +1 -0
- package/dist/component/affiliatePayouts/recipientGuard.js +26 -0
- package/dist/component/affiliatePayouts/recipientGuard.js.map +1 -0
- package/dist/component/affiliateRecovery/accounting.d.ts +28 -0
- package/dist/component/affiliateRecovery/accounting.d.ts.map +1 -0
- package/dist/component/affiliateRecovery/accounting.js +71 -0
- package/dist/component/affiliateRecovery/accounting.js.map +1 -0
- package/dist/component/affiliateRecovery/mutations.d.ts +65 -0
- package/dist/component/affiliateRecovery/mutations.d.ts.map +1 -0
- package/dist/component/affiliateRecovery/mutations.js +250 -0
- package/dist/component/affiliateRecovery/mutations.js.map +1 -0
- package/dist/component/affiliateRecovery/queries.d.ts +305 -0
- package/dist/component/affiliateRecovery/queries.d.ts.map +1 -0
- package/dist/component/affiliateRecovery/queries.js +113 -0
- package/dist/component/affiliateRecovery/queries.js.map +1 -0
- package/dist/component/affiliateRecovery/schema.d.ts +171 -0
- package/dist/component/affiliateRecovery/schema.d.ts.map +1 -0
- package/dist/component/affiliateRecovery/schema.js +71 -0
- package/dist/component/affiliateRecovery/schema.js.map +1 -0
- package/dist/component/connect/mutations.d.ts +36 -23
- package/dist/component/connect/mutations.d.ts.map +1 -1
- package/dist/component/connect/mutations.js +11 -151
- package/dist/component/connect/mutations.js.map +1 -1
- package/dist/component/connect/payoutWrites.d.ts +21 -0
- package/dist/component/connect/payoutWrites.d.ts.map +1 -0
- package/dist/component/connect/payoutWrites.js +19 -0
- package/dist/component/connect/payoutWrites.js.map +1 -0
- package/dist/component/connect/queries.d.ts +10 -0
- package/dist/component/connect/queries.d.ts.map +1 -1
- package/dist/component/connect/queries.js +3 -40
- package/dist/component/connect/queries.js.map +1 -1
- package/dist/component/connect/reversalClaims.d.ts +169 -0
- package/dist/component/connect/reversalClaims.d.ts.map +1 -0
- package/dist/component/connect/reversalClaims.js +176 -0
- package/dist/component/connect/reversalClaims.js.map +1 -0
- package/dist/component/connect/reversalDiagnostics.d.ts +59 -0
- package/dist/component/connect/reversalDiagnostics.d.ts.map +1 -0
- package/dist/component/connect/reversalDiagnostics.js +50 -0
- package/dist/component/connect/reversalDiagnostics.js.map +1 -0
- package/dist/component/connect/reversalReceipts.d.ts +27 -0
- package/dist/component/connect/reversalReceipts.d.ts.map +1 -0
- package/dist/component/connect/reversalReceipts.js +85 -0
- package/dist/component/connect/reversalReceipts.js.map +1 -0
- package/dist/component/connect/schema.d.ts +31 -1
- package/dist/component/connect/schema.d.ts.map +1 -1
- package/dist/component/connect/schema.js +3 -1
- package/dist/component/connect/schema.js.map +1 -1
- package/dist/component/connect/validators.d.ts +49 -1
- package/dist/component/connect/validators.d.ts.map +1 -1
- package/dist/component/connect/validators.js +2 -0
- package/dist/component/connect/validators.js.map +1 -1
- package/dist/component/earnings/triggers.d.ts +454 -2
- package/dist/component/earnings/triggers.d.ts.map +1 -1
- package/dist/component/payoutRelease/mutations.d.ts.map +1 -1
- package/dist/component/payoutRelease/mutations.js +6 -0
- package/dist/component/payoutRelease/mutations.js.map +1 -1
- package/dist/component/payoutRelease/queries.d.ts.map +1 -1
- package/dist/component/payoutRelease/queries.js +3 -1
- package/dist/component/payoutRelease/queries.js.map +1 -1
- package/dist/component/payoutSync/helpers.d.ts +44 -0
- package/dist/component/payoutSync/helpers.d.ts.map +1 -0
- package/dist/component/payoutSync/helpers.js +60 -0
- package/dist/component/payoutSync/helpers.js.map +1 -0
- package/dist/component/payoutSync/mutations.d.ts +51 -0
- package/dist/component/payoutSync/mutations.d.ts.map +1 -0
- package/dist/component/payoutSync/mutations.js +88 -0
- package/dist/component/payoutSync/mutations.js.map +1 -0
- package/dist/component/payoutSync/queries.d.ts +16 -0
- package/dist/component/payoutSync/queries.d.ts.map +1 -0
- package/dist/component/payoutSync/queries.js +11 -0
- package/dist/component/payoutSync/queries.js.map +1 -0
- package/dist/component/payoutSync/schema.d.ts +46 -0
- package/dist/component/payoutSync/schema.d.ts.map +1 -0
- package/dist/component/payoutSync/schema.js +22 -0
- package/dist/component/payoutSync/schema.js.map +1 -0
- package/dist/component/payoutSync/validators.d.ts +137 -0
- package/dist/component/payoutSync/validators.d.ts.map +1 -0
- package/dist/component/payoutSync/validators.js +19 -0
- package/dist/component/payoutSync/validators.js.map +1 -0
- package/dist/component/schema.d.ts +268 -1
- package/dist/component/schema.js +11 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/settlement/clawbackSchema.d.ts +161 -0
- package/dist/component/settlement/clawbackSchema.d.ts.map +1 -0
- package/dist/component/settlement/clawbackSchema.js +73 -0
- package/dist/component/settlement/clawbackSchema.js.map +1 -0
- package/dist/component/settlement/clawbacks.d.ts +55 -0
- package/dist/component/settlement/clawbacks.d.ts.map +1 -0
- package/dist/component/settlement/clawbacks.js +146 -0
- package/dist/component/settlement/clawbacks.js.map +1 -0
- package/dist/component/settlement/mutations.d.ts +16 -0
- package/dist/component/settlement/mutations.d.ts.map +1 -1
- package/dist/component/settlement/mutations.js +48 -0
- package/dist/component/settlement/mutations.js.map +1 -1
- package/dist/component/settlement/queries.d.ts +53 -0
- package/dist/component/settlement/queries.d.ts.map +1 -1
- package/dist/component/settlement/queries.js +26 -0
- package/dist/component/settlement/queries.js.map +1 -1
- package/dist/react/workflows/AffiliateRecovery.d.ts +46 -0
- package/dist/react/workflows/AffiliateRecovery.d.ts.map +1 -0
- package/dist/react/workflows/AffiliateRecovery.js +84 -0
- package/dist/react/workflows/AffiliateRecovery.js.map +1 -0
- package/dist/react/workflows/index.d.ts +2 -0
- package/dist/react/workflows/index.d.ts.map +1 -1
- package/dist/react/workflows/index.js +1 -0
- package/dist/react/workflows/index.js.map +1 -1
- package/dist/testing/affiliate-recovery.d.ts +10 -0
- package/dist/testing/affiliate-recovery.d.ts.map +1 -0
- package/dist/testing/affiliate-recovery.js +126 -0
- package/dist/testing/affiliate-recovery.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/docs/affiliate-clawbacks.md +306 -0
- package/docs/read-contracts.md +40 -5
- package/docs/settlement.md +17 -0
- package/package.json +2 -1
- package/src/client/affiliateRecovery/disputes.ts +62 -0
- package/src/client/affiliateRecovery/engine.ts +196 -0
- package/src/client/affiliateRecovery/payoutGate.ts +65 -0
- package/src/client/affiliateRecovery/provider.ts +127 -0
- package/src/client/affiliateRecovery/review.ts +117 -0
- package/src/client/affiliateRecovery/validators.ts +38 -0
- package/src/client/affiliateRecovery.ts +173 -0
- package/src/client/connect/commissionReceipts.test.ts +157 -0
- package/src/client/connect/commissionReceipts.ts +23 -0
- package/src/client/connect/payoutSync.test.ts +313 -0
- package/src/client/connect/payoutSync.ts +93 -0
- package/src/client/connect/payoutSyncTestHelpers.ts +82 -0
- package/src/client/connect/payoutSyncValidation.ts +74 -0
- package/src/client/connect/payouts.ts +75 -15
- package/src/client/connect/refundPrincipal.test.ts +46 -0
- package/src/client/connect/refundPrincipal.ts +26 -0
- package/src/client/connect/reversalExecution.ts +130 -0
- package/src/client/connect/transfers.test.ts +3 -3
- package/src/client/connect/transfers.ts +93 -92
- package/src/client/index.ts +20 -18
- package/src/client/readContracts.ts +12 -0
- package/src/client/settlement/execution.test.ts +5 -0
- package/src/client/settlement/execution.ts +50 -3
- package/src/client/settlement/reconciliation.ts +20 -2
- package/src/client/webhooks/disputeRecovery.ts +74 -0
- package/src/client/webhooks/processors.test.ts +36 -53
- package/src/client/webhooks/processors.ts +18 -31
- package/src/component/_generated/api.ts +34 -0
- package/src/component/_generated/component.ts +696 -1
- package/src/component/affiliate-clawbacks.test.ts +370 -0
- package/src/component/affiliate-component-pagination.test.ts +83 -0
- package/src/component/affiliate-direct-payout.test.ts +160 -0
- package/src/component/affiliate-recovery-lifecycle.test.ts +587 -0
- package/src/component/affiliateEarnings/mutations.ts +196 -0
- package/src/component/affiliatePayouts/mutations.ts +121 -0
- package/src/component/affiliatePayouts/queries.ts +28 -0
- package/src/component/affiliatePayouts/recipientGuard.ts +42 -0
- package/src/component/affiliateRecovery/accounting.ts +111 -0
- package/src/component/affiliateRecovery/mutations.ts +275 -0
- package/src/component/affiliateRecovery/queries.ts +138 -0
- package/src/component/affiliateRecovery/schema.ts +86 -0
- package/src/component/connect/mutations.ts +13 -190
- package/src/component/connect/payoutWrites.ts +25 -0
- package/src/component/connect/queries.ts +4 -47
- package/src/component/connect/reversalClaims.ts +232 -0
- package/src/component/connect/reversalDiagnostics.ts +61 -0
- package/src/component/connect/reversalReceipts.ts +147 -0
- package/src/component/connect/schema.ts +3 -3
- package/src/component/connect/validators.ts +2 -0
- package/src/component/payout-recovery-integration.test.ts +172 -0
- package/src/component/payout-sync.test.ts +117 -0
- package/src/component/payoutRelease/mutations.ts +9 -0
- package/src/component/payoutRelease/queries.ts +3 -1
- package/src/component/payoutSync/helpers.ts +86 -0
- package/src/component/payoutSync/mutations.ts +102 -0
- package/src/component/payoutSync/queries.ts +10 -0
- package/src/component/payoutSync/schema.ts +25 -0
- package/src/component/payoutSync/validators.ts +21 -0
- package/src/component/schema.ts +20 -0
- package/src/component/settlement/clawbackSchema.ts +78 -0
- package/src/component/settlement/clawbacks.ts +233 -0
- package/src/component/settlement/mutations.ts +54 -0
- package/src/component/settlement/queries.ts +35 -0
- package/src/component/summary-arithmetic.test.ts +9 -4
- package/src/react/workflows/AffiliateRecovery.test.tsx +106 -0
- package/src/react/workflows/AffiliateRecovery.tsx +273 -0
- package/src/react/workflows/index.ts +9 -0
- package/src/testing/affiliate-recovery.ts +137 -0
- package/src/testing/index.ts +2 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { Component, RunCtx } from "../helpers.js";
|
|
3
|
+
import { runMutationOrThrow } from "../helpers.js";
|
|
4
|
+
import { componentRef } from "../webhooks/helpers.js";
|
|
5
|
+
import { recoverSettlementTransfers } from "../settlement/reconciliation.js";
|
|
6
|
+
import {
|
|
7
|
+
executeClaimedSlices,
|
|
8
|
+
type ReversalClaim,
|
|
9
|
+
} from "../connect/reversalExecution.js";
|
|
10
|
+
import { reconcileAffiliateCharge } from "./engine.js";
|
|
11
|
+
|
|
12
|
+
/** Record proportional original commission exposure for a monetary dispute. */
|
|
13
|
+
export async function reverseTransfersForDispute(
|
|
14
|
+
stripe: Stripe,
|
|
15
|
+
component: Component,
|
|
16
|
+
ctx: RunCtx,
|
|
17
|
+
opts: {
|
|
18
|
+
sourceChargeId: string;
|
|
19
|
+
operationId: string;
|
|
20
|
+
chargeAmount: number;
|
|
21
|
+
amount: number;
|
|
22
|
+
finalized: boolean;
|
|
23
|
+
disputeState?: "open" | "lost" | "won";
|
|
24
|
+
refundedPrincipal?: number;
|
|
25
|
+
},
|
|
26
|
+
) {
|
|
27
|
+
await recoverSettlementTransfers(stripe, component, ctx, opts.sourceChargeId);
|
|
28
|
+
const claim = (await runMutationOrThrow(
|
|
29
|
+
ctx,
|
|
30
|
+
componentRef(component, "connect/mutations/claimReversalSlices"),
|
|
31
|
+
{
|
|
32
|
+
sourceChargeId: opts.sourceChargeId,
|
|
33
|
+
operationId: opts.operationId,
|
|
34
|
+
mode:
|
|
35
|
+
opts.disputeState === "won"
|
|
36
|
+
? {
|
|
37
|
+
kind: "fraction",
|
|
38
|
+
chargeAmount: opts.chargeAmount,
|
|
39
|
+
amountRefunded: 0,
|
|
40
|
+
}
|
|
41
|
+
: { kind: "amount", amount: opts.amount },
|
|
42
|
+
commissionExposure: {
|
|
43
|
+
kind: "dispute",
|
|
44
|
+
chargeAmount: opts.chargeAmount,
|
|
45
|
+
principal: opts.amount,
|
|
46
|
+
finalized: opts.finalized,
|
|
47
|
+
disputeState: opts.disputeState,
|
|
48
|
+
refundedPrincipal: opts.refundedPrincipal,
|
|
49
|
+
disputeId: opts.operationId,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
)) as ReversalClaim;
|
|
53
|
+
const result = await executeClaimedSlices(
|
|
54
|
+
stripe,
|
|
55
|
+
component,
|
|
56
|
+
ctx,
|
|
57
|
+
opts.operationId,
|
|
58
|
+
{ ...claim, slices: claim.slices.filter((s) => !s.commissionTracked) },
|
|
59
|
+
);
|
|
60
|
+
await reconcileAffiliateCharge(stripe, component, ctx, opts.sourceChargeId);
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { Component, RunCtx } from "../helpers.js";
|
|
3
|
+
import { runMutationOrThrow } from "../helpers.js";
|
|
4
|
+
import { componentRef } from "../webhooks/helpers.js";
|
|
5
|
+
import {
|
|
6
|
+
classifyRecoveryError,
|
|
7
|
+
findReceipt,
|
|
8
|
+
verifyConnectPath,
|
|
9
|
+
type RecoveryObligation,
|
|
10
|
+
type RecoveryOperation,
|
|
11
|
+
} from "./provider.js";
|
|
12
|
+
export async function reconcileObligation(
|
|
13
|
+
stripe: Stripe,
|
|
14
|
+
component: Component,
|
|
15
|
+
ctx: RunCtx,
|
|
16
|
+
obligationId: string,
|
|
17
|
+
) {
|
|
18
|
+
let row = (await ctx.runQuery(
|
|
19
|
+
componentRef(component, "affiliateRecovery/queries/getObligation"),
|
|
20
|
+
{ obligationId },
|
|
21
|
+
)) as RecoveryObligation | null;
|
|
22
|
+
if (!row || row.needsReconciliation)
|
|
23
|
+
return {
|
|
24
|
+
state: "unsupported",
|
|
25
|
+
reason: "Original commission provenance requires reconciliation.",
|
|
26
|
+
};
|
|
27
|
+
// Finish uncertain recovery before restoring its actual proceeds. Each pass
|
|
28
|
+
// claims one immutable operation atomically with debt/offset reservations.
|
|
29
|
+
let lastState = "settled";
|
|
30
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
31
|
+
const net =
|
|
32
|
+
row.recoveredAmount + (row.offsetAmount ?? 0) - (row.restoredAmount ?? 0);
|
|
33
|
+
const kind =
|
|
34
|
+
net > row.finalizedAmount + row.provisionalAmount
|
|
35
|
+
? "restoration"
|
|
36
|
+
: "recovery";
|
|
37
|
+
const op = (await runMutationOrThrow(
|
|
38
|
+
ctx,
|
|
39
|
+
componentRef(component, "affiliateRecovery/mutations/reserveOperation"),
|
|
40
|
+
{ obligationId, kind, now: Date.now() },
|
|
41
|
+
)) as RecoveryOperation | null;
|
|
42
|
+
if (!op) {
|
|
43
|
+
if ((row.debtAmount ?? 0) > 0) lastState = "offset_required";
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const receiptId = op.attempted
|
|
48
|
+
? await findReceipt(stripe, op, row)
|
|
49
|
+
: null;
|
|
50
|
+
if (!receiptId && op.attempted && op.kind === "recovery") {
|
|
51
|
+
const original = await stripe.transfers.retrieve(row.stripeTransferId!);
|
|
52
|
+
if (
|
|
53
|
+
original.amount_reversed >
|
|
54
|
+
(op.providerBaseline ?? row.recoveredAmount)
|
|
55
|
+
) {
|
|
56
|
+
const error =
|
|
57
|
+
"Provider reversal aggregate changed without an attributable operation receipt; operator receipt review is required.";
|
|
58
|
+
await runMutationOrThrow(
|
|
59
|
+
ctx,
|
|
60
|
+
componentRef(
|
|
61
|
+
component,
|
|
62
|
+
"affiliateRecovery/mutations/completeOperation",
|
|
63
|
+
),
|
|
64
|
+
{ operationId: op._id, state: "unknown", error },
|
|
65
|
+
);
|
|
66
|
+
return { state: "unknown", error };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let confirmed = receiptId;
|
|
70
|
+
if (!confirmed) {
|
|
71
|
+
const started = (await runMutationOrThrow(
|
|
72
|
+
ctx,
|
|
73
|
+
componentRef(component, "affiliateRecovery/mutations/beginOperation"),
|
|
74
|
+
{ operationId: op._id },
|
|
75
|
+
)) as RecoveryOperation;
|
|
76
|
+
if (!["pending", "unknown"].includes(started.state)) {
|
|
77
|
+
lastState = started.state;
|
|
78
|
+
if (started.state === "canceled") {
|
|
79
|
+
row = (await ctx.runQuery(
|
|
80
|
+
componentRef(
|
|
81
|
+
component,
|
|
82
|
+
"affiliateRecovery/queries/getObligation",
|
|
83
|
+
),
|
|
84
|
+
{ obligationId },
|
|
85
|
+
)) as RecoveryObligation;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
const capability = await verifyConnectPath(stripe, row, op.kind);
|
|
91
|
+
if (!capability.supported) {
|
|
92
|
+
await runMutationOrThrow(
|
|
93
|
+
ctx,
|
|
94
|
+
componentRef(
|
|
95
|
+
component,
|
|
96
|
+
"affiliateRecovery/mutations/completeOperation",
|
|
97
|
+
),
|
|
98
|
+
{
|
|
99
|
+
operationId: op._id,
|
|
100
|
+
state: op.attempted ? "unknown" : "unsupported",
|
|
101
|
+
error: capability.reason,
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
return {
|
|
105
|
+
state: op.attempted ? "unknown" : "unsupported",
|
|
106
|
+
reason: capability.reason,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const metadata = {
|
|
110
|
+
bsRecoveryOperation: op._id,
|
|
111
|
+
bsOriginalCharge: row.sourceChargeId,
|
|
112
|
+
};
|
|
113
|
+
if (op.kind === "recovery") {
|
|
114
|
+
const receipt = await stripe.transfers.createReversal(
|
|
115
|
+
row.stripeTransferId!,
|
|
116
|
+
{ amount: op.amount, metadata },
|
|
117
|
+
{ idempotencyKey: `bs_affiliate_${op._id}` },
|
|
118
|
+
);
|
|
119
|
+
if (receipt.amount !== op.amount)
|
|
120
|
+
throw new Error("Affiliate provider reversal amount mismatch.");
|
|
121
|
+
confirmed = receipt.id;
|
|
122
|
+
} else {
|
|
123
|
+
const receipt = await stripe.transfers.create(
|
|
124
|
+
{
|
|
125
|
+
amount: op.amount,
|
|
126
|
+
currency: row.currency,
|
|
127
|
+
destination: row.affiliateAccountId,
|
|
128
|
+
transfer_group: `bs_restore_${row._id}`,
|
|
129
|
+
metadata,
|
|
130
|
+
},
|
|
131
|
+
{ idempotencyKey: `bs_affiliate_${op._id}` },
|
|
132
|
+
);
|
|
133
|
+
if (receipt.amount !== op.amount || receipt.currency !== row.currency)
|
|
134
|
+
throw new Error("Affiliate provider restoration amount mismatch.");
|
|
135
|
+
confirmed = receipt.id;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const confirmedTransferReversed =
|
|
139
|
+
op.kind === "recovery"
|
|
140
|
+
? (await stripe.transfers.retrieve(row.stripeTransferId!))
|
|
141
|
+
.amount_reversed
|
|
142
|
+
: undefined;
|
|
143
|
+
await runMutationOrThrow(
|
|
144
|
+
ctx,
|
|
145
|
+
componentRef(
|
|
146
|
+
component,
|
|
147
|
+
"affiliateRecovery/mutations/completeOperation",
|
|
148
|
+
),
|
|
149
|
+
{
|
|
150
|
+
operationId: op._id,
|
|
151
|
+
state: "succeeded",
|
|
152
|
+
receiptId: confirmed,
|
|
153
|
+
confirmedTransferReversed,
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
lastState = "succeeded";
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const outcome = classifyRecoveryError(error);
|
|
159
|
+
// This write deliberately occurs outside the provider/receipt transaction.
|
|
160
|
+
// Unknown outcomes retain the reservation and block commission release.
|
|
161
|
+
await runMutationOrThrow(
|
|
162
|
+
ctx,
|
|
163
|
+
componentRef(
|
|
164
|
+
component,
|
|
165
|
+
"affiliateRecovery/mutations/completeOperation",
|
|
166
|
+
),
|
|
167
|
+
{ operationId: op._id, ...outcome },
|
|
168
|
+
);
|
|
169
|
+
return outcome;
|
|
170
|
+
}
|
|
171
|
+
row = (await ctx.runQuery(
|
|
172
|
+
componentRef(component, "affiliateRecovery/queries/getObligation"),
|
|
173
|
+
{ obligationId },
|
|
174
|
+
)) as RecoveryObligation;
|
|
175
|
+
}
|
|
176
|
+
return { state: lastState };
|
|
177
|
+
}
|
|
178
|
+
export async function reconcileAffiliateCharge(
|
|
179
|
+
stripe: Stripe,
|
|
180
|
+
component: Component,
|
|
181
|
+
ctx: RunCtx,
|
|
182
|
+
sourceChargeId: string,
|
|
183
|
+
) {
|
|
184
|
+
const rows = (await ctx.runQuery(
|
|
185
|
+
componentRef(component, "affiliateRecovery/queries/chargeObligations"),
|
|
186
|
+
{ sourceChargeId },
|
|
187
|
+
)) as RecoveryObligation[] | null;
|
|
188
|
+
const results = [];
|
|
189
|
+
for (const row of rows ?? [])
|
|
190
|
+
if (
|
|
191
|
+
typeof row._id === "string" &&
|
|
192
|
+
typeof row.originalCommission === "number"
|
|
193
|
+
)
|
|
194
|
+
results.push(await reconcileObligation(stripe, component, ctx, row._id));
|
|
195
|
+
return results;
|
|
196
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { Component, RunCtx } from "../helpers.js";
|
|
3
|
+
import { runMutationOrThrow } from "../helpers.js";
|
|
4
|
+
import { componentRef } from "../webhooks/helpers.js";
|
|
5
|
+
import type { Doc } from "../../component/_generated/dataModel.js";
|
|
6
|
+
export async function reserveAffiliatePayout(
|
|
7
|
+
component: Component,
|
|
8
|
+
ctx: RunCtx,
|
|
9
|
+
opts: {
|
|
10
|
+
stripeAccountId: string;
|
|
11
|
+
currency?: string;
|
|
12
|
+
amount: number;
|
|
13
|
+
minimumAmount?: number;
|
|
14
|
+
idempotencyKey?: string;
|
|
15
|
+
},
|
|
16
|
+
) {
|
|
17
|
+
return runMutationOrThrow(
|
|
18
|
+
ctx,
|
|
19
|
+
componentRef(component, "affiliatePayouts/mutations/reservePayout"),
|
|
20
|
+
{
|
|
21
|
+
affiliateAccountId: opts.stripeAccountId,
|
|
22
|
+
currency: opts.currency ?? "usd",
|
|
23
|
+
amount: opts.amount,
|
|
24
|
+
minimumAmount: opts.minimumAmount ?? 1,
|
|
25
|
+
operationKey: opts.idempotencyKey,
|
|
26
|
+
now: Date.now(),
|
|
27
|
+
},
|
|
28
|
+
) as Promise<Doc<"affiliatePayoutReleases"> | null>;
|
|
29
|
+
}
|
|
30
|
+
export async function completeAffiliatePayout(
|
|
31
|
+
component: Component,
|
|
32
|
+
ctx: RunCtx,
|
|
33
|
+
operationKey: string,
|
|
34
|
+
state: "unknown" | "submitted" | "failed",
|
|
35
|
+
stripePayoutId?: string,
|
|
36
|
+
) {
|
|
37
|
+
return runMutationOrThrow(
|
|
38
|
+
ctx,
|
|
39
|
+
componentRef(component, "affiliatePayouts/mutations/finishPayout"),
|
|
40
|
+
{ operationKey, state, stripePayoutId },
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
export async function reconcileAffiliatePayout(
|
|
44
|
+
stripe: Stripe,
|
|
45
|
+
release: Doc<"affiliatePayoutReleases">,
|
|
46
|
+
) {
|
|
47
|
+
let count = 0;
|
|
48
|
+
for await (const receipt of stripe.payouts.list(
|
|
49
|
+
{ created: { gte: Math.floor(release.createdAt / 1000) }, limit: 100 },
|
|
50
|
+
{ stripeAccount: release.affiliateAccountId },
|
|
51
|
+
)) {
|
|
52
|
+
if (++count > 1000)
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Affiliate payout history requires operator reconciliation.",
|
|
55
|
+
);
|
|
56
|
+
if (receipt.metadata?.bsAffiliatePayout !== release._id) continue;
|
|
57
|
+
if (
|
|
58
|
+
receipt.currency !== release.currency ||
|
|
59
|
+
receipt.amount !== release.amount
|
|
60
|
+
)
|
|
61
|
+
throw new Error("Affiliate payout receipt mismatch.");
|
|
62
|
+
return receipt.id;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { Doc } from "../../component/_generated/dataModel.js";
|
|
3
|
+
export type RecoveryOperation = Doc<"affiliateRecoveryOperations">;
|
|
4
|
+
export type RecoveryObligation = Doc<"affiliateObligations">;
|
|
5
|
+
export async function findReceipt(
|
|
6
|
+
stripe: Stripe,
|
|
7
|
+
op: RecoveryOperation,
|
|
8
|
+
row: RecoveryObligation,
|
|
9
|
+
) {
|
|
10
|
+
let count = 0;
|
|
11
|
+
if (op.kind === "recovery") {
|
|
12
|
+
if (!row.stripeTransferId) return null;
|
|
13
|
+
for await (const receipt of stripe.transfers.listReversals(
|
|
14
|
+
row.stripeTransferId,
|
|
15
|
+
{ limit: 100 },
|
|
16
|
+
)) {
|
|
17
|
+
if (++count > 1000)
|
|
18
|
+
throw new Error("Affiliate receipt history exceeds supported bound.");
|
|
19
|
+
if (receipt.metadata?.bsRecoveryOperation !== op._id) continue;
|
|
20
|
+
if (receipt.amount !== op.amount)
|
|
21
|
+
throw new Error("Affiliate reversal receipt amount mismatch.");
|
|
22
|
+
return receipt.id;
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
for await (const receipt of stripe.transfers.list({
|
|
26
|
+
transfer_group: `bs_restore_${row._id}`,
|
|
27
|
+
limit: 100,
|
|
28
|
+
})) {
|
|
29
|
+
if (++count > 1000)
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Affiliate restoration history exceeds supported bound.",
|
|
32
|
+
);
|
|
33
|
+
if (receipt.metadata?.bsRecoveryOperation !== op._id) continue;
|
|
34
|
+
const destination =
|
|
35
|
+
typeof receipt.destination === "string"
|
|
36
|
+
? receipt.destination
|
|
37
|
+
: receipt.destination?.id;
|
|
38
|
+
if (
|
|
39
|
+
receipt.amount !== op.amount ||
|
|
40
|
+
receipt.currency !== row.currency ||
|
|
41
|
+
destination !== row.affiliateAccountId
|
|
42
|
+
)
|
|
43
|
+
throw new Error("Affiliate restoration receipt identity mismatch.");
|
|
44
|
+
return receipt.id;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
export async function verifyConnectPath(
|
|
50
|
+
stripe: Stripe,
|
|
51
|
+
row: RecoveryObligation,
|
|
52
|
+
kind: RecoveryOperation["kind"],
|
|
53
|
+
) {
|
|
54
|
+
const account = await stripe.v2.core.accounts.retrieve(
|
|
55
|
+
row.affiliateAccountId,
|
|
56
|
+
{ include: ["configuration.recipient"] },
|
|
57
|
+
);
|
|
58
|
+
const capability =
|
|
59
|
+
account.configuration?.recipient?.capabilities?.stripe_balance
|
|
60
|
+
?.stripe_transfers;
|
|
61
|
+
if (capability?.status !== "active")
|
|
62
|
+
return {
|
|
63
|
+
supported: false,
|
|
64
|
+
reason:
|
|
65
|
+
"Connect recipient stripe_transfers capability is not active; operator review required.",
|
|
66
|
+
};
|
|
67
|
+
if (kind === "recovery") {
|
|
68
|
+
if (!row.stripeTransferId)
|
|
69
|
+
return {
|
|
70
|
+
supported: false,
|
|
71
|
+
reason:
|
|
72
|
+
"No original Connect transfer; recover through future commissions.",
|
|
73
|
+
};
|
|
74
|
+
const original = await stripe.transfers.retrieve(row.stripeTransferId);
|
|
75
|
+
const destination =
|
|
76
|
+
typeof original.destination === "string"
|
|
77
|
+
? original.destination
|
|
78
|
+
: original.destination?.id;
|
|
79
|
+
if (
|
|
80
|
+
destination !== row.affiliateAccountId ||
|
|
81
|
+
original.currency !== row.currency ||
|
|
82
|
+
original.source_transaction !== row.sourceChargeId ||
|
|
83
|
+
original.amount !== (row.releasedCommission ?? row.originalCommission)
|
|
84
|
+
)
|
|
85
|
+
return {
|
|
86
|
+
supported: false,
|
|
87
|
+
reason:
|
|
88
|
+
"Original Connect transfer identity does not match the commission; operator review required.",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
supported: true,
|
|
93
|
+
reason:
|
|
94
|
+
"Original Connect path verified; reversal remains subject to available balance and provider restrictions.",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function classifyRecoveryError(error: unknown): {
|
|
98
|
+
state: "unknown" | "insufficient_balance" | "unsupported" | "failed";
|
|
99
|
+
error: string;
|
|
100
|
+
} {
|
|
101
|
+
const value = error as { code?: string; type?: string; message?: string };
|
|
102
|
+
const code = value.code ?? "";
|
|
103
|
+
const state = ["balance_insufficient", "insufficient_funds"].includes(code)
|
|
104
|
+
? "insufficient_balance"
|
|
105
|
+
: [
|
|
106
|
+
"account_invalid",
|
|
107
|
+
"transfers_not_allowed",
|
|
108
|
+
"resource_missing",
|
|
109
|
+
"platform_account_required",
|
|
110
|
+
].includes(code)
|
|
111
|
+
? "unsupported"
|
|
112
|
+
: [
|
|
113
|
+
"StripeConnectionError",
|
|
114
|
+
"StripeAPIError",
|
|
115
|
+
"StripeRateLimitError",
|
|
116
|
+
].includes(value.type ?? "") ||
|
|
117
|
+
code === "idempotency_key_in_use" ||
|
|
118
|
+
!value.type
|
|
119
|
+
? "unknown"
|
|
120
|
+
: "failed";
|
|
121
|
+
return {
|
|
122
|
+
state,
|
|
123
|
+
error: (
|
|
124
|
+
value.message ?? "Affiliate provider outcome requires reconciliation."
|
|
125
|
+
).slice(0, 500),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import type { RecoveryObligation } from "./provider.js";
|
|
3
|
+
import { successfulRefundPrincipal } from "../connect/refundPrincipal.js";
|
|
4
|
+
/** Operator-selected provenance is verified against exact provider receipts. */
|
|
5
|
+
export async function verifiedLegacyReview(
|
|
6
|
+
stripe: Stripe,
|
|
7
|
+
row: RecoveryObligation,
|
|
8
|
+
opts: {
|
|
9
|
+
reversalIds: string[];
|
|
10
|
+
restorationTransferIds?: string[];
|
|
11
|
+
note: string;
|
|
12
|
+
},
|
|
13
|
+
) {
|
|
14
|
+
if (
|
|
15
|
+
!opts.note.trim() ||
|
|
16
|
+
opts.reversalIds.length > 100 ||
|
|
17
|
+
new Set(opts.reversalIds).size !== opts.reversalIds.length ||
|
|
18
|
+
(opts.restorationTransferIds?.length ?? 0) > 100
|
|
19
|
+
)
|
|
20
|
+
throw new Error("Provide a review note and at most 100 unique receipts.");
|
|
21
|
+
if (!row.stripeTransferId)
|
|
22
|
+
throw new Error("Legacy review requires an original Connect transfer.");
|
|
23
|
+
const original = await stripe.transfers.retrieve(row.stripeTransferId);
|
|
24
|
+
const destination =
|
|
25
|
+
typeof original.destination === "string"
|
|
26
|
+
? original.destination
|
|
27
|
+
: original.destination?.id;
|
|
28
|
+
if (
|
|
29
|
+
destination !== row.affiliateAccountId ||
|
|
30
|
+
original.currency !== row.currency ||
|
|
31
|
+
original.source_transaction !== row.sourceChargeId
|
|
32
|
+
)
|
|
33
|
+
throw new Error("Legacy transfer identity mismatch.");
|
|
34
|
+
const receipts = [];
|
|
35
|
+
for (const id of opts.reversalIds) {
|
|
36
|
+
const receipt = await stripe.transfers.retrieveReversal(
|
|
37
|
+
row.stripeTransferId,
|
|
38
|
+
id,
|
|
39
|
+
);
|
|
40
|
+
if (
|
|
41
|
+
receipt.transfer !== row.stripeTransferId ||
|
|
42
|
+
receipt.currency !== row.currency ||
|
|
43
|
+
receipt.id !== id
|
|
44
|
+
)
|
|
45
|
+
throw new Error("Legacy reversal receipt identity mismatch.");
|
|
46
|
+
receipts.push({ id, amount: receipt.amount });
|
|
47
|
+
}
|
|
48
|
+
const recoveredAmount = receipts.reduce((sum, r) => sum + r.amount, 0);
|
|
49
|
+
if (recoveredAmount > original.amount_reversed)
|
|
50
|
+
throw new Error("Legacy receipts exceed confirmed provider reversals.");
|
|
51
|
+
let restoredAmount = 0;
|
|
52
|
+
const restorationIds = opts.restorationTransferIds ?? [];
|
|
53
|
+
if (new Set(restorationIds).size !== restorationIds.length)
|
|
54
|
+
throw new Error("Restoration receipts must be unique.");
|
|
55
|
+
for (const id of restorationIds) {
|
|
56
|
+
const transfer = await stripe.transfers.retrieve(id);
|
|
57
|
+
const recipient =
|
|
58
|
+
typeof transfer.destination === "string"
|
|
59
|
+
? transfer.destination
|
|
60
|
+
: transfer.destination?.id;
|
|
61
|
+
if (
|
|
62
|
+
recipient !== row.affiliateAccountId ||
|
|
63
|
+
transfer.currency !== row.currency ||
|
|
64
|
+
transfer.metadata.bsReinstateOf !== row.sourceChargeId
|
|
65
|
+
)
|
|
66
|
+
throw new Error("Legacy restoration receipt identity mismatch.");
|
|
67
|
+
restoredAmount += transfer.amount;
|
|
68
|
+
}
|
|
69
|
+
const charge = await stripe.charges.retrieve(row.sourceChargeId);
|
|
70
|
+
const refundedPrincipal = await successfulRefundPrincipal(
|
|
71
|
+
stripe,
|
|
72
|
+
row.sourceChargeId,
|
|
73
|
+
);
|
|
74
|
+
const disputes = [];
|
|
75
|
+
for await (const dispute of stripe.disputes.list({
|
|
76
|
+
charge: row.sourceChargeId,
|
|
77
|
+
limit: 100,
|
|
78
|
+
})) {
|
|
79
|
+
if (disputes.length >= 10)
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Legacy dispute history requires manual source allocation.",
|
|
82
|
+
);
|
|
83
|
+
if (
|
|
84
|
+
!dispute.status.startsWith("warning_") &&
|
|
85
|
+
dispute.status !== "prevented"
|
|
86
|
+
)
|
|
87
|
+
disputes.push(dispute);
|
|
88
|
+
}
|
|
89
|
+
if (disputes.length > 1)
|
|
90
|
+
throw new Error(
|
|
91
|
+
"Multiple monetary disputes require manual source allocation.",
|
|
92
|
+
);
|
|
93
|
+
const dispute = disputes[0];
|
|
94
|
+
return {
|
|
95
|
+
obligationId: row._id,
|
|
96
|
+
recoveredAmount,
|
|
97
|
+
restoredAmount,
|
|
98
|
+
receipts,
|
|
99
|
+
restorationIds,
|
|
100
|
+
note: opts.note,
|
|
101
|
+
exposure: {
|
|
102
|
+
kind: dispute ? ("dispute" as const) : ("refund" as const),
|
|
103
|
+
chargeAmount: charge.amount,
|
|
104
|
+
principal: dispute?.amount ?? refundedPrincipal,
|
|
105
|
+
finalized: dispute ? dispute.status === "lost" : true,
|
|
106
|
+
refundedPrincipal,
|
|
107
|
+
disputeId: dispute?.id,
|
|
108
|
+
disputeState: dispute
|
|
109
|
+
? dispute.status === "won"
|
|
110
|
+
? ("won" as const)
|
|
111
|
+
: dispute.status === "lost"
|
|
112
|
+
? ("lost" as const)
|
|
113
|
+
: ("open" as const)
|
|
114
|
+
: undefined,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { obligationFields } from "../../component/settlement/clawbackSchema.js";
|
|
3
|
+
import {
|
|
4
|
+
balanceFields,
|
|
5
|
+
operationFields,
|
|
6
|
+
} from "../../component/affiliateRecovery/schema.js";
|
|
7
|
+
export const affiliateObligationValidator = v.object({
|
|
8
|
+
...obligationFields,
|
|
9
|
+
_id: v.string(),
|
|
10
|
+
_creationTime: v.number(),
|
|
11
|
+
earningId: v.optional(v.string()),
|
|
12
|
+
activeOperationId: v.optional(v.string()),
|
|
13
|
+
});
|
|
14
|
+
export const affiliateOperationValidator = v.object({
|
|
15
|
+
...operationFields,
|
|
16
|
+
_id: v.string(),
|
|
17
|
+
_creationTime: v.number(),
|
|
18
|
+
obligationId: v.string(),
|
|
19
|
+
});
|
|
20
|
+
export const affiliateSnapshotValidator = v.object({
|
|
21
|
+
balance: v.union(
|
|
22
|
+
v.object({ ...balanceFields, _id: v.string(), _creationTime: v.number() }),
|
|
23
|
+
v.null(),
|
|
24
|
+
),
|
|
25
|
+
obligations: v.object({
|
|
26
|
+
page: v.array(affiliateObligationValidator),
|
|
27
|
+
continueCursor: v.string(),
|
|
28
|
+
isDone: v.boolean(),
|
|
29
|
+
splitCursor: v.optional(v.union(v.string(), v.null())),
|
|
30
|
+
pageStatus: v.optional(
|
|
31
|
+
v.union(
|
|
32
|
+
v.literal("SplitRecommended"),
|
|
33
|
+
v.literal("SplitRequired"),
|
|
34
|
+
v.null(),
|
|
35
|
+
),
|
|
36
|
+
),
|
|
37
|
+
}),
|
|
38
|
+
});
|