Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 4 additions & 7 deletions apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getPayoutEligibilityFilter } from "@/lib/api/payouts/payout-eligibility-filter";
import { payoutIdSelectionWhere } from "@/lib/api/payouts/payout-id-selection-where";
import { FAST_ACH_FEE_CENTS, FOREX_MARKUP_RATE } from "@/lib/constants/payouts";
import { qstash } from "@/lib/cron";
import { calculatePayoutFeeWithWaiver } from "@/lib/partners/calculate-payout-fee-with-waiver";
Expand Down Expand Up @@ -56,7 +57,7 @@ interface ProcessPayoutsProps {
userId: string;
paymentMethodId: string;
cutoffPeriod?: CUTOFF_PERIOD_TYPES;
selectedPayoutId?: string;
selectedPayoutIds?: string[];
excludedPayoutIds?: string[];
}

Expand All @@ -67,7 +68,7 @@ export async function processPayouts({
userId,
paymentMethodId,
cutoffPeriod,
selectedPayoutId,
selectedPayoutIds,
excludedPayoutIds,
}: ProcessPayoutsProps) {
const cutoffPeriodValue = CUTOFF_PERIOD.find(
Expand All @@ -76,11 +77,7 @@ export async function processPayouts({

const res = await prisma.payout.updateMany({
where: {
...(selectedPayoutId
? { id: selectedPayoutId }
: excludedPayoutIds && excludedPayoutIds.length > 0
? { id: { notIn: excludedPayoutIds } }
: {}),
...payoutIdSelectionWhere({ selectedPayoutIds, excludedPayoutIds }),
...getPayoutEligibilityFilter({ program, workspace }),
...(cutoffPeriodValue && {
periodEnd: {
Expand Down
8 changes: 4 additions & 4 deletions apps/web/app/(ee)/api/cron/payouts/process/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const processPayoutsCronSchema = z.object({
invoiceId: z.string(),
paymentMethodId: z.string(),
cutoffPeriod: CUTOFF_PERIOD_ENUM,
selectedPayoutId: z.string().optional(),
selectedPayoutIds: z.array(z.string()).optional(),
excludedPayoutIds: z.array(z.string()).optional(),
});

Expand All @@ -36,7 +36,7 @@ export async function POST(req: Request) {
invoiceId,
paymentMethodId,
cutoffPeriod,
selectedPayoutId,
selectedPayoutIds,
excludedPayoutIds,
} = processPayoutsCronSchema.parse(JSON.parse(rawBody));

Expand Down Expand Up @@ -84,7 +84,7 @@ export async function POST(req: Request) {
program,
workspace,
cutoffPeriod,
selectedPayoutId,
selectedPayoutIds,
excludedPayoutIds,
});
}
Expand All @@ -96,7 +96,7 @@ export async function POST(req: Request) {
userId,
paymentMethodId,
cutoffPeriod,
selectedPayoutId,
selectedPayoutIds,
excludedPayoutIds,
});

Expand Down
11 changes: 4 additions & 7 deletions apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createId } from "@/lib/api/create-id";
import { getPayoutEligibilityFilter } from "@/lib/api/payouts/payout-eligibility-filter";
import { payoutIdSelectionWhere } from "@/lib/api/payouts/payout-id-selection-where";
import {
CUTOFF_PERIOD,
CUTOFF_PERIOD_TYPES,
Expand All @@ -12,22 +13,18 @@ export async function splitPayouts({
program,
workspace,
cutoffPeriod,
selectedPayoutId,
selectedPayoutIds,
excludedPayoutIds,
}: {
program: Pick<Program, "id" | "name" | "minPayoutAmount" | "payoutMode">;
workspace: Pick<Project, "plan">;
cutoffPeriod: CUTOFF_PERIOD_TYPES;
selectedPayoutId?: string;
selectedPayoutIds?: string[];
excludedPayoutIds?: string[];
}) {
const payouts = await prisma.payout.findMany({
where: {
...(selectedPayoutId
? { id: selectedPayoutId }
: excludedPayoutIds && excludedPayoutIds.length > 0
? { id: { notIn: excludedPayoutIds } }
: {}),
...payoutIdSelectionWhere({ selectedPayoutIds, excludedPayoutIds }),
...getPayoutEligibilityFilter({ program, workspace }),
},
include: {
Expand Down
14 changes: 12 additions & 2 deletions apps/web/app/(ee)/api/partner-profile/payouts/count/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,22 @@ export const GET = withPartnerProfile(async ({ partner, searchParams }) => {
return NextResponse.json(counts);
}

const count = await prisma.payout.count({
const count = await prisma.payout.aggregate({
where: {
...where,
status,
},
_count: true,
_sum: {
amount: true,
},
});

return NextResponse.json(count);
return NextResponse.json([
{
count: count._count ?? 0,
amount: count._sum?.amount ?? 0,
status: status ?? "all",
},
]);
});
14 changes: 12 additions & 2 deletions apps/web/app/(ee)/api/payouts/count/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,22 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {
return NextResponse.json(counts);
}

const count = await prisma.payout.count({
const count = await prisma.payout.aggregate({
where: {
...where,
status,
},
_count: true,
_sum: {
amount: true,
},
});

return NextResponse.json(count);
return NextResponse.json([
{
count: count._count ?? 0,
amount: count._sum?.amount ?? 0,
status: status ?? "all",
},
]);
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getEligiblePayouts } from "@/lib/api/payouts/get-eligible-payouts";
import { getPayoutEligibilityFilter } from "@/lib/api/payouts/payout-eligibility-filter";
import { payoutIdSelectionWhere } from "@/lib/api/payouts/payout-id-selection-where";
import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw";
import { getProgramOrThrow } from "@/lib/api/programs/get-program-or-throw";
import { withWorkspace } from "@/lib/auth";
Expand All @@ -14,7 +15,7 @@ import { NextResponse } from "next/server";
export const GET = withWorkspace(async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);

const { cutoffPeriod, selectedPayoutId, excludedPayoutIds } =
const { cutoffPeriod, selectedPayoutIds, excludedPayoutIds } =
eligiblePayoutsCountQuerySchema.parse(searchParams);

const program = await getProgramOrThrow({
Expand All @@ -32,7 +33,7 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {
program,
workspace,
cutoffPeriod,
selectedPayoutId,
selectedPayoutIds,
excludedPayoutIds,
pageSize: Infinity,
page: 1,
Expand All @@ -46,11 +47,7 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => {

const data = await prisma.payout.aggregate({
where: {
...(selectedPayoutId
? { id: selectedPayoutId }
: excludedPayoutIds && excludedPayoutIds.length > 0
? { id: { notIn: excludedPayoutIds } }
: {}),
...payoutIdSelectionWhere({ selectedPayoutIds, excludedPayoutIds }),
...getPayoutEligibilityFilter({ program, workspace }),
},
_count: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function PayoutStatsCard({
export function PayoutStats() {
const { partner } = usePartnerProfile();

const { payoutsCount, error } = usePartnerPayoutsCount<PayoutsCount[]>({
const { payoutsCount, error } = usePartnerPayoutsCount({
groupBy: "status",
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function PayoutTable() {
const sortOrder = searchParams.get("sortOrder") === "asc" ? "asc" : "desc";

const { payouts, error, loading } = usePartnerPayouts();
const { payoutsCount } = usePartnerPayoutsCount<number>();
const { payoutsCount } = usePartnerPayoutsCount();

const [detailsSheetState, setDetailsSheetState] = useState<
| { open: false; payout: PartnerPayoutResponse | null }
Expand Down Expand Up @@ -216,7 +216,7 @@ export function PayoutTable() {
thClassName: "border-l-0",
tdClassName: "border-l-0",
resourceName: (p) => `payout${p ? "s" : ""}`,
rowCount: payoutsCount || 0,
rowCount: payoutsCount?.[0]?.count ?? 0,
});

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import usePartnerPayoutsCount from "@/lib/swr/use-partner-payouts-count";
import useProgramEnrollments from "@/lib/swr/use-program-enrollments";
import { PayoutsCount } from "@/lib/types";
import { PayoutStatusBadges } from "@/ui/partners/payout-status-badges";
import { useRouterStuff } from "@dub/ui";
import { CircleDotted, GridIcon } from "@dub/ui/icons";
Expand All @@ -10,7 +9,7 @@ import { useCallback, useMemo } from "react";
export function usePayoutFilters() {
const { searchParamsObj, queryParams } = useRouterStuff();

const { payoutsCount } = usePartnerPayoutsCount<PayoutsCount[]>({
const { payoutsCount } = usePartnerPayoutsCount({
groupBy: "status",
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ import {
Veriff,
VerifiedBadge,
} from "@dub/ui/icons";
import { cn } from "@dub/utils";
import { cn, DUPLICATE_IDENTITY_DECLINE_REASON } from "@dub/utils";
import { useAction } from "next-safe-action/hooks";
import { Dispatch, SetStateAction } from "react";
import { toast } from "sonner";

export function IdentityVerificationSection({
partner,
setShowMergePartnerAccountsModal,
}: {
partner?: PartnerProps;
setShowMergePartnerAccountsModal: Dispatch<SetStateAction<boolean>>;
}) {
const { mutate } = usePartnerProfile();

Expand Down Expand Up @@ -111,6 +114,10 @@ export function IdentityVerificationSection({
}
}

const IS_DUPLICATE_IDENTITY_DECLINE =
identityVerificationStatus === "declined" &&
failedReason === DUPLICATE_IDENTITY_DECLINE_REASON;

switch (identityVerificationStatus) {
case "started":
buttonText = "Complete verification";
Expand All @@ -134,6 +141,27 @@ export function IdentityVerificationSection({
<p className="leading-0 text-sm font-medium text-amber-900">
<span className="font-semibold">Verification failed:</span>{" "}
{failedReason}
{IS_DUPLICATE_IDENTITY_DECLINE && (
<span>
{" "}
Please{" "}
<button
onClick={() => setShowMergePartnerAccountsModal(true)}
className="font-semibold underline underline-offset-2"
>
merge your accounts
</button>{" "}
or{" "}
<a
href="https://dub.co/support"
className="font-semibold underline underline-offset-2"
target="_blank"
>
contact support
</a>{" "}
if you believe this is a mistake.
</span>
)}
</p>
</div>
)}
Expand All @@ -146,9 +174,9 @@ export function IdentityVerificationSection({
<div className="relative flex flex-col items-center gap-3 px-6 py-3">
{identityVerificationStatus === "approved" ? (
<VerifiedBadge className="size-6" />
) : (
) : !IS_DUPLICATE_IDENTITY_DECLINE ? (
<ShieldCheck className="size-6 text-neutral-400" />
)}
) : null}

{identityVerificationStatus === "approved" && (
<StatusBadge
Expand All @@ -174,7 +202,9 @@ export function IdentityVerificationSection({
!isPendingReview &&
buttonText && (
<Button
text={buttonText}
text={
IS_DUPLICATE_IDENTITY_DECLINE ? "Merge accounts" : buttonText
}
variant="secondary"
disabled={isMaxAttemptsReached || cannotUpdateProfile}
disabledTooltip={
Expand All @@ -184,16 +214,24 @@ export function IdentityVerificationSection({
? "You have reached the maximum number of verification attempts. Please contact support if you need help."
: undefined
}
onClick={() => executeAsync()}
onClick={() => {
if (IS_DUPLICATE_IDENTITY_DECLINE) {
setShowMergePartnerAccountsModal(true);
} else {
executeAsync();
}
}}
loading={isPending}
className="h-10 w-fit rounded-lg px-4 py-1.5"
/>
)}

<div className="flex items-center gap-1 text-xs font-medium text-neutral-400">
<span>Powered by</span>
<Veriff className="w-auto" />
</div>
{!IS_DUPLICATE_IDENTITY_DECLINE && (
<div className="flex items-center gap-1 text-xs font-medium text-neutral-400">
<span>Powered by</span>
<Veriff className="w-auto" />
</div>
)}
</div>
</div>
</div>
Expand Down
Loading
Loading