# Audit complet du système — juillet 2026

Audit de bout en bout du plugin `cdv-property-manager` (réservations, paiements
Stripe, emails, synchro iCal, tâches planifiées, parcours client), mené après
l'incident SMTP de juillet (emails bloqués, demande de solde #9 perdue).

Chaque point indique : la gravité, l'impact client concret, et l'état
(✅ corrigé dans ce lot, 🔜 recommandé ensuite).

---

## 1. CRITIQUE — Paiements & argent

### 1.1 Confirmation de paiement falsifiable ✅
`confirm_payment()` (AJAX public) marquait une réservation « payée/confirmée »
sur la seule foi du navigateur, sans vérifier auprès de Stripe que le paiement
a réellement réussi. N'importe qui pouvait obtenir une réservation gratuite.
**Fix :** vérification Stripe (PaymentIntent `succeeded` + appartenance à la
réservation) avant toute écriture, comme le faisait déjà le paiement du solde.

### 1.2 Acompte payé mais réservation jamais confirmée (webhook) ✅
Si le client fermait l'onglet juste après le paiement Stripe de l'acompte, le
webhook encaissait (`deposit_paid`) mais ne confirmait jamais la réservation :
aucun email, dates non bloquées (revendables !), pas de mission ménage.
**Fix :** le webhook fait maintenant le même travail que le chemin navigateur
(statut confirmé + emails + blocage calendrier) pour l'acompte, le paiement
complet et le solde.

### 1.3 Double réservation possible (course entre deux clients) ✅
La validation de disponibilité ignorait les réservations en cours de paiement,
et rien ne re-vérifiait la disponibilité au moment de la confirmation : deux
clients simultanés pouvaient payer les mêmes nuits.
**Fix :** les réservations `pending_payment` récentes (< 30 min) bloquent
désormais les dates, et la disponibilité est re-vérifiée juste avant chaque
confirmation (navigateur ET webhook) avec alerte admin en cas de conflit.

### 1.4 Annulation en ligne possible APRÈS le séjour, remboursée à 100 % ✅
Le lien d'annulation des emails n'expirait jamais et une date d'arrivée passée
était traitée comme « plus de 30 jours avant » → remboursement intégral
automatique d'un séjour déjà consommé.
**Fix :** arrivée passée (ou jour J) = annulation en ligne refusée, message
« contactez-nous ».

### 1.5 Remboursements cassés ou dangereux ✅
- Paiement en 2 fois (20 % + 80 %) : le remboursement intégral échouait
  (un seul des deux paiements Stripe était remboursé, montant refusé).
- Aucune protection contre le double remboursement (double clic = 2×50 %).
- Remboursements sans `reverse_transfer` : l'argent reversé au propriétaire
  n'était pas récupéré → échec dès que le solde plateforme est insuffisant.
- Bon d'achat créé AVANT l'annulation effective (bon fantôme si erreur).
**Fix :** verrou d'idempotence (une annulation ne peut être traitée qu'une
fois), remboursement réparti sur tous les paiements, `reverse_transfer` +
`refund_application_fee`, bon d'achat créé après l'annulation réussie.

### 1.6 Une réservation annulée pouvait encore encaisser le solde ✅
La page /paiement-solde/ testait un statut qui n'existe jamais
(`payment_status = 'cancelled'`) : un client annulé/remboursé pouvait payer
80 % d'un séjour inexistant via son ancien email.
**Fix :** blocage sur `booking_status = 'cancelled'` et statuts remboursés,
côté page ET côté serveur de confirmation.

### 1.7 Lien de paiement admin : client débité de 100 % puis re-facturé 80 % ✅
Le lien Stripe Checkout envoyé depuis l'admin encaissait le TOTAL, mais la
réservation restait typée « acompte » → demande de solde de 80 % envoyée à
J-30 au client qui avait déjà tout payé.
**Fix :** le lien Checkout marque la réservation en paiement complet.

### 1.8 Prix manipulables et options jamais facturées ✅
Le prix du ménage et les options étaient acceptés tels quels depuis le
navigateur (un prix négatif faisait chuter le total), et un décalage de
nommage faisait que les options sélectionnées n'étaient jamais facturées
(total affiché ≠ total débité).
**Fix :** valeurs clampées ≥ 0 côté serveur, prise en compte des options
transmises par le panier.

---

## 2. CRITIQUE — Emails clients & tâches planifiées

### 2.1 Code d'accès J-1 : perdu si le cron rate le bon jour ✅
L'email avec le code de la boîte à clés ne ciblait que les arrivées de
« demain » : un cron raté ce jour-là = client sans code, jamais rattrapé.
**Fix :** fenêtre élargie (aujourd'hui + demain), l'anti-doublon garantit un
seul envoi.

### 2.2 Demande de solde J-30 : aucun rattrapage (l'incident de la résa #9) ✅
Un unique événement planifié portait l'envoi ; s'il était perdu, rien ne le
rejouait avant la relance J-7.
**Fix :** le balayage quotidien détecte désormais les réservations à ≤ 30
jours dont la demande de solde n'est pas partie et l'envoie (idempotent).

### 2.3 Bouton « Envoyer la demande de solde » : renvoi avalé par l'anti-doublon ✅
L'anti-doublon sans date pouvait jeter silencieusement un renvoi volontaire
(admin voyait « envoyé », client ne recevait rien).
**Fix :** clé anti-doublon datée par jour + le statut `balance_requested`
n'est posé que si l'envoi a réussi.

### 2.4 Emails d'annulation/remboursement jamais envoyés ✅
Un client qui annulait avec remboursement ne recevait AUCUN email de
confirmation. Le reçu après paiement du solde n'existait pas non plus.
**Fix :** email d'annulation envoyé pour tous les types (remboursement, bon
d'achat, sans paiement) + email « reçu de solde » ajouté.

### 2.5 Emails clients encore hors journal (sans trace ni relance) ✅
Le lien de paiement (2 endroits), la confirmation de réservation manuelle et
le bon d'achat partaient en `wp_mail()` brut : perdus sans trace en cas de
panne SMTP.
**Fix :** migrés vers le mailer central (journal + retry + bouton Renvoyer).

### 2.6 Statut « Entièrement payé » (admin) invisible côté client ✅
Une résa marquée `fully_paid` dans l'admin s'affichait « En attente » sur le
portail voyageur, avec un bouton « Régler le solde » fonctionnel → risque de
double paiement.
**Fix :** `fully_paid` reconnu partout comme payé (portail, facture, page
solde, remboursements).

### 2.7 Emails en double possibles (course cron / watchdog) ✅
Le retry d'un email pouvait s'exécuter deux fois en parallèle.
**Fix :** verrou par ligne (revendication atomique avant envoi) + les emails
figés en `pending` sont maintenant repris par le watchdog.

---

## 3. CRITIQUE/MAJEUR — Synchro calendriers (double réservation)

### 3.1 Fenêtre de vente pendant chaque synchro iCal ✅
L'import faisait « tout supprimer puis tout réinsérer » sans transaction : à
chaque synchro (toutes les 5 min), les dates Airbnb/Booking étaient
brièvement libres ; un timeout au mauvais moment les libérait durablement.
**Fix :** import transactionnel (les anciennes dates ne disparaissent que si
les nouvelles sont écrites).

### 3.2 Conflit partiel = réservation externe pas bloquée du tout ✅
Une résa Airbnb chevauchant partiellement une résa directe était ignorée EN
ENTIER : les nuits non conflictuelles restaient vendables.
**Fix :** le blocage est inséré même en cas de conflit (l'alerte est
conservée).

### 3.3 Annulations externes importées comme réservations ✅
Les événements `STATUS:CANCELLED` des flux étaient importés comme des dates
bloquées.
**Fix :** ignorés au parsing.

### 3.4 Garde anti-écrasement inopérante (mauvais nom de colonne) ✅
Deux requêtes utilisaient une colonne `status` inexistante (`booking_status`)
→ un propriétaire pouvait débloquer des dates par-dessus une réservation
confirmée, et son calendrier n'affichait aucune résa.
**Fix :** colonne corrigée.

---

## 4. Recommandé ensuite (🔜 non inclus dans ce lot, à décider)

1. **Jeton sur /paiement-solde/** : la page reste accessible par simple
   numéro (`?booking_id=N`) → un tiers peut voir les infos d'un séjour.
   Le fix impose de changer le format des liens dans les emails (les anciens
   liens envoyés casseraient) — à planifier proprement.
2. **Fuseaux horaires iCal** : les flux en datetime UTC (rares chez
   Airbnb/Booking, fréquents via Google Calendar) peuvent créer un décalage
   d'un jour. À traiter si vous branchez d'autres sources.
3. **Échos « Not available »** : vos propres résas ré-importées depuis Airbnb
   génèrent de fausses alertes de conflit — filtrage à affiner.
4. **Réutilisation des PaymentIntents de solde** : chaque renvoi crée un
   nouveau PI Stripe (les anciens restent techniquement confirmables).
5. **Nettoyage** : classe `CDV_Booking_Integration` morte, handler
   `cdv_validate_voucher` dupliqué sans nonce, expiration des bons d'achat
   jamais planifiée, texte de relance J-7 menaçant d'une annulation auto
   désactivée (reformulé dans ce lot), page /confirmation-reservation/ vide.
6. **Cron OVH / pinger externe** (cron-job.org toutes les 5 min) : toujours
   recommandé — voir FIX-EMAILS-EN-ATTENTE.md.
7. **Brevo** : migration du mailer toujours recommandée.

---

## 5. Ce qui a été vérifié et va bien

- Jetons du portail voyageur : aléatoires forts, comparaison sûre.
- Annulation client : nonce lié au jeton, logique cohérente avec l'affichage.
- Bornes de dates import/export iCal : pas de décalage dans le cas nominal.
- Verrous à durée limitée partout (pas de blocage permanent après crash).
- Montants Stripe : pas de paiement à 0 €, pas de bug de conversion devise.
