💳

CORRECTIONS_PAIEMENT_ROBUSTESSE

Corrections de robustesse du système de paiement

✅ Corrections appliquées

1. Validation du montant débité vs montant calculé ✅

Fichiers modifiés :

  • app/Services/PaymentVerificationService.php
  • app/Http/Controllers/CheckoutController.php

Corrections :

  • Vérification que le montant débité par Stripe correspond au montant calculé (tolérance de 0.01€ pour les arrondis)
  • Dans verifyAndMarkPaid() : comparaison $session->amount_total vs $echeance->montant_final
  • Dans markEcheancePaidFromPaymentIntent() : comparaison $pi->amount vs $echeance->montant_final
  • Dans CheckoutController::charge() : vérification après création du PaymentIntent

Code ajouté :

// Tolérance de 0.01€ pour les arrondis
if (abs($amountPaid - $expectedAmount) > 0.01) {
    // Rejet du paiement
}

2. Verrous transactionnels pour éviter les doubles paiements ✅

Fichiers modifiés :

  • app/Services/PaymentVerificationService.php
  • app/Http/Controllers/CheckoutController.php

Corrections :

  • Utilisation de DB::transaction() avec lockForUpdate() pour verrouiller l'échéance pendant le traitement
  • Rechargement de l'échéance avec verrou avant mise à jour
  • Vérification que l'échéance n'est pas déjà payée après le verrou

Code ajouté :

DB::transaction(function () use ($echeance) {
    $echeanceLocked = Echeance::where('id', $echeance->id)
        ->lockForUpdate()
        ->first();
    
    if (!$echeanceLocked || $echeanceLocked->estPayee()) {
        return; // Déjà payée
    }
    
    $echeanceLocked->update([...]);
});

3. Exclusion des statuts annulé/arrêté ✅

Fichiers modifiés :

  • app/Services/PaymentVerificationService.php
  • app/Http/Controllers/CheckoutController.php

Corrections :

  • Vérification explicite que l'échéance n'est pas STATUT_ANNULE ou STATUT_ARRETE
  • Dans charge() : whereNotIn('statut', [Echeance::STATUT_ANNULE, Echeance::STATUT_ARRETE])
  • Dans verifyAndMarkPaid() et markEcheancePaidFromPaymentIntent() : vérification avant traitement

Code ajouté :

if (in_array($echeance->statut, [Echeance::STATUT_ANNULE, Echeance::STATUT_ARRETE], true)) {
    return ['ok' => false, 'message' => 'Échéance annulée ou arrêtée, impossible de payer.'];
}

4. Validation du montant dans les webhooks ✅

Fichiers modifiés :

  • app/Services/PaymentVerificationService.php

Corrections :

  • Vérification du montant dans verifyAndMarkPaid() avant de marquer l'échéance payée
  • Comparaison $session->amount_total avec $echeance->montant_final
  • Log d'avertissement si le montant ne correspond pas

5. Amélioration de la validation des codes promo ✅

Fichiers modifiés :

  • app/Http/Controllers/CheckoutController.php

Corrections :

  • Validation du code promo avant le calcul du montant
  • Vérification que le code promo n'a pas déjà été utilisé pour cette échéance
  • Rejet immédiat si le code est invalide

Code ajouté :

if ($codePromo) {
    $promo = PromoCode::validateCode($codePromo, $user);
    if (!$promo) {
        return response()->json(['success' => false, 'error' => 'Code promo invalide ou expiré.'], 422);
    }
}

6. Validation stricte du montant final ✅

Fichiers modifiés :

  • app/Http/Controllers/CheckoutController.php

Corrections :

  • Vérification que $montantFinal > 0
  • Vérification que $montantFinal <= $montantDu (le montant final ne peut pas dépasser le montant dû)
  • Rejet avec message d'erreur clair si validation échoue

Code ajouté :

if ($montantFinal > $montantDu) {
    return response()->json(['success' => false, 'error' => 'Erreur de calcul du montant. Contactez le support.'], 422);
}

🧪 Tests de robustesse créés

1. PaymentVerificationServiceTest.php

Tests unitaires pour PaymentVerificationService :

  • ✅ Vérification que le montant débité correspond au montant attendu
  • ✅ Rejet si le montant débité ne correspond pas
  • ✅ Rejet si l'échéance est annulée
  • ✅ Idempotence (ne pas payer deux fois)
  • ✅ Tolérance d'arrondi de 0.01€
  • ✅ Rejet si différence > 0.01€

Note : Ces tests nécessitent une base de données configurée et des mocks Stripe.


2. CalculMontantDuServiceTest.php

Tests unitaires pour CalculMontantDuService :

  • ✅ Calcul correct pour une échéance simple
  • ✅ Application d'un code promo pourcentage
  • ✅ Application d'un code promo montant fixe
  • ✅ Réduction manuelle appliquée
  • ✅ Montant final ne peut pas être négatif
  • ✅ Code promo expiré n'est pas appliqué
  • ✅ Code promo inactif n'est pas appliqué
  • ✅ Arrondi correct à 2 décimales

3. PaymentRaceConditionTest.php

Tests de race conditions :

  • ✅ Vérifier que le verrou transactionnel empêche les doubles paiements
  • ✅ Vérifier que les échéances annulées sont rejetées
  • ✅ Vérifier que les échéances déjà payées sont rejetées
  • ✅ Vérifier la validation du montant final

📋 Checklist de déploiement

Avant de déployer en production :

  • [ ] Exécuter les tests : php artisan test
  • [ ] Vérifier que les migrations sont à jour
  • [ ] Tester manuellement un paiement avec montant exact
  • [ ] Tester un paiement avec code promo
  • [ ] Tester un paiement d'échéance annulée (doit être rejeté)
  • [ ] Tester un double paiement simultané (doit être bloqué)
  • [ ] Vérifier les logs d'audit pour les erreurs de montant
  • [ ] Monitorer les webhooks Stripe pour détecter les incohérences

🔍 Points de vigilance

  1. Tolérance d'arrondi : La tolérance de 0.01€ peut masquer de petites différences. À surveiller dans les logs.

  2. Performance : Les verrous lockForUpdate() peuvent ralentir les paiements simultanés. Monitorer les temps de réponse.

  3. Logs : Tous les rejets de paiement sont loggés dans PaymentAuditLog. Vérifier régulièrement.

  4. Webhooks : Si un webhook échoue, le CRON de réconciliation devrait rattraper. Vérifier que le CRON tourne correctement.


📝 Notes techniques

  • Les verrous transactionnels utilisent lockForUpdate() qui bloque la ligne jusqu'à la fin de la transaction
  • La tolérance de 0.01€ est nécessaire car Stripe peut arrondir différemment selon les devises
  • Les tests utilisent RefreshDatabase pour isoler chaque test
  • Les mocks Stripe ne sont pas implémentés dans les tests actuels (nécessite configuration)

🚀 Prochaines améliorations possibles

  1. Alertes automatiques : Système d'alerte si un montant ne correspond pas
  2. Réconciliation automatique : CRON plus fréquent pour réconcilier les échéances en attente
  3. Tests d'intégration : Tests avec Stripe Test Mode pour valider le flux complet
  4. Monitoring : Dashboard pour surveiller les incohérences de montant

Document généré le 2026-01-25