Parrainage
Système de parrainage avec code unique, tracking et hooks pour récompenses.
Chaque utilisateur reçoit automatiquement un code de parrainage unique
(8 caractères alphanumériques lisibles, sans 0/O/1/I/L) à la création
de son compte. Il peut le partager via un lien https://tonsite.com/r/{code} —
quand quelqu'un clique, on pose un cookie ref (httpOnly, 30j) puis on redirige
vers /register. À l'inscription, le nouveau user est lié au parrain via
User.referredById.
Architecture
| Fichier | Rôle |
|---|---|
prisma/schema.prisma | Champs referralCode (unique) et referredById (self-relation) sur User |
lib/referral.ts | Génération du code + cookie name/TTL |
app/r/[code]/route.ts | Landing page : valide le code, pose le cookie, redirige vers /register |
app/api/auth/register/route.ts | Lit le cookie au signup credentials et fill referredById |
lib/auth.ts | events.createUser fait pareil pour les signups OAuth |
app/api/user/referrals/route.ts | API : code + count + liste filleuls |
app/[locale]/(account)/account/referrals/page.tsx | UI : code, lien, partage, stats |
Activer les récompenses
Le boilerplate trace qui parraine qui mais ne distribue rien. C'est à toi de brancher la logique de récompense en fonction de ton modèle.
1. Choisir le déclencheur
| Déclencheur | Quand | Avantages | Risques |
|---|---|---|---|
| Inscription | register/route.ts | Immédiat | Fraud massive (faux comptes) |
| Email vérifié | verify-email/route.ts | Filtre les bots simples | Reste fraude-able |
| Premier paiement | stripe/webhook (customer.subscription.created) | Seulement de "vrais" users — recommandé pour SaaS | Récompense différée |
| Activité (X jours / N actions) | Custom trigger | Filtre les comptes morts | Plus complexe |
2. Choisir la forme de la récompense
// Option A : crédit en cents sur User (appliqué à la prochaine facture)
model User {
// ...
creditCents Int @default(0)
}
// Option B : table dédiée (audit + historique)
model ReferralReward {
id String @id @default(cuid())
userId String // bénéficiaire (parrain ou filleul)
referredId String? // l'user qui a déclenché la récompense
kind String // "credit", "coupon", "free_month"
amount Int // cents ou jours
redeemedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
// Option C : coupon Stripe
// → créer un coupon via Stripe API et stocker l'ID sur User3. Exemple : reward sur premier paiement
// app/api/stripe/webhook/route.ts
case "customer.subscription.created": {
const subscription = event.data.object as Stripe.Subscription;
const user = await prisma.user.findFirst({
where: { subscription: { stripeCustomerId: subscription.customer as string } },
select: { id: true, referredById: true },
});
if (user?.referredById) {
// Anti-self-referral
if (user.referredById === user.id) break;
// Récompense le parrain : 10€ de crédit
await prisma.user.update({
where: { id: user.referredById },
data: { creditCents: { increment: 1000 } },
});
// Optionnel : récompense le filleul aussi (-5€ sur prochaine facture)
await prisma.user.update({
where: { id: user.id },
data: { creditCents: { increment: 500 } },
});
// Log audit + notif
await logAudit({ userId: user.referredById, action: "referral.rewarded" });
await createNotification(user.referredById, {
title: "Tu as gagné 10€ !",
message: "Quelqu'un que tu as parrainé vient de souscrire.",
});
}
break;
}Anti-fraude
À garder en tête quand tu implémentes les récompenses :
- Auto-parrainage :
user.referredById !== user.id - Email vérifié obligatoire avant de récompenser
- Rate-limit signup par IP (Upstash Redis dans
lib/rate-limit.ts) - Détection de patterns : 50 inscriptions depuis la même IP en 1h → flag manuel
- Capping : limite le nombre de filleuls qui rapportent par mois
Désactiver le parrainage
Si tu n'utilises pas la feature, retire simplement le lien dans la nav
(components/layout/user-shell.tsx) et la page /account/referrals. Les
champs referralCode et referredById restent en DB mais n'embêtent personne.