# ADR-020 : Paiements mobile money déclarés et rapprochement quotidien

**Statut :** Accepté
**Date :** 2026-10-23
**Contexte :** Encaissement des ventes par Wave, Orange Money, MTN et Moov, sans prestataire de paiement. Complète l'ADR-017, qui ne couvre que les abonnements.

---

## Contexte

Les clients des commerçants paient de plus en plus par mobile money, sur le QR marchand du commerçant. Dans gestionstock, un mode de paiement n'a qu'un nom (`payment_modes.name`) et une vente en caisse n'enregistre aucune référence de transaction. Personne ne peut donc vérifier qu'un « paiement Wave » saisi par un vendeur est bien arrivé sur le compte du commerçant. Un même reçu peut aussi être présenté deux fois.

Le compte GeniusPay de l'application (ADR-017) appartient à Welely. L'utiliser pour encaisser les ventes des clients ferait transiter leur argent par Welely : activité de paiement pour compte de tiers encadrée par la BCEAO, reversements à gérer, montants qui ne sont pas des revenus de Welely. Ce choix est écarté.

## Décision

### 1. L'argent va directement au commerçant, l'application contrôle

Le client paie sur le QR ou le numéro marchand **du commerçant**. L'application ne déclenche ni ne reçoit aucun paiement : elle enregistre la **référence de la transaction** saisie par le vendeur, puis permet de rapprocher chaque jour les paiements déclarés du relevé de l'opérateur.

### 2. Nature des modes de paiement

- `payment_modes.kind` : `cash`, `mobile_money`, `card`, `bank_transfer`, `cheque`, `other` (enum `PaymentModeKind`).
- `payment_modes.reference_required` : la référence de transaction est obligatoire pour ce mode. Vrai par défaut pour `mobile_money`, réglable par l'admin pour les autres.
- La migration classe les modes existants d'après leur nom (Wave, Orange, MTN, Moov, Flooz, « Mobile Money » deviennent `mobile_money` avec référence obligatoire ; espèces, carte, virement et chèque sont reconnus). Les modes non reconnus restent `other`, sans obligation.

### 3. Référence de transaction

- `payments.external_reference` (64 caractères), normalisée : espaces retirés, majuscules. Format accepté : lettres, chiffres, `.`, `_`, `-`, entre 4 et 64 caractères.
- **Unique par mode de paiement**, garanti par la base (`payments_mode_reference_unique`). Une référence Wave déjà enregistrée est refusée (422), ce qui bloque la réutilisation d'un même reçu. Une course entre deux requêtes simultanées est convertie en 422, pas en erreur 500.
- La règle s'applique aux trois points de création d'un paiement : la caisse (`PosOrderService`), le règlement d'une facture (`OrderPaymentController`) et l'écran Paiements (`PaymentController`). Elle vit dans un service unique, `PaymentReferenceService`, appelé par les trois.
- Elle vaut pour les encaissements comme pour les décaissements : un paiement à un fournisseur par mobile money a lui aussi une référence.

### 4. Rapprochement quotidien

- Sont rapprochés les **encaissements** (`payment_type = in`) des modes à référence obligatoire, par entrepôt, par mode et par jour (`payments.date`).
- Écran « Rapprochement » : pour chaque jour et chaque mode, le total déclaré, le nombre de transactions et le statut. Le détail d'un jour liste chaque transaction (heure, montant, référence, vendeur) avec une case « retrouvée sur le relevé » (`payments.verified_at`, `verified_by`).
- **Clôturer un jour** enregistre une ligne `payment_reconciliations` : total déclaré et nombre de transactions **figés**, total du relevé saisi, écart, statut `reconciled` (écart nul) ou `discrepancy`, auteur et date. Unique par entrepôt, mode et jour.
- **Un jour clôturé est figé** : ses paiements ne peuvent plus être modifiés (mode, date, référence, case cochée) ni supprimés. Pour corriger, l'admin **rouvre** le jour (la ligne est supprimée), corrige, puis clôture à nouveau. Clôture et réouverture sont tracées dans le journal d'activité du tenant.
- Les jours passés non clôturés, ou clôturés en écart, apparaissent dans les alertes (7 derniers jours).

### 5. Accès

| Action | Permission | Garde de plan |
|---|---|---|
| Consulter, cocher, clôturer, rouvrir | `payments_reconcile` | Aucune : capacité générique (ADR-006) |

La permission est accordée au rôle `admin`. Le vendeur saisit la référence ; il ne contrôle pas ses propres encaissements.

## Alternatives Considérées

### Encaisser par le compte GeniusPay de Welely
Rejeté (voir Contexte) : risque réglementaire, responsabilité des reversements, montants de tiers dans les comptes de Welely.

### Chaque entreprise connecte son propre compte de prestataire
Différé. Confirmation automatique des paiements, mais le commerçant doit ouvrir un compte marchand (KYC). `PaymentGatewayContract` permettra de le faire plus tard, avec des clés par tenant.

### Comptes marchands secondaires chez GeniusPay (répartition des paiements)
Différé, en attente de la réponse de GeniusPay sur l'existence de cette offre. Ce serait la cible si elle existe : l'argent ne passerait jamais par Welely.

### Import du relevé de l'opérateur
Différé. Les formats d'export (Wave Business, Orange Money, MTN) ne sont ni documentés ni stables. La saisie du total du relevé et le pointage ligne à ligne suffisent pour une première version.

## Conséquences

- Nouvelles colonnes : `payment_modes.kind`, `payment_modes.reference_required`, `payments.external_reference`, `payments.verified_at`, `payments.verified_by`. Nouvelle table `payment_reconciliations`. Nouvelle permission `payments_reconcile`.
- Ajout de colonnes nullables et d'index sur `payments` : opérations en ligne sous MySQL 8, sans recopie de la table.
- Le vendeur doit saisir une référence pour chaque paiement mobile money, en caisse comme en back-office.
- Supprimer une vente dont le paiement appartient à un jour clôturé n'est pas bloqué dans cette version : le rapprochement garde son total figé et l'écart apparaîtra si le jour est rouvert.
- La caisse hors ligne (ADR-019) devra enregistrer la référence hors ligne et traiter une référence en double à la synchronisation.

## Quand Réviser

- Si GeniusPay propose des comptes marchands secondaires : ADR de remplacement pour l'encaissement en ligne.
- Si les opérateurs publient un format de relevé stable : import et pointage automatique.
- Si des écarts répétés montrent des déclarations frauduleuses : rapprochement par vendeur et alerte dédiée.
