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:
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user