Aller au contenu

Story CP-008: Self-Registration Form + Duplicate Detection

Module: control-plane Slice: Slice 8a from architecture (split from original CP-008) Brand context: Papillon HR Suite (warm, guided registration UX) Priority: 8 Depends on: CP-001, CP-002, CP-003, CP-004, CP-005, CP-006, CP-007 Estimated complexity: M


Objective

Build the self-registration form (Step 1) with company info collection, duplicate detection (email/phone exact match blocking, fuzzy company name flagging), hCaptcha validation, and rate limiting. This is the entry point to the onboarding journey. Creates a tenant with status PENDING_VERIFICATION and sends a verification email. Email verification, trial/payment flow, module selection, and provisioning are in subsequent stories (CP-022, CP-023, CP-024).


Backend Scope

Entities & Records

DTOs:

public record SelfRegistrationRequest(
    @NotBlank @Size(min=2, max=200) String companyName,
    @NotBlank @Size(min=2, max=2) String countryCode,  // "CI" only for MVP
    @NotBlank String employeeCountRange,  // "2-12", "13-29", etc.
    @NotBlank @Size(min=2, max=200) String adminFullName,  // Split at first space → User.first_name + User.last_name
    @NotBlank @Email @Size(max=254) String adminEmail,
    @NotBlank String adminPhone,  // CI format: 10 digits
    @Nullable @Size(max=50) String rccm,
    @NotEmpty List<String> selectedModuleCodes,
    boolean freeTrial,
    @NotBlank String hCaptchaToken
) {}

public record RegistrationResponse(
    UUID tenantId, String message
) {}

Service Layer

OnboardingService.java: - register(SelfRegistrationRequest) → RegistrationResponse: 1. Validate hCaptcha token (external API call) 2. Check rate limit (3 per IP per hour) — counters stored in Caffeine local cache (single instance MVP) or Redis (multi-instance production). Key: rate_limit:register:{ip}, TTL: 1 hour, value: request count. 3. Check duplicate email/phone (exact match → BLOCK) 4. Check fuzzy company name (Levenshtein < 3 after normalization → FLAG for ops, don't block) 5. Create Tenant with status=PENDING_VERIFICATION 6. Create TenantSettings with defaults 7. Store selected modules (not yet activated) 8. Send verification email (FR-CP-002) 9. Return tenantId

DuplicateDetectionService.java: - checkDuplicates(String email, String phone, String companyName) → DuplicateResult - Exact match email → BLOCK - Exact match phone → BLOCK - Fuzzy company name (normalize: lowercase, remove legal suffixes "SARL"/"SA"/"SAS"/"SUARL", trim, Levenshtein distance < 3) → FLAG

API Endpoints

Method Path Request Body Response Auth
POST /api/v1/onboarding/register SelfRegistrationRequest RegistrationResponse Public (hCaptcha)

Validation Rules

  • companyName: min 2, max 200
  • countryCode: must be "CI" (MVP)
  • employeeCountRange: must be one of "2-12", "13-29", "30-49", "50-99", "100-199", "200-350"
  • adminEmail: valid email, unique globally
  • adminPhone: CI format (10 digits), unique globally
  • selectedModuleCodes: at least 1, all must be valid sellable module codes
  • hCaptchaToken: validated against hCaptcha API
  • Rate limit: 3 registrations per IP per hour

Multi-Tenant Considerations

  • Registration is a PRE-tenant operation — no TenantContext exists yet
  • This slice only creates the Tenant row with PENDING_VERIFICATION status; provisioning (CP-024) creates the actual tenant context

Frontend Scope

Pages & Routes

  • /inscription — Registration form (Step 1 only in this slice)

Components

  • RegistrationStep1Page.tsx — Company info form (company name, country, employee count range, admin name, email, phone, RCCM, free trial checkbox)
  • StepIndicator.tsx — Component spec in design §19.1 (shows 1/3 active)

UI States (ALL REQUIRED)

Registration Step 1:

State Behavior French Copy
Loading N/A (static form)
Empty All fields empty except country (pre-filled CI) Title: "Créez votre compte Papillon en 5 minutes"
Validation error Red border + error below field, field shakes (150ms) Per-field error messages (see below)
Duplicate Error message with login link "Un compte existe déjà avec cet e-mail." / "Un compte existe déjà avec ce numéro."
Submitting Button: spinner + "Envoi en cours…", all fields disabled "Envoi en cours…"
Server error Toast, fields remain filled "Une erreur est survenue. Veuillez réessayer."
Rate limited Toast "Trop de tentatives. Veuillez réessayer dans quelques minutes."
Success Redirect to Step 2 or email verification

Responsive Behavior

Registration Step 1: - 360px (mobile): Single column card on warm bg (#FFFDF7). Step indicator at top. Fields stacked vertically. - 1024px+ (desktop): Split layout — left panel (50%): deep teal #0D6B6E with geometric SVG, tagline "Simplifiez la gestion de vos RH", trust badges. Right panel (50%): white card with form.

Interactions

  • Step 1 submit: "Continuer →" validates all fields, triggers hCaptcha, submits to backend. On success, sends verification email and shows confirmation message.
  • Phone auto-format: As user types, format to XX XX XX XX XX. Country code +225 pre-filled and non-editable.
  • Email/phone uniqueness: Checked on submit (server-side). If duplicate → error message with login link.
  • hCaptcha: Invisible challenge first; falls back to visible challenge if suspicious.

French micro-copy (registration):

Key French Text
registration.title "Créez votre compte Papillon en 5 minutes"
registration.subtitle "Simplifiez la gestion de vos ressources humaines"
registration.company_name.label "Nom de l'entreprise"
registration.company_name.placeholder "Ex : Acme SARL"
registration.country.label "Pays"
registration.employee_count.label "Nombre de salariés"
registration.employee_count.placeholder "Sélectionnez une tranche"
registration.admin_name.label "Votre nom complet"
registration.admin_name.placeholder "Ex : Moussa Koné"
registration.email.label "Adresse e-mail"
registration.email.placeholder "[email protected]"
registration.phone.label "Numéro de téléphone"
registration.phone.placeholder "07 12 34 56 78"
registration.rccm.label "RCCM (optionnel)"
registration.rccm.helper "Vous pourrez le fournir plus tard"
registration.trial.label "Démarrer un essai gratuit de 14 jours"
registration.submit "Continuer →"
registration.already_registered "Déjà inscrit ?"
registration.login_link "Se connecter"
registration.duplicate_email "Un compte existe déjà avec cet e-mail."
registration.duplicate_phone "Un compte existe déjà avec ce numéro."
registration.rate_limited "Trop de tentatives. Veuillez réessayer dans quelques minutes."
registration.server_error "Une erreur est survenue. Veuillez réessayer."
verification.email_sent "Un e-mail de vérification a été envoyé à {email}"
verification.check_inbox "Vérifiez votre boîte de réception (et les spams)"

Acceptance Criteria

AC-061: Successful self-registration
Given I am on the registration page
When I fill all required fields, pass hCaptcha, and submit
Then a tenant is created with status PENDING_VERIFICATION
And I receive a verification email within 60 seconds

AC-064: Duplicate email blocks registration
Given email "[email protected]" is already registered
When I try to register with that email
Then I see "Un compte existe déjà avec cet e-mail."

AC-065: Fuzzy company name flags for ops
Given a tenant "Acme SARL" already exists
When I register "ACME Sarl" (Levenshtein distance < 3 after normalization)
Then registration proceeds (NOT blocked)
And an alert is created for ALTARYS ops in admin console

AC-066: Rate limiting
Given 3 registrations have been attempted from my IP in the last hour
When I try to register again
Then I see "Trop de tentatives. Veuillez réessayer dans quelques minutes."

OHADA & Regulatory Rules

  • Loi 2013-450: Registration collects personal data (name, email, phone). Consent is implied by form submission. CGV must include data processing clause (legal/operational, not code).
  • RCCM optionality: RCCM is not mandatory at registration (many small businesses don't have it yet). It can be provided later. But it's needed for FNE-compliant invoicing (see CP-009).
  • hCaptcha: Privacy-focused alternative to Google reCAPTCHA. Important for CI data protection compliance.

Standards & Conventions

  • docs/standards/java-spring-guidelines.md — §External API calls (hCaptcha), §Application Events
  • docs/standards/api-guidelines.md — §Public endpoints, §Rate limiting, §Error responses
  • docs/standards/react-typescript-guidelines.md — §Multi-step forms, §StepIndicator, §Public pages
  • docs/standards/offline-sync-guidelines.md — N/A (registration always online)

Testing Requirements

Unit Tests

  • DuplicateDetectionService: exact email match, exact phone match, fuzzy name match
  • DuplicateDetectionService: normalization (remove SARL/SA/SAS, lowercase, trim)
  • OnboardingService.register: hCaptcha validation, rate limit check, duplicate check, tenant creation
  • Validation: all field constraints (companyName min/max, countryCode "CI" only, phone format, email format)

Integration Tests

  • Full registration flow: POST /api/v1/onboarding/register → tenant created with PENDING_VERIFICATION
  • Duplicate detection: email, phone, fuzzy name
  • Rate limiting: 4th attempt in 1 hour → 429
  • hCaptcha token validation (mocked external API)

What QA Will Validate

  • Registration form: all fields, validation messages, phone auto-format
  • Step indicator shows progress (1/3 active)
  • Desktop split layout (branding left, form right)
  • Mobile single-column layout
  • hCaptcha appears on submit
  • Trust badges visible on desktop ("Conforme OHADA", "Donnees securisees", "Support reactif")
  • Duplicate email/phone shows correct error messages with login link

Out of Scope

  • Email verification + trial/payment flow — CP-022
  • Module selection + pack detection UI — CP-023
  • Provisioning pipeline — CP-024
  • Payment flow (Step 3 when not using free trial) — that's CP-009
  • Channel B (CRM) onboarding — that's CP-013
  • Channel C (Admin back-office) onboarding — V2
  • Password creation flow — Keycloak handles this
  • Post-registration dashboard (welcome celebration) — Host shell feature

Definition of Done

  • [ ] All backend endpoints implemented and returning correct responses
  • [ ] All frontend pages render correctly at 360px, 768px, 1024px
  • [ ] All 5 UI states implemented (loading, empty, error, offline, success)
  • [ ] French micro-copy matches design spec exactly
  • [ ] All acceptance criteria pass manually
  • [ ] Unit tests written and passing
  • [ ] Integration tests written and passing
  • [ ] Multi-tenant isolation verified (new tenant's data is isolated)
  • [ ] Offline write+sync works (Papillon) — N/A (registration always online)
  • [ ] No TypeScript any — all types explicit
  • [ ] No Java raw types — all generics explicit
  • [ ] API responses follow standard envelope format
  • [ ] Audit trail entries created for every data mutation