Skip to content

Commit c67d67b

Browse files
authored
Merge pull request #1803 from dubinc/improve-payouts-cron
Improve payouts cron
2 parents ed26384 + c11f005 commit c67d67b

File tree

3 files changed

+97
-84
lines changed

3 files changed

+97
-84
lines changed

apps/web/app/api/cron/process-payouts/route.ts

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,30 @@ export async function GET(req: Request) {
1414
try {
1515
await verifyVercelSignature(req);
1616

17-
const partners = await prisma.programEnrollment.findMany({
17+
const pendingSales = await prisma.sale.groupBy({
18+
by: ["programId", "partnerId"],
1819
where: {
19-
status: "approved",
20+
status: "pending",
21+
},
22+
_count: {
23+
id: true,
2024
},
2125
});
2226

23-
if (!partners.length) {
27+
if (!pendingSales.length) {
2428
return;
2529
}
2630

27-
const currentDate = new Date();
28-
29-
const periodStart = new Date(
30-
currentDate.getFullYear(),
31-
currentDate.getMonth(),
32-
1,
33-
);
34-
35-
const periodEnd = new Date(
36-
currentDate.getFullYear(),
37-
currentDate.getMonth() + 1,
38-
0,
39-
);
40-
4131
// TODO:
4232
// We need a batter way to handle this recursively
43-
for (const { programId, partnerId } of partners) {
33+
for (const { programId, partnerId } of pendingSales) {
4434
await createSalesPayout({
4535
programId,
4636
partnerId,
47-
periodStart,
48-
periodEnd,
4937
});
5038
}
5139

52-
return NextResponse.json({
53-
message: `Calculated payouts for ${partners.length} partners`,
54-
});
40+
return NextResponse.json(pendingSales);
5541
} catch (error) {
5642
return handleAndReturnErrorResponse(error);
5743
}

apps/web/lib/actions/partners/create-manual-payout.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,24 @@ export const createManualPayoutAction = authActionClient
4949
throw new Error("No short link found for this partner in this program.");
5050
}
5151

52-
let payout: Payout | undefined = undefined;
52+
let payout: Payout | null = null;
5353

5454
// Create a payout for sales
5555
if (type === "sales") {
56-
// create the payout
57-
payout = await createSalesPayout({
58-
programId,
59-
partnerId,
60-
periodStart: start!,
61-
periodEnd: end!,
62-
description: description ?? undefined,
63-
});
56+
payout =
57+
(await createSalesPayout({
58+
programId,
59+
partnerId,
60+
periodStart: start,
61+
periodEnd: end,
62+
description: description ?? undefined,
63+
})) ?? null;
64+
65+
if (!payout) {
66+
throw new Error(
67+
"No valid sales found for this period. Payout was not created.",
68+
);
69+
}
6470
}
6571

6672
// Create a payout for clicks, leads, and custom events

apps/web/lib/partners/create-sales-payout.ts

Lines changed: 73 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export const createSalesPayout = async ({
1212
}: {
1313
programId: string;
1414
partnerId: string;
15-
periodStart: Date;
16-
periodEnd: Date;
15+
periodStart?: Date | null;
16+
periodEnd?: Date | null;
1717
description?: string;
1818
}) => {
1919
return await prisma.$transaction(async (tx) => {
@@ -23,10 +23,6 @@ export const createSalesPayout = async ({
2323
partnerId,
2424
payoutId: null,
2525
status: "pending", // We only want to pay out sales that are pending (not refunded / fraud / duplicate)
26-
createdAt: {
27-
gte: periodStart,
28-
lte: periodEnd,
29-
},
3026
// Referral commissions are held for 30 days before becoming available.
3127
// createdAt: {
3228
// lte: subDays(new Date(), 30),
@@ -35,75 +31,100 @@ export const createSalesPayout = async ({
3531
select: {
3632
id: true,
3733
earnings: true,
34+
createdAt: true,
3835
},
3936
});
4037

4138
if (!sales.length) {
4239
return;
4340
}
4441

45-
const amount = sales.reduce((total, sale) => total + sale.earnings, 0);
4642
const quantity = sales.length;
47-
let payout: Payout | null = null;
48-
49-
// Check if the partner has another pending payout
50-
payout = await tx.payout.findFirst({
51-
where: {
52-
programId,
53-
partnerId,
54-
status: "pending",
55-
type: "sales",
56-
},
57-
});
43+
const amount = sales.reduce((total, sale) => total + sale.earnings, 0);
5844

59-
// Update the existing payout
60-
if (payout) {
61-
await tx.payout.update({
62-
where: {
63-
id: payout.id,
64-
},
65-
data: {
66-
amount: {
67-
increment: amount,
68-
},
69-
quantity: {
70-
increment: quantity,
71-
},
72-
periodEnd,
73-
},
74-
});
45+
if (!periodStart) {
46+
// get the earliest sale date
47+
periodStart = sales.reduce(
48+
(min, sale) => (sale.createdAt < min ? sale.createdAt : min),
49+
sales[0].createdAt,
50+
);
51+
}
7552

76-
console.info("Payout updated", payout);
53+
if (!periodEnd) {
54+
// get the end of the month of the latest sale
55+
// e.g. if the latest sale is 2024-12-16, the periodEnd should be 2024-12-31
56+
const latestSale = sales.reduce(
57+
(max, sale) => (sale.createdAt > max ? sale.createdAt : max),
58+
sales[0].createdAt,
59+
);
60+
periodEnd = new Date(
61+
latestSale.getFullYear(),
62+
latestSale.getMonth() + 1,
63+
0,
64+
);
7765
}
7866

79-
// Create the payout
80-
else {
81-
payout = await tx.payout.create({
82-
data: {
83-
id: createId({ prefix: "po_" }),
67+
let payout: Payout | null = null;
68+
69+
// only create a payout if the total sale amount is greater than 0
70+
if (amount > 0) {
71+
// Check if the partner has another pending payout
72+
const existingPayout = await tx.payout.findFirst({
73+
where: {
8474
programId,
8575
partnerId,
86-
amount,
87-
periodStart,
88-
periodEnd,
89-
quantity,
90-
description,
76+
status: "pending",
77+
type: "sales",
9178
},
9279
});
9380

94-
console.info("Payout created", payout);
95-
}
81+
// Update the existing payout
82+
if (existingPayout) {
83+
payout = await tx.payout.update({
84+
where: {
85+
id: existingPayout.id,
86+
},
87+
data: {
88+
amount: {
89+
increment: amount,
90+
},
91+
quantity: {
92+
increment: quantity,
93+
},
94+
periodEnd,
95+
description: existingPayout.description ?? "Dub Partners payout",
96+
},
97+
});
9698

97-
if (!payout) {
98-
throw new Error(
99-
`Failed to create payout for ${partnerId} in ${programId}.`,
100-
);
99+
console.info("Payout updated", payout);
100+
}
101+
102+
// Create the payout
103+
else {
104+
payout = await tx.payout.create({
105+
data: {
106+
id: createId({ prefix: "po_" }),
107+
programId,
108+
partnerId,
109+
amount,
110+
periodStart,
111+
periodEnd,
112+
quantity,
113+
description: description ?? "Dub Partners payout",
114+
},
115+
});
116+
117+
console.info("Payout created", payout);
118+
}
101119
}
102120

103121
// Update the sales records
104122
await tx.sale.updateMany({
105123
where: { id: { in: sales.map((sale) => sale.id) } },
106-
data: { payoutId: payout.id, status: "processed" },
124+
data: {
125+
status: "processed",
126+
...(payout ? { payoutId: payout.id } : {}),
127+
},
107128
});
108129

109130
return payout;

0 commit comments

Comments
 (0)