Affiliate Dashboard
Affiliate program differs from referral: partners (affiliates) are external publishers, bloggers, websites. They place links and earn commission on conversions. Dashboard is partner tool for tracking clicks, conversions, and payouts.
Tracking Architecture
model Affiliate {
id String @id @default(cuid())
userId String @unique
status AffiliateStatus @default(PENDING)
commissionRate Decimal @default(0.20) // 20%
payoutThreshold Int @default(5000) // min for payout ($50)
payoutMethod String? // 'paypal' | 'bank' | 'crypto'
payoutDetails Json?
user User @relation(fields: [userId], references: [id])
links AffiliateLink[]
clicks AffiliateClick[]
conversions AffiliateConversion[]
payouts AffiliatePayout[]
}
model AffiliateLink {
id String @id @default(cuid())
affiliateId String
code String @unique // PARTNER123
targetUrl String
campaign String?
createdAt DateTime @default(now())
affiliate Affiliate @relation(fields: [affiliateId], references: [id])
clicks AffiliateClick[]
}
model AffiliateClick {
id String @id @default(cuid())
linkId String
affiliateId String
ip String
userAgent String
referrer String?
clickedAt DateTime @default(now())
converted Boolean @default(false)
link AffiliateLink @relation(fields: [linkId], references: [id])
}
model AffiliateConversion {
id String @id @default(cuid())
affiliateId String
clickId String?
orderId String @unique
orderAmount Int // in cents
commission Int
status String @default('pending')
createdAt DateTime @default(now())
}
Click Tracking
// app/api/aff/[code]/route.ts
export async function GET(request: NextRequest, { params }: { params: { code: string } }) {
const link = await db.affiliateLink.findUnique({
where: { code: params.code },
include: { affiliate: true }
});
if (!link || link.affiliate.status !== 'ACTIVE') redirect('/');
// Record click (fire & forget)
db.affiliateClick.create({
data: {
linkId: link.id,
affiliateId: link.affiliateId,
ip: request.headers.get('x-forwarded-for') ?? 'unknown',
userAgent: request.headers.get('user-agent') ?? '',
referrer: request.headers.get('referer') ?? '',
}
}).catch(console.error);
// Cookie for conversion attribution (30-day window)
const response = Response.redirect(link.targetUrl, 302);
response.headers.set(
'Set-Cookie',
`aff_code=${params.code}; Max-Age=${30 * 24 * 60 * 60}; Path=/; HttpOnly; SameSite=Lax`
);
return response;
}
Partner Dashboard
export default async function AffiliateDashboard() {
const session = await auth();
const affiliate = await db.affiliate.findUnique({
where: { userId: session!.user.id },
include: {
links: {
include: {
_count: { select: { clicks: true } }
}
}
}
});
if (!affiliate) redirect('/affiliate/apply');
if (affiliate.status === 'PENDING') return <AffiliatePendingPage />;
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const stats = await db.$transaction([
db.affiliateClick.count({
where: { affiliateId: affiliate.id, clickedAt: { gte: thirtyDaysAgo } }
}),
db.affiliateConversion.findMany({
where: { affiliateId: affiliate.id, createdAt: { gte: thirtyDaysAgo } }
}),
db.affiliateConversion.aggregate({
where: { affiliateId: affiliate.id, status: 'approved' },
_sum: { commission: true }
}),
]);
const [clickCount, conversions, balance] = stats;
const conversionRate = clickCount > 0
? (conversions.length / clickCount * 100).toFixed(1)
: '0';
return (
<div className="space-y-8">
<StatsGrid
clicks={clickCount}
conversions={conversions.length}
conversionRate={conversionRate}
balance={balance._sum.commission ?? 0}
commissionRate={affiliate.commissionRate}
/>
<LinksTable links={affiliate.links} affiliateId={affiliate.id} />
<ConversionsTable conversions={conversions} />
<PayoutSection
balance={balance._sum.commission ?? 0}
threshold={affiliate.payoutThreshold}
payoutMethod={affiliate.payoutMethod}
/>
</div>
);
}
Payouts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function processPayout(affiliateId: string) {
const affiliate = await db.affiliate.findUnique({
where: { id: affiliateId },
include: {
conversions: {
where: { status: 'approved' }
}
}
});
const totalAmount = affiliate!.conversions.reduce(
(sum, c) => sum + c.commission, 0
);
if (totalAmount < affiliate!.payoutThreshold) {
throw new Error(`Below minimum payout threshold`);
}
const transfer = await stripe.transfers.create({
amount: totalAmount,
currency: 'usd',
destination: affiliate!.payoutDetails?.stripeAccountId as string,
metadata: { affiliateId },
});
await db.$transaction([
db.affilaitePayout.create({
data: {
affiliateId,
amount: totalAmount,
stripeTransferId: transfer.id,
status: 'processing',
}
}),
db.affiliateConversion.updateMany({
where: { affiliateId, status: 'approved' },
data: { status: 'paid' }
}),
]);
}
Development of affiliate program with click tracking, conversions and partner dashboard — 5–8 working days.







