Flux de données
# Bénéficiaire app/wallet → GET /api/v1/vouchers/me → PostgreSQL (table vouchers) # Validation QR commerçant /pos/scan → POST /api/v1/vouchers/validate → SELECT FOR UPDATE (atomique) → UPDATE vouchers (remainingValue, status) → INSERT voucher_transactions (displayRef, amount, commission) → INSERT ledger_entries ×2 (VOUCHER_LIABILITY → MERCHANT_PAYABLE) # Reversement T+1 Cron 23h00 UTC → Bull Queue → Wave API / Orange Money API → INSERT merchant_settlements + ledger_entries (SETTLE)
http://localhost:3001http://localhost:3000localhost:5432 · database : kadolocalhost:6379http://localhost:5555 (via npx prisma studio)https://kadoapi-production.up.railway.apphttps://kado.sn · kado.app · kado.africarailway variablesmain déclenche un redéploiement automatique Railwayprisma migrate deploy avant nest build. Les migrations sont appliquées à chaque déploiement.Workflow standard
# 1. Développer en local npm run dev # API (3001) + Web (3000) en parallèle # 2. Vérifier les types et le lint npm run typecheck && npm run lint # 3. Créer une nouvelle migration si le schéma a changé npx prisma migrate dev --name "description_courte" # ⚠ Créer aussi prisma/migrations/{nom}/down.sql immédiatement # 4. Commit et push → Railway redéploie automatiquement git add . && git commit -m "feat: ..." git push origin main # 5. Suivre les logs de déploiement railway logs --tail
migration.sql doit être accompagné d'un down.sql dans le même dossier, créé au moment de la migration.| Migration | Description | Down disponible |
|---|---|---|
20260430_add_vendor_and_display_ref | Table vendors, colonnes displayRef/vendorId sur voucher_transactions | Oui |
20260426_add_merchant_apikey_prefix | Colonne apiKeyPrefix sur merchants | Oui |
20260425_add_wa_session | Table wa_sessions (session Baileys WhatsApp) | Oui |
20260422_add_merchant_apikey_and_payrequest_ecommerce | apiKey merchant + champs orderId/callbackUrl/description sur PayRequest | Oui |
20260420_confirm_pending_vouchers | Migration neutralisée (no-op) | Oui |
20260418_recharge_settlement_status | Enum SettlementStatus, champs de suivi sur merchant_settlements | Oui |
20260416_subscription_management | Enum SubscriptionStatus, table saas_payments, abonnement sur Company | Oui |
20260414_add_pay_requests_and_pin | Table pay_requests, colonne pinHash sur User | À créer |
20260325_add_company_payment_treasury | Table company_payments, trésorerie | À créer |
20260316_init | Schéma initial complet | Non requis |
Convention down.sql
-- Exemple : prisma/migrations/20260430_xxx/down.sql -- Toujours dans l'ordre inverse du up.sql : -- 1. Supprimer les contraintes étrangères ALTER TABLE "voucher_transactions" DROP CONSTRAINT IF EXISTS "voucher_transactions_vendorId_fkey"; -- 2. Supprimer les index DROP INDEX IF EXISTS "voucher_transactions_merchantId_createdAt_idx"; -- 3. Supprimer les colonnes ALTER TABLE "voucher_transactions" DROP COLUMN IF EXISTS "vendorId"; -- 4. Supprimer les tables DROP TABLE IF EXISTS "vendors";
| Compte | Type | Description |
|---|---|---|
PROVISION_COMPANY:{id} | Passif | Fonds prépayés par l'entreprise |
VOUCHER_LIABILITY:{id} | Passif | Engagement envers le bénéficiaire |
MERCHANT_PAYABLE:{id} | Passif | Montant dû au commerçant |
REVENUE_COMMISSION | Produit | Commission kado (5%) |
REVENUE_SAAS | Produit | Abonnements SaaS |
MERCHANT_SETTLED:{id} | Actif | Montant versé au commerçant |
EXPIRED_FORFEIT | Produit | Solde bons expirés (forfait) |
Écritures types
| Opération | Débit | Crédit |
|---|---|---|
| ISSUE Émission bon | PROVISION_COMPANY:{cId} | VOUCHER_LIABILITY:{vId} |
| REDEEM Validation QR | VOUCHER_LIABILITY:{vId} | MERCHANT_PAYABLE:{mId} (net) + REVENUE_COMMISSION (5%) |
| EXPIRE Expiration | VOUCHER_LIABILITY:{vId} | EXPIRED_FORFEIT |
| CANCEL Annulation RH | VOUCHER_LIABILITY:{vId} | PROVISION_COMPANY:{cId} |
| SETTLE Reversement | MERCHANT_PAYABLE:{mId} | MERCHANT_SETTLED:{mId} |
psql et railway CLI installés et être authentifié : railway loginCommandes
# Rollback complet (code + DB) — interactif npm run rollback # Rollback base de données uniquement npm run rollback:db # Rollback code uniquement (sans toucher à la DB) npm run rollback:code # Ou directement : bash scripts/rollback.sh bash scripts/rollback.sh --db-only bash scripts/rollback.sh --code-only
Ce que fait le script
migration.sql modifiés entre le commit actuel et la cible. Liste les down.sql à exécuter dans l'ordre inverse.down.sql via psql sur la base Railway. Supprime les entrées correspondantes dans _prisma_migrations pour que Prisma ne considère plus ces migrations comme appliquées.reset --hard ou push --force.git push origin main déclenche le pipeline Railway. Le build ré-exécute prisma migrate deploy (déjà cohérent avec le down.sql exécuté).ledger_entries) sont INSERT ONLY. Un rollback DB ne supprime PAS les écritures comptables passées. Pour une correction, créer une écriture corrective.# ── Développement local ────────────────────────────────────── npm run dev # API + Web en parallèle docker-compose up -d # PostgreSQL + Redis npx prisma studio # Interface graphique DB npx prisma migrate dev # Nouvelle migration locale npx prisma db seed # Fixtures de test # ── Tests ──────────────────────────────────────────────────── npm run test:unit # Vitest avec coverage npm run test:e2e # Playwright E2E npm run test:load # k6 — 100 validations simultanées # ── Production Railway ─────────────────────────────────────── railway logs --tail # Logs en temps réel railway variables # Variables d'environnement railway run npx prisma migrate deploy # Migration manuelle en prod # ── Rollback ───────────────────────────────────────────────── npm run rollback # Rollback interactif (code + DB) npm run rollback:db # DB uniquement npm run rollback:code # Code uniquement # ── Génération clés JWT RS256 ──────────────────────────────── openssl genrsa -out private.pem 4096 openssl rsa -in private.pem -pubout -out public.pem openssl rand -hex 32 # HMAC_VOUCHER_SECRET
Erreurs bons (vouchers)
| Code | HTTP | Signification | Cause probable | Recours |
|---|---|---|---|---|
VOUCHER_EXPIRED |
409 | Bon expiré | La date expiresAt est dépassée (validité J+180 par défaut) |
Contacter le service RH de l'entreprise émettrice pour réémission. Vérifier le cron d'expiration (00h01 UTC). |
VOUCHER_ALREADY_USED |
409 | Bon épuisé | Le statut est USED — solde à 0 |
Vérifier l'historique des transactions sur ce bon. Si erreur, contacter support. |
INSUFFICIENT_BALANCE |
409 | Solde insuffisant | Le montant demandé dépasse remainingValue |
Réduire le montant de la transaction. Afficher le solde restant au bénéficiaire (API lookup). |
QR_INVALID |
400 | Signature HMAC incorrecte | QR scanné corrompu, falsifié, ou HMAC_VOUCHER_SECRET différent entre émission et validation |
Vérifier que HMAC_VOUCHER_SECRET est identique sur tous les services. Ne jamais régénérer cette clé en production sans re-signer tous les bons. |
VOUCHER_NOT_FOUND |
404 | Bon introuvable | ID/code incorrect, ou bon supprimé (ne devrait pas arriver) | Vérifier l'UUID dans la base. Confirmer que le QR n'est pas tronqué. |
TYPE_NOT_ALLOWED |
409 | Type bon incompatible commerçant | Bon de type MEAL_TICKET présenté chez un commerçant GENERAL |
Vérifier la catégorie du commerçant et le type du bon. Configurer le commerçant dans l'admin. |
VOUCHER_NOT_YOURS |
409 | Bon n'appartient pas à ce bénéficiaire | Le beneficiaryPhone ou beneficiaryId ne correspond pas |
IDOR — vérifier les guards. Le bénéficiaire doit se connecter avec le numéro associé au bon. |
Erreurs provisions & émission
| Code | HTTP | Signification | Cause probable | Recours |
|---|---|---|---|---|
INSUFFICIENT_PROVISION |
409 | Provision entreprise épuisée | Le solde Company.provisionBalance est inférieur au montant total du lot |
Demander à l'entreprise de recharger sa provision via Wave ou Orange Money. Vérifier dans l'admin dashboard. |
LIMIT_EXCEEDED |
422 | Plafond légal IRPP dépassé | Le montant des bons dépasse le plafond légal de déductibilité fiscale sénégalais | Fractionner l'émission. Contacter le service juridique de l'entreprise. |
SUBSCRIPTION_EXPIRED |
402 | Abonnement SaaS expiré | Le subscriptionStatus est OVERDUE ou SUSPENDED |
Régulariser le paiement SaaS dans l'admin kado. Mettre à jour subscriptionPaidUntil. |
Erreurs transactions & paiements
| Code | HTTP | Signification | Cause probable | Recours |
|---|---|---|---|---|
DUPLICATE_TRANSACTION |
409 | Doublon détecté (idempotence) | La référence UUID de la transaction existe déjà dans ledger_entries |
Rejouer avec un nouvel UUID de référence. Vérifier si la transaction initiale a abouti avant de rejouer. |
TXR_EXPIRED |
404 | Transaction request expirée | Le token Redis txreq:{token} a expiré (TTL 180s) |
Le commerçant doit créer une nouvelle transaction request. Afficher un message d'expiration côté bénéficiaire. |
TXR_ALREADY_USED |
409 | Transaction déjà traitée | Le statut Redis est paid — double soumission |
Le paiement a déjà été effectué. Afficher la confirmation. Ne pas rejouer. |
CONFIRM_EXPIRED |
409 | Session de confirmation expirée | Le confirmationId Redis a expiré (TTL 300s) |
Recommencer le flux depuis POST /vouchers/initiate-pay. |
CONFIRM_INVALID |
409 | Mot de passe incorrect | Le bénéficiaire a saisi un mauvais mot de passe kado | Permettre 3 tentatives puis bloquer 5 min. Proposer la réinitialisation du mot de passe. |
Erreurs authentification (OTP / JWT)
| Code | HTTP | Signification | Cause probable | Recours |
|---|---|---|---|---|
OTP_BLOCKED |
429 | Trop de tentatives OTP | 3 échecs en moins d'1 heure → blocage 30 min (otp_blocked:{phone} Redis) |
Attendre 30 min. En urgence prod : redis-cli DEL otp_blocked:+221XXXXXXXXX |
OTP_INVALID |
400 | Code OTP incorrect ou expiré | TTL Redis de 5 min dépassé ou code mal saisi | Demander un nouveau code. Vérifier la livraison SMS via Nexah. |
RATE_LIMIT_EXCEEDED |
429 | Trop de requêtes | Dépasse 100 req/min (global) ou 30/min sur /auth/otp |
Implémenter un backoff exponentiel côté client. Vérifier les bots. |
TOKEN_BLACKLISTED |
401 | Token JWT révoqué | Token dans la blacklist Redis (déconnexion explicite ou rotation) | Se reconnecter. Le token ne peut pas être réutilisé. |
Erreurs webhooks paiement
| Code | HTTP | Signification | Cause probable | Recours |
|---|---|---|---|---|
WEBHOOK_SIGNATURE_INVALID |
401 | Signature HMAC webhook invalide | Header x-wave-signature ne correspond pas au body (clé erronée ou body altéré) |
Vérifier WAVE_WEBHOOK_SECRET et OM_WEBHOOK_SECRET. Ne jamais logger ni traiter un webhook à signature invalide. |
WEBHOOK_ALREADY_PROCESSED |
409 | Webhook déjà traité (idempotence) | La référence existe dans webhook_logs |
Répondre 200 sans retraiter. Comportement normal sur les retry Wave/OM. |
Erreurs système
| Code | HTTP | Signification | Cause probable | Recours |
|---|---|---|---|---|
INTERNAL_ERROR |
500 | Erreur interne serveur | Exception non gérée — voir logs Sentry/Railway | railway logs --tail. Vérifier Sentry DSN. Si récurrent : rollback. |
DB_TIMEOUT |
503 | Timeout transaction DB | Transaction atomique dépasse 5s (SELECT FOR UPDATE bloqué) | Vérifier les locks PostgreSQL : SELECT * FROM pg_locks;. Augmenter le timeout si justifié. |
REDIS_UNAVAILABLE |
503 | Redis inaccessible | Railway Redis redémarré ou REDIS_URL incorrect |
Vérifier l'état Railway. L'API ne démarre pas sans Redis (OTP et blacklist JWT inaccessibles). |
https://kadoapi-production.up.railway.app/api/v1Authentification
| Méthode | Route | Description | Auth |
|---|---|---|---|
| POST | /auth/otp/send | Envoyer OTP par SMS | Public |
| POST | /auth/otp/verify | Vérifier OTP → JWT | Public |
| POST | /auth/refresh | Renouveler access token | Refresh token |
| POST | /auth/logout | Révoquer les tokens | JWT |
Bons (vouchers)
| Méthode | Route | Description | Rôle |
|---|---|---|---|
| GET | /vouchers/me | Bons du bénéficiaire connecté | BENEFICIARY |
| GET | /vouchers/:id | Détail d'un bon | JWT |
| GET | /vouchers/:id/transactions | Historique des utilisations | JWT |
| POST | /vouchers/lookup | Preview QR sans débit | MERCHANT|ADMIN |
| POST | /vouchers/validate | Valider QR — débit atomique | MERCHANT |
| GET | /vouchers/transaction-request/:token | Infos transaction request | BENEFICIARY |
| POST | /vouchers/initiate-pay | Initier paiement avec confirmation | BENEFICIARY |
| POST | /vouchers/confirm-pay | Confirmer avec mot de passe | BENEFICIARY |
Commerçants (espace POS)
| Méthode | Route | Description | Rôle |
|---|---|---|---|
| GET | /merchants/me/dashboard | Résumé du jour + transactions | MERCHANT |
| GET | /merchants/me/transactions | Journal paginé avec filtres date | MERCHANT |
| POST | /merchants/me/transaction-request | Créer une demande de paiement | MERCHANT |
| GET | /merchants/me/flash-offers | Lister les offres flash | MERCHANT |
| POST | /merchants/me/flash-offers | Créer une offre flash | MERCHANT |
| PUT | /merchants/me/flash-offers/:id | Modifier une offre flash | MERCHANT |
| DELETE | /merchants/me/flash-offers/:id | Supprimer une offre flash | MERCHANT |
| GET | /merchants/me/vendors | Lister les vendeurs | MERCHANT |
| POST | /merchants/me/vendors | Ajouter un vendeur | MERCHANT |
| PUT | /merchants/me/vendors/:id | Modifier un vendeur | MERCHANT |
| DELETE | /merchants/me/vendors/:id | Désactiver un vendeur | MERCHANT |
| GET | /merchants/nearby | Commerçants proches (géo) | Public |
timingSafeEqual — jamais de comparaison ===SELECT FOR UPDATE dans toutes les transactions de validation QR/auth/otp.envPENDING → ISSUED (webhook EME confirme emeConfirmedAt) ISSUED → PARTIAL (paiement partiel — remainingValue > 0) ISSUED → USED (paiement total — remainingValue = 0) ISSUED → EXPIRED (cron 00h01 UTC — expiresAt dépassé) ISSUED → CANCELLED (annulation RH — remboursement provision) PARTIAL → USED (solde épuisé) PARTIAL → EXPIRED (cron expiration) USED → terminal (aucune transition) EXPIRED → terminal (aucune transition) CANCELLED → terminal (aucune transition)
amount / 100. Commission : Math.round(amount * 0.05)Wave Checkout API — recharge provision entreprise
Quand une entreprise recharge sa provision via Wave, kado utilise l'API Wave Checkout (pas l'API EME directe). Le flux est le suivant :
POST /companies/me/topup avec { method: "WAVE", amountCentimes }. kado crée un enregistrement CompanyPayment puis appelle POST https://api.wave.com/v1/checkout/sessions avec le montant en XOF (FCFA, sans décimales). La session Wave renvoie un wave_launch_url.wave_launch_url. L'utilisateur paie via Wave Mobile (ou Wave Web). La référence idempotente est l'UUID du CompanyPayment (client_reference).POST /api/v1/payments/webhooks/wave. Le champ checkout_status (vs event pour les webhooks EME) identifie les webhooks Checkout. Si checkout_status === "complete", kado incrémente provisionBalance de façon atomique et marque le CompanyPayment en RECU.// Payload webhook Wave Checkout (exemple) { "checkout_status": "complete", "client_reference": "uuid-company-payment-id", "amount": 50000, // en XOF (FCFA) "currency": "XOF" } // Payload webhook Wave EME direct (exemple — activation voucher) { "event": "checkout.session.completed", "data": { ... } }
checkout_status indique un webhook Checkout API (recharge provision). Le champ event indique un webhook EME direct (activation de voucher). Le service processWaveWebhook() les route automatiquement.Reversements commerçants
SETTLE-{merchantId}-{YYYYMMDD} · Unique en baseMath.round(amount * 0.05) · Compte REVENUE_COMMISSIONPOST /payments/webhooks/wave · Header x-wave-signature · HMAC-SHA256 sur rawBodyPOST /payments/webhooks/orange-money · Header x-om-signaturerawBody brut, avant tout parsing JSON (rawBody: true activé dans NestFactory).Modèle A — Le commerçant scanne le QR bénéficiaire (legacy)
Le commerçant utilise le scanner du POS pour lire le QR affiché sur l'écran du bénéficiaire. Il saisit le montant et valide. Flux : POST /vouchers/validate depuis le compte MERCHANT.
Modèle B — Le bénéficiaire scanne le terminal (conforme BCEAO)
Le commerçant saisit le montant sur son POS (POST /merchants/me/transaction-request) qui génère un token Redis (TTL 180s) et un QR. Le bénéficiaire scanne ce QR avec son app, consulte le détail (GET /vouchers/transaction-request/:token), entre son mot de passe kado, et confirme (POST /vouchers/confirm-pay). La validation est effectuée côté bénéficiaire — il est l'initiateur.
| Modèle A | Modèle B (BCEAO) | |
|---|---|---|
| Qui scanne | Commerçant scanne QR bénéficiaire | Bénéficiaire scanne QR commerçant |
| Initiateur | Commerçant (appelle /validate) | Bénéficiaire (appelle /confirm-pay) |
| Authentification | JWT MERCHANT | JWT BENEFICIARY + mot de passe kado |
| TTL session | N/A | 180s (token Redis) |
| Conformité BCEAO | Acceptable | Recommandé |
Multi-PSP configurable
Architecture multi-prestataires de paiement — Bictorys, Wave Direct, Orange Money, extensible
Configuration centralisée
Les configurations PSP sont stockées dans la table PlatformSettings sous la clé payment_provider_{nom}. Chaque entrée contient :
| Champ | Type | Description |
|---|---|---|
enabled | boolean | PSP actif ou non |
environment | 'sandbox' | 'production' | Environnement d'utilisation |
usage | 'collect' | 'payout' | 'both' | Usage encaissement ou reversement |
priority | number | Ordre de fallback (1 = principal) |
Comparaison des PSP intégrés
| PSP | Méthodes | Frais | Usage |
|---|---|---|---|
| Bictorys | Wave + OM + Cartes | 1% + 100 FCFA fixe | Collect + Payout |
| Wave Direct | Wave uniquement | 1% | Payout uniquement |
| Orange Money Direct | OM uniquement | 1.5% à 2.5% | Collect + Payout |
Endpoints admin
| Méthode | Route | Description |
|---|---|---|
| GET | /admin/settings/payment_provider_{name} | Lire la config d'un PSP |
| PUT | /admin/settings/payment_provider_{name} | Mettre à jour la config (active/désactive) |
Demande d'intégration nouveau PSP
L'admin peut demander l'intégration d'un nouveau PSP depuis l'interface (ex: PayDunya, CinetPay). La demande crée automatiquement un ticket support de catégorie TECHNICAL qui notifie l'équipe dev par email.
BICTORYS_SECRET_KEY, WAVE_WEBHOOK_SECRET) restent dans les variables d'environnement Railway. L'interface admin ne stocke que les métadonnées de configuration.
Payouts groupés
Système de reversement par cron jobs (DAILY/WEEKLY/BIWEEKLY) — réduction des frais agrégateurs jusqu'à 95% vs INSTANT
• INSTANT : 540 000 FCFA/mois de frais (auto à chaque transaction)
• WEEKLY : 18 000 FCFA/mois (-95%)
Économie annuelle : ~6,3 M FCFA
Enum PayoutSchedule
enum PayoutSchedule {
INSTANT // Au fil de l'eau — payout immédiat (option premium)
DAILY // Tous les jours à 09h Africa/Dakar
WEEKLY // Tous les lundis à 09h (défaut)
BIWEEKLY // 1er et 15 du mois à 09h
}
Cron jobs (timezone Africa/Dakar)
| Fréquence | Cron | Service |
|---|---|---|
| DAILY | 0 9 * * * | ScheduledPayoutsService.runDailyPayouts() |
| WEEKLY | 0 9 * * 1 | ScheduledPayoutsService.runWeeklyPayouts() |
| BIWEEKLY | 0 9 1,15 * * | ScheduledPayoutsService.runBiweeklyPayouts() |
Logique de calcul du solde dû
// Pour chaque commerçant ayant la fréquence cible :
totalDue = SUM(VoucherTransaction.netAmount WHERE merchantId = X)
totalPaid = SUM(MerchantSettlement.amount WHERE merchantId = X AND status = CONFIRMED)
balanceDue = MAX(0, totalDue - totalPaid)
// Si balanceDue > 0 : enqueue Bull job avec retry exponentiel
// Reference idempotente : SETTLE-{merchantId[:8]}-{timestamp}
Choix de la fréquence par commerçant
Chaque commerçant choisit sa propre fréquence depuis son dashboard POS via PATCH /merchants/me/payout-schedule. Le champ payoutSchedule est sur le modèle Merchant (défaut WEEKLY).
Simulateur admin
Page admin /admin/dashboard?tab=payouts-simulator permet de simuler en temps réel les 4 modèles selon :
- Volume mensuel total
- Nombre de commerçants
- Commission kado (%)
- Frais agrégateur (variable + fixe)
- Nombre moyen de transactions par commerçant par jour
Le simulateur affiche pour chaque modèle : payouts/mois, frais totaux, marge nette, % marge conservée, et recommande automatiquement le modèle optimal.
Géolocalisation
Système de géolocalisation bénéficiaire et commerçant — recherche de proximité, itinéraires Google Maps
Backend — modèle Merchant
Le modèle Merchant a deux champs optionnels :
latitude Float? longitude Float?
Endpoints
| Méthode | Route | Description | Auth |
|---|---|---|---|
| GET | /merchants/nearby?lat&lng&radius | Recherche commerçants proches (haversine) | Public |
| PATCH | /merchants/me/location | Le POS envoie sa position GPS | MERCHANT |
| PATCH | /admin/merchants/:id/location | Admin met à jour la position | ADMIN |
Algorithme haversine
Calcul de distance entre deux coordonnées GPS (formule haversine). Approximation valide jusqu'à ~50 km avec une erreur < 0,5%.
// Approximation utilisée pour la recherche par rayon deltaLat = radiusMeters / 111_000 deltaLng = radiusMeters / (111_000 * cos(lat * PI / 180)) // Bounding box puis vérification haversine exacte
Frontend bénéficiaire
- Popup explicatif au premier login (localStorage
geo_intro_seen) - Rayons configurables : 1 / 2 / 5 / 10 km (défaut 2 km)
- Bouton "Itinéraire" → ouvre Google Maps avec navigation directe
- Composant
GeoHelpModaladaptatif iOS/Android/macOS/Desktop
Frontend commerçant POS
- Capture manuelle de la position (bouton dans le dashboard)
- Pas de popup automatique au login (UX moins intrusive)
- Section permanente "Localisation du magasin"
https://www.google.com/maps/dir/?api=1&destination=lat,lng&travelmode=driving. S'ouvre dans Google Maps si installé, sinon dans le navigateur.
Types de bons adaptés au Sénégal
11 catégories culturellement pertinentes — fin mai 2026
Liste finale
| Enum | Label | Usage |
|---|---|---|
GIFT_VOUCHER | Cadeau | Cadeau classique |
MEAL_TICKET | Ticket repas | Restauration salariés |
TRANSPORT | Transport | Frais déplacement |
CEREMONIE_RELIGIEUSE | Cérémonie religieuse | Korité, Tabaski, Magal, Gamou |
EVENEMENT_FAMILIAL | Événement familial | Baptême, mariage, décès |
SANTE | Santé / Pharmacie | Soins, médicaments |
EDUCATION | Éducation / Scolaire | Fournitures, inscriptions |
CARBURANT | Carburant | Stations service |
ALIMENTAIRE | Alimentaire | Courses alimentaires |
BIEN_ETRE | Bien-être | Coiffure, sport, soins |
AIDE_SOCIALE | Aide sociale | Solidarité, soutien employé |
Valeurs dépréciées (rétrocompatibilité)
Les valeurs suivantes restent dans l'enum Prisma pour ne pas casser les bons existants en base, mais ne sont plus proposées à l'émission depuis le 23 mai 2026 :
BONUS— remplacé par AIDE_SOCIALE ou GIFT_VOUCHER selon le contexteINDEMNITE— remplacé par AIDE_SOCIALERECOMPENSE— remplacé par GIFT_VOUCHERBEAUTE— remplacé par BIEN_ETRE
Support admin enrichi
Onglet support amélioré pour la gestion efficace des tickets — templates, appels directs, notifications email
Templates de réponses pré-rédigées
6 templates accessibles en 1 clic depuis la zone de réponse (mode "Envoyer au contact" uniquement) :
| Clé | Label | Cas d'usage |
|---|---|---|
welcome | Bienvenue | Premier contact, accusé de réception |
info_needed | Demande info | Compléments requis pour traiter |
resolved | Résolu | Confirmation de résolution |
follow_up | Suivi | Vérification de satisfaction |
escalation | Escalade tech | Transmission équipe technique |
closure | Clôture | Fermeture sans réponse |
Boutons d'appel direct
Trois boutons d'action rapide affichés dans la fiche ticket (si numéro/email renseignés) :
- Appeler — protocole
tel: - WhatsApp —
wa.me/{numéro nettoyé} - Email —
mailto:avec sujet pré-rempli[{ref}] {sujet}
Notifications email automatiques
Chaque nouveau ticket (toutes sources confondues : merchant, company, beneficiary, internal) déclenche un email automatique vers SUPPORT_ADMIN_EMAIL (défaut support@kado.sn) avec template HTML pro :
- Référence + sujet en en-tête violet kado
- Badge priorité coloré (LOW/MEDIUM/HIGH/URGENT)
- Source + catégorie traduites en français
- Informations contact si renseignées
- Description complète avec formatage préservé
- Bouton "Ouvrir dans l'admin"
Lookup utilisateur intelligent
L'onglet "Accès & Comptes" permet de rechercher un utilisateur par téléphone ou email. La fonction normalizeIdentifier() supprime les espaces des numéros avant le lookup (ex: +221 77 358 88 28 → +221773588828) pour éviter les "Aucun utilisateur trouvé" injustifiés.
Reset password intelligent
Après réinitialisation, le backend renvoie { hasCompanyAccess, role }. Le frontend affiche conditionnellement :
- Bénéficiaire pur (BENEFICIARY) → redirection auto vers
/app/login - RH multi-rôles (COMPANY_ADMIN ou COMPANY_VIEWER avec companyId) → choix d'espace
SUPPORT_ADMIN_EMAIL dans Railway permet de personnaliser le destinataire des notifications. Si non défini, valeur par défaut : support@kado.sn.