feat: auto-activation après paiement Wave — plus de page d'attente

- auth_bloc: quand onboardingState==VALIDATED, refresh token et vérifier
  si statutCompte==ACTIF → dashboard direct (pas d'écran d'attente)
- Edge case: si activation backend échouée, fallback vers AwaitingValidationPage
  avec polling 15s
- onboarding_bloc: séparer VALIDATED de AWAITING_VALIDATION dans le switch
This commit is contained in:
dahoud
2026-04-18 08:07:30 +00:00
parent f96ab6e86e
commit 33f5b5a707
2 changed files with 57 additions and 7 deletions

View File

@@ -74,7 +74,7 @@ class AuthAccountNotActive extends AuthState {
/// Compte EN_ATTENTE_VALIDATION — l'OrgAdmin doit compléter l'onboarding. /// Compte EN_ATTENTE_VALIDATION — l'OrgAdmin doit compléter l'onboarding.
/// On ne déconnecte PAS pour permettre les appels API de souscription. /// On ne déconnecte PAS pour permettre les appels API de souscription.
class AuthPendingOnboarding extends AuthState { class AuthPendingOnboarding extends AuthState {
final String onboardingState; // NO_SUBSCRIPTION | AWAITING_PAYMENT | PAYMENT_INITIATED | AWAITING_VALIDATION final String onboardingState; // NO_SUBSCRIPTION | AWAITING_PAYMENT | PAYMENT_INITIATED | AWAITING_VALIDATION | VALIDATED
final String? souscriptionId; final String? souscriptionId;
final String? organisationId; final String? organisationId;
final String? typeOrganisation; final String? typeOrganisation;
@@ -145,6 +145,29 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
if (status != null && status.isPendingOnboarding) { if (status != null && status.isPendingOnboarding) {
// Souscription VALIDATED = paiement confirmé, activation imminente.
// Rafraîchir le token : si le backend a déjà activé le membre,
// les rôles MEMBRE/MEMBRE_ACTIF seront dans le nouveau token → dashboard.
if (status.onboardingState == 'VALIDATED') {
await _authService.refreshToken();
final refreshedStatus = await _authService.getAuthStatus(AppConfig.apiBaseUrl);
if (refreshedStatus != null && refreshedStatus.isActive) {
// Activation effective → procéder au dashboard
final refreshedRawUser = await _authService.getCurrentUser();
final user = await _enrichUserWithOrgContext(refreshedRawUser ?? rawUser);
final permissions = await PermissionEngine.getEffectivePermissions(user);
final token = await _authService.getValidToken();
await DashboardCacheManager.invalidateForRole(user.primaryRole);
emit(AuthAuthenticated(
user: user,
effectiveRole: user.primaryRole,
effectivePermissions: permissions,
accessToken: token ?? '',
));
return;
}
// Activation pas encore effective (edge case) → afficher page d'attente avec polling
}
// OrgAdmin en attente → rediriger vers l'onboarding (sans déconnecter) // OrgAdmin en attente → rediriger vers l'onboarding (sans déconnecter)
final user = await _enrichUserWithOrgContext(rawUser); final user = await _enrichUserWithOrgContext(rawUser);
final orgId = status.organisationId ?? final orgId = status.organisationId ??
@@ -244,6 +267,26 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
if (status != null && status.isPendingOnboarding) { if (status != null && status.isPendingOnboarding) {
// Souscription VALIDATED : vérifier si le backend a finalisé l'activation
if (status.onboardingState == 'VALIDATED') {
await _authService.refreshToken();
final refreshedStatus = await _authService.getAuthStatus(AppConfig.apiBaseUrl);
if (refreshedStatus != null && refreshedStatus.isActive) {
final refreshedRawUser = await _authService.getCurrentUser();
final userActive = await _enrichUserWithOrgContext(refreshedRawUser ?? rawUser);
final permissions = await PermissionEngine.getEffectivePermissions(userActive);
final token = await _authService.getValidToken();
await DashboardCacheManager.invalidateForRole(userActive.primaryRole);
emit(AuthAuthenticated(
user: userActive,
effectiveRole: userActive.primaryRole,
effectivePermissions: permissions,
accessToken: token ?? '',
));
return;
}
// Activation pas encore effective → continuer vers la page d'attente
}
final user = await _enrichUserWithOrgContext(rawUser); final user = await _enrichUserWithOrgContext(rawUser);
final orgId = status.organisationId ?? final orgId = status.organisationId ??
(user.organizationContexts.isNotEmpty (user.organizationContexts.isNotEmpty

View File

@@ -17,7 +17,7 @@ abstract class OnboardingEvent extends Equatable {
/// Démarre le workflow (charge les formules + état courant si souscription existante) /// Démarre le workflow (charge les formules + état courant si souscription existante)
class OnboardingStarted extends OnboardingEvent { class OnboardingStarted extends OnboardingEvent {
final String? existingSouscriptionId; final String? existingSouscriptionId;
final String initialState; // NO_SUBSCRIPTION | AWAITING_PAYMENT | PAYMENT_INITIATED | AWAITING_VALIDATION final String initialState; // NO_SUBSCRIPTION | AWAITING_PAYMENT | PAYMENT_INITIATED | AWAITING_VALIDATION | VALIDATED
final String? typeOrganisation; final String? typeOrganisation;
final String? organisationId; final String? organisationId;
const OnboardingStarted({ const OnboardingStarted({
@@ -155,7 +155,7 @@ class OnboardingPaiementEchoue extends OnboardingState {
List<Object?> get props => [message, souscription, waveLaunchUrl]; List<Object?> get props => [message, souscription, waveLaunchUrl];
} }
/// Étape 5 : en attente de validation SuperAdmin /// Étape 5 : en attente d'activation (paiement reçu, activation en cours)
class OnboardingStepAttente extends OnboardingState { class OnboardingStepAttente extends OnboardingState {
final SouscriptionStatusModel? souscription; final SouscriptionStatusModel? souscription;
const OnboardingStepAttente({this.souscription}); const OnboardingStepAttente({this.souscription});
@@ -233,10 +233,17 @@ class OnboardingBloc extends Bloc<OnboardingEvent, OnboardingState> {
} }
case 'AWAITING_VALIDATION': case 'AWAITING_VALIDATION':
case 'VALIDATED': // Paiement confirmé mais activation compte non encore effective final soscAwait = await _datasource.getMaSouscription();
final sosc = await _datasource.getMaSouscription(); _souscription = soscAwait;
_souscription = sosc; emit(OnboardingStepAttente(souscription: soscAwait));
emit(OnboardingStepAttente(souscription: sosc));
case 'VALIDATED':
// Paiement confirmé, souscription validée, mais activation pas encore effective
// (edge case : auth_bloc a déjà tenté un refresh sans succès).
// Afficher la page d'attente avec polling 15s (AwaitingValidationPage).
final soscValidated = await _datasource.getMaSouscription();
_souscription = soscValidated;
emit(OnboardingStepAttente(souscription: soscValidated));
case 'REJECTED': case 'REJECTED':
final sosc = await _datasource.getMaSouscription(); final sosc = await _datasource.getMaSouscription();