Aller au contenu

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 tokens
  • docs/standards/api-guidelines.md — §Endpoints publics, §Réponses d'erreur
  • docs/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 any en 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