diff --git a/apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx b/apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx index f30a7f40772..0e6371229dc 100644 --- a/apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx +++ b/apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx @@ -140,7 +140,7 @@ export default function UserInfo({ data }: { data: UserInfoProps }) { {item.isCurrency - ? `$${nFormatter(program[item.id], { full: true })}` + ? `$${nFormatter(program[item.id] / 100, { full: true })}` : nFormatter(program[item.id], { full: true })} diff --git a/apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts b/apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts index d5b2cea7454..499964410c3 100644 --- a/apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts +++ b/apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts @@ -1,10 +1,16 @@ import { handleAndReturnErrorResponse } from "@/lib/api/errors"; +import { qstash } from "@/lib/cron"; import { verifyQstashSignature } from "@/lib/cron/verify-qstash"; import { stripe } from "@/lib/stripe"; import { sendEmail } from "@dub/email"; import PartnerPayoutWithdrawalInitiated from "@dub/email/templates/partner-payout-withdrawal-initiated"; import { prisma } from "@dub/prisma"; -import { currencyFormatter, formatDate, log } from "@dub/utils"; +import { + APP_DOMAIN_WITH_NGROK, + currencyFormatter, + formatDate, + log, +} from "@dub/utils"; import { z } from "zod"; import { logAndRespond } from "../../utils"; export const dynamic = "force-dynamic"; @@ -44,43 +50,42 @@ export async function POST(req: Request) { stripeAccount, }); - // Check if there's any available balance - if (balance.available.length === 0 || balance.available[0].amount === 0) { + if (balance.available.length === 0) { + // should never happen, but just in case return logAndRespond( - `No available balance found for partner ${partner.email} (${stripeAccount}). Skipping...`, + `Partner ${partner.email} (${stripeAccount}) has no available balance. Skipping...`, + { + logLevel: "error", + }, ); } - const { amount, currency } = balance.available[0]; + let { amount: availableBalance, currency } = balance.available[0]; - const { data: stripePayouts } = await stripe.payouts.list( - { - status: "pending", - }, - { - stripeAccount, - }, - ); - - let availableBalance = amount; - - // Subtract the pending/in-transit payouts from the available balance - if (stripePayouts.length > 0) { - const pendingOrInTransitPayouts = stripePayouts.filter( - ({ status }) => status === "pending" || status === "in_transit", - ); + // if available balance is 0, check if there's any pending balance + if (availableBalance === 0) { + const pendingBalance = balance.pending[0].amount; - const alreadyPaidOutAmount = pendingOrInTransitPayouts.reduce( - (acc, payout) => acc + payout.amount, - 0, - ); + // if there's a pending balance, schedule another check in 1 hour + if (pendingBalance > 0) { + const res = await qstash.publishJSON({ + url: `${APP_DOMAIN_WITH_NGROK}/api/cron/payouts/balance-available`, + delay: 60 * 60, // check again in 1 hour + body: { + stripeAccount, + }, + }); + console.log( + `Scheduled another check for partner ${partner.email} (${stripeAccount}) in 1 hour: ${res.messageId}`, + ); - availableBalance = availableBalance - alreadyPaidOutAmount; - } + return logAndRespond( + `Pending balance found for partner ${partner.email} (${stripeAccount}): ${currencyFormatter(pendingBalance / 100, { currency: "USD" })}. Scheduling another check in 1 hour...`, + ); + } - if (availableBalance <= 0) { return logAndRespond( - `The available balance (${currencyFormatter(availableBalance / 100, { currency })}) for partner ${partner.email} (${stripeAccount}) is less than or equal to 0 after subtracting pending payouts. Skipping...`, + `Partner ${partner.email} (${stripeAccount})'s available balance is 0. Skipping...`, ); }