Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
15b39d6
wip
devkiran Nov 29, 2025
c94a068
Merge branch 'main' into fraud-event-v2
devkiran Dec 1, 2025
8eb8488
Update the Prisma schema
devkiran Dec 1, 2025
5d98de5
Refactor fraud event handling to utilize FraudRuleType constants for …
devkiran Dec 1, 2025
b2d20f5
Refactor fraud event handling by removing the deprecated fraud events…
devkiran Dec 1, 2025
d512af6
Rename
devkiran Dec 1, 2025
706d4ac
Refactor fraud event API to support group-based retrieval, removing d…
devkiran Dec 1, 2025
0a4d0f7
Refactor fraud-related components to use updated hooks and improve na…
devkiran Dec 1, 2025
dffb2a1
Create resolved-fraud-event-groups-table.tsx
devkiran Dec 1, 2025
403c665
Refactor fraud event handling to replace deprecated resolveFraudEvent…
devkiran Dec 1, 2025
1d7b095
Update ban-partner-modal.tsx
devkiran Dec 1, 2025
0fec1b1
Consistent naming
devkiran Dec 1, 2025
acf9700
WIP
devkiran Dec 2, 2025
697661c
Add partner relationship to FraudEvent and include partner in fraud e…
devkiran Dec 2, 2025
893493f
Rename with resolveFraudGroupAction
devkiran Dec 2, 2025
9f05aeb
Update resolve-fraud-events-modal.tsx
devkiran Dec 2, 2025
e5595c2
Add bulk resolve functionality for fraud event groups
devkiran Dec 2, 2025
978eb0c
Fix the test
devkiran Dec 2, 2025
04b26ca
Refactor fraud event handling to utilize fraud event groups, updating…
devkiran Dec 2, 2025
13483ca
Add migration scripts
devkiran Dec 2, 2025
0128903
Enhance fraud event processing by adding partnerId selection and refi…
devkiran Dec 2, 2025
02fabdc
Update utils.ts
devkiran Dec 2, 2025
9d2c0a5
Optimize the number of queries to run in createFraudEvents
devkiran Dec 2, 2025
ec44bc5
Update link.prisma
devkiran Dec 2, 2025
e0cd69f
Merge branch 'main' into fraud-event-v2
devkiran Dec 2, 2025
88a6aa5
Rename
devkiran Dec 2, 2025
684ca94
Merge branch 'fraud-event-v2' of https://github.com/dubinc/dub into f…
devkiran Dec 2, 2025
c0e0977
A few more rename
devkiran Dec 2, 2025
d4ca6de
Rename zod schemas
devkiran Dec 2, 2025
53c2774
use fraudGroup for var name
devkiran Dec 2, 2025
e180fbc
Update utils.ts
devkiran Dec 2, 2025
d035647
A few more name consistency change
devkiran Dec 2, 2025
cd5d703
Update utils.ts
devkiran Dec 3, 2025
4bc61cb
Merge branch 'main' into fraud-event-v2
devkiran Dec 3, 2025
24d42df
Improved the FraudGroupTable component by adding reject application f…
devkiran Dec 3, 2025
54b505c
Refactor partner application rejection process by introducing a dedic…
devkiran Dec 3, 2025
5117298
Implement bulk rejection of partner applications with fraud reporting…
devkiran Dec 3, 2025
747d03e
Enhance fraud event handling by adding special case for partnerCrossP…
devkiran Dec 3, 2025
89901e1
Update fraud-review-sheet.tsx
devkiran Dec 3, 2025
19b136d
Refactor bulk rejection process in partner applications modal to hand…
devkiran Dec 3, 2025
6dafe0e
Merge branch 'main' into fraud-event-v2
devkiran Dec 3, 2025
8aec451
Backfill fingerprint for existing fraud events
devkiran Dec 3, 2025
70fcbac
Merge branch 'fraud-event-v2' of https://github.com/dubinc/dub into f…
devkiran Dec 3, 2025
de40382
Address CodeRabbit feedbacks
devkiran Dec 3, 2025
26c0810
Update seed-fraud-events.ts
devkiran Dec 3, 2025
6e2649d
More CodeRabbit fixes
devkiran Dec 3, 2025
73fe99d
Update fraud-group-table.tsx
devkiran Dec 3, 2025
ff7b38a
Renaming for onsistency
devkiran Dec 3, 2025
2b9b556
Sanitize metadata by removing fields that are stored separately or sh…
devkiran Dec 3, 2025
0f491d3
Update create-fraud-events.ts
devkiran Dec 3, 2025
993122a
Refactor duplicate payout method fraud detection logic into a new fun…
devkiran Dec 3, 2025
8530a64
Update utils.ts
devkiran Dec 3, 2025
2a5079c
Update resolve-fraud-events-modal.tsx
devkiran Dec 3, 2025
cf21976
Update fraud-review-sheet.tsx
devkiran Dec 3, 2025
85cd52e
Add ResolveFraudEventsModal component and update related imports
devkiran Dec 3, 2025
2b494ea
Update fraud-group-table.tsx
devkiran Dec 3, 2025
f95388c
Update fraud-group-table.tsx
devkiran Dec 3, 2025
e7de3ac
Fix the modal usage
devkiran Dec 3, 2025
5d0ea24
Update fraud-review-sheet.tsx
devkiran Dec 3, 2025
34c6145
Refactor modal usage in fraud-related components to use memoized comp…
devkiran Dec 3, 2025
2825cab
Fix ban partner modal
devkiran Dec 3, 2025
9ec3249
Update reject-partner-application-modal.tsx
devkiran Dec 3, 2025
6fc4662
fix build
devkiran Dec 3, 2025
64b3095
Update resolve-fraud-group-modal.tsx
devkiran Dec 3, 2025
3c40426
Rename component and hook to align with group-based terminology.
devkiran Dec 3, 2025
56b54a5
Merge branch 'main' into fraud-event-v2
steven-tey Dec 3, 2025
1e64eb1
Merge branch 'main' into fraud-event-v2
steven-tey Dec 4, 2025
c8d526d
stash
steven-tey Dec 4, 2025
1e552a7
Fix migration script
devkiran Dec 4, 2025
f3faa4d
Merge branch 'main' into fraud-event-v2
devkiran Dec 4, 2025
e391704
Update bulk-resolve-fraud-groups.ts
devkiran Dec 4, 2025
7ade631
Update fraud.ts
devkiran Dec 4, 2025
07e9e0c
Update route.ts
devkiran Dec 4, 2025
9a2e30b
Update seed-fraud-events.ts
devkiran Dec 4, 2025
2aa8fe3
Create "Duplicate payout method" fraud event only if both the duplica…
devkiran Dec 4, 2025
064c65b
Send warning email if the partner connect duplicate payment method
devkiran Dec 4, 2025
3102713
Merge branch 'fraud-event-v2' of https://github.com/dubinc/dub into f…
steven-tey Dec 4, 2025
0c267d5
Merge branch 'main' into fraud-event-v2
steven-tey Dec 4, 2025
2acb87e
Refactor fraud event creation logic to improve deduplication and grou…
devkiran Dec 4, 2025
9e85d59
Replace fingerpring with hash on FraudEvent table
devkiran Dec 4, 2025
f8f1675
Update create-fraud-events.ts
devkiran Dec 4, 2025
80068de
Update utils.ts
devkiran Dec 4, 2025
ce24179
Display the note as well on table
devkiran Dec 4, 2025
5c097b8
Add cleanup script for duplicate payout method fraud events
devkiran Dec 4, 2025
5f1e78d
Move to utils
devkiran Dec 4, 2025
c85c7b4
Cleanup script for duplicate payout method fraud events.
devkiran Dec 4, 2025
56bca64
Update migrate-duplicate-payout-fraud-events.ts
devkiran Dec 4, 2025
4aea684
Update migrate-duplicate-payout-fraud-events.ts
devkiran Dec 4, 2025
a6c08c2
Update migrate-fraud-events.ts
devkiran Dec 4, 2025
19b7e77
Update migrate-fraud-events.ts
devkiran Dec 4, 2025
bffd622
Update fraud-paid-traffic-detected-table.tsx
devkiran Dec 4, 2025
17fe0a3
Fix the rerender
devkiran Dec 4, 2025
86ff712
Merge partner accounts should resolve the fraud events
devkiran Dec 4, 2025
0894732
Update route.ts
devkiran Dec 4, 2025
56df7ea
Update detect-record-fraud-application.ts
devkiran Dec 4, 2025
a85a5d2
Merge branch 'main' into fraud-event-v2
steven-tey Dec 4, 2025
7a74164
Merge branch 'main' into fraud-event-v2
steven-tey Dec 5, 2025
65b3a64
move Bulk Ban Partners out of BulkActionsMenu
steven-tey Dec 5, 2025
8631ae5
Merge branch 'main' into fraud-event-v2
steven-tey Dec 5, 2025
c102581
Merge branch 'main' into fraud-event-v2
steven-tey Dec 5, 2025
930240f
Merge branch 'main' into fraud-event-v2
steven-tey Dec 5, 2025
39630e3
Merge branch 'main' into fraud-event-v2
steven-tey Dec 5, 2025
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
59 changes: 37 additions & 22 deletions apps/web/app/(ee)/api/cron/fraud/summary/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { FRAUD_RULES_BY_TYPE } from "@/lib/api/fraud/constants";
import { getGroupedFraudEvents } from "@/lib/api/fraud/get-grouped-fraud-events";
import { getWorkspaceUsers } from "@/lib/api/get-workspace-users";
import { qstash } from "@/lib/cron";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
Expand Down Expand Up @@ -40,11 +39,10 @@ async function handler(req: Request) {
// Get batch of programs with unresolved fraud events
const programs = await prisma.program.findMany({
where: {
fraudEvents: {
fraudEventGroups: {
some: {
status: "pending",
// Only include programs with fraud events created today so daily summaries
createdAt: {
lastEventAt: {
gte: startOfDay(new Date()),
},
},
Expand Down Expand Up @@ -82,28 +80,36 @@ async function handler(req: Request) {

for (const program of programs) {
try {
const fraudEvents = await getGroupedFraudEvents({
programId: program.id,
status: "pending",
page: 1,
pageSize: 6,
sortBy: "createdAt",
sortOrder: "asc",
const fraudGroups = await prisma.fraudEventGroup.findMany({
where: {
programId: program.id,
status: "pending",
lastEventAt: {
gte: startOfDay(new Date()),
},
},
select: {
id: true,
type: true,
eventCount: true,
partner: {
select: {
id: true,
name: true,
image: true,
},
},
},
orderBy: {
lastEventAt: "desc",
},
take: 6,
});

if (fraudEvents.length === 0) {
if (fraudGroups.length === 0) {
continue;
}

const transformedFraudEvents = fraudEvents.map(
({ type, count, groupKey, partner }) => ({
name: FRAUD_RULES_BY_TYPE[type].name,
count,
groupKey,
partner,
}),
);

// Get workspace users to send the email to
const { users } = await getWorkspaceUsers({
role: "owner",
Expand All @@ -115,6 +121,15 @@ async function handler(req: Request) {
continue;
}

const transformedFraudGroups = fraudGroups.map(
({ id, type, eventCount, partner }) => ({
id,
name: FRAUD_RULES_BY_TYPE[type].name,
count: eventCount,
partner,
}),
);

await queueBatchEmail<typeof UnresolvedFraudEventsSummary>(
users.map((user) => ({
to: user.email,
Expand All @@ -125,7 +140,7 @@ async function handler(req: Request) {
email: user.email,
workspace: program.workspace,
program,
fraudEvents: transformedFraudEvents,
fraudGroups: transformedFraudGroups,
},
})),
{
Expand Down
24 changes: 24 additions & 0 deletions apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { resolveFraudGroups } from "@/lib/api/fraud/resolve-fraud-groups";
import { linkCache } from "@/lib/api/links/cache";
import { includeProgramEnrollment } from "@/lib/api/links/include-program-enrollment";
import { includeTags } from "@/lib/api/links/include-tags";
Expand Down Expand Up @@ -59,6 +60,7 @@ export async function POST(req: Request) {
select: {
id: true,
email: true,
payoutMethodHash: true,
programs: {
select: {
programId: true,
Expand Down Expand Up @@ -269,6 +271,28 @@ export async function POST(req: Request) {
);
}

// After merging, check if the fraud condition has been resolved.
// If no other partners share the same payout method hash, we can
// automatically resolve any pending fraud groups for this partner.
if (targetAccount.payoutMethodHash) {
const duplicatePartners = await prisma.partner.count({
where: {
payoutMethodHash: targetAccount.payoutMethodHash,
},
});

if (duplicatePartners <= 1) {
await resolveFraudGroups({
where: {
partnerId: targetPartnerId,
type: "partnerDuplicatePayoutMethod",
},
resolutionReason:
"Automatically resolved because partners with duplicate payout methods were merged. No other partners share this payout method.",
});
}
}

// Make sure the cache is cleared
await redis.del(`${CACHE_KEY_PREFIX}:${userId}`);

Expand Down
6 changes: 5 additions & 1 deletion apps/web/app/(ee)/api/cron/partners/ban/process/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { BAN_PARTNER_REASONS } from "@/lib/zod/schemas/partners";
import { sendEmail } from "@dub/email";
import PartnerBanned from "@dub/email/templates/partner-banned";
import { prisma } from "@dub/prisma";
import { FraudRuleType } from "@dub/prisma/client";
import { log } from "@dub/utils";
import { z } from "zod";
import { logAndRespond } from "../../../utils";
Expand Down Expand Up @@ -151,6 +152,9 @@ export async function POST(req: Request) {
},
status: "approved",
},
select: {
programId: true,
},
});

// Create partnerCrossProgramBan fraud events for other programs where this partner
Expand All @@ -159,7 +163,7 @@ export async function POST(req: Request) {
programEnrollments.map(({ programId }) => ({
programId,
partnerId,
type: "partnerCrossProgramBan",
type: FraudRuleType.partnerCrossProgramBan,
})),
);

Expand Down
23 changes: 0 additions & 23 deletions apps/web/app/(ee)/api/fraud/events/count/route.ts

This file was deleted.

70 changes: 0 additions & 70 deletions apps/web/app/(ee)/api/fraud/events/raw/route.ts

This file was deleted.

117 changes: 107 additions & 10 deletions apps/web/app/(ee)/api/fraud/events/route.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,120 @@
import { getGroupedFraudEvents } from "@/lib/api/fraud/get-grouped-fraud-events";
import { DubApiError } from "@/lib/api/errors";
import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw";
import { withWorkspace } from "@/lib/auth";
import { groupedFraudEventsQuerySchema } from "@/lib/zod/schemas/fraud";
import {
fraudEventQuerySchema,
fraudEventSchemas,
} from "@/lib/zod/schemas/fraud";
import { prisma } from "@dub/prisma";
import { FraudRuleType, Prisma } from "@dub/prisma/client";
import { NextResponse } from "next/server";
import { z } from "zod";

// GET /api/fraud/events - Get the fraud events for a program grouped by rule type
// GET /api/fraud/events - Get the fraud events for a group
export const GET = withWorkspace(
async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);
const parsedParams = groupedFraudEventsQuerySchema.parse(searchParams);
const parsedQueryParams = fraudEventQuerySchema.parse(searchParams);

console.time("getGroupedFraudEvents");
const fraudEvents = await getGroupedFraudEvents({
...parsedParams,
programId,
let where: Prisma.FraudEventWhereInput = {};
let eventGroupType: FraudRuleType | undefined;

// Filter by group ID
if ("groupId" in parsedQueryParams) {
const { groupId } = parsedQueryParams;

const fraudGroup = await prisma.fraudEventGroup.findUnique({
where: {
id: groupId,
},
select: {
programId: true,
partnerId: true,
type: true,
},
});

if (!fraudGroup) {
throw new DubApiError({
code: "not_found",
message: "Fraud event group not found.",
});
}

if (fraudGroup.programId !== programId) {
throw new DubApiError({
code: "not_found",
message: "Fraud event group not found in this program.",
});
}

where = {
fraudEventGroupId: groupId,
};

eventGroupType = fraudGroup.type;

// Special case for partnerCrossProgramBan rule type
if (eventGroupType === FraudRuleType.partnerCrossProgramBan) {
const bannedProgramEnrollments =
await prisma.programEnrollment.findMany({
where: {
partnerId: fraudGroup.partnerId,
programId: {
not: programId,
},
status: "banned",
},
select: {
bannedAt: true,
bannedReason: true,
},
});

return NextResponse.json(
z
.array(fraudEventSchemas["partnerCrossProgramBan"])
.parse(bannedProgramEnrollments),
);
}
}

// Filter by customer ID and type
if ("customerId" in parsedQueryParams && "type" in parsedQueryParams) {
const { customerId, type } = parsedQueryParams;

where = {
customerId,
fraudEventGroup: {
programId,
type,
},
};

eventGroupType = type;
}

if (!eventGroupType) {
throw new DubApiError({
code: "not_found",
message: "Fraud event group type not found.",
});
}

const zodSchema = fraudEventSchemas[eventGroupType];

const fraudEvents = await prisma.fraudEvent.findMany({
where,
include: {
partner: true,
customer: true,
},
orderBy: {
id: "asc",
},
});
console.timeEnd("getGroupedFraudEvents");

return NextResponse.json(fraudEvents);
return NextResponse.json(z.array(zodSchema).parse(fraudEvents));
},
{
requiredPlan: ["advanced", "enterprise"],
Expand Down
Loading