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

Skip to content
Merged
42 changes: 26 additions & 16 deletions apps/web/app/(ee)/api/cron/payouts/confirm/confirm-payouts.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { createId } from "@/lib/api/create-id";
import {
DIRECT_DEBIT_PAYMENT_METHOD_TYPES,
PAYMENT_METHOD_TYPES,
} from "@/lib/partners/constants";
import { DIRECT_DEBIT_PAYMENT_METHOD_TYPES } from "@/lib/partners/constants";
import {
CUTOFF_PERIOD,
CUTOFF_PERIOD_TYPES,
} from "@/lib/partners/cutoff-period";
import { calculatePayoutFee } from "@/lib/payment-methods";
import { calculatePayoutFeeForMethod } from "@/lib/payment-methods";
import { stripe } from "@/lib/stripe";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
Expand All @@ -23,7 +20,10 @@ export async function confirmPayouts({
paymentMethodId,
cutoffPeriod,
}: {
workspace: Pick<Project, "id" | "stripeId" | "plan" | "invoicePrefix">;
workspace: Pick<
Project,
"id" | "stripeId" | "plan" | "invoicePrefix" | "payoutFee"
>;
program: Pick<Program, "id" | "name" | "logo" | "minPayoutAmount">;
userId: string;
paymentMethodId: string;
Expand Down Expand Up @@ -83,14 +83,21 @@ export async function confirmPayouts({
const newInvoice = await prisma.$transaction(async (tx) => {
const amount = payouts.reduce((total, payout) => total + payout.amount, 0);

const fee =
amount *
calculatePayoutFee({
paymentMethod: paymentMethod.type,
plan: workspace.plan,
});
const payoutFee = calculatePayoutFeeForMethod({
paymentMethod: paymentMethod.type,
payoutFee: workspace.payoutFee,
});

const total = amount + fee;
if (!payoutFee) {
throw new Error("Failed to calculate payout fee.");
}

console.info(
`Using payout fee of ${payoutFee} for payment method ${paymentMethod.type}`,
);

const totalFee = amount * payoutFee;
const total = amount + totalFee;

// Generate the next invoice number
const totalInvoices = await tx.invoice.count({
Expand All @@ -108,7 +115,7 @@ export async function confirmPayouts({
programId: program.id,
workspaceId: workspace.id,
amount,
fee,
fee: totalFee,
total,
},
});
Expand All @@ -120,10 +127,12 @@ export async function confirmPayouts({
await stripe.paymentIntents.create({
amount: invoice.total,
customer: workspace.stripeId!,
payment_method_types: PAYMENT_METHOD_TYPES,
payment_method: paymentMethod.id,
automatic_payment_methods: {
enabled: true,
allow_redirects: "never",
},
currency: "usd",
confirmation_method: "automatic",
confirm: true,
transfer_group: invoice.id,
statement_descriptor: "Dub Partners",
Expand Down Expand Up @@ -177,6 +186,7 @@ export async function confirmPayouts({
payouts.filter((payout) => payout.partner.email),
100,
);

for (const payoutChunk of payoutChunks) {
await resend.batch.send(
payoutChunk.map((payout) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import { PAYOUT_FEES } from "@/lib/partners/constants";
import usePartnersCount from "@/lib/swr/use-partners-count";
import useTagsCount from "@/lib/swr/use-tags-count";
import useUsers from "@/lib/swr/use-users";
Expand Down Expand Up @@ -44,6 +43,7 @@ export default function PlanUsage() {
totalLinks,
payoutsUsage,
payoutsLimit,
payoutFee,
domains,
domainsLimit,
foldersUsage,
Expand All @@ -59,10 +59,6 @@ export default function PlanUsage() {
status: "approved",
});

const payoutFees = plan
? PAYOUT_FEES[plan.toLowerCase()]?.direct_debit
: null;

const { data: tags } = useTagsCount();
const { users } = useUsers();

Expand Down Expand Up @@ -219,13 +215,7 @@ export default function PlanUsage() {
<UsageCategory
title="Payout fees"
icon={CirclePercentage}
usage={
plan
? payoutFees
? `${Math.round(payoutFees * 100)}%`
: "-"
: undefined
}
usage={plan && payoutFee && `${payoutFee * 100}%`}
href="https://dub.co/help/article/partner-payouts#payout-fees-and-timing"
/>
</div>
Expand Down
16 changes: 0 additions & 16 deletions apps/web/lib/partners/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,6 @@ import { PaymentMethodOption } from "../types";
export const PAYOUTS_SHEET_ITEMS_LIMIT = 10;
export const REFERRALS_EMBED_EARNINGS_LIMIT = 8;
export const CUSTOMER_PAGE_EVENTS_LIMIT = 8;

export const PAYOUT_FEES = {
business: {
direct_debit: 0.05,
card: 0.1,
},
advanced: {
direct_debit: 0.05,
card: 0.08,
},
enterprise: {
direct_debit: 0.03,
card: 0.06,
},
} as const;

export const DUB_MIN_PAYOUT_AMOUNT_CENTS = 10000;
export const PAYOUT_FAILURE_FEE_CENTS = 1000; // 10 USD

Expand Down
25 changes: 9 additions & 16 deletions apps/web/lib/payment-methods.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
import Stripe from "stripe";
import {
DIRECT_DEBIT_PAYMENT_METHOD_TYPES,
PAYOUT_FEES,
} from "./partners/constants";
import { DIRECT_DEBIT_PAYMENT_METHOD_TYPES } from "./partners/constants";

export const calculatePayoutFee = ({
export const calculatePayoutFeeForMethod = ({
paymentMethod,
plan,
payoutFee,
}: {
paymentMethod: Stripe.PaymentMethod.Type;
plan: string | undefined;
payoutFee: number | undefined;
}) => {
if (!paymentMethod) {
return null;
}

const planType = plan?.split(" ")[0] ?? "business";

if (!Object.keys(PAYOUT_FEES).includes(planType)) {
if (!paymentMethod || payoutFee === undefined || payoutFee === null) {
return null;
}

if (["link", "card"].includes(paymentMethod)) {
return PAYOUT_FEES[planType].card;
return payoutFee + 0.03;
}

if (DIRECT_DEBIT_PAYMENT_METHOD_TYPES.includes(paymentMethod)) {
return PAYOUT_FEES[planType].direct_debit;
return payoutFee;
}

throw new Error(`Unsupported payment method ${paymentMethod}.`);
};
3 changes: 3 additions & 0 deletions apps/web/lib/zod/schemas/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export const WorkspaceSchema = z
.describe(
"The max dollar amount of partner payouts that can be processed within a billing cycle (in cents).",
),
payoutFee: z
.number()
.describe("The payout fee Dub Partners takes for direct debit payments."),
domainsLimit: z.number().describe("The domains limit of the workspace."),
tagsLimit: z.number().describe("The tags limit of the workspace."),
foldersUsage: z.number().describe("The folders usage of the workspace."),
Expand Down
16 changes: 11 additions & 5 deletions apps/web/ui/partners/payout-invoice-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
CUTOFF_PERIOD,
CUTOFF_PERIOD_TYPES,
} from "@/lib/partners/cutoff-period";
import { calculatePayoutFee } from "@/lib/payment-methods";
import { calculatePayoutFeeForMethod } from "@/lib/payment-methods";
import { mutatePrefix } from "@/lib/swr/mutate";
import usePaymentMethods from "@/lib/swr/use-payment-methods";
import useWorkspace from "@/lib/swr/use-workspace";
Expand Down Expand Up @@ -79,7 +79,13 @@ type SelectPaymentMethod =

function PayoutInvoiceSheetContent() {
const { queryParams } = useRouterStuff();
const { id: workspaceId, slug, plan, defaultProgramId } = useWorkspace();
const {
id: workspaceId,
slug,
plan,
defaultProgramId,
payoutFee,
} = useWorkspace();

const { paymentMethods, loading: paymentMethodsLoading } =
usePaymentMethods();
Expand Down Expand Up @@ -125,9 +131,9 @@ function PayoutInvoiceSheetContent() {
const base = {
...paymentMethod,
id: pm.id,
fee: calculatePayoutFee({
fee: calculatePayoutFeeForMethod({
paymentMethod: pm.type,
plan,
payoutFee,
}),
};

Expand Down Expand Up @@ -267,7 +273,7 @@ function PayoutInvoiceSheetContent() {
),
tooltipContent: selectedPaymentMethod ? (
<SimpleTooltipContent
title={`${Math.round(selectedPaymentMethod.fee * 100)}% processing fee. ${!DIRECT_DEBIT_PAYMENT_METHOD_TYPES.includes(selectedPaymentMethod.type as Stripe.PaymentMethod.Type) ? " Switch to Direct Debit for a reduced fee." : ""}`}
title={`${selectedPaymentMethod.fee * 100}% processing fee. ${!DIRECT_DEBIT_PAYMENT_METHOD_TYPES.includes(selectedPaymentMethod.type as Stripe.PaymentMethod.Type) ? " Switch to Direct Debit for a reduced fee." : ""}`}
cta="Learn more"
href="https://d.to/payouts"
/>
Expand Down
2 changes: 1 addition & 1 deletion packages/embeds/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@dub/embed-core",
"description": "Vanilla JS core script that embeds Dub's dashboards.",
"version": "0.0.14",
"version": "0.0.15",
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/embeds/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@dub/embed-react",
"description": "Embed React components for Dub.",
"version": "0.0.14",
"version": "0.0.15",
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
13 changes: 7 additions & 6 deletions packages/prisma/schema/workspace.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ model Project {
totalLinks Int @default(0) // Total number of links in the workspace
totalClicks Int @default(0) // Total number of clicks in the workspace

usage Int @default(0)
usageLimit Int @default(1000)
linksUsage Int @default(0)
linksLimit Int @default(25)
payoutsUsage Int @default(0)
payoutsLimit Int @default(0)
usage Int @default(0)
usageLimit Int @default(1000)
linksUsage Int @default(0)
linksLimit Int @default(25)
payoutsUsage Int @default(0)
payoutsLimit Int @default(0)
payoutFee Float @default(0.05) // payout fee for direct debit payments

domainsLimit Int @default(3)
tagsLimit Int @default(5)
Expand Down