Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3bbfb45
Add workflows/create-partner-commission
steven-tey May 29, 2026
73c2c89
Merge branch 'main' into create-partner-commission-workflow
devkiran May 29, 2026
b43272a
Improve QStash workflow triggering with retries, labels, and Axiom lo…
devkiran May 29, 2026
ce77e40
Move partner commission creation into create-partner-commission workf…
devkiran May 29, 2026
9af6848
Queue partner commissions via QStash from createPartnerCommission.
devkiran May 29, 2026
2e9eef2
Rename createPartnerCommission to queuePartnerCommissionCreation
devkiran May 29, 2026
9d30c46
Improve partner commission workflow types, flow control, and schemas
devkiran May 29, 2026
f0f08c3
Link bounty submissions to commissions in partner commission workflow
devkiran May 29, 2026
4baa357
Update route.ts
devkiran May 29, 2026
8b5576e
Move fraud detection to partner commission workflow side effects
devkiran May 29, 2026
d7b7f1d
Pass isFirstConversion through partner commission workflow
devkiran May 29, 2026
7974bfd
Update qstash-workflow.ts
devkiran May 29, 2026
e49d717
Run fraud detection before commission side effects and queue bounty c…
devkiran May 29, 2026
4f33974
Update route.ts
devkiran May 29, 2026
201962b
Merge branch 'main' into create-partner-commission-workflow
steven-tey May 29, 2026
90e7ced
rename queuePartnerCommissionCreation, fix tests
steven-tey May 29, 2026
0b26c64
Merge branch 'main' into create-partner-commission-workflow
steven-tey May 31, 2026
5f844ba
increase timeout
steven-tey May 31, 2026
3ac23e6
Merge branch 'main' into create-partner-commission-workflow
steven-tey May 31, 2026
56c582c
Update verify-fraud-event.ts
devkiran May 31, 2026
0f6236f
finalize workflow
steven-tey May 31, 2026
ad443a1
fix isFirstConversion flag
steven-tey May 31, 2026
ceca508
address coderabbit feedback
steven-tey May 31, 2026
3254564
Create referral commissions in workflows instead of payout cron jobs
devkiran Jun 1, 2026
216d7c3
Return skip reasons from createReferralCommission
devkiran Jun 1, 2026
ef08b51
Restore referral commission cron route for backfill jobs
devkiran Jun 1, 2026
9ea8242
Queue network referral commissions on payout completion
devkiran Jun 1, 2026
c5901ec
Merge branch 'main' into realtime-referral-commissions
devkiran Jun 2, 2026
4ec7c36
Void referral commissions when source commission is refunded
devkiran Jun 2, 2026
2fa0ad9
Void referral commissions on manual commission status updates
devkiran Jun 2, 2026
3ef1379
Update void-referral-commissions.ts
devkiran Jun 2, 2026
b3de67f
Update bulk-update-partner-commissions.ts
devkiran Jun 2, 2026
e93b1d5
Address CR feedback
devkiran Jun 2, 2026
6c1b0d0
Only create referral commissions for sale commissions
devkiran Jun 2, 2026
fc6a69c
Rename network referral cron from referrals/queue to referrals/network
devkiran Jun 2, 2026
f68af1b
Format
devkiran Jun 2, 2026
089f34d
address CR feedback
devkiran Jun 2, 2026
59a74ab
Merge branch 'main' into realtime-referral-commissions
devkiran Jun 3, 2026
9da49ed
Queue voided referral commissions and cancel below-threshold rewards
devkiran Jun 3, 2026
a536251
Reconcile payouts and sync totals when canceling threshold referral c…
devkiran Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { sqlGranularityMap } from "@/lib/planetscale/granularity";
import { conn } from "@/lib/planetscale/connection";
import { sqlGranularityMap } from "@/lib/planetscale/granularity";
import { InvoiceStatus } from "@dub/prisma/client";
import { ACME_PROGRAM_ID } from "@dub/utils";
import { format } from "date-fns";
Expand Down
32 changes: 25 additions & 7 deletions apps/web/app/(ee)/api/cron/commissions/referrals/create/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { withCron } from "@/lib/cron/with-cron";
import { createReferralCommission } from "@/lib/partner-referrals/create-referral-commission";
import {
createReferralCommission,
CreateReferralCommissionArgs,
} from "@/lib/partner-referrals/create-referral-commission";
import * as z from "zod/v4";
import { logAndRespond } from "../../../utils";

Expand All @@ -14,13 +17,28 @@ const inputSchema = z.union([
export const POST = withCron(async ({ rawBody }) => {
const inputParsed = inputSchema.parse(JSON.parse(rawBody));

const referralCommission = await createReferralCommission(inputParsed);
let args: CreateReferralCommissionArgs;

if (referralCommission === null) {
return logAndRespond("Referral commission creation skipped.");
if ("sourceCommissionId" in inputParsed) {
args = {
source: "commission",
sourceCommissionId: inputParsed.sourceCommissionId,
};
} else {
args = {
source: "partner",
programId: inputParsed.programId,
partnerId: inputParsed.partnerId,
};
}

return logAndRespond(
`Referral commission ${referralCommission.id} created successfully.`,
);
const { commission, reason } = await createReferralCommission(args);

if (commission) {
return logAndRespond(
`Referral commission ${commission.id} created successfully.`,
);
}

return logAndRespond(reason ?? "Referral commission creation skipped.");
});
56 changes: 56 additions & 0 deletions apps/web/app/(ee)/api/cron/commissions/referrals/network/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { withCron } from "@/lib/cron/with-cron";
import { createNetworkReferralCommission } from "@/lib/partner-referrals/create-network-referral-commission";
import { prisma } from "@dub/prisma";
import * as z from "zod/v4";
import { logAndRespond } from "../../../utils";

export const dynamic = "force-dynamic";

const inputSchema = z.object({
payoutId: z.string(),
});

// POST /api/cron/commissions/referrals/network
// Creates a network referral commission for the referrer when a referred partner's payout is sent or completed.
export const POST = withCron(async ({ rawBody }) => {
const { payoutId } = inputSchema.parse(JSON.parse(rawBody));

const payout = await prisma.payout.findUnique({
where: {
id: payoutId,
},
include: {
partner: {
select: {
id: true,
referredByPartnerId: true,
},
},
},
});

if (!payout) {
return logAndRespond(`Payout ${payoutId} not found.`);
}

if (!["sent", "completed"].includes(payout.status)) {
return logAndRespond(
`Payout ${payoutId} is not in a valid status to create referral commissions.`,
);
}

const commission = await createNetworkReferralCommission({
partner: payout.partner,
payout,
});

if (commission) {
return logAndRespond(
`Created network referral commission for payout ${payout.id}.`,
);
}

return logAndRespond(
`No referral commission created for payout ${payout.id}.`,
);
});
161 changes: 0 additions & 161 deletions apps/web/app/(ee)/api/cron/commissions/referrals/queue/route.ts

This file was deleted.

39 changes: 39 additions & 0 deletions apps/web/app/(ee)/api/cron/commissions/referrals/void/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
cancelReferralCommissionsBelowThreshold,
voidReferralCommissions,
voidReferralCommissionsSchema,
} from "@/lib/api/commissions/void-referral-commissions";
import { withCron } from "@/lib/cron/with-cron";
import { logAndRespond } from "../../../utils";

export const dynamic = "force-dynamic";

// POST /api/cron/commissions/referrals/void
export const POST = withCron(async ({ rawBody }) => {
const {
workspaceId,
programId,
userId,
sourceCommissionIds,
sourceCommissionStatus,
} = voidReferralCommissionsSchema.parse(JSON.parse(rawBody));

await voidReferralCommissions({
workspaceId,
programId,
userId,
sourceCommissionIds,
sourceCommissionStatus,
});

await cancelReferralCommissionsBelowThreshold({
workspaceId,
userId,
sourceCommissionIds,
programId,
});

return logAndRespond(
`Voided referral commissions for ${sourceCommissionIds.length} source commission(s).`,
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export async function sendPaypalPayouts(invoice: Pick<Invoice, "id">) {
enqueueBatchJobs(
payouts.map((payout) => ({
queueName: "create-referral-commissions",
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/commissions/referrals/queue`,
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/commissions/referrals/network`,
body: {
payoutId: payout.id,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { trackCommissionStatusUpdate } from "@/lib/api/commissions/track-commission-update-activity-log";
import { queueVoidReferralCommissions } from "@/lib/api/commissions/void-referral-commissions";
import { syncTotalCommissions } from "@/lib/api/partners/sync-total-commissions";
import { stripeAppClient } from "@/lib/stripe";
import { StripeMode } from "@/lib/types";
import { prisma } from "@dub/prisma";
import { CommissionStatus } from "@dub/prisma/client";
import type Stripe from "stripe";

// Handle event "charge.refunded"
Expand Down Expand Up @@ -140,6 +142,13 @@ export async function chargeRefunded(
newStatus: "refunded",
});

await queueVoidReferralCommissions({
workspaceId: workspace.id,
programId: commission.programId,
sourceCommissionIds: [commission.id],
sourceCommissionStatus: CommissionStatus.refunded,
});

return {
response: `Commission ${commission.id} updated to status "refunded"`,
workspaceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { calculateSaleEarnings } from "@/lib/api/sales/calculate-sale-earnings";
import { executeWorkflows } from "@/lib/api/workflows/execute-workflows";
import { logger } from "@/lib/axiom/server";
import { getWorkflowConfig } from "@/lib/cron/qstash-workflow";
import { createReferralCommission } from "@/lib/partner-referrals/create-referral-commission";
import { constructWebhookPartner } from "@/lib/partners/constuct-webhook-partner";
import { determinePartnerReward } from "@/lib/partners/determine-partner-reward";
import { getRewardAmount } from "@/lib/partners/get-reward-amount";
Expand All @@ -21,6 +22,7 @@ import { DEFAULT_PARTNER_GROUP } from "@/lib/zod/schemas/groups";
import { prisma } from "@dub/prisma";
import {
Commission,
CommissionType,
Link,
Partner,
PartnerGroup,
Expand Down Expand Up @@ -51,7 +53,7 @@ type StepFunctionInput = Input & {
};

type StepCreateCommissionOutput = {
commission: Pick<Commission, "id"> | null;
commission: Pick<Commission, "id" | "type"> | null;
outputLog: string;
isFirstCommission?: boolean;
};
Expand Down Expand Up @@ -121,6 +123,16 @@ export const { POST } = serve<Input>(
}
});
}

// Step 4: Create referral commission
if (commission && commission.type === CommissionType.sale) {
await context.run("create-referral-commission", async () => {
return await createReferralCommission({
source: "commission",
sourceCommissionId: commission.id,
});
});
}
},
{
initialPayloadParser: (requestPayload) => {
Expand Down
Loading
Loading