Module PRD+FSD - Facturation (BILL)¶
Papillon Collection Solution · V1 (MVP)¶
Auteur : ANALYST (Claude Code) · Date : 2026-04-30
Branche : docs/billing-prd · Module prefix : BILL
Pays cible V1 : Côte d'Ivoire (CI) uniquement
Mode : A - nouveau PRD (aucun PRD existant)
Source legal audit :
docs/legal/audit/memo-conformite-CI-v2-audit.md(audité le 30/04/2026 - disposition LEGAL REVIEW REQUIRED). Mémo source :docs/legal/memo-conformite-CI-v2.md.Legal status discipline (memo v2) - toute obligation
INCERTAIN - avocat requisbloque l'implémentation des stories qui en dépendent et impose une note[LEGAL-REVIEW]dans la spec/story. Toute obligationPRÉLIMINAIREdéclenche un avertissement +[LEGAL-REVIEW]. La conformité ne se déduit jamais d'un statutINCERTAIN.Note de périmètre pour BILL - le mémo
memo-conformite-CI-v2couvre la chaîne client → courtier (CIMA, données personnelles, paiement mobile money, preuve). Il ne couvre PAS la facturation plateforme → tenant (régime DGI / FNE Côte d'Ivoire pour les tenantsCI; régimes équivalentsSN,BJ- cf.CLAUDE.md§Multi-Tenant Model). Ce périmètre relève d'un mémo de conformité distinct à produire avant V1 go-live (étape 0A du workflow). Tant qu'un audit légal FNE n'a pas été produit, toute story BILL qui touche au format de facture, à l'envoi à la DGI, ou au cycle de vie d'une facture certifiée doit porter[LEGAL-REVIEW]et appliquer la même discipline tri-état dès que ce mémo existera.
V0 Envelope Scope (ajout 2026-05-21)¶
BILL est entièrement HORS V0. Aucune sous-version de l'enveloppe V0 (v0.0.0 à v0.1.0) ne livre de code, de schéma ou d'UI du module Facturation. La résolution de la question ouverte V0-OQ-05 (
docs/prd/prd-v0.md§ 10) confirme cette mise hors périmètre. La ligne TENANT du § 4 du PRD V0 rappelle que la facturation plateforme -> tenant (et notamment l'intégration FNE pour les tenantsCI) ne ship qu'avec V1.Conséquences pratiques pour la phase V0 :
- Aucune story
BILL-NNNn'entre dans le backlog V0. Les sous-versions v0.0.0 à v0.1.0 n'en planifient aucune (cf.prd-v0.md§ 8 : la colonneBILLdu mapping n'existe pas).- Aucun placeholder, aucun stub : pas de table
invoice, pas d'endpoint, pas de feature flag câblé dans le code V0. Le module reste inerte côté repo jusqu'à l'ouverture du chantier V1.- Le chiffrage commercial des tenants V0 est manuel / hors plateforme. Les contrats Basile (démo) et NSIA (pilote v0.0.4 -> v0.0.5) ne déclenchent aucun appel FNE / DGI depuis Papillon Collection Solution. La facturation effective de ces tenants pendant la phase V0 est faite via les outils existants du founder (hors scope produit).
- Le mémo de conformité FNE / DGI (mentionné dans la note de périmètre ci-dessus) n'est pas un prérequis pour V0 go-live. Il devient bloquant uniquement quand le chantier V1 du module BILL démarre (étape 0A du workflow, fresh ANALYST + LEGAL_AUDITOR session).
Ce que V0 garantit malgré tout pour ne pas re-bloquer V1 :
- La colonne
country_codeportée partenantdès v0.0.0 (cf.CLAUDE.mdMulti-Tenant Model +prd-v0.md§ 5) suffit à router plus tard une intégration FNE conditionnelle (CI/SN/BJ) sans migration de schéma sur les tables tenant-scopées.- La table
country_profileseedéeCIonly en v0.0.0 réserve les colonnes nécessaires à un futureinvoicing_provider+einvoicing_endpoint(la création de ces colonnes elle-même est V1 ; aucune valeur n'est exigée en V0).- Le pattern outbox d'AUDIT (
spring-modulith-events-jdbc, anchor D4 + D7) accueillera naturellement les futurs événementsINVOICE_ISSUED/INVOICE_FNE_ACCEPTED/INVOICE_FNE_REJECTEDà V1 sans rework.Statuts légaux : aucun statut
INCERTAIN - avocat requisouPRÉLIMINAIREdu futur mémo FNE n'est arbitré par cette mise hors V0. Le module reste intégralement à instruire (mémo + audit + PRD + arch + stories) au moment du démarrage V1.
Table des matières¶
- Partie A - Exigences Produit
- A1. Résumé exécutif
- A2. Personas utilisateurs
- A3. User Stories
- A4. Exigences non fonctionnelles
- A5. Périmètre MVP
- A6. Questions ouvertes
- A7. Temporairement validé (TV)
- Partie B - Spécifications Fonctionnelles
- B1. Règles fonctionnelles
- B2. Machine à états - Cycle de vie d'une facture
- B3. Tables de décision
- B4. Modèle de données
- B5. Implications multi-tenant
- B6. Dépendances inter-modules
- Partie C - Contraintes et Limites
- C1. Conformité réglementaire
- C2. Localisation
- C3. Sécurité
- C4. Connectivité
- C5. Hors périmètre
Partie A - Exigences Produit¶
A1. Résumé exécutif¶
Problème adressé¶
La plateforme Papillon Collection Solution facture ses services aux cabinets de courtage (tenants) sur une base d'abonnement. En Côte d'Ivoire, toute facture B2B doit obligatoirement être certifiée par la Facture Normalisée Électronique (FNE) du système DGI avant d'être fiscalement opposable. Sans un module de facturation intégrant cette exigence, la plateforme ne peut pas légalement encaisser ses abonnements dans le respect du droit fiscal ivoirien.
Par ailleurs, les tenants (petits cabinets en phase pilote) doivent pouvoir consulter, payer et télécharger leurs factures en libre-service depuis leur back-office, sans dépendre d'échanges par email ou WhatsApp avec l'équipe de la plateforme - c'est un standard de toute application SaaS B2B moderne.
Valeur créée¶
| Bénéficiaire | Valeur |
|---|---|
| Plateforme (opérateur) | Facturation traçable, certifiée DGI, encaissement en mobile money ou virement, audit complet de chaque transaction. |
| Tenant admin | Tableau de bord de ses factures, paiement en un clic (mobile money), téléchargement de la facture certifiée DGI et de la quittance pour sa comptabilité. |
| Comptable tenant | Factures au format standard CI (HT / TVA 18 % / TTC, NIF, numéro FNE, QR code DGI) directement téléchargeables. |
Positionnement V1¶
Le module BILL est un module de contrôle (Control plane) consommé par l'administration de la plateforme. Il n'est pas visible des assurés finaux. Il opère en tandem avec le module TENANT (configuration du tenant) et réutilise l'abstraction de paiement du module PAY.
A2. Personas utilisateurs¶
Administrateur plateforme [ADMIN]¶
Employé de l'entreprise opératrice de Papillon. Seul acteur autorisé à configurer les paramètres de facturation d'un tenant, à générer des factures, à marquer un virement reçu, et à re-soumettre une facture en cas d'échec FNE.
Workflow typique : créer/modifier la configuration de facturation d'un tenant → générer une facture d'abonnement ou de frais d'installation → suivre le statut FNE → réconcilier les virements bancaires → consulter les tableaux de bord de paiement.
Administrateur tenant [OPERATOR]¶
Responsable du cabinet, accès au back-office opérateur. Consulte les factures de sa propre organisation, initie le paiement en mobile money ou prend note des coordonnées bancaires pour le virement. Télécharge les documents pour sa comptabilité.
Accès : restreint aux factures de son tenant uniquement.
Système (backend) [BACKEND]¶
Jobs planifiés et webhooks : soumission FNE asynchrone, détection des factures en retard, envoi des rappels automatiques, traitement des webhooks de confirmation de paiement mobile money.
A3. User Stories¶
BILL-US-001 - Configuration de facturation d'un tenant [ADMIN][V1]¶
En tant qu' administrateur plateforme, je veux configurer les paramètres de facturation d'un tenant afin de définir son plan tarifaire, son contact de facturation et ses coordonnées fiscales avant d'émettre la première facture.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Un tenant existe sans configuration de facturation | L'admin ouvre l'onglet "Facturation" sur la fiche tenant | Il voit un formulaire vierge avec les champs : type de plan (MENSUEL/ANNUEL), montant HT (XOF), montant HT annuel (XOF, si annuel), NIF/CC du tenant, email de contact facturation, délai de paiement (défaut 15 j), frais d'installation HT (0 si exonéré), statut de facturation (ACTIF/PAUSÉ) |
| AC2 | La config est sauvegardée | Une facture est générée pour ce tenant | La facture utilise les valeurs de la config au moment de la génération (snapshot, pas la config courante) |
| AC3 | Une config existe déjà | L'admin la modifie et sauvegarde | Un événement audit TENANT_BILLING_CONFIG_UPDATED est créé avec l'ancien et le nouvel état des champs modifiés |
| AC4 | L'email de contact facturation est absent | L'admin tente de sauvegarder | Le système bloque avec le message : « L'adresse email de contact facturation est obligatoire. » |
BILL-US-002 - Génération manuelle d'une facture d'abonnement [ADMIN][V1]¶
En tant qu' administrateur plateforme, je veux générer manuellement une facture d'abonnement pour un tenant afin de le facturer pour la période en cours.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Un tenant a une config de facturation ACTIVE | L'admin clique "Générer une facture d'abonnement", renseigne la période (date début, date fin) et confirme | Une facture est créée en statut DRAFT avec le numéro BILL-{AAAA}-{NNNN}, les montants HT/TVA/TTC calculés, et la date d'échéance = date de facture + 15 jours |
| AC2 | La facture est créée | - | La soumission FNE est mise en file d'attente immédiatement ; un email "Nouvelle facture" est envoyé au contact de facturation du tenant |
| AC3 | Deux demandes de génération pour le même tenant, même période et même type sont soumises simultanément | - | Une seule facture est créée (vérification d'idempotence sur tenant_id + period_start + period_end + invoice_type) ; la seconde requête retourne la facture existante |
| AC4 | L'admin souhaite ajuster le montant (ex : pro-rata premier mois) | Il modifie le montant HT avant de confirmer | Le système accepte le montant HT saisi manuellement et recalcule TVA et TTC en conséquence ; la ligne de détail affiche la description saisie par l'admin |
BILL-US-003 - Génération d'une facture de frais d'installation [ADMIN][V1]¶
En tant qu' administrateur plateforme, je veux générer une facture de frais d'installation pour un tenant afin de lui facturer la mise en place initiale ou toute prestation ponctuelle.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | La config tenant contient onboarding_fee_ht > 0 |
L'admin déclenche une facture de frais d'installation | Une facture de type ONBOARDING_FEE est créée avec le montant de la config ; même flux FNE que l'abonnement |
| AC2 | L'admin saisit un montant personnalisé (frais de dérogation) | Il entre un montant HT différent et une description libre | Le système crée la facture avec le montant saisi et la description personnalisée |
| AC3 | Une facture de frais d'installation a déjà été payée pour ce tenant | L'admin tente d'en générer une seconde | Le système affiche un avertissement « Une facture de frais d'installation payée existe déjà pour ce tenant. Confirmer pour continuer ? » ; l'admin peut passer outre |
BILL-US-004 - Réconciliation d'un virement bancaire [ADMIN][V1]¶
En tant qu' administrateur plateforme, je veux marquer une facture comme payée par virement bancaire afin de mettre à jour le statut et notifier le tenant.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Une facture est en statut DRAFT, CERTIFIED ou OVERDUE | L'admin saisit la référence bancaire et la date de paiement puis clique "Marquer payée" | Le statut de la facture passe à PAID, un enregistrement BillingPayment est créé avec la référence et la date, et le contact de facturation reçoit un email de confirmation de paiement |
| AC2 | La facture est déjà en statut PAID | L'admin tente de la marquer payée | Le système rejette l'action avec le message : « Cette facture est déjà marquée comme payée. » |
| AC3 | La référence bancaire est absente | L'admin tente de valider | Le système bloque avec le message : « La référence de la transaction bancaire est obligatoire. » |
BILL-US-005 - Tableau de bord facturation (admin plateforme) [ADMIN][V1]¶
En tant qu' administrateur plateforme, je veux consulter l'ensemble des factures de tous les tenants afin de suivre les encaissements et détecter les retards.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Je suis sur le tableau de bord facturation | - | Je vois la liste de toutes les factures (tous tenants) avec les colonnes : nom du tenant, numéro de facture, type, période, montant TTC, date d'échéance, statut facture, statut FNE |
| AC2 | Je filtre par statut = OVERDUE | - | Seules les factures en retard sont affichées |
| AC3 | Je filtre par tenant | - | Seules les factures du tenant sélectionné sont affichées |
| AC4 | Je clique sur une facture | - | J'accède au détail : lignes, FNE status, historique de paiement, bouton de réconciliation bancaire, bouton de re-soumission FNE (si fne_status = FAILED) |
BILL-US-006 - Consultation des factures par le tenant admin [OPERATOR][V1]¶
En tant qu' administrateur tenant, je veux consulter mes factures plateforme en libre-service afin de suivre mes engagements financiers et télécharger les documents pour ma comptabilité.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Je suis connecté en tant qu'admin tenant | Je navigue vers "Mon abonnement" | Je vois la liste de mes factures avec : numéro, type, période, montant TTC, statut, date d'échéance, boutons d'action |
| AC2 | Une facture a fne_status = CERTIFIED |
- | Un lien "Télécharger la facture certifiée DGI (PDF)" est visible |
| AC3 | Une facture est en statut DRAFT et fne_status ≠ CERTIFIED |
- | Un badge "Certification DGI en cours…" est affiché à côté du numéro de facture ; le bouton de téléchargement PDF certifié est absent |
| AC4 | Une facture est en statut PAID | - | Un lien "Télécharger la quittance de paiement (PDF)" est visible |
BILL-US-007 - Paiement d'une facture par mobile money [OPERATOR][V1]¶
En tant qu' administrateur tenant, je veux payer une facture directement en mobile money depuis le back-office afin de régler mon abonnement sans me déplacer ni faire un virement bancaire.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Une facture est en statut DRAFT, CERTIFIED ou OVERDUE | Je clique "Payer maintenant" | Je vois l'écran de sélection du mode de paiement : Mobile money / Virement bancaire |
| AC2 | Je sélectionne Mobile money et valide | - | Je suis redirigé vers le flux de paiement du prestataire (saisie numéro, confirmation USSD ou push) |
| AC3 | Le prestataire confirme le paiement via webhook | - | Le statut de la facture passe à PAID, je reçois un email de confirmation avec la référence de transaction et un lien pour télécharger la quittance |
| AC4 | Le paiement échoue ou expire | - | Je vois le message : « Le paiement a échoué. Vous pouvez réessayer. » avec le bouton "Réessayer" ; aucun double débit n'est possible grâce à la clé d'idempotence |
BILL-US-008 - Paiement d'une facture par virement bancaire [OPERATOR][V1]¶
En tant qu' administrateur tenant, je veux obtenir les instructions de virement pour payer une facture par ma banque afin de régler avec mes outils bancaires habituels.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Je sélectionne "Virement bancaire" sur l'écran de paiement | - | Je vois les coordonnées bancaires de la plateforme (banque, titulaire, IBAN/RIB) et la référence exacte à mentionner dans l'ordre de virement (numéro de facture BILL-AAAA-NNNN) |
| AC2 | Je reviens consulter la facture avant que l'admin ait réconcilié | - | Le statut reste DRAFT / CERTIFIED / OVERDUE, avec le message : « Virement attendu. Référence : BILL-AAAA-NNNN. » |
| AC3 | L'admin plateforme marque le virement reçu | - | Mon statut de facture passe à PAID et je reçois un email de confirmation |
BILL-US-009 - Soumission FNE asynchrone [BACKEND][V1]¶
En tant que système, je veux soumettre chaque facture à la FNE en arrière-plan et mettre à jour le statut de certification afin que les factures soient certifiées DGI sans bloquer le flux de génération.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Une facture est créée en statut DRAFT | La soumission FNE réussit | fne_status = CERTIFIED, fne_number et fne_qr_code_url sont enregistrés, le PDF certifié est généré et stocké dans MinIO |
| AC2 | La soumission FNE échoue | Les 5 tentatives de la politique de relance sont épuisées (~3 h) | fne_status = FAILED et une alerte est envoyée à l'administrateur plateforme |
| AC3 | fne_status = FAILED |
L'admin plateforme clique "Re-soumettre à la FNE" | Une nouvelle tentative est déclenchée et le compteur de relances est réinitialisé |
| AC4 | La FNE certifie une facture déjà payée (paiement reçu avant certification) | - | La facture reste PAID ; le fne_number et le PDF certifié sont mis à jour ; aucun statut ne régresse |
BILL-US-010 - Détection automatique des factures en retard et rappel [BACKEND][V1]¶
En tant que système, je veux détecter les factures impayées au-delà de leur date d'échéance et envoyer un rappel unique afin d'alerter les tenants sans intervention manuelle.
| # | Étant donné | Quand | Alors |
|---|---|---|---|
| AC1 | Une facture a status ∈ {DRAFT, CERTIFIED} et due_date < today |
Le job quotidien s'exécute | Le statut passe à OVERDUE et l'événement audit INVOICE_OVERDUE est enregistré |
| AC2 | Une facture vient de passer à OVERDUE et overdue_reminder_sent_at IS NULL |
- | Un email de rappel unique est envoyé au contact de facturation ; overdue_reminder_sent_at est renseigné |
| AC3 | overdue_reminder_sent_at IS NOT NULL |
- | Aucun second rappel automatique n'est envoyé (pas de chaîne de relance en V1) |
| AC4 | Une facture OVERDUE est payée | - | Le statut passe à PAID ; le badge "En retard" disparaît de l'interface tenant |
A4. Exigences non fonctionnelles¶
| # | Catégorie | Exigence |
|---|---|---|
| NFR-01 | Performance | Le tableau de bord facturation admin (tous tenants) doit se charger en < 2 s pour ≤ 500 factures (pagination après). |
| NFR-02 | Disponibilité FNE | Le module BILL ne doit pas être indisponible si la FNE est down : la génération de facture réussit en DRAFT ; la certification est asynchrone. |
| NFR-03 | Idempotence paiements | Chaque tentative de paiement mobile money doit porter une clé d'idempotence unique. En cas de double webhook, le paiement n'est enregistré qu'une fois. |
| NFR-04 | Intégrité des montants | Tous les montants sont stockés en XOF entiers (pas de virgule flottante). La TVA est calculée par ROUND(montant_ht × tva_rate) avec arrondi arithmétique (0,5 → 1). |
| NFR-05 | Stockage PDF | Les PDFs de factures (brouillon et certifié) sont stockés dans MinIO avec une rétention minimale conforme au droit fiscal CI (durée TV - voir C1). |
| NFR-06 | Isolation tenant | Un tenant admin ne voit jamais les factures d'un autre tenant. Chaque requête est scopée par tenant_id issu du contexte d'authentification Keycloak. |
| NFR-07 | Audit | Chaque événement du cycle de vie d'une facture produit une entrée dans le module AUDIT avec : event_type, timestamp, actor (admin user_id ou SYSTEM), tenant_id, invoice_id, result. |
| NFR-08 | Localisation | Toutes les interfaces et emails sont en français. Montants affichés en format XOF (espace comme séparateur de milliers, 0 décimale) : 59 000 XOF. |
| NFR-09 | Sécurité emails | Les emails de facturation ne contiennent jamais de lien de paiement actif en clair. Le tenant clique pour ouvrir son back-office Keycloak-authentifié. |
| NFR-10 | Retry FNE | Politique de relance exponentielle : 5 s → 30 s → 5 min → 30 min → 2 h (5 tentatives totales). Si toutes échouent : fne_status = FAILED + alerte admin. |
A5. Périmètre MVP¶
V1 - Inclus¶
| Fonctionnalité | Notes |
|---|---|
| Génération manuelle de factures d'abonnement (mensuel ou annuel) | Toutes les factures sont déclenchées manuellement par l'admin plateforme en V1. Pas de scheduler automatique. |
| Génération manuelle de factures de frais d'installation | Montant configurable par tenant ; peut être 0 si exonéré. |
| Soumission FNE asynchrone + certification DGI | Brouillon immédiatement visible ; PDF certifié disponible après certification. |
| Paiement en libre-service (mobile money) | Via la même abstraction de prestataire de paiement que le module PAY. |
| Paiement par virement bancaire + réconciliation manuelle | Admin plateforme marque la facture payée avec la référence bancaire. |
| TVA 18 % HT/TVA/TTC sur toutes les factures | Taux configuré dans le profil pays CI ; champ TVA sur le tenant. |
| Tableau de bord tenant : liste factures, paiement, téléchargement PDF certifié + quittance | Interface back-office opérateur. |
| Tableau de bord admin plateforme : liste globale, config tenant, réconciliation, re-soumission FNE | Interface admin. |
| Rappel automatique unique à J+16 par email | Job quotidien. |
| Audit complet (10 événements) | Module AUDIT. |
| Configuration facturation par tenant (plan, prix, NIF, email contact) | Module TENANT. |
V2 - Différé¶
| Fonctionnalité | Justification |
|---|---|
| Scheduler de facturation automatique (récurrence mensuelle/annuelle) | Trop peu de tenants en V1 pour justifier la complexité. |
| Avoirs (credit notes) / annulation de facture | Cas rares en V1 ; gérés manuellement hors plateforme. |
| Dunning automatisé (relances multiples, escalade) | Dépasse le cadre du pilot ; V2 avec la gestion des comptes structurés. |
| Suspension automatique du tenant en cas d'impayé | V2 - nécessite une gouvernance claire et un préavis légal. |
| Switching plan mid-cycle avec calcul de prorata automatique | V2 - nécessite un moteur de crédit/débit. |
| Self-service pricing (grille tarifaire publique, choix du plan par le tenant) | V2 - modèle de vente assistée en V1. |
| Facturation multi-pays (FNE équivalents SN/BJ, TVA locale) | V2+ - résolution via le profil pays du tenant. |
| Tableau de bord analytique des revenus récurrents (MRR, ARR, churn) | V2/V3. |
| Exports comptables (CSV, connexion Sage/QuickBooks) | V2. |
V3 - Vision¶
- Marketplace de facturation (multi-marque, white-label).
- API de facturation exposée aux partenaires.
- Prédiction de churn basée sur les comportements de paiement.
A6. Questions ouvertes¶
| ID | Question | Impact | Propriétaire |
|---|---|---|---|
| OQ-01 | Quel est le numéro NIF/CC officiel de la société opératrice de la plateforme pour figurer sur les factures émises ? | Bloque la génération du PDF de facture | Fondateur |
| OQ-02 | Quelle banque et quels coordonnées bancaires (IBAN/RIB) figurer sur les factures pour le paiement par virement ? | Bloque la story BILL-US-008 | Fondateur |
| OQ-03 | Faut-il un numéro de commande (bon de commande / PO number) sur la facture pour certains tenants (grandes organisations) ? |
Ajout d'un champ optionnel sur la config tenant | Fondateur |
| OQ-04 | Quelle est la durée légale de rétention des factures et PDFs en CI (droit fiscal + ARTCI) ? | Paramètre de rétention MinIO et politique d'archivage | /legal-audit FNE |
| OQ-05 | Est-ce que l'API FNE de la DGI est disponible en mode sandbox pour les tests d'intégration ? | Bloquerait les tests BILL-US-009 en dev/staging | Fondateur / DGI |
| OQ-06 | Le format exact d'une facture FNE certifiée (champs obligatoires, positionnement QR code, signature DGI) est-il documenté dans une spécification DGI publique ? | Gabarit PDF certifié | /legal-audit FNE |
| OQ-07 | Un avoir (credit note) est-il également soumis à FNE si jamais on en émet un en dehors de la plateforme ? | Hors V1 mais à anticiper pour V2 | /legal-audit FNE |
A7. Temporairement validé (TV)¶
Les éléments suivants sont implémentés dans l'état actuel des connaissances mais doivent être confirmés par un /legal-audit FNE avant la mise en production de BILL.
| ID | Affirmation | Source actuelle | Risque si invalide |
|---|---|---|---|
| TV-01 | La TVA applicable aux services SaaS B2B fournis en CI est de 18 %. | Connaissance générale du code fiscal CI. | Taux incorrect sur toutes les factures → redressement fiscal. |
| TV-02 | Le format FNE exige : numéro de facture DGI, QR code, NIF émetteur + récepteur, date, montants HT/TVA/TTC, désignation des produits/services. | Connaissance générale FNE / DGI CI. | Factures rejetées par FNE ou non conformes à l'audit DGI. |
| TV-03 | Un prestataire SaaS peut émettre une facture avec un numéro interne (BILL-AAAA-NNNN) avant la certification FNE, laquelle peut intervenir en arrière-plan. | Pratique observée dans le marché CI. | La DGI pourrait exiger que le numéro de facture définitif ne soit assigné qu'après certification. |
| TV-04 | Le tenant peut légalement payer une facture avant que la certification FNE soit complète (paiement sur brouillon). | Décision produit validée par le fondateur ; pratique observée. | Risque fiscal pour le tenant (TVA non déductible sur facture non certifiée). |
| TV-05 | La durée de rétention légale des factures en CI est d'au moins 10 ans (délai de prescription commerciale et fiscale). | Connaissance générale du droit des affaires CI. | MinIO configuré avec une durée inadéquate. |
| TV-06 | Les emails transactionnels de facturation vers un administrateur tenant (personne morale) ne nécessitent pas de base légale ARTCI particulière au-delà de l'exécution du contrat. | Application du principe contractuel ; loi n°2013-450 non vérifiée article par article. | Risque ARTCI si une déclaration préalable est requise. |
Partie B - Spécifications Fonctionnelles¶
B1. Règles fonctionnelles¶
Groupe BILL-R-100 : Numérotation et génération de factures¶
BILL-R-101 - Numérotation des factures
Format : BILL-{AAAA}-{NNNN}
AAAA = année civile de la date de facture (ex : 2026)
NNNN = séquence sur 4 chiffres, remise à zéro le 1er janvier de chaque année,
globale (toutes tenants confondus)
Exemples : BILL-2026-0001, BILL-2026-0042
Règle : le numéro interne est assigné à la création de la facture (statut DRAFT).
Le numéro FNE certifié (attribué par la DGI) est un champ distinct (fne_number).
Les deux numéros figurent sur le PDF certifié.
Contrainte : UNIQUE sur (invoice_number) - jamais réutilisé même si la facture est annulée.
BILL-R-102 - Idempotence de génération
SI une facture existe avec (tenant_id, invoice_type, period_start, period_end)
ALORS retourner la facture existante sans en créer une nouvelle.
Exception : invoice_type = ONE_OFF → pas de contrainte d'unicité sur la période.
BILL-R-103 - Montant de la facture
Pour chaque ligne :
total_ht = quantité × prix_unitaire_ht
montant_ht_facture = SUM(total_ht de toutes les lignes)
tva_amount = ROUND(montant_ht_facture × tva_rate, 0) -- arrondi arithmétique, 0 décimale
montant_ttc = montant_ht_facture + tva_amount
Tous les montants sont en XOF entiers (INTEGER).
Pas de devise autre que XOF en V1.
BILL-R-104 - Données de snapshot à la génération
À la génération, les champs suivants sont copiés depuis TenantBillingConfig
dans l'entité Invoice (snapshot immuable) :
- tva_rate
- tenant_nif
- billing_contact_email
- payment_terms_days
Si TenantBillingConfig est modifié ultérieurement, les factures existantes ne sont pas affectées.
BILL-R-105 - Date d'échéance
Groupe BILL-R-200 : FNE (Facture Normalisée Électronique)¶
BILL-R-201 - Soumission FNE à la création [TV-02][TV-03]
À chaque création de facture :
1. Créer la facture avec fne_status = PENDING
2. Mettre en file d'attente un job de soumission FNE (asynchrone)
3. Le job envoie à l'API FNE : NIF émetteur (plateforme), NIF récepteur (tenant),
numéro interne, date, lignes, montants HT/TVA/TTC
4. SI FNE répond avec succès :
fne_status = CERTIFIED
fne_number = numéro retourné par DGI
fne_qr_code_url = URL du QR code retourné
fne_certified_at = now()
→ Générer le PDF certifié (voir BILL-R-203)
5. SI FNE retourne une erreur :
→ Appliquer BILL-R-202 (politique de relance)
BILL-R-202 - Politique de relance FNE
Tentative | Délai avant la tentative
1 | 5 secondes
2 | 30 secondes
3 | 5 minutes
4 | 30 minutes
5 | 2 heures
Après la 5e tentative échouée :
fne_status = FAILED
→ Envoyer une alerte email à l'administrateur plateforme
→ Enregistrer l'événement audit INVOICE_FNE_FAILED
Re-soumission manuelle : l'admin clique "Re-soumettre à FNE"
→ Réinitialiser le compteur de tentatives
→ Retourner à l'étape 3 de BILL-R-201
BILL-R-203 - Génération du PDF certifié
Déclencheur : fne_status passe à CERTIFIED
Le PDF certifié doit contenir (TV-02) :
En-tête émetteur :
- Nom légal de la plateforme
- Adresse, NIF/CC plateforme
En-tête récepteur :
- Nom légal du tenant
- Adresse du tenant, NIF/CC tenant (snapshot)
Identifiants :
- Numéro de facture interne (BILL-AAAA-NNNN)
- Numéro FNE certifié (fne_number)
- QR code DGI (fne_qr_code_url)
Dates :
- Date de facturation (invoice_date)
- Date d'échéance (due_date)
Corps :
- Lignes de détail : description, quantité, prix unitaire HT, total HT
- Sous-total HT
- TVA {tva_rate × 100} % : {tva_amount} XOF
- Total TTC : {montant_ttc} XOF
Pied de page :
- Coordonnées bancaires plateforme (OQ-02)
- Référence à indiquer au virement : {invoice_number}
- Mention légale : « Papillon Software est un prestataire de services techniques agréé. Facture certifiée par la Direction Générale des Impôts - Côte d'Ivoire. »
Stockage : MinIO, chemin : invoices/{tenant_id}/{invoice_id}/certified.pdf
Rétention : TV-05 (10 ans minimum)
BILL-R-204 - Comportement si paiement reçu avant certification FNE [TV-03][TV-04]
SI invoice.status = PAID ET invoice.fne_status = PENDING ou SUBMITTED
ALORS
Continuer la soumission FNE en arrière-plan
Quand FNE certifie : fne_number et pdf_certified_url mis à jour sur la facture
Le statut PAID n'est pas régressé
Le PDF certifié devient disponible au téléchargement rétroactivement
SINON
Flux normal
Groupe BILL-R-300 : Paiement par mobile money¶
BILL-R-301 - Initiation du paiement mobile money
Prérequis : invoice.status ∈ {DRAFT, CERTIFIED, OVERDUE}
1. Générer une clé d'idempotence : BILL-{invoice_id}-{attempt_number}
(attempt_number = nombre de tentatives précédentes + 1)
2. Appeler l'abstraction PaymentProvider (module PAY) avec :
- montant = invoice.montant_ttc
- devise = XOF
- référence = invoice_number
- idempotency_key
- description = « Facture {invoice_number} - Papillon Collection Solution »
3. Retourner au tenant l'URL ou le prompt de paiement du prestataire
BILL-R-302 - Confirmation de paiement mobile money (webhook)
À réception d'un webhook du prestataire de paiement :
1. Vérifier la signature du webhook (même logique que module PAY)
2. Trouver la facture via la référence
3. SI status ≠ PAID (vérification anti-doublon) :
invoice.status = PAID
invoice.paid_at = now()
Créer BillingPayment(payment_method = MOBILE_MONEY,
provider_reference = transaction_id_prestataire,
amount_ttc = montant_ttc,
paid_at = now(),
idempotency_key)
Émettre événement audit INVOICE_PAID_MOBILE_MONEY
Envoyer email de confirmation au contact de facturation
4. SI status = PAID déjà → ignorer (idempotent)
BILL-R-303 - Échec ou timeout de paiement mobile money
SI le prestataire retourne une erreur ou si le webhook n'est pas reçu dans le délai attendu :
Afficher au tenant : « Le paiement a échoué ou n'a pas pu être confirmé.
Vous pouvez réessayer. »
Bouton "Réessayer" : déclenche BILL-R-301 avec attempt_number incrémenté
La clé d'idempotence différente garantit qu'aucun double débit n'est possible
si le prestataire a en réalité traité la première tentative.
Groupe BILL-R-400 : Paiement par virement bancaire¶
BILL-R-401 - Affichage des instructions de virement
Lorsque le tenant sélectionne "Virement bancaire" :
Afficher :
- Banque : {bank_name de la config plateforme}
- Titulaire : {account_holder_name}
- IBAN/RIB : {iban_rib} (OQ-02)
- Référence obligatoire : {invoice_number}
- Montant exact à virer : {montant_ttc} XOF
- Message : « Mentionnez impérativement la référence {invoice_number}
dans le libellé de votre virement. »
BILL-R-402 - Réconciliation manuelle par l'admin plateforme
L'admin plateforme saisit :
- bank_transaction_id (obligatoire, chaîne libre)
- payment_date (obligatoire, date)
- notes (optionnel)
Puis clique "Marquer payée" :
SI invoice.status = PAID → erreur : « Facture déjà payée »
SINON :
invoice.status = PAID
invoice.paid_at = payment_date
Créer BillingPayment(payment_method = BANK_TRANSFER,
provider_reference = bank_transaction_id,
amount_ttc = montant_ttc,
paid_at = payment_date,
recorded_by = admin_user_id)
Émettre audit INVOICE_PAID_BANK_TRANSFER
Envoyer email de confirmation au contact de facturation
Groupe BILL-R-500 : Gestion des retards et rappels¶
BILL-R-501 - Détection des factures en retard
Job planifié : quotidien, heure fixe (ex : 08h00 UTC)
POUR CHAQUE facture WHERE status ∈ {DRAFT, CERTIFIED} AND due_date < CURRENT_DATE :
invoice.status = OVERDUE
Émettre audit INVOICE_OVERDUE
Note : une facture peut passer OVERDUE avec fne_status = PENDING si FNE est lente.
Le passage à OVERDUE n'affecte pas le flux FNE.
BILL-R-502 - Rappel unique de retard
Immédiatement après BILL-R-501, pour chaque facture nouvellement OVERDUE :
SI overdue_reminder_sent_at IS NULL :
Envoyer email à billing_contact_email (snapshot) :
Sujet : « Rappel : Facture {invoice_number} en retard de paiement »
Corps : numéro, montant TTC, date d'échéance, lien vers le back-office tenant,
message : « Cette facture était due le {due_date}.
Merci de procéder au règlement dès que possible. »
invoice.overdue_reminder_sent_at = now()
Émettre audit INVOICE_OVERDUE_REMINDER_SENT
SINON :
Ne pas envoyer (un seul rappel automatique en V1)
Groupe BILL-R-600 : Notifications email¶
BILL-R-601 - Email "Nouvelle facture émise"
Déclencheur : création d'une facture (statut DRAFT)
Destinataire : billing_contact_email (snapshot)
Canal : email direct (pas via le module NOTIF)
Contenu :
Sujet : « Nouvelle facture Papillon Collection Solution - {invoice_number} »
Corps :
- Type de facture (Abonnement / Frais d'installation)
- Période couverte
- Montant TTC : {montant_ttc} XOF
- Date d'échéance : {due_date}
- Lien : « Accéder à votre espace facturation » → back-office tenant (authentification Keycloak)
- Note : « La certification DGI (FNE) est en cours.
Vous serez notifié dès que votre facture certifiée sera disponible au téléchargement. »
BILL-R-602 - Email "Paiement confirmé"
Déclencheur : invoice.status passe à PAID (mobile money ou virement)
Destinataire : billing_contact_email (snapshot)
Contenu :
Sujet : « Paiement reçu - Facture {invoice_number} »
Corps :
- Numéro de facture
- Montant réglé : {montant_ttc} XOF
- Mode de paiement : Mobile money / Virement bancaire
- Référence de transaction : {provider_reference}
- Lien : « Télécharger votre quittance de paiement »
BILL-R-603 - Email de rappel de retard → voir BILL-R-502
Groupe BILL-R-700 : Événements d'audit¶
Chaque événement produit une entrée dans le module AUDIT avec les champs requis par le PRD V1 §18.
| Code événement | Déclencheur | Actor |
|---|---|---|
INVOICE_GENERATED |
Création d'une facture | admin user_id |
INVOICE_FNE_SUBMITTED |
Tentative de soumission FNE | SYSTEM |
INVOICE_FNE_CERTIFIED |
Retour de certification FNE | SYSTEM |
INVOICE_FNE_FAILED |
Toutes les relances FNE épuisées | SYSTEM |
INVOICE_FNE_RESUBMITTED |
Re-soumission manuelle déclenchée par admin | admin user_id |
INVOICE_PAID_MOBILE_MONEY |
Webhook de confirmation du prestataire | SYSTEM |
INVOICE_PAID_BANK_TRANSFER |
Admin marque le virement reçu | admin user_id |
INVOICE_OVERDUE |
Job quotidien détecte le retard | SYSTEM |
INVOICE_OVERDUE_REMINDER_SENT |
Email de rappel envoyé | SYSTEM |
TENANT_BILLING_CONFIG_UPDATED |
Admin modifie la config facturation | admin user_id |
Champs requis : event_id, event_type, occurred_at, actor_id, actor_type (USER/SYSTEM), tenant_id, invoice_id (nullable), payload (delta pour CONFIG_UPDATED).
B2. Machine à états - Cycle de vie d'une facture¶
stateDiagram-v2
[*] --> DRAFT : Génération manuelle (BILL-R-101)
DRAFT --> CERTIFIED : FNE certifie (BILL-R-201)
DRAFT --> PAID : Paiement reçu avant FNE (BILL-R-302/402)
DRAFT --> OVERDUE : due_date dépassée, job quotidien (BILL-R-501)
CERTIFIED --> PAID : Paiement mobile money ou virement (BILL-R-302/402)
CERTIFIED --> OVERDUE : due_date dépassée, job quotidien (BILL-R-501)
OVERDUE --> PAID : Paiement reçu après retard (BILL-R-302/402)
PAID --> [*] : Statut final
note right of DRAFT
fne_status : PENDING → SUBMITTED → CERTIFIED
↘ FAILED (après 5 relances)
Paiement autorisé dès DRAFT [TV-04]
end note
note right of PAID
Si paiement reçu avant FNE :
La certification FNE continue en arrière-plan (BILL-R-204)
end note
Machine à états FNE (orthogonale au statut facture) :
stateDiagram-v2
[*] --> PENDING : Facture créée
PENDING --> SUBMITTED : Tentative de soumission envoyée
SUBMITTED --> CERTIFIED : Réponse DGI positive
SUBMITTED --> PENDING : Échec → relance (< 5 tentatives)
SUBMITTED --> FAILED : Toutes les relances épuisées
FAILED --> SUBMITTED : Re-soumission manuelle admin
CERTIFIED --> [*]
B3. Tables de décision¶
Table 1 - Boutons d'action affichés sur une facture (vue tenant)¶
| Statut facture | fne_status | Bouton "Payer maintenant" | Lien "Facture certifiée PDF" | Lien "Quittance de paiement" |
|---|---|---|---|---|
| DRAFT | PENDING / SUBMITTED | ✅ Visible + actif [TV-04] | ❌ | ❌ |
| DRAFT | CERTIFIED | ✅ Visible + actif | ✅ | ❌ |
| DRAFT | FAILED | ✅ Visible + actif | ❌ | ❌ |
| CERTIFIED | CERTIFIED | ✅ Visible + actif | ✅ | ❌ |
| OVERDUE | CERTIFIED | ✅ Visible + actif (avec badge "En retard") | ✅ | ❌ |
| OVERDUE | PENDING | ✅ Visible + actif (avec badge "En retard") | ❌ | ❌ |
| PAID | CERTIFIED | ❌ | ✅ | ✅ |
| PAID | PENDING / SUBMITTED | ❌ | ❌ (en cours) | ✅ |
| PAID | FAILED | ❌ | ❌ | ✅ |
Table 2 - Actions disponibles pour l'admin plateforme sur une facture¶
| Statut facture | fne_status | "Marquer payée" (virement) | "Re-soumettre FNE" | "Générer quittance PDF" |
|---|---|---|---|---|
| DRAFT | PENDING / SUBMITTED | ✅ | ❌ | ❌ |
| DRAFT | CERTIFIED | ✅ | ❌ | ❌ |
| DRAFT | FAILED | ✅ | ✅ | ❌ |
| CERTIFIED | CERTIFIED | ✅ | ❌ | ❌ |
| OVERDUE | tout | ✅ | ✅ (si FAILED) | ❌ |
| PAID | tout | ❌ | ✅ (si FAILED) | ✅ |
B4. Modèle de données¶
Les entités suivantes s'ajoutent au modèle V1 PRD §27. Toutes les tables sont scopées par
tenant_id.
tenant_billing_config¶
| Colonne | Type | Contraintes | Description |
|---|---|---|---|
id |
UUID | PK | |
tenant_id |
UUID | FK → tenant, UNIQUE |
Un seul config par tenant |
plan_type |
VARCHAR | NOT NULL, CHECK ∈ {MONTHLY, ANNUAL} | |
price_ht |
INTEGER | NOT NULL, > 0 | XOF, mensuel |
annual_price_ht |
INTEGER | NULL | XOF, annuel ; NULL si plan MONTHLY uniquement |
tva_rate |
NUMERIC(5,4) | NOT NULL, DEFAULT 0.1800 | |
tenant_nif |
VARCHAR(50) | NULL | NIF/CC du tenant pour la facture |
billing_contact_email |
VARCHAR(255) | NOT NULL | |
payment_terms_days |
INTEGER | NOT NULL, DEFAULT 15 | |
onboarding_fee_ht |
INTEGER | NOT NULL, DEFAULT 0 | XOF |
billing_status |
VARCHAR | NOT NULL, DEFAULT 'ACTIVE', CHECK ∈ {ACTIVE, PAUSED} | SUSPENDED = V2 |
notes |
TEXT | NULL | Notes internes admin plateforme |
created_at |
TIMESTAMPTZ | NOT NULL | |
updated_at |
TIMESTAMPTZ | NOT NULL |
invoice¶
| Colonne | Type | Contraintes | Description |
|---|---|---|---|
id |
UUID | PK | |
tenant_id |
UUID | FK → tenant, NOT NULL |
|
invoice_number |
VARCHAR(20) | UNIQUE, NOT NULL | BILL-AAAA-NNNN |
invoice_type |
VARCHAR | NOT NULL, CHECK ∈ {SUBSCRIPTION, ONBOARDING_FEE, ONE_OFF} | |
period_start |
DATE | NULL | Nul pour ONE_OFF |
period_end |
DATE | NULL | Nul pour ONE_OFF |
invoice_date |
DATE | NOT NULL | |
due_date |
DATE | NOT NULL | |
status |
VARCHAR | NOT NULL, CHECK ∈ {DRAFT, CERTIFIED, PAID, OVERDUE} | |
fne_status |
VARCHAR | NOT NULL, CHECK ∈ {PENDING, SUBMITTED, CERTIFIED, FAILED} | |
fne_number |
VARCHAR(100) | NULL | Numéro DGI |
fne_qr_code_url |
TEXT | NULL | URL du QR code DGI |
fne_submitted_at |
TIMESTAMPTZ | NULL | |
fne_certified_at |
TIMESTAMPTZ | NULL | |
fne_retry_count |
INTEGER | NOT NULL, DEFAULT 0 | |
amount_ht |
INTEGER | NOT NULL | XOF |
tva_amount |
INTEGER | NOT NULL | XOF |
amount_ttc |
INTEGER | NOT NULL | XOF |
tva_rate_snapshot |
NUMERIC(5,4) | NOT NULL | Snapshot au moment de la génération |
tenant_nif_snapshot |
VARCHAR(50) | NULL | Snapshot |
billing_email_snapshot |
VARCHAR(255) | NOT NULL | Snapshot |
payment_terms_days_snapshot |
INTEGER | NOT NULL | Snapshot |
pdf_draft_url |
TEXT | NULL | MinIO path |
pdf_certified_url |
TEXT | NULL | MinIO path, disponible après FNE |
overdue_reminder_sent_at |
TIMESTAMPTZ | NULL | |
paid_at |
TIMESTAMPTZ | NULL | |
created_by |
UUID | FK → user |
Admin plateforme |
created_at |
TIMESTAMPTZ | NOT NULL | |
updated_at |
TIMESTAMPTZ | NOT NULL |
Contrainte d'idempotence :
UNIQUE (tenant_id, invoice_type, period_start, period_end)
WHERE invoice_type IN ('SUBSCRIPTION', 'ONBOARDING_FEE')
invoice_line_item¶
| Colonne | Type | Contraintes | Description |
|---|---|---|---|
id |
UUID | PK | |
invoice_id |
UUID | FK → invoice, NOT NULL |
|
description |
VARCHAR(500) | NOT NULL | |
quantity |
INTEGER | NOT NULL, DEFAULT 1 | |
unit_price_ht |
INTEGER | NOT NULL | XOF |
total_ht |
INTEGER | NOT NULL | = quantity × unit_price_ht |
sort_order |
INTEGER | NOT NULL, DEFAULT 0 | |
created_at |
TIMESTAMPTZ | NOT NULL |
billing_payment¶
| Colonne | Type | Contraintes | Description |
|---|---|---|---|
id |
UUID | PK | |
invoice_id |
UUID | FK → invoice, NOT NULL |
|
payment_method |
VARCHAR | NOT NULL, CHECK ∈ {MOBILE_MONEY, BANK_TRANSFER} | |
provider_reference |
VARCHAR(255) | NOT NULL | ID transaction prestataire ou référence virement |
amount_ttc |
INTEGER | NOT NULL | XOF |
paid_at |
TIMESTAMPTZ | NOT NULL | |
recorded_by |
UUID | FK → user, NULL |
NULL = automatique (webhook) |
idempotency_key |
VARCHAR(255) | NULL | Clé d'idempotence mobile money |
created_at |
TIMESTAMPTZ | NOT NULL |
Contrainte : UNIQUE (idempotency_key) WHERE idempotency_key IS NOT NULL
B5. Implications multi-tenant¶
Isolation des données¶
- Toutes les requêtes sur
invoice,invoice_line_item,billing_paymentsont scopées partenant_idrésolu depuis le contexte d'authentification Keycloak. - Un administrateur tenant ne voit que ses propres factures. La résolution du
tenant_idest faite côté serveur, jamais fourni par le client. - L'administrateur plateforme voit toutes les factures (rôle
PLATFORM_ADMIN), mais chaque ligne affiche le nom du tenant pour identification.
Profil pays et TVA¶
- Le taux TVA par défaut (
tva_rate = 0.1800) est défini dans le profil pays CI. - En V1, tous les tenants sont CI → taux fixe 18 %.
- En V2, le profil pays du tenant déterminera le taux applicable (ex : CI → 18 %, CM → 19,25 %, etc.).
- Le champ
tva_rate_snapshotdansinvoicegarantit qu'une modification du profil pays n'affecte pas les factures déjà émises.
Config par tenant vs config globale¶
TenantBillingConfigest une entité par tenant : le prix, le NIF, le contact de facturation et les frais d'installation sont tous propres au tenant.- Les coordonnées bancaires de la plateforme (émetteur des factures) sont une configuration globale (non scopée par tenant), stockée dans la configuration applicative (non dans la DB).
Profils DB V1 (shared + BYO-DB)¶
- Le module BILL fonctionne identiquement sur les deux profils DB V1 (shared-schema et BYO-DB).
- Sur BYO-DB : les tables
tenant_billing_config,invoice,invoice_line_item,billing_paymentsont créées dans la base du tenant comme toutes les autres tables scopées tenant. - Note :
invoice_number(BILL-AAAA-NNNN) doit rester globalement unique sur l'ensemble de la plateforme → sur BYO-DB, la séquence globale doit être maintenue dans la base partagée de la plateforme, non dans la base du tenant. À préciser par l'ARCHITECT.
B6. Dépendances inter-modules¶
Modules consommés par BILL¶
| Module | Dépendance | Détail |
|---|---|---|
| TENANT | Lecture | tenant_id, nom légal, adresse, country_code pour les factures. TenantBillingConfig est créée/éditée via l'interface TENANT admin. |
| AUTH | Authz | Rôle PLATFORM_ADMIN pour les actions admin ; rôle TENANT_ADMIN pour la vue tenant. Keycloak fournit le token avec les claims nécessaires. |
| PAY | Abstraction paiement | BILL consomme l'abstraction PaymentProvider du module PAY pour les paiements mobile money. Le prestataire concret (agrégateur ou direct) est résolu par le profil pays CI via PAY. BILL ne contient pas son propre client prestataire. |
| AUDIT | Écriture | Tous les événements BILL-R-700 sont émis vers le module AUDIT. |
API / événements exposés par BILL¶
| Endpoint / Événement | Consommateur | Description |
|---|---|---|
GET /api/billing/invoices |
Tenant admin (front) | Liste des factures du tenant authentifié |
GET /api/billing/invoices/{id} |
Tenant admin (front) | Détail d'une facture |
POST /api/billing/invoices/{id}/pay/mobile-money |
Tenant admin (front) | Initier le paiement mobile money |
GET /api/billing/invoices/{id}/pdf/certified |
Tenant admin (front) | Télécharger le PDF certifié (302 redirect MinIO presigned URL) |
GET /api/billing/invoices/{id}/pdf/receipt |
Tenant admin (front) | Télécharger la quittance de paiement |
POST /api/v1/internal/billing/webhook/payment |
Prestataire de paiement | Webhook de confirmation de paiement |
GET /api/admin/billing/invoices |
Admin plateforme (front) | Liste globale de toutes les factures |
POST /api/admin/billing/invoices |
Admin plateforme (front) | Générer une nouvelle facture |
POST /api/admin/billing/invoices/{id}/mark-paid |
Admin plateforme (front) | Réconciliation virement bancaire |
POST /api/admin/billing/invoices/{id}/resubmit-fne |
Admin plateforme (front) | Re-soumettre à FNE |
GET /api/admin/billing/tenants/{id}/config |
Admin plateforme (front) | Lire la config de facturation d'un tenant |
PUT /api/admin/billing/tenants/{id}/config |
Admin plateforme (front) | Mettre à jour la config de facturation |
Événements internes émis¶
| Événement Spring (ApplicationEvent) | Consommateur | Déclencheur |
|---|---|---|
InvoiceGenerated |
AUDIT | Création facture |
InvoicePaid |
AUDIT | Paiement confirmé |
InvoiceOverdue |
AUDIT | Job quotidien |
FneCertified |
AUDIT, PDF generation service | Certification DGI |
FneFailed |
AUDIT, email admin | Toutes relances épuisées |
Partie C - Contraintes et Limites¶
C1. Conformité réglementaire¶
FNE - Facture Normalisée Électronique (CI) [TV-01 à TV-06]¶
⚠️ Temporairement validé - l'ensemble des règles FNE ci-dessous est basé sur la connaissance publique du dispositif DGI CI. Un
/legal-audit FNEdoit être conduit avant la mise en production de BILL-001.
- Obligation : toute facture B2B émise par une entreprise domiciliée en CI ou facturant un client en CI doit être certifiée par le système FNE de la Direction Générale des Impôts.
- Certification : la DGI attribue un numéro de facture normalisée (
fne_number) et un QR code permettant à l'administration fiscale de retrouver la facture dans sa base. Ce numéro doit figurer sur le PDF remis au client. - TVA : les services SaaS B2B sont assujettis à la TVA au taux de 18 % (TV-01). Les montants HT, TVA et TTC doivent figurer en clair sur la facture.
- Rétention : les factures et leurs PDFs doivent être conservés au minimum 10 ans (TV-05 - à confirmer par
/legal-audit FNE). - Paiement avant certification : autorisé par décision produit (TV-04) - à valider légalement.
TVA et obligations fiscales du prestataire¶
- La plateforme opératrice est l'émetteur des factures. Son NIF/CC doit figurer sur chaque facture (OQ-01 - à renseigner avant BILL-001).
- Le module BILL ne calcule pas de déclaration TVA ; il fournit les données nécessaires à la comptabilité de la plateforme (export CSV en V2).
ARTCI / Loi n°2013-450 (CI)¶
- Les emails transactionnels de facturation (invoice issued, payment confirmed, overdue) sont adressés à l'administrateur du tenant (personne morale B2B), sur la base de l'exécution du contrat de service. La base légale ARTCI est présumée contractuelle (TV-06 - à confirmer).
- Les données personnelles traitées par BILL sont limitées à : email de contact facturation, NIF/CC du tenant (donnée de la personne morale, pas de la personne physique). Minimisation respectée.
C2. Localisation¶
| Dimension | Règle |
|---|---|
| Langue | Français uniquement en V1 pour toutes les interfaces BILL (back-office, emails, PDFs). |
| Monnaie | XOF uniquement. Affichage : espace comme séparateur de milliers, 0 décimale. Ex : 59 000 XOF. |
| Dates | Format DD/MM/AAAA. Ex : 01/05/2026. |
| Taux TVA | Toujours affiché sous la forme TVA 18 %. |
| Emails | Envoyés depuis une adresse noreply@[domaine-plateforme] avec un nom d'expéditeur en français. |
| Gabarit en français, police lisible, compatible impression A4. | |
| Numéro de téléphone | Non utilisé dans le module BILL (email uniquement). |
C3. Sécurité¶
| Règle | Détail |
|---|---|
| Isolation tenant | Toute requête d'un tenant admin est scopée par tenant_id résolu depuis le token Keycloak. Aucune donnée de facturation d'un autre tenant n'est jamais exposée. |
| Contrôle d'accès | Les endpoints /admin/billing/* exigent le rôle PLATFORM_ADMIN. Les endpoints /billing/* (vue tenant) exigent TENANT_ADMIN. Authz sur chaque endpoint. |
| Vérification des webhooks de paiement | BILL vérifie la signature HMAC de chaque webhook reçu du prestataire de paiement avant tout traitement (même logique que PAY). |
| Idempotence des webhooks | Un webhook de paiement déjà traité est ignoré silencieusement (vérification sur idempotency_key). |
| PDFs en MinIO | Les PDFs de factures et quittances sont stockés en MinIO avec une URL pré-signée à durée limitée (ex : 15 min) lors du téléchargement. Jamais d'URL permanente publique. |
| Pas de données de carte en clair | BILL ne stocke aucune donnée de carte bancaire. Seule la référence de transaction du prestataire est stockée. |
| Montants non modifiables | Une fois une facture créée, ses montants (amount_ht, tva_amount, amount_ttc) sont immuables. Toute correction nécessite une annulation hors plateforme + nouvelle facture (V1). |
| Chiffrement en transit | Toutes les communications BILL → FNE, BILL → prestataire paiement, BILL → serveur email se font en HTTPS/TLS. |
| Audit log immuable | Tous les événements BILL alimentent le module AUDIT (append-only). |
C4. Connectivité¶
Le module BILL est un module opérateur-side (back-office) et admin-side uniquement. Il n'est pas exposé aux assurés finaux. Les contraintes de connectivité basse bande passante du module CP (portail client) ne s'appliquent pas ici.
Exigences spécifiques :
| Scénario | Comportement attendu |
|---|---|
| FNE indisponible | La génération de facture réussit (DRAFT). FNE est soumise en arrière-plan avec la politique de relance BILL-R-202. L'admin plateforme est alerté si FNE reste indisponible > 3 h. |
| Prestataire de paiement indisponible | Le bouton "Payer maintenant" affiche un message d'erreur clair ; le tenant peut réessayer plus tard. Aucune donnée de paiement n'est créée jusqu'à confirmation du prestataire. |
| Webhook de paiement retardé | La facture reste dans son statut courant (DRAFT/CERTIFIED/OVERDUE) jusqu'à réception du webhook. Une requête de statut polling peut être implémentée en V2. En V1, la réconciliation manuelle (virement) reste disponible en fallback si le webhook ne revient pas. |
| Serveur email indisponible | L'échec d'envoi d'un email transactionnel est loggé (événement audit) mais ne bloque pas la génération ou la confirmation de facture. L'email peut être re-déclenché manuellement par l'admin en V2. |
C5. Hors périmètre¶
Les fonctionnalités suivantes sont explicitement hors périmètre V1 afin de prévenir le scope creep :
| Hors périmètre | Version cible | Justification |
|---|---|---|
| Scheduler de récurrence automatique (génération mensuelle/annuelle sans action admin) | V2 | Trop peu de tenants pilotes pour justifier la complexité. |
| Avoirs (credit notes) et annulations de factures dans la plateforme | V2 | Cas rares en phase pilote ; gérés manuellement hors plateforme. |
| Dunning automatisé (chaîne de relances, escalades, suspension) | V2 | Nécessite une gouvernance contractuelle claire et un préavis légal. |
| Suspension automatique d'accès tenant en cas d'impayé | V2 | Idem ; impact opérationnel fort. |
| Switching de plan mid-cycle avec crédit automatique | V2 | Nécessite un moteur de crédit/débit. |
| Self-service pricing et activation de plan par le tenant | V2 | Modèle de vente assistée en V1. |
| Facturation multi-pays (SN, BJ, CM, GA - autres régimes e-invoicing) | V2/V3 | V1 = CI uniquement ; la résolution via le profil pays est déjà architecturée. |
| Exports comptables (CSV, Sage, QuickBooks) | V2 | Non demandé en V1. |
| Calcul déclaration TVA ou liasse fiscale | Hors périmètre produit | Responsabilité de la comptabilité de la plateforme. |
| Paiement par carte bancaire (CB/Visa/Mastercard) | V2 | Selon disponibilité du prestataire et KYC B2B ; non prioritaire en V1. |
| API de facturation exposée aux partenaires | V3 | Architecture API-first = V3. |
| Tableau de bord MRR/ARR/churn | V2/V3 | Analytics = V2 minimum. |
Quittance tenant téléchargeable avant FNE si fne_status = FAILED |
V1 | La quittance de paiement (preuve de transaction) est disponible ; seul le PDF certifié DGI est bloqué sur fne_status ≠ CERTIFIED. |
Fin du document - billing-prd.md - v1.0 - 2026-04-30