Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
8 changes: 7 additions & 1 deletion apps/web/app/(ee)/api/commissions/count/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ import { NextResponse } from "next/server";
export const GET = withWorkspace(async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);

const parsedParams = getCommissionsCountQuerySchema.parse(searchParams);
const isHoldStatus = searchParams.status === "hold";
const { status: _status, ...restSearchParams } = searchParams;

const parsedParams = getCommissionsCountQuerySchema.parse(
isHoldStatus ? restSearchParams : searchParams,
);

const counts = await getCommissionsCount({
...parsedParams,
programId,
isHoldStatus,
});

return NextResponse.json(counts);
Expand Down
9 changes: 7 additions & 2 deletions apps/web/app/(ee)/api/commissions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ import * as z from "zod/v4";
export const GET = withWorkspace(async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);

let { partnerId, tenantId, ...filters } =
getCommissionsQuerySchema.parse(searchParams);
const isHoldStatus = searchParams.status === "hold";
const { status: _status, ...restSearchParams } = searchParams;

let { partnerId, tenantId, ...filters } = getCommissionsQuerySchema.parse(
isHoldStatus ? restSearchParams : searchParams,
);

if (tenantId && !partnerId) {
const partner = await prisma.programEnrollment.findUnique({
Expand Down Expand Up @@ -45,6 +49,7 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {
...filters,
partnerId,
programId,
isHoldStatus,
});

return NextResponse.json(
Expand Down
23 changes: 20 additions & 3 deletions apps/web/app/(ee)/api/programs/[programId]/payouts/count/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
import { payoutsCountQuerySchema } from "@/lib/zod/schemas/payouts";
import { prisma } from "@dub/prisma";
import { PayoutStatus, Prisma } from "@dub/prisma/client";
import { FraudEventStatus, PayoutStatus, Prisma } from "@dub/prisma/client";
import { NextResponse } from "next/server";

// GET /api/programs/[programId]/payouts/count
export const GET = withWorkspace(async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);

const { partnerId, groupBy, eligibility, status, invoiceId } =
payoutsCountQuerySchema.parse(searchParams);
const isHoldStatus = searchParams.status === "hold";
const { status: _status, ...restSearchParams } = searchParams;
let { status, partnerId, groupBy, eligibility, invoiceId } =
payoutsCountQuerySchema.parse(
isHoldStatus ? restSearchParams : searchParams,
);

if (isHoldStatus) {
status = PayoutStatus.pending;
}

const program = await getProgramOrThrow({
workspaceId: workspace.id,
Expand All @@ -26,6 +34,15 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {
...getPayoutEligibilityFilter({ program, workspace }),
}),
...(invoiceId && { invoiceId }),
...(isHoldStatus && {
programEnrollment: {
fraudEventGroups: {
some: {
status: FraudEventStatus.pending,
},
},
},
}),
};

// Get payout count by status
Expand Down
68 changes: 38 additions & 30 deletions apps/web/app/(ee)/api/programs/[programId]/payouts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@ import {
payoutsQuerySchema,
} from "@/lib/zod/schemas/payouts";
import { prisma } from "@dub/prisma";
import { FraudEventStatus, PayoutStatus } from "@dub/prisma/client";
import { NextResponse } from "next/server";
import * as z from "zod/v4";

// GET /api/programs/[programId]/payouts - get all payouts for a program
export const GET = withWorkspace(async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);

const { status, partnerId, invoiceId, sortBy, sortOrder, page, pageSize } =
payoutsQuerySchema.parse(searchParams);
const isHoldStatus = searchParams.status === "hold";
const { status: _status, ...restSearchParams } = searchParams;

let { status, partnerId, invoiceId, sortBy, sortOrder, page, pageSize } =
payoutsQuerySchema.parse(isHoldStatus ? restSearchParams : searchParams);

if (isHoldStatus) {
status = PayoutStatus.pending;
}

const program = await getProgramOrThrow({
workspaceId: workspace.id,
Expand All @@ -28,21 +36,19 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {
...(status && { status }),
...(partnerId && { partnerId }),
...(invoiceId && { invoiceId }),
},
include: {
partner: {
include: {
programs: {
where: {
programId,
},
select: {
tenantId: true,
...(isHoldStatus && {
programEnrollment: {
fraudEventGroups: {
some: {
status: FraudEventStatus.pending,
},
},
},
},
user: true,
}),
},
include: {
programEnrollment: true,
partner: true,
},
skip: (page - 1) * pageSize,
take: pageSize,
Expand All @@ -51,23 +57,25 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {
},
});

const transformedPayouts = payouts.map(({ partner, ...payout }) => {
const mode =
payout.mode ??
getEffectivePayoutMode({
payoutMode: program.payoutMode,
payoutsEnabledAt: partner.payoutsEnabledAt,
});
const transformedPayouts = payouts.map(
({ partner, programEnrollment, ...payout }) => {
const mode =
payout.mode ??
getEffectivePayoutMode({
payoutMode: program.payoutMode,
payoutsEnabledAt: partner.payoutsEnabledAt,
});

return {
...payout,
mode,
partner: {
...partner,
...partner.programs[0],
},
};
});
return {
...payout,
mode,
partner: {
...partner,
tenantId: programEnrollment.tenantId,
},
};
},
);

return NextResponse.json(
z.array(PayoutResponseSchema).parse(transformedPayouts),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ export function useCommissionFilters() {
key: "status",
icon: CircleDotted,
label: "Status",
options: Object.entries(CommissionStatusBadges)
.filter(([key]) => key !== "hold")
.map(([value, { label }]) => {
options: Object.entries(CommissionStatusBadges).map(
([value, { label }]) => {
const Icon = CommissionStatusBadges[value].icon;
return {
value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ export function usePayoutFilters() {
key: "status",
icon: CircleDotted,
label: "Status",
options: Object.entries(PayoutStatusBadges)
.filter(([key]) => key !== "hold")
.map(([value, { label }]) => {
options: Object.entries(PayoutStatusBadges).map(
([value, { label }]) => {
const Icon = PayoutStatusBadges[value].icon;
const count = payoutsCount?.find((p) => p.status === value)?.count;

Expand All @@ -66,9 +65,12 @@ export function usePayoutFilters() {
)}
/>
),
right: nFormatter(count || 0, { full: true }),
...(value !== "hold" && {
right: nFormatter(count || 0, { full: true }),
}),
};
}),
},
),
},
{
key: "invoiceId",
Expand Down
41 changes: 28 additions & 13 deletions apps/web/lib/api/commissions/get-commissions-count.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
import { getCommissionsCountQuerySchema } from "@/lib/zod/schemas/commissions";
import { prisma } from "@dub/prisma";
import { CommissionStatus } from "@dub/prisma/client";
import { CommissionStatus, FraudEventStatus } from "@dub/prisma/client";
import * as z from "zod/v4";

type CommissionsCountFilters = z.infer<
typeof getCommissionsCountQuerySchema
> & {
programId: string;
isHoldStatus?: boolean;
};

export async function getCommissionsCount(filters: CommissionsCountFilters) {
Expand All @@ -23,6 +24,7 @@ export async function getCommissionsCount(filters: CommissionsCountFilters) {
interval,
timezone,
programId,
isHoldStatus,
} = filters;

const { startDate, endDate } = getStartEndDates({
Expand All @@ -32,6 +34,27 @@ export async function getCommissionsCount(filters: CommissionsCountFilters) {
timezone,
});

const statusFilter = isHoldStatus
? { in: [CommissionStatus.pending, CommissionStatus.processed] }
: status ?? {
notIn: [
CommissionStatus.duplicate,
CommissionStatus.fraud,
CommissionStatus.canceled,
],
};

const programEnrollmentFilter = {
...(groupId && { groupId }),
...(isHoldStatus && {
fraudEventGroups: {
some: {
status: FraudEventStatus.pending,
},
},
}),
};

const commissionsCount = await prisma.commission.groupBy({
by: ["status"],
where: {
Expand All @@ -40,24 +63,16 @@ export async function getCommissionsCount(filters: CommissionsCountFilters) {
},
programId,
partnerId,
status: status ?? {
notIn: [
CommissionStatus.duplicate,
CommissionStatus.fraud,
CommissionStatus.canceled,
],
},
status: statusFilter,
type,
payoutId,
customerId,
createdAt: {
gte: startDate,
lte: endDate,
},
...(groupId && {
programEnrollment: {
groupId,
},
...(Object.keys(programEnrollmentFilter).length > 0 && {
programEnrollment: programEnrollmentFilter,
}),
},
_count: true,
Expand All @@ -77,7 +92,7 @@ export async function getCommissionsCount(filters: CommissionsCountFilters) {
return acc;
},
{} as Record<
CommissionStatus | "all",
CommissionStatus | "all" | "hold",
{
count: number;
amount: number;
Expand Down
39 changes: 27 additions & 12 deletions apps/web/lib/api/commissions/get-commissions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
import { getCommissionsQuerySchema } from "@/lib/zod/schemas/commissions";
import { prisma } from "@dub/prisma";
import { CommissionStatus } from "@dub/prisma/client";
import { CommissionStatus, FraudEventStatus } from "@dub/prisma/client";
import * as z from "zod/v4";

type CommissionsFilters = z.infer<typeof getCommissionsQuerySchema> & {
programId: string;
isHoldStatus?: boolean;
};

export async function getCommissions(filters: CommissionsFilters) {
Expand All @@ -26,6 +27,7 @@ export async function getCommissions(filters: CommissionsFilters) {
pageSize,
sortBy,
sortOrder,
isHoldStatus,
} = filters;

const { startDate, endDate } = getStartEndDates({
Expand All @@ -35,6 +37,27 @@ export async function getCommissions(filters: CommissionsFilters) {
timezone,
});

const statusFilter = isHoldStatus
? { in: [CommissionStatus.pending, CommissionStatus.processed] }
: status ?? {
notIn: [
CommissionStatus.duplicate,
CommissionStatus.fraud,
CommissionStatus.canceled,
],
};

const programEnrollmentFilter = {
...(groupId && { groupId }),
...(isHoldStatus && {
fraudEventGroups: {
some: {
status: FraudEventStatus.pending,
},
},
}),
};

return await prisma.commission.findMany({
where: invoiceId
? {
Expand All @@ -47,24 +70,16 @@ export async function getCommissions(filters: CommissionsFilters) {
},
programId,
partnerId,
status: status ?? {
notIn: [
CommissionStatus.duplicate,
CommissionStatus.fraud,
CommissionStatus.canceled,
],
},
status: statusFilter,
type,
customerId,
payoutId,
createdAt: {
gte: startDate,
lte: endDate,
},
...(groupId && {
programEnrollment: {
groupId,
},
...(Object.keys(programEnrollmentFilter).length > 0 && {
programEnrollment: programEnrollmentFilter,
}),
},
include: {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ export type UsageResponse = z.infer<typeof usageResponse>;
export type PartnersCount = Record<ProgramEnrollmentStatus | "all", number>;

export type CommissionsCount = Record<
CommissionStatus | "all",
CommissionStatus | "all" | "hold",
{
count: number;
amount: number;
Expand Down
Loading
Loading