# Affiliate & Referral Systems **Meta-Description:** Affiliate und Referral Programme implementieren. Tracking Links, Provisionsberechnung und Auszahlungen mit Node.js und Stripe automatisieren. **Keywords:** Affiliate Marketing, Referral Program, Commission Tracking, Partner Program, Referral Links, Payout System, Revenue Share --- ## Einführung **Affiliate und Referral Systeme** treiben Wachstum durch Empfehlungen. Von **Tracking-Links** über **Commission-Berechnung** bis **automatisierte Payouts** – ein gut implementiertes System skaliert Kundenakquise kosteneffizient. Dieser Guide zeigt Production-ready Implementierungen. --- ## System Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ AFFILIATE & REFERRAL SYSTEM │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ AFFILIATE │ │ REFERRAL │ │ │ │ (B2B/Partner) │ │ (Customer) │ │ │ ├─────────────────┤ ├─────────────────┤ │ │ │ • Influencers │ │ • Invite friends│ │ │ │ • Bloggers │ │ • Share links │ │ │ │ • Review sites │ │ • Earn rewards │ │ │ │ • Partners │ │ • Both benefit │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ └───────────┬───────────────┘ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ TRACKING SYSTEM │ │ │ ├─────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ Unique Link ──► Click Track ──► Cookie/Store │ │ │ │ │ │ │ │ │ │ │ ?ref=ABC123 IP, Device, 30-90 day │ │ │ │ Timestamp attribution │ │ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ CONVERSION & COMMISSION │ │ │ │ │ │ │ │ Purchase ──► Attribution ──► Commission ──► Payout│ │ │ │ │ │ │ │ │ │ │ │ Order ID Match Cookie Calculate Monthly │ │ │ │ completed to Affiliate 20-50% Stripe │ │ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ Commission Models: │ │ ├── One-time: Single payment per conversion │ │ ├── Recurring: % of subscription for lifetime │ │ ├── Tiered: Higher rates at volume thresholds │ │ └── Hybrid: One-time + recurring combined │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Database Schema ```typescript // prisma/schema.prisma model Affiliate { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id]) // Affiliate identity code String @unique // ref=ABC123 slug String? @unique // custom vanity URL // Commission settings commissionType CommissionType @default(PERCENTAGE) commissionRate Decimal @default(20) // 20% recurringRate Decimal? // For subscription commissions // Tier (affects commission rates) tier AffiliateTier @default(STANDARD) // Payout info payoutMethod PayoutMethod @default(STRIPE) stripeAccountId String? paypalEmail String? minimumPayout Int @default(5000) // $50.00 minimum // Status status AffiliateStatus @default(PENDING) approvedAt DateTime? suspendedAt DateTime? // Tracking clicks Click[] conversions Conversion[] payouts Payout[] // Stats (denormalized for performance) totalClicks Int @default(0) totalConversions Int @default(0) totalEarnings Int @default(0) // cents pendingBalance Int @default(0) // cents createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Click { id String @id @default(cuid()) affiliateId String affiliate Affiliate @relation(fields: [affiliateId], references: [id]) // Tracking data ipAddress String? userAgent String? referer String? landingPage String // UTM parameters utmSource String? utmMedium String? utmCampaign String? // Visitor identification visitorId String // Fingerprint or cookie ID sessionId String? // Conversion tracking converted Boolean @default(false) conversionId String? createdAt DateTime @default(now()) @@index([affiliateId, createdAt]) @@index([visitorId]) } model Conversion { id String @id @default(cuid()) affiliateId String affiliate Affiliate @relation(fields: [affiliateId], references: [id]) // Order info orderId String @unique customerId String orderAmount Int // cents currency String @default("EUR") // Commission commissionAmount Int // cents commissionRate Decimal commissionType CommissionType // Status status ConversionStatus @default(PENDING) approvedAt DateTime? rejectedAt DateTime? rejectionReason String? // Payout tracking payoutId String? payout Payout? @relation(fields: [payoutId], references: [id]) paidAt DateTime? // Attribution clickId String? attributionWindow Int // days // For recurring commissions isRecurring Boolean @default(false) parentConversionId String? recurringMonth Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([affiliateId, status]) @@index([affiliateId, createdAt]) } model Payout { id String @id @default(cuid()) affiliateId String affiliate Affiliate @relation(fields: [affiliateId], references: [id]) amount Int // cents currency String @default("EUR") fee Int @default(0) // Processing fee method PayoutMethod stripeTransferId String? paypalPayoutId String? status PayoutStatus @default(PENDING) processedAt DateTime? failedAt DateTime? failureReason String? conversions Conversion[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // Referral-specific (customer referrals) model Referral { id String @id @default(cuid()) referrerId String // User who referred referrer User @relation("ReferralsMade", fields: [referrerId], references: [id]) referredId String? // User who signed up referred User? @relation("ReferralsReceived", fields: [referredId], references: [id]) code String @unique email String? // Invited email (before signup) // Rewards referrerReward Int? // cents referredReward Int? // cents rewardType RewardType @default(CREDIT) // Status status ReferralStatus @default(PENDING) signedUpAt DateTime? qualifiedAt DateTime? // Met qualification criteria rewardedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } enum CommissionType { PERCENTAGE FIXED TIERED } enum AffiliateTier { STANDARD // 20% SILVER // 25% GOLD // 30% PLATINUM // 35% } enum AffiliateStatus { PENDING ACTIVE SUSPENDED TERMINATED } enum PayoutMethod { STRIPE PAYPAL BANK_TRANSFER } enum ConversionStatus { PENDING // Waiting for approval period APPROVED // Commission confirmed REJECTED // Refund, fraud, etc. PAID // Included in payout } enum PayoutStatus { PENDING PROCESSING COMPLETED FAILED } enum RewardType { CREDIT // Account credit CASH // Cash payout DISCOUNT // Discount code } enum ReferralStatus { PENDING // Invited, not signed up SIGNED_UP // Created account QUALIFIED // Met criteria (purchase, etc.) REWARDED // Rewards distributed EXPIRED // Never signed up } ``` --- ## Affiliate Link Generation ```typescript // lib/affiliate/links.ts import { db } from '@/lib/db'; import crypto from 'crypto'; export async function generateAffiliateCode(): Promise { const code = crypto.randomBytes(4).toString('hex').toUpperCase(); // Ensure uniqueness const existing = await db.affiliate.findUnique({ where: { code } }); if (existing) { return generateAffiliateCode(); // Retry } return code; } export function buildAffiliateLink( baseUrl: string, affiliateCode: string, campaign?: string ): string { const url = new URL(baseUrl); url.searchParams.set('ref', affiliateCode); if (campaign) { url.searchParams.set('utm_campaign', campaign); url.searchParams.set('utm_source', 'affiliate'); url.searchParams.set('utm_medium', 'referral'); } return url.toString(); } // Generate vanity URL export async function setAffiliateSlug( affiliateId: string, slug: string ): Promise { // Validate slug format if (!/^[a-z0-9-]{3,30}$/.test(slug)) { throw new Error('Invalid slug format'); } // Check reserved words const reserved = ['admin', 'api', 'app', 'www', 'help', 'support']; if (reserved.includes(slug)) { throw new Error('Slug is reserved'); } try { await db.affiliate.update({ where: { id: affiliateId }, data: { slug } }); return true; } catch { throw new Error('Slug already taken'); } } // Link resolver export async function resolveAffiliateLink( refCode: string ): Promise { // Try code first let affiliate = await db.affiliate.findUnique({ where: { code: refCode, status: 'ACTIVE' } }); // Try slug if (!affiliate) { affiliate = await db.affiliate.findUnique({ where: { slug: refCode, status: 'ACTIVE' } }); } return affiliate; } ``` --- ## Click Tracking ```typescript // lib/affiliate/tracking.ts import { db } from '@/lib/db'; import { cookies } from 'next/headers'; import { NextRequest } from 'next/server'; import crypto from 'crypto'; const COOKIE_NAME = 'aff_ref'; const COOKIE_MAX_AGE = 30 * 24 * 60 * 60; // 30 days interface TrackClickParams { affiliateId: string; request: NextRequest; landingPage: string; } export async function trackClick(params: TrackClickParams): Promise { const { affiliateId, request, landingPage } = params; const visitorId = getVisitorId(request); const searchParams = new URL(request.url).searchParams; const click = await db.click.create({ data: { affiliateId, ipAddress: request.headers.get('x-forwarded-for'), userAgent: request.headers.get('user-agent'), referer: request.headers.get('referer'), landingPage, utmSource: searchParams.get('utm_source'), utmMedium: searchParams.get('utm_medium'), utmCampaign: searchParams.get('utm_campaign'), visitorId, sessionId: crypto.randomUUID() } }); // Update affiliate stats await db.affiliate.update({ where: { id: affiliateId }, data: { totalClicks: { increment: 1 } } }); return click; } function getVisitorId(request: NextRequest): string { // Try to get existing visitor ID from cookie const existingId = request.cookies.get('visitor_id')?.value; if (existingId) return existingId; // Generate fingerprint from request headers const components = [ request.headers.get('user-agent'), request.headers.get('accept-language'), request.headers.get('accept-encoding') ].filter(Boolean).join('|'); return crypto.createHash('sha256').update(components).digest('hex').slice(0, 16); } // Set affiliate cookie export function setAffiliateCookie(affiliateCode: string): void { const cookieStore = cookies(); cookieStore.set(COOKIE_NAME, affiliateCode, { maxAge: COOKIE_MAX_AGE, httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/' }); } // Get affiliate from cookie export function getAffiliateFromCookie(): string | null { const cookieStore = cookies(); return cookieStore.get(COOKIE_NAME)?.value || null; } // Attribution window check export async function findAttributableClick( visitorId: string, affiliateId: string, windowDays: number = 30 ): Promise { const windowStart = new Date(); windowStart.setDate(windowStart.getDate() - windowDays); return db.click.findFirst({ where: { affiliateId, visitorId, createdAt: { gte: windowStart }, converted: false }, orderBy: { createdAt: 'desc' } }); } ``` --- ## Tracking Middleware ```typescript // middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function middleware(request: NextRequest) { const response = NextResponse.next(); const refCode = request.nextUrl.searchParams.get('ref'); if (refCode) { // Validate affiliate exists and is active const isValid = await validateAffiliateCode(refCode); if (isValid) { // Set cookie response.cookies.set('aff_ref', refCode, { maxAge: 30 * 24 * 60 * 60, // 30 days httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' }); // Track click asynchronously trackClickAsync(refCode, request); // Remove ref from URL for cleaner UX const cleanUrl = new URL(request.url); cleanUrl.searchParams.delete('ref'); if (cleanUrl.toString() !== request.url) { return NextResponse.redirect(cleanUrl); } } } return response; } async function validateAffiliateCode(code: string): Promise { // Fast validation via edge-compatible check const response = await fetch( `${process.env.NEXT_PUBLIC_APP_URL}/api/affiliate/validate?code=${code}` ); return response.ok; } function trackClickAsync(refCode: string, request: NextRequest): void { // Fire and forget fetch(`${process.env.NEXT_PUBLIC_APP_URL}/api/affiliate/track`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refCode, landingPage: request.nextUrl.pathname, userAgent: request.headers.get('user-agent'), referer: request.headers.get('referer'), ip: request.headers.get('x-forwarded-for') }) }).catch(console.error); } export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] }; ``` --- ## Commission Calculation ```typescript // lib/affiliate/commission.ts import { db } from '@/lib/db'; import { Decimal } from '@prisma/client/runtime/library'; interface CommissionResult { amount: number; rate: Decimal; type: CommissionType; } // Tier-based commission rates const TIER_RATES: Record = { STANDARD: 20, SILVER: 25, GOLD: 30, PLATINUM: 35 }; export function calculateCommission( orderAmount: number, affiliate: Affiliate ): CommissionResult { let rate: Decimal; let amount: number; switch (affiliate.commissionType) { case 'PERCENTAGE': rate = affiliate.tier ? new Decimal(TIER_RATES[affiliate.tier]) : affiliate.commissionRate; amount = Math.floor(orderAmount * rate.toNumber() / 100); break; case 'FIXED': rate = affiliate.commissionRate; amount = rate.toNumber() * 100; // Convert to cents break; case 'TIERED': const tierResult = calculateTieredCommission(orderAmount, affiliate); rate = tierResult.rate; amount = tierResult.amount; break; default: rate = new Decimal(20); amount = Math.floor(orderAmount * 0.2); } return { amount, rate, type: affiliate.commissionType }; } function calculateTieredCommission( orderAmount: number, affiliate: Affiliate ): { amount: number; rate: Decimal } { // Get affiliate's total conversions this month const monthStart = new Date(); monthStart.setDate(1); monthStart.setHours(0, 0, 0, 0); // Tiered rates based on monthly volume const tiers = [ { threshold: 10000_00, rate: 20 }, // $10k+ = 20% { threshold: 5000_00, rate: 25 }, // $5k+ = 25% { threshold: 1000_00, rate: 30 }, // $1k+ = 30% { threshold: 0, rate: 35 } // Base = 35% ]; // For simplicity, use the affiliate's total earnings to determine tier const volumeTier = tiers.find(t => affiliate.totalEarnings >= t.threshold); const rate = volumeTier?.rate || 20; return { amount: Math.floor(orderAmount * rate / 100), rate: new Decimal(rate) }; } // Calculate recurring commission for subscriptions export function calculateRecurringCommission( subscriptionAmount: number, affiliate: Affiliate, month: number ): CommissionResult | null { // Most programs cap recurring at 12 months if (month > 12) return null; // Recurring rate might be lower than initial const recurringRate = affiliate.recurringRate || affiliate.commissionRate; const amount = Math.floor(subscriptionAmount * recurringRate.toNumber() / 100); return { amount, rate: recurringRate, type: 'PERCENTAGE' }; } ``` --- ## Conversion Recording ```typescript // lib/affiliate/conversions.ts import { db } from '@/lib/db'; import { calculateCommission } from './commission'; import { getAffiliateFromCookie } from './tracking'; interface RecordConversionParams { orderId: string; customerId: string; orderAmount: number; currency?: string; isSubscription?: boolean; } export async function recordConversion( params: RecordConversionParams ): Promise { const { orderId, customerId, orderAmount, currency = 'EUR', isSubscription } = params; // Check for existing conversion (prevent duplicates) const existing = await db.conversion.findUnique({ where: { orderId } }); if (existing) return existing; // Get affiliate from cookie or customer's first touch const affiliateCode = getAffiliateFromCookie(); if (!affiliateCode) return null; const affiliate = await db.affiliate.findUnique({ where: { code: affiliateCode, status: 'ACTIVE' } }); if (!affiliate) return null; // Calculate commission const commission = calculateCommission(orderAmount, affiliate); // Find attributable click const click = await db.click.findFirst({ where: { affiliateId: affiliate.id, converted: false, createdAt: { gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // 30 days } }, orderBy: { createdAt: 'desc' } }); // Create conversion const conversion = await db.$transaction(async (tx) => { // Create conversion record const conv = await tx.conversion.create({ data: { affiliateId: affiliate.id, orderId, customerId, orderAmount, currency, commissionAmount: commission.amount, commissionRate: commission.rate, commissionType: commission.type, clickId: click?.id, attributionWindow: 30, isRecurring: isSubscription } }); // Mark click as converted if (click) { await tx.click.update({ where: { id: click.id }, data: { converted: true, conversionId: conv.id } }); } // Update affiliate stats await tx.affiliate.update({ where: { id: affiliate.id }, data: { totalConversions: { increment: 1 }, pendingBalance: { increment: commission.amount } } }); return conv; }); return conversion; } // Auto-approve conversions after hold period export async function approveConversions(): Promise { const holdPeriod = 14; // days const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - holdPeriod); const pendingConversions = await db.conversion.findMany({ where: { status: 'PENDING', createdAt: { lte: cutoffDate } } }); let approved = 0; for (const conversion of pendingConversions) { // Check if order was refunded const wasRefunded = await checkOrderRefunded(conversion.orderId); if (wasRefunded) { await db.conversion.update({ where: { id: conversion.id }, data: { status: 'REJECTED', rejectedAt: new Date(), rejectionReason: 'Order refunded' } }); // Reduce pending balance await db.affiliate.update({ where: { id: conversion.affiliateId }, data: { pendingBalance: { decrement: conversion.commissionAmount } } }); } else { await db.conversion.update({ where: { id: conversion.id }, data: { status: 'APPROVED', approvedAt: new Date() } }); // Move from pending to earned await db.affiliate.update({ where: { id: conversion.affiliateId }, data: { pendingBalance: { decrement: conversion.commissionAmount }, totalEarnings: { increment: conversion.commissionAmount } } }); approved++; } } return approved; } async function checkOrderRefunded(orderId: string): Promise { // Check with payment provider const order = await db.order.findUnique({ where: { id: orderId } }); return order?.status === 'REFUNDED'; } ``` --- ## Payout System ```typescript // lib/affiliate/payouts.ts import { db } from '@/lib/db'; import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); interface ProcessPayoutsResult { processed: number; failed: number; totalAmount: number; } export async function processPayouts(): Promise { const result = { processed: 0, failed: 0, totalAmount: 0 }; // Find affiliates with approved conversions ready for payout const affiliatesWithBalance = await db.affiliate.findMany({ where: { status: 'ACTIVE', totalEarnings: { gte: db.affiliate.fields.minimumPayout } }, include: { conversions: { where: { status: 'APPROVED', payoutId: null } } } }); for (const affiliate of affiliatesWithBalance) { if (affiliate.conversions.length === 0) continue; const payoutAmount = affiliate.conversions.reduce( (sum, c) => sum + c.commissionAmount, 0 ); if (payoutAmount < affiliate.minimumPayout) continue; try { const payout = await createPayout(affiliate, payoutAmount); // Link conversions to payout await db.conversion.updateMany({ where: { id: { in: affiliate.conversions.map(c => c.id) } }, data: { payoutId: payout.id, status: 'PAID', paidAt: new Date() } }); // Reset affiliate balance await db.affiliate.update({ where: { id: affiliate.id }, data: { totalEarnings: { decrement: payoutAmount } } }); result.processed++; result.totalAmount += payoutAmount; } catch (error) { console.error(`Payout failed for ${affiliate.id}:`, error); result.failed++; } } return result; } async function createPayout( affiliate: Affiliate, amount: number ): Promise { // Create payout record const payout = await db.payout.create({ data: { affiliateId: affiliate.id, amount, currency: 'EUR', method: affiliate.payoutMethod, status: 'PROCESSING' } }); try { switch (affiliate.payoutMethod) { case 'STRIPE': await processStripePayout(affiliate, payout); break; case 'PAYPAL': await processPayPalPayout(affiliate, payout); break; default: throw new Error('Unsupported payout method'); } await db.payout.update({ where: { id: payout.id }, data: { status: 'COMPLETED', processedAt: new Date() } }); } catch (error) { await db.payout.update({ where: { id: payout.id }, data: { status: 'FAILED', failedAt: new Date(), failureReason: error instanceof Error ? error.message : 'Unknown error' } }); throw error; } return payout; } async function processStripePayout( affiliate: Affiliate, payout: Payout ): Promise { if (!affiliate.stripeAccountId) { throw new Error('No Stripe account connected'); } const transfer = await stripe.transfers.create({ amount: payout.amount, currency: payout.currency.toLowerCase(), destination: affiliate.stripeAccountId, metadata: { payoutId: payout.id, affiliateId: affiliate.id } }); await db.payout.update({ where: { id: payout.id }, data: { stripeTransferId: transfer.id } }); } async function processPayPalPayout( affiliate: Affiliate, payout: Payout ): Promise { if (!affiliate.paypalEmail) { throw new Error('No PayPal email configured'); } // PayPal Payouts API integration const paypalPayout = await createPayPalPayout({ email: affiliate.paypalEmail, amount: (payout.amount / 100).toFixed(2), currency: payout.currency, note: `Affiliate commission payout #${payout.id}` }); await db.payout.update({ where: { id: payout.id }, data: { paypalPayoutId: paypalPayout.batch_id } }); } ``` --- ## Customer Referral System ```typescript // lib/referral/referrals.ts import { db } from '@/lib/db'; import crypto from 'crypto'; const REFERRAL_REWARDS = { referrer: 1000, // $10 credit referred: 500 // $5 credit }; export async function createReferralCode(userId: string): Promise { const existing = await db.referral.findFirst({ where: { referrerId: userId } }); if (existing) return existing.code; const code = crypto.randomBytes(4).toString('hex').toUpperCase(); await db.referral.create({ data: { referrerId: userId, code, status: 'PENDING' } }); return code; } export async function processReferralSignup( newUserId: string, referralCode: string ): Promise { const referral = await db.referral.findUnique({ where: { code: referralCode } }); if (!referral || referral.status !== 'PENDING') { return false; } // Prevent self-referral if (referral.referrerId === newUserId) { return false; } await db.referral.update({ where: { id: referral.id }, data: { referredId: newUserId, status: 'SIGNED_UP', signedUpAt: new Date() } }); return true; } export async function qualifyReferral( referredUserId: string ): Promise { const referral = await db.referral.findFirst({ where: { referredId: referredUserId, status: 'SIGNED_UP' } }); if (!referral) return; await db.$transaction([ // Update referral status db.referral.update({ where: { id: referral.id }, data: { status: 'QUALIFIED', qualifiedAt: new Date(), referrerReward: REFERRAL_REWARDS.referrer, referredReward: REFERRAL_REWARDS.referred } }), // Credit referrer db.user.update({ where: { id: referral.referrerId }, data: { credit: { increment: REFERRAL_REWARDS.referrer } } }), // Credit referred user db.user.update({ where: { id: referredUserId }, data: { credit: { increment: REFERRAL_REWARDS.referred } } }) ]); // Send notifications await notifyReferralReward(referral.referrerId, REFERRAL_REWARDS.referrer); await notifyReferralReward(referredUserId, REFERRAL_REWARDS.referred); } ``` --- ## Affiliate Dashboard API ```typescript // app/api/affiliate/dashboard/route.ts import { NextRequest, NextResponse } from 'next/server'; import { db } from '@/lib/db'; import { auth } from '@/lib/auth'; export async function GET(request: NextRequest) { const session = await auth(); if (!session?.user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const affiliate = await db.affiliate.findUnique({ where: { userId: session.user.id } }); if (!affiliate) { return NextResponse.json({ error: 'Not an affiliate' }, { status: 404 }); } // Get date range const { searchParams } = new URL(request.url); const range = searchParams.get('range') || '30d'; const startDate = getStartDate(range); // Fetch stats const [clicks, conversions, recentConversions] = await Promise.all([ db.click.count({ where: { affiliateId: affiliate.id, createdAt: { gte: startDate } } }), db.conversion.aggregate({ where: { affiliateId: affiliate.id, createdAt: { gte: startDate } }, _count: true, _sum: { commissionAmount: true, orderAmount: true } }), db.conversion.findMany({ where: { affiliateId: affiliate.id }, orderBy: { createdAt: 'desc' }, take: 10, select: { id: true, orderAmount: true, commissionAmount: true, status: true, createdAt: true } }) ]); // Calculate conversion rate const conversionRate = clicks > 0 ? ((conversions._count || 0) / clicks * 100).toFixed(2) : '0.00'; return NextResponse.json({ affiliate: { code: affiliate.code, tier: affiliate.tier, commissionRate: affiliate.commissionRate, status: affiliate.status }, stats: { clicks, conversions: conversions._count || 0, conversionRate: `${conversionRate}%`, revenue: conversions._sum.orderAmount || 0, earnings: conversions._sum.commissionAmount || 0, pendingBalance: affiliate.pendingBalance, totalEarnings: affiliate.totalEarnings }, recentConversions, links: { main: `${process.env.NEXT_PUBLIC_APP_URL}?ref=${affiliate.code}`, pricing: `${process.env.NEXT_PUBLIC_APP_URL}/pricing?ref=${affiliate.code}` } }); } function getStartDate(range: string): Date { const now = new Date(); switch (range) { case '7d': return new Date(now.setDate(now.getDate() - 7)); case '30d': return new Date(now.setDate(now.getDate() - 30)); case '90d': return new Date(now.setDate(now.getDate() - 90)); case 'ytd': return new Date(now.getFullYear(), 0, 1); default: return new Date(now.setDate(now.getDate() - 30)); } } ``` --- ## Best Practices | Aspect | Recommendation | |--------|----------------| | **Attribution** | 30-90 day cookie window | | **Hold Period** | 14-30 days before approval | | **Commission** | 20-50% for digital products | | **Minimum Payout** | $50-100 threshold | | **Fraud Prevention** | IP tracking, refund checks | | **Communication** | Real-time dashboard, email alerts | --- ## Fazit Affiliate & Referral Systems erfordern: 1. **Tracking**: Cookies, Attribution-Windows 2. **Commission**: Faire, klare Berechnung 3. **Payouts**: Automatisiert, zuverlässig 4. **Dashboard**: Transparenz für Partner Gute Partner-Programme wachsen organisch durch zufriedene Affiliates. --- ## Bildprompts 1. "Affiliate dashboard interface, earnings chart and conversion metrics" 2. "Referral flow diagram, invite friend reward visualization" 3. "Commission payout system, money transfer illustration" --- ## Quellen - [Stripe Connect for Marketplaces](https://stripe.com/docs/connect) - [First Promoter](https://firstpromoter.com/) - [Rewardful API](https://www.rewardful.com/) - [Affiliate Marketing Best Practices](https://www.refersion.com/blog/affiliate-marketing-best-practices/)