Cash-Out Intent Flow
Vue d'ensemble
┌─────────────────────────────────────────────────────────────────────┐
│ POINTS D'ENTRÉE │
├──────────────────────────┬──────────────────────────────────────────┤
│ Mobile Pro (Agent) │ CMMS Simulateur (Admin) │
│ Bearer token agent │ agentPhone + clientPhone │
│ QR code client │ Pas de QR, pas de token agent │
└──────────┬───────────────┴──────────────┬───────────────────────────┘
│ │
└──────────┬───────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ ORCHESTRATOR — IntentsController │
│ │
│ resolveAgent(): │
│ agentPhone fourni? → customerService.findByPhone → authService │
│ sinon → @CurrentUser() du token │
│ │
│ resolveAgentFromIntent(): │
│ admin (role admin/operator)? → intent.initiatorUserId │
│ sinon → user.userId (le caller) │
└─────────────────────────────┬───────────────────────────────────────┘
│
▼Flow Step-by-Step
┌──────────────────┐
│ POST /intents │
│ Créer l'intent │
└────────┬─────────┘
│
┌────────▼─────────┐
│ CreateIntentUseCase│
│ │
│ 1. Résoudre le │
│ client (phone │
│ ou QR token) │
│ 2. Risk engine │
│ evaluate() │
│ 3. Créer intent │
│ en DB (ledger)│
│ 4. Status: │
│ CREATED │
└────────┬─────────┘
│
┌────────▼──────────────┐
│ POST /intents/:id/ │
│ send-otp │
└────────┬──────────────┘
│
┌────────▼─────────┐
│ SendOtpUseCase │
│ │
│ 1. Auth: générer │
│ OTP aléatoire │
│ (bcrypt hash) │
│ 2. Auth → Notif: │
│ envoyer OTP │
│ 3. Status: │
│ OTP_SENT │
└────────┬─────────┘
│
┌────────────────▼────────────────┐
│ NOTIFICATIONS SERVICE │
│ │
│ OtpRoutingService: │
│ Canal: préf client → config │
│ système → WHATSAPP │
│ │
│ Production: │
│ → Envoyer au client │
│ │
│ Non-production: │
│ Whitelisté? → Envoyer │
│ Non whitelisté? → Skip │
│ + Copie email (Brevo) │
│ + Copie WhatsApp (Tambila) │
└─────────────────────────────────┘
│
┌────────▼──────────────┐
│ POST /intents/:id/ │
│ verify-otp │
└────────┬──────────────┘
│
┌────────▼─────────┐
│ VerifyOtpUseCase │
│ │
│ 1. Auth: verify │
│ OTP (bcrypt │
│ compare) │
│ 2. Status: │
│ OTP_VERIFIED │
└────────┬─────────┘
│
┌────────▼──────────────┐
│ POST /intents/:id/ │
│ verify-pin │
└────────┬──────────────┘
│
┌────────▼─────────┐
│ VerifyPinUseCase │
│ │
│ Token fourni? │
│ → verifyPin │
│ (token, pin) │
│ │
│ Pas de token? │
│ → verifyPinBy │
│ UserId │
│ (userId, pin) │
│ │
│ 3 tentatives max │
│ Status: │
│ PIN_VERIFIED │
└────────┬─────────┘
│
┌────────▼──────────────┐
│ POST /intents/:id/ │
│ confirm │
└────────┬──────────────┘
│
┌────────▼──────────────────┐
│ ConfirmIntentUseCase │
│ │
│ 1. Résoudre comptes │
│ (agent wallet, │
│ client wallet) │
│ 2. Vérifier solde client │
│ 3. Risk engine confirm │
│ 4. → PROCESSING │
│ 5. Créer transaction P2P │
│ (client → agent) │
│ 6. → SUCCEEDED │
│ 7. Notification client │
│ 8. Commission agent │
└────────┬──────────────────┘
│
▼
┌──────────────────┐
│ RÉSULTAT │
│ │
│ transactionId │
│ referenceId │
│ amount + fees │
│ status: SUCCEEDED│
└──────────────────┘State Machine
┌──────────────┐
│ CREATED │
└──────┬───────┘
│
┌──────▼───────┐
┌───────│ OTP_SENT │◄──┐ (resend max 3x)
│ └──────┬───────┘───┘
│ │
│ ┌──────▼───────┐
│ │ OTP_VERIFIED │
│ └──────┬───────┘
│ │
│ ┌──────▼───────┐
│ │ PIN_VERIFIED │
│ └──────┬───────┘
│ │
│ ┌──────▼───────┐
│ │ PROCESSING │
│ └───┬──────┬───┘
│ │ │
┌──────▼───┐ ┌────▼──┐ ┌─▼─────┐
│ EXPIRED │ │SUCCEED│ │FAILED │
└──────────┘ └───────┘ └───────┘
┌──────────┐
│ CANCELED │ ← depuis CREATED, OTP_SENT, OTP_VERIFIED
└──────────┘Services impliqués
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
│ CMMS / App │────▶│ Orchestrator│────▶│ Ledger-Wallets │
│ (Frontend) │ │ (Stateless)│ │ (Intent + TX) │
└─────────────┘ └──────┬──────┘ └─────────────────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼────┐ ┌────▼─────┐ ┌────▼──────┐
│ Auth │ │ Customer │ │Risk Engine│
│ (PIN/OTP)│ │ (Profils)│ │ (Limites) │
└─────┬────┘ └──────────┘ └───────────┘
│
┌─────▼────────────┐
│ Notifications │
│ (WhatsApp/SMS/ │
│ Email) │
└──────────────────┘