Cas CP-022 : Vérification par e-mail + Flux d'essai/de paiement¶
Module
: plan de contrôle Tranche
: tranche 8b de l'architecture (dissociée de CP-008) Contexte de marque
: Papillon HR Suite (expérience utilisateur d'inscription conviviale et guidée) Priorité
: 8 (après CP-008) Dépend de
: CP-008 Complexité estimée
: M
Objectif¶
Mettre en place le flux de vérification par e-mail qui valide le token d'inscription et fait passer le tenant au statut TRIAL (essai) ou PAYMENT_REQUIRED (paiement requis). Cela inclut la gestion de l'expiration du token (24 heures), le renvoi de la vérification avec limitation du taux (max. 3 par 24 heures) et la page frontale EmailVerificationPage affichant les 4 états du token. Une fois la vérification réussie, déclenche le pipeline de provisionnement (CP-024).
Portée du backend¶
Entités et¶
DTO
d'enregistrements
:
public record VerifyEmailRequest(String token) {}
public record VerifyEmailResponse(
String status, // "VERIFIED", "EXPIRED", "INVALID", "ALREADY_VERIFIED"
@Nullable String redirectUrl
) {}
public record ResendVerificationRequest(String email) {}
```###
**Entity EmailVerificationToken**
de la couche de repository
**(base de données de la plateforme) :**
| Field | Type | Constraints |
|-------|------|-------------|
| id | UUID v7 | PK |
| tenant_id | UUID | NOT NULL, FK to Tenant |
| token | String | NOT NULL, unique |
| email | String | NOT NULL |
| expires_at | Instant | NOT NULL (24 hours after creation) |
| is_used | boolean | NOT NULL, default false |
| used_at | Instant | Nullable |
| created_date | Instant | NOT NULL |
- `EmailVerificationTokenRepository.java` — Portée : plateforme :
- `findByToken(String) → Optional<EmailVerificationToken>`
- `findByEmailAndIsUsedFalse(String) → List<EmailVerificationToken>`
- `countByEmailAndCreatedDateAfter(String, Instant) → int` — pour la limitation du taux de réenvoi
- `save(EmailVerificationToken) → EmailVerificationToken`
### Couche de service
**OnboardingService.java (extension de CP-008) :**
- `verifyEmail(VerifyEmailRequest) → VerifyEmailResponse` :
1. Valider le token (à usage unique, expire au bout de 24 heures)
2. SI valide + freeTrial → transition vers TRIAL, publier l'événement `TenantStatusChanged` (déclenche le provisionnement dans CP-024)
3. SI valide + pas de freeTrial → transition vers PAYMENT_REQUIRED, publier l'événement `TenantStatusChanged` (déclenche le provisionnement dans CP-024)
4. SI expiré → renvoyer « EXPIRED » avec option de renvoi
5. SI invalide → renvoie « INVALID »
6. SI déjà vérifié → renvoie « ALREADY_VERIFIED »
- `resendVerification(ResendVerificationRequest) → void` :
- Max. 3 renvois par 24 heures par e-mail
- Génère un nouveau token, invalide l'ancien
- Envoie un nouvel e-mail de vérification
| Method | Path | Request Body | Response | Auth |
|--------|------|-------------|----------|------|
| POST | /api/v1/onboarding/verify-email | VerifyEmailRequest | VerifyEmailResponse | Public |
| POST | /api/v1/onboarding/resend-verification | ResendVerificationRequest | 204 | Public |
### Règles de validation
- `token` : ne doit pas être vide, doit correspondre à un enregistrement en attente de vérification
- `email` (pour le renvoi) : adresse e-mail valide, doit correspondre à un tenant PENDING_VERIFICATION existant
- Limite de renvoi : 3 au maximum par 24 heures et par adresse e-mail
### Considérations relatives à l'environnement multi-tenant
- La vérification est une opération préalable à la création du tenant — aucun TenantContext n'existe encore
- La recherche du token s'effectue par valeur de token (unique au niveau global), et non au niveau du tenant
- La transition de statut déclenche le provisionnement qui crée le contexte de tenant réel
---
## Portée du frontend
### Pages et routes
- `/verification` — Page d'accueil de vérification d'adresse e-mail (reçoit le paramètre de requête `?token=...` provenant du lien dans l'e-mail)
### Composants
- `EmailVerificationPage.tsx` — Affichage du résultat de la validation du token avec 4 états
### États de l'interface utilisateur (TOUS OBLIGATOIRES)
**Vérification de l'adresse e-mail :**
| State | Behavior | French Copy |
|-------|----------|-------------|
| Loading | Spinner + checking message | "Verification en cours..." |
| Valid | Green checkmark + auto-redirect (2s) | "Adresse e-mail confirmee !" |
| Expired | Message + resend button | "Ce lien a expire." + "Renvoyer un e-mail de verification" |
| Invalid | Message + link to registration | "Ce lien n'est pas valide." + "Retour a l'inscription" |
| Already verified | Message + login link | "Votre adresse est deja confirmee." + "Se connecter" |
| Resend success | Toast | "E-mail de verification renvoye !" |
| Resend limit reached | Toast | "Nombre maximum de renvois atteint. Veuillez reessayer dans 24 heures." |
### Comportement adaptatif
- **360px (mobile)**
: Carte centrée sur un fond chaud (#FFFDF7). Icône + message + bouton d'action empilés verticalement.
- **1024px+ (ordinateur de bureau)**
: Même mise en page avec carte centrée. Largeur maximale de 480px.
### Interactions
- La page appelle automatiquement `POST /api/v1/onboarding/verify-email` avec le token de l'URL lors du chargement
- Valide → délai de 2 secondes → redirection automatique vers la création de mot de passe (Keycloak) ou la page de paiement
- Expiré → le bouton « Renvoyer » appelle `POST /api/v1/onboarding/resend-verification`
- Non valide → le lien « Retour à l'inscription » redirige vers `/inscription`
- Déjà vérifié → le lien « Se connecter » redirige vers la page de connexion Keycloak
**Micro-copie en français :**
| Key | French Text |
|-----|------------|
| `verification.loading` | "Verification en cours..." |
| `verification.success` | "Adresse e-mail confirmee !" |
| `verification.expired` | "Ce lien a expire." |
| `verification.resend` | "Renvoyer un e-mail de verification" |
| `verification.resend_success` | "E-mail de verification renvoye !" |
| `verification.resend_limit` | "Nombre maximum de renvois atteint. Veuillez reessayer dans 24 heures." |
| `verification.invalid` | "Ce lien n'est pas valide." |
| `verification.back_to_register` | "Retour a l'inscription" |
| `verification.already_done` | "Votre adresse est deja confirmee." |
| `verification.login` | "Se connecter" |
---
## Critères d'acceptation
```gherkin
AC-062: Free trial activation
Given I checked "Demarrer un essai gratuit de 14 jours" during registration
When I click the email verification link
Then my tenant status is TRIAL
And all selected modules are active
And I am redirected to password creation -> Dashboard
AC-063: Registration without trial
Given I did NOT check free trial
When I click the email verification link
Then my tenant status is PAYMENT_REQUIRED
And modules are read-only
And I am redirected to payment choice page (Step 3)
AC-067: Email verification token expires
Given I received a verification email
When I click the link after 24 hours
Then I see "Ce lien a expire." with a "Renvoyer" button
AC-068: Resend verification limit
Given I have already resent the verification email 3 times in 24 hours
When I try to resend again
Then the request is rejected
AC-069: Email verification token single-use enforcement
Given I received a verification email with a valid token
When I click the link and verify my email successfully
Then the token is marked as used (used_at set to current time)
When I try to click the same link again
Then I receive status "ALREADY_VERIFIED"
And I am shown "Votre adresse est deja confirmee." with a login link
OHADA et règles réglementaires¶
- Loi n° 2013-450 : la vérification de l'adresse e-mail fait partie du processus de consentement. L'utilisateur doit vérifier son adresse e-mail avant d'accéder à la plateforme.
- Aucune exigence spécifique de l'OHADA concernant la vérification de l'adresse e-mail.
Normes et conventions¶
docs/standards/java-spring-guidelines.md— §Événements d'application (TenantStatusChanged), §Gestion des tokensdocs/standards/api-guidelines.md— §Endpoints publics, §Réponses d'erreurdocs/standards/react-typescript-guidelines.md— §Pages publiques, §Modèle de redirection automatique- OnboardingService.verifyEmail : token expiré → réponse EXPIRED
- OnboardingService.verifyEmail : token invalide → réponse INVALID
- OnboardingService.verifyEmail : déjà vérifié → réponse ALREADY_VERIFIED
- OnboardingService.resendVerification : succès dans la limite
- OnboardingService.resendVerification : rejeté à la limite (4e tentative)
Tests d'intégration¶
- Flux complet : vérification de l'adresse e-mail avec un token valide → statut du tenant mis à jour
- Expiration du token : vérification après 24 heures → EXPIRED
- Renvoi : 3 renvois réussis, 4e rejeté
- Événement TenantStatusChanged publié après une vérification réussie
Ce que l'assurance qualité va valider¶
- Page de vérification de l'adresse e-mail : les 4 états s'affichent correctement
- Redirection automatique après une vérification réussie
- Le bouton de renvoi fonctionne et affiche une notification de réussite/limite
- Mises en page mobile et bureau
Hors du périmètre¶
- Formulaire d'inscription (Étape 1) — CP-008
- Sélection du module (Étape 2) — CP-023
- Pipeline de provisionnement — CP-024 (déclenché par l'événement TenantStatusChanged de cette tranche)
- Flux de paiement — CP-009
- Création de mot de passe — Keycloak s'en charge
Définition de « Terminé »¶
- [ ] Tous les endpoints backend sont implémentés et renvoient des réponses correctes
- [ ] Toutes les pages frontend s'affichent correctement à 360px, 768px et 1024px
- [ ] Les 5 états de l'interface utilisateur sont implémentés (chargement, vide, erreur, hors ligne, succès)
- [ ] Les micro-textes en français correspondent exactement au cahier des charges
- [ ] Tous les critères d'acceptation sont validés manuellement
- [ ] Les tests unitaires sont écrits et réussis
- [ ] Les tests d'intégration sont écrits et réussis
- [ ] Isolation multi-tenant vérifiée — N/A (opération pré-locataire)
- [ ] L'écriture et la synchronisation hors ligne fonctionnent — N/A (vérification toujours en ligne)
- [ ] Pas de
anyen TypeScript — tous les types sont explicites - [ ] Pas de types bruts en Java — tous les génériques sont explicites
- [ ] Les réponses API respectent le format d'enveloppe standard
- [ ] Des entrées de piste d'audit sont créées pour chaque mutation de données