---
title: "Parrainage"
description: "Chaque utilisateur reçoit, au moment de son inscription, un code de parrainage unique qu'il pourra communiquer à de futurs inscrits. Lorsqu'un nouvel inscr…"
---

Chaque utilisateur reçoit, **au moment de son inscription**, un code de parrainage unique qu'il pourra communiquer à de futurs inscrits. Lorsqu'un nouvel inscrit fournit ce code dans son `POST /api/auth/register` (champ optionnel `sponsorshipCode`), une relation parrain → filleul est enregistrée. Deux compteurs sont maintenus côté parrain :

- `numUserTotal` — nombre total de filleuls associés. Incrémenté **à l'inscription du filleul** (au moment où le code est validé et l'edge `sponsorship_user` insérée).
- `numUserValid` — nombre de filleuls ayant **confirmé leur adresse e-mail**. Incrémenté au moment du clic sur le lien de confirmation.

L'invariant côté base est `numUserValid <= numUserTotal`. L'écart entre les deux représente les filleuls inscrits mais qui n'ont pas (encore) activé leur compte.

## Notification au parrain

À chaque association d'un filleul, un e-mail est envoyé au parrain pour l'informer de l'inscription (template [`templates/mails/sponsorship_referred_signup.twig`](../templates/mails/sponsorship_referred_signup.twig), traductions dans `resources/lang/<locale>/mails.php` sous la clé `sponsorshipReferredSignup`). Le mail est rendu dans la locale du **parrain** (et non celle de la requête HTTP du filleul). L'échec d'envoi est silencieusement absorbé côté `RegistrationService` : la notif est informationnelle, elle ne doit jamais faire échouer l'inscription du filleul.

## Format du code

- **Longueur actuelle** : 6 caractères.
- **Alphabet** : Crockford Base32 — `0-9` + `A-Z` sans les lettres ambiguës `I`, `L`, `O`, `U`. Cela rend le code facile à dicter oralement et illisible-de-travers (pas de confusion `0`/`O` ou `1`/`I`/`L`).
- **Casse** : le serveur normalise en majuscules ; côté client la saisie est case-insensitive.
- **Codespace** : 32⁶ ≈ 1,07 × 10⁹ combinaisons — largement suffisant pour la base actuelle, avec possibilité d'allonger la longueur plus tard sans casser les codes existants (la longueur n'est imposée qu'à la **génération**, la validation accepte 6 caractères pour le moment et sera assouplie si besoin).
- **Génération** : tirages CSPRNG via `random_int` caractère par caractère ; en cas de collision sur l'index unique (très improbable mais possible), la transaction d'inscription retente jusqu'à 5 fois avant d'échouer.

Voir [SponsorshipCode](../src/Domain/Sponsorship/SponsorshipCode.php) pour les détails d'implémentation.

## Validation à l'inscription

Lorsque `sponsorshipCode` est fourni dans `POST /api/auth/register` :

- Si le format ne correspond pas à l'alphabet/longueur attendus → erreur `422` avec le code `sponsorship.codeInvalid` (pointeur `/data/attributes/sponsorshipCode`).
- Si le format est valide mais qu'aucun utilisateur ne porte ce code → erreur `422` avec le code `sponsorship.codeNotFound`.
- Dans les deux cas, **aucun utilisateur n'est créé** : la résolution du code se fait avant l'insertion pour éviter les comptes fantômes.
- Si le code résout vers l'utilisateur en train de s'inscrire (cas théorique impossible puisque le compte n'existe pas encore, mais protégé en interne), l'association est silencieusement ignorée — on ne s'auto-parraine pas.

Quand la résolution réussit, l'inscription provisionne dans une seule transaction logique :
1. La ligne `user`.
2. La ligne `sponsorship` (code unique du nouvel inscrit).
3. La ligne `sponsorship_stats` initialisée à zéro.
4. La ligne `sponsorship_user` reliant le filleul au parrain.

## Incrémentation du compteur `numUserValid`

Le compteur est incrémenté **atomiquement** au moment de la confirmation d'e-mail du filleul ([EmailConfirmationService](../src/Domain/Auth/EmailConfirmationService.php)). L'idempotence repose sur le fait que les tokens de confirmation sont à usage unique : un même filleul ne peut donc déclencher l'incrément qu'une fois. Si le filleul n'a pas de parrain associé, l'opération est un no-op.

---

## `GET /api/users/me/sponsorship`

Retourne le code de parrainage du viewer authentifié et ses statistiques courantes.

- **Auth** : requise (Bearer token).
- **Réponse** :
  - `200 OK` — l'utilisateur dispose bien d'une ligne `sponsorship` (cas standard).
  - `404` avec le code interne `Sponsorship not provisioned` — l'utilisateur a été créé **avant** l'introduction du parrainage et n'a jamais été provisionné. Le client doit afficher un message "fonctionnalité indisponible pour ce compte" plutôt que tenter de générer un code à la volée (un `GET` ne doit pas écrire en base).

### Exemple de réponse

```json
{
  "data": {
    "type": "sponsorships",
    "id": "5f6e2d4a-1b8c-4d2e-9a3b-7c1d8e9f0a2b",
    "attributes": {
      "code": "A7K3M9",
      "numUserTotal": 7,
      "numUserValid": 4,
      "createdAt": "2026-06-10T14:23:11+00:00"
    }
  }
}
```

[GetMySponsorshipAction](../src/Http/Action/Api/Users/GetMySponsorshipAction.php).

---
