# PLANNING STRIPE - DÉVELOPPEUR FLUTTER
## App Flutter - Intégration Stripe Terminal Payments
### V1 ✅ Stripe Connect (Réalisée - 01/09/2024)
### V2 🔄 Tap to Pay (En cours de développement)

---

## 🎯 V2 - TAP TO PAY (NFC intégré uniquement)
### Période estimée : 1.5 semaine de développement
### Dernière mise à jour : 29/09/2025

### 📱 CONFIGURATIONS STRIPE TAP TO PAY CONFIRMÉES
- **iOS** : iPhone XS ou plus récent + iOS 16.4 minimum (source : Stripe docs officielles)
- **Android** : Appareils certifiés par Stripe (liste mise à jour hebdomadairement via API)
- **SDK Terminal** : Version 4.6.0 utilisée (minimum requis 2.23.0 ✅)
- **Batterie minimum** : 10% pour les paiements
- **NFC** : Obligatoire et activé
- **Web** : Non supporté (même sur mobile avec NFC)

---

## 📋 RÉSUMÉ EXÉCUTIF V2

### 🎯 Objectif Principal
Permettre aux membres des amicales de pompiers d'encaisser des paiements par carte bancaire sans contact directement depuis leur téléphone (iPhone XS+ avec iOS 16.4+ dans un premier temps).

### 💡 Fonctionnalités Clés
- **Tap to Pay** sur iPhone/Android (utilisation du NFC intégré du téléphone uniquement)
- **Montants flexibles** : Prédéfinis (10€, 20€, 30€, 50€) ou personnalisés
- **Mode offline** : File d'attente avec synchronisation automatique
- **Dashboard vendeur** : Suivi des ventes en temps réel
- **Reçus numériques** : Envoi par email/SMS
- **Multi-rôles** : Intégration avec le système de permissions existant

### ⚠️ Contraintes Techniques
- **iOS uniquement en V2.1** : iPhone XS minimum, iOS 16.4+
- **Android en V2.2** : Liste d'appareils certifiés via API
- **Connexion internet** : Requise pour initialisation, mode offline disponible ensuite
- **Compte Stripe** : L'amicale doit avoir complété l'onboarding V1

---

## 🗓️ PLANNING DÉTAILLÉ V2

### 📦 PHASE 1 : SETUP TECHNIQUE ET ARCHITECTURE
**Durée estimée : 1 jour**
**Objectif : Préparer l'environnement et l'architecture pour Stripe Tap to Pay**

#### 📚 1.1 Installation des packages (4h)
- [x] Ajouter `mek_stripe_terminal: ^4.6.0` dans pubspec.yaml ✅ FAIT
- [x] Ajouter `flutter_stripe: ^12.0.0` pour le SDK Stripe ✅ FAIT
- [x] Ajouter `device_info_plus: ^10.1.0` pour détecter le modèle d'iPhone ✅ FAIT
- [x] Ajouter `battery_plus: ^6.1.0` pour le niveau de batterie ✅ FAIT
- [x] Ajouter `network_info_plus: ^5.0.3` pour l'IP et WiFi ✅ FAIT
- [x] Ajouter `nfc_manager: ^3.5.0` pour la détection NFC ✅ FAIT
- [x] Connectivity déjà présent : `connectivity_plus: ^6.1.3` ✅ FAIT
- [x] Exécuter `flutter pub get` ✅ FAIT
- [ ] Exécuter `cd ios && pod install`
- [ ] Vérifier la compilation iOS sans erreurs
- [ ] Documenter les versions exactes installées

#### 🔧 1.2a Configuration iOS native (2h)
- [ ] Modifier `ios/Runner/Info.plist` avec les permissions NFC
- [ ] Ajouter `NSLocationWhenInUseUsageDescription` (requis par Stripe)
- [ ] Configurer les entitlements Tap to Pay Apple Developer
- [ ] Tester sur simulateur iOS
- [ ] Vérifier les permissions sur appareil physique
- [ ] Documenter les changements dans Info.plist

#### 🤖 1.2b Configuration Android native (2h)
- [ ] Modifier `android/app/src/main/AndroidManifest.xml` avec permissions NFC
- [ ] Ajouter `<uses-permission android:name="android.permission.NFC" />`
- [ ] Ajouter `<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />`
- [ ] Ajouter `<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />`
- [ ] Ajouter `<uses-feature android:name="android.hardware.nfc" android:required="false" />`
- [ ] Vérifier/modifier `minSdkVersion 28` dans `android/app/build.gradle`
- [ ] Vérifier `targetSdkVersion 33` ou plus récent
- [ ] Tester sur appareil Android certifié Stripe
- [ ] Documenter les changements

#### 🏗️ 1.3 Architecture des services (4h)
- [ ] Créer `lib/core/services/stripe_tap_to_pay_service.dart`
- [ ] Implémenter le singleton StripeTapToPayService
- [ ] Créer la méthode `initialize()` avec gestion du token
- [ ] Créer la méthode `_fetchConnectionToken()` via API
- [ ] Implémenter la connexion au "lecteur" local (le téléphone)
- [ ] Créer `lib/core/repositories/payment_repository.dart`
- [ ] Implémenter les méthodes CRUD pour les paiements
- [ ] Intégrer avec le pattern Repository existant
- [ ] Ajouter les injections dans `app.dart`

---

### 🔍 PHASE 2 : VÉRIFICATION COMPATIBILITÉ
**Durée estimée : 1.5 jours**
**Objectif : Détecter et informer sur la compatibilité Tap to Pay**

#### 📱 2.1 Service de détection d'appareil (4h) ✅ COMPLÉTÉ
- [x] Créer `lib/core/services/device_info_service.dart` ✅ FAIT
- [x] Lister les modèles iPhone compatibles (XS, XR, 11, 12, 13, 14, 15, 16) ✅ FAIT
- [x] Vérifier la version iOS (≥ 16.4 pour Tap to Pay) ✅ FAIT - iOS 16.4 minimum confirmé par Stripe
- [x] Créer méthode `collectDeviceInfo()` et `canUseTapToPay()` ✅ FAIT avec batterie minimum 10%
- [x] Retourner les infos : model, osVersion, isCompatible, batteryLevel, IP ✅ FAIT
- [x] Gérer le cas Android (SDK ≥ 28 pour Tap to Pay) ✅ FAIT
- [x] Ajouter logs de debug pour diagnostic ✅ FAIT
- [x] Envoi automatique à l'API après login : POST `/users/device-info` ✅ FAIT dans ApiService
- [x] Sauvegarde dans Hive box settings ✅ FAIT avec préfixe `device_`
- [x] **NOUVEAU** : Vérification certification Stripe via API `/stripe/devices/check-tap-to-pay` ✅ FAIT
- [x] **NOUVEAU** : Méthode `checkStripeCertification()` pour Android ✅ FAIT
- [x] **NOUVEAU** : Stockage `device_stripe_certified` dans Hive ✅ FAIT
- [x] **NOUVEAU** : Messages d'erreur détaillés selon le problème (NFC, certification, batterie) ✅ FAIT

#### 🎨 2.2 Écran de vérification (4h)
- [ ] Créer `lib/presentation/payment/compatibility_check_page.dart`
- [ ] Design responsive avec icônes et messages clairs
- [ ] Afficher le modèle d'appareil détecté
- [ ] Afficher la version iOS
- [ ] Message explicatif si non compatible
- [ ] Bouton "Continuer" si compatible
- [ ] Bouton "Retour" si non compatible
- [ ] Intégrer avec la navigation existante

#### 🔄 2.3 Intégration dans le flux utilisateur (4h)
- [ ] Ajouter vérification au démarrage de l'app
- [ ] Sauvegarder le résultat dans SharedPreferences
- [ ] Afficher/masquer les fonctionnalités selon compatibilité
- [ ] Ajouter indicateur dans le dashboard utilisateur
- [ ] Gérer le cas de mise à jour iOS pendant utilisation

---

### 💳 PHASE 3 : INTERFACE DE PAIEMENT
**Durée estimée : 2 jours**
**Objectif : Créer les écrans de sélection et confirmation de paiement**

#### 🎯 3.1 Écran de sélection du montant (6h)
- [ ] Créer `lib/presentation/payment/payment_amount_page.dart`
- [ ] Design avec chips pour montants prédéfinis (10€, 20€, 30€, 50€)
- [ ] Champ de saisie pour montant personnalisé
- [ ] Validation min 1€, max 999€
- [ ] Afficher info amicale en header
- [ ] Calculer et afficher les frais Stripe (si applicable)
- [ ] Bouton "Continuer" avec montant sélectionné
- [ ] Animation de sélection des chips
- [ ] Responsive pour toutes tailles d'écran

#### 📝 3.2 Écran de détails du paiement (4h)
- [ ] Créer `lib/presentation/payment/payment_details_page.dart`
- [ ] Formulaire optionnel : nom, email, téléphone du donateur
- [ ] Checkbox pour reçu (email ou SMS)
- [ ] Résumé : montant, amicale, date
- [ ] Bouton "Payer avec carte sans contact"
- [ ] Possibilité d'ajouter une note/commentaire
- [ ] Sauvegarde locale des infos saisies

#### 🎨 3.3 Composants UI réutilisables (4h)
- [ ] Créer `lib/presentation/widgets/payment/amount_selector_widget.dart`
- [ ] Créer `lib/presentation/widgets/payment/payment_summary_card.dart`
- [ ] Créer `lib/presentation/widgets/payment/donor_info_form.dart`
- [ ] Styles cohérents avec le design existant
- [ ] Animations et feedback visuel

---

### 📲 PHASE 4 : FLUX TAP TO PAY
**Durée estimée : 3 jours**
**Objectif : Implémenter le processus de paiement sans contact**

#### 🎯 4.1 Écran Tap to Pay principal (8h)
- [ ] Créer `lib/presentation/payment/tap_to_pay_page.dart`
- [ ] Afficher montant en grand format
- [ ] Animation NFC (ondes pulsantes)
- [ ] Instructions "Approchez la carte du dos de l'iPhone"
- [ ] Gestion des états : attente, lecture, traitement, succès, échec
- [ ] Bouton annuler pendant l'attente
- [ ] Timeout après 60 secondes
- [ ] Son/vibration au succès

#### 🔄 4.2 Intégration Stripe Tap to Pay (6h)
- [ ] Initialiser le service Tap to Pay local (pas de découverte de lecteurs)
- [ ] Créer PaymentIntent via API backend
- [ ] Implémenter `collectPaymentMethod()` avec NFC du téléphone
- [ ] Implémenter `confirmPaymentIntent()`
- [ ] Gérer les erreurs Stripe spécifiques
- [ ] Logs détaillés pour debug
- [ ] Gestion des timeouts et retry

#### ✅ 4.3 Écran de confirmation (4h)
- [ ] Créer `lib/presentation/payment/payment_success_page.dart`
- [ ] Animation de succès (check vert)
- [ ] Afficher montant et référence de transaction
- [ ] Options : Envoyer reçu, Nouveau paiement, Retour
- [ ] Partage du reçu (share sheet iOS)
- [ ] Sauvegarde locale de la transaction

#### ❌ 4.4 Gestion des erreurs (4h)
- [ ] Créer `lib/presentation/payment/payment_error_page.dart`
- [ ] Messages d'erreur traduits en français
- [ ] Différencier : carte refusée, solde insuffisant, erreur réseau, etc.
- [ ] Bouton "Réessayer" avec même montant
- [ ] Bouton "Changer de montant"
- [ ] Logs pour support technique

---

### 📶 PHASE 5 : MODE OFFLINE ET SYNCHRONISATION
**Durée estimée : 2 jours**
**Objectif : Permettre les paiements sans connexion internet**

#### 💾 5.1 Service de queue offline (6h)
- [ ] Créer `lib/core/services/offline_payment_queue_service.dart`
- [ ] Stocker les paiements dans SharedPreferences
- [ ] Structure : amount, timestamp, amicale_id, user_id, status
- [ ] Méthode `addToQueue()` pour nouveaux paiements
- [ ] Méthode `getQueueSize()` pour badge notification
- [ ] Méthode `clearQueue()` après sync réussie
- [ ] Limite de 100 paiements en queue
- [ ] Expiration après 7 jours

#### 🔄 5.2 Service de synchronisation (6h)
- [ ] Créer `lib/core/services/payment_sync_service.dart`
- [ ] Détecter le retour de connexion avec ConnectivityPlus
- [ ] Envoyer les paiements par batch à l'API
- [ ] Gérer les échecs partiels
- [ ] Retry avec backoff exponentiel
- [ ] Notification de sync réussie
- [ ] Logs de synchronisation

#### 📊 5.3 UI du mode offline (4h)
- [ ] Indicateur "Mode hors ligne" dans l'app bar
- [ ] Badge avec nombre de paiements en attente
- [ ] Écran de détail de la queue
- [ ] Bouton "Forcer la synchronisation"
- [ ] Messages informatifs sur l'état

---

### 📈 PHASE 6 : DASHBOARD ET STATISTIQUES
**Durée estimée : 2 jours**
**Objectif : Tableau de bord pour suivre les ventes**

#### 📊 6.1 Dashboard vendeur (8h)
- [ ] Créer `lib/presentation/dashboard/vendor_dashboard_page.dart`
- [ ] Widget statistiques du jour (nombre, montant total)
- [ ] Widget statistiques de la semaine
- [ ] Widget statistiques du mois
- [ ] Graphique d'évolution (fl_chart)
- [ ] Liste des 10 dernières transactions
- [ ] Filtres par période
- [ ] Export CSV des données

#### 📱 6.2 Détail d'une transaction (4h)
- [ ] Créer `lib/presentation/payment/transaction_detail_page.dart`
- [ ] Afficher toutes les infos de la transaction
- [ ] Status : succès, en attente, échoué
- [ ] Option renvoyer le reçu
- [ ] Option annuler (si possible)
- [ ] Historique des actions

#### 🔔 6.3 Notifications et rappels (4h)
- [ ] Widget de rappel de synchronisation
- [ ] Notification de paiements en attente
- [ ] Alerte si compte Stripe a un problème
- [ ] Rappel de fin de journée pour sync

---

### 🧪 PHASE 7 : TESTS ET VALIDATION
**Durée estimée : 2 jours**
**Objectif : Assurer la qualité et la fiabilité**

#### ✅ 7.1 Tests unitaires (6h)
- [ ] Tests StripeTerminalService
- [ ] Tests DeviceCompatibilityService
- [ ] Tests OfflineQueueService
- [ ] Tests PaymentRepository
- [ ] Tests de validation des montants
- [ ] Tests de sérialisation/désérialisation
- [ ] Coverage > 80%

#### 📱 7.2 Tests d'intégration (6h)
- [ ] Test flux complet de paiement
- [ ] Test mode offline vers online
- [ ] Test gestion des erreurs
- [ ] Test sur différents iPhones
- [ ] Test avec cartes de test Stripe
- [ ] Test limites et edge cases

#### 🎭 7.3 Tests utilisateurs (4h)
- [ ] Créer scénarios de test
- [ ] Test avec 5 utilisateurs pilotes
- [ ] Collecter les retours
- [ ] Corriger les bugs identifiés
- [ ] Valider l'ergonomie

---

### 🚀 PHASE 8 : DÉPLOIEMENT ET DOCUMENTATION
**Durée estimée : 1 jour**
**Objectif : Mise en production et formation**

#### 📦 8.1 Build et déploiement (4h)
- [ ] Build iOS release
- [ ] Upload sur TestFlight
- [ ] Tests de non-régression
- [ ] Déploiement sur App Store
- [ ] Monitoring des premières 24h

#### 📚 8.2 Documentation (4h)
- [ ] Guide utilisateur pompier (PDF)
- [ ] Vidéo tutoriel Tap to Pay
- [ ] FAQ problèmes courants
- [ ] Documentation technique
- [ ] Formation équipe support

---

## 🔄 FLOW COMPLET DE PAIEMENT TAP TO PAY

### 📋 Vue d'ensemble du processus

Le flow de paiement se déroule en plusieurs étapes distinctes entre l'application Flutter, l'API PHP et Stripe :

```
App Flutter → API PHP → Stripe Terminal API → Retour App → NFC Payment → Confirmation
```

### 🎯 Étapes détaillées du flow

#### 1️⃣ **PRÉPARATION DU PAIEMENT (App Flutter)**
- L'utilisateur sélectionne ou crée un passage
- Choix du montant et sélection "Carte Bancaire"
- Récupération du `passage_id` existant ou 0 pour nouveau

#### 2️⃣ **CRÉATION DU PAYMENT INTENT (App → API → Stripe)**

**Requête App → API:**
```json
POST /api/stripe/payments/create-intent
{
  "amount": 2000,           // en centimes
  "currency": "eur",
  "payment_method_types": ["card_present"],
  "passage_id": 123,        // ou 0 si nouveau
  "amicale_id": 45,
  "member_id": 67,
  "stripe_account": "acct_xxx",
  "metadata": {
    "passage_id": "123",
    "type": "tap_to_pay"
  }
}
```

**L'API fait alors :**
1. Validation des données reçues
2. Appel Stripe API pour créer le PaymentIntent
3. Stockage en base de données locale
4. Retour à l'app avec `payment_intent_id` et `client_secret`

**Réponse API → App:**
```json
{
  "payment_intent_id": "pi_xxx",
  "client_secret": "pi_xxx_secret_xxx",
  "amount": 2000,
  "status": "requires_payment_method"
}
```

#### 3️⃣ **COLLECTE DE LA CARTE (App avec SDK Stripe Terminal)**

L'application utilise le SDK natif pour :
1. Activer le NFC du téléphone
2. Afficher l'écran "Approchez la carte"
3. Lire les données de la carte sans contact
4. Traiter le paiement localement via le SDK

#### 4️⃣ **TRAITEMENT DU PAIEMENT (SDK → Stripe)**

Le SDK Stripe Terminal :
- Envoie les données cryptées de la carte à Stripe
- Traite l'autorisation bancaire
- Retourne le statut du paiement à l'app

#### 5️⃣ **CONFIRMATION ET SAUVEGARDE (App → API)**

**Si paiement réussi :**
```json
POST /api/stripe/payments/confirm
{
  "payment_intent_id": "pi_xxx",
  "status": "succeeded",
  "amount": 2000
}
```

**Puis sauvegarde du passage :**
```json
POST /api/passages
{
  "id": 123,
  "fk_type": 1,  // Effectué
  "montant": "20.00",
  "fk_type_reglement": 3,  // CB
  "stripe_payment_id": "pi_xxx",
  ...
}
```

### 📊 Différences Web vs Tap to Pay

| Aspect | Paiement Web | Tap to Pay |
|--------|-------------|------------|
| **payment_method_types** | ["card"] | ["card_present"] |
| **SDK utilisé** | Stripe.js | Stripe Terminal SDK |
| **Collecte carte** | Formulaire web | NFC téléphone |
| **Metadata** | type: "web" | type: "tap_to_pay" |
| **Environnement** | Navigateur | App native |
| **Prérequis** | Aucun | iPhone XS+ iOS 16.4+ |

### ⚡ Points clés du flow

1. **Passage ID** : Toujours inclus (existant ou 0)
2. **Double confirmation** : PaymentIntent ET Passage sauvegardé
3. **Metadata Stripe** : Permet la traçabilité bidirectionnelle
4. **Endpoint unifié** : `/api/stripe/payments/` pour tous types
5. **Gestion erreurs** : À chaque étape du processus

## 🔄 PHASE 9 : ÉVOLUTIONS FUTURES (V2.2+)

### 📱 Support Android (V2.2)
- [ ] Vérification appareils Android certifiés via API
- [ ] Intégration SDK Android Tap to Pay
- [ ] Tests sur appareils Android certifiés

### 🌍 Fonctionnalités avancées (V2.3)
- [ ] Multi-devises
- [ ] Paiements récurrents (abonnements)
- [ ] Programme de fidélité
- [ ] Intégration comptable
- [ ] Rapports fiscaux automatiques

---

## 📊 MÉTRIQUES DE SUCCÈS

### KPIs Techniques
- [ ] Taux de succès des paiements > 95%
- [ ] Temps moyen de transaction < 15 secondes
- [ ] Synchronisation offline réussie > 99%
- [ ] Crash rate < 0.1%

### KPIs Business
- [ ] Adoption par > 50% des membres en 3 mois
- [ ] Augmentation des dons de 30%
- [ ] Satisfaction utilisateur > 4.5/5
- [ ] Réduction des paiements espèces de 60%

---

## ⚠️ RISQUES ET MITIGATION

### Risques Techniques
| Risque | Impact | Probabilité | Mitigation |
|--------|--------|-------------|------------|
| Incompatibilité iOS | Élevé | Moyen | Détection précoce, messages clairs |
| Problèmes réseau | Moyen | Élevé | Mode offline robuste |
| Erreurs Stripe | Élevé | Faible | Retry logic, logs détaillés |
| Performance | Moyen | Moyen | Optimisation, cache |

### Risques Business
| Risque | Impact | Probabilité | Mitigation |
|--------|--------|-------------|------------|
| Résistance au changement | Élevé | Moyen | Formation, support, incentives |
| Conformité RGPD | Élevé | Faible | Audit, documentation |
| Coûts Stripe | Moyen | Certain | Communication transparente |

---

## 📅 HISTORIQUE V1 - STRIPE CONNECT (COMPLÉTÉE)

### ✅ Fonctionnalités V1 Réalisées (01/09/2024)

#### Configuration Stripe Connect
- ✅ Widget `amicale_form.dart` avec intégration Stripe
- ✅ Service `stripe_connect_service.dart` complet
- ✅ Création de comptes Stripe Express
- ✅ Génération de liens d'onboarding
- ✅ Vérification du statut en temps réel
- ✅ Messages utilisateur en français
- ✅ Interface responsive mobile/desktop

#### API Endpoints Intégrés
- ✅ `/amicales/{id}/stripe/create-account` - Création compte
- ✅ `/amicales/{id}/stripe/account-status` - Vérification statut
- ✅ `/amicales/{id}/stripe/onboarding-link` - Lien configuration
- ✅ `/amicales/{id}/stripe/create-location` - Location Terminal

#### Statuts et Messages
- ✅ "💳 Activez les paiements par carte bancaire"
- ✅ "⏳ Configuration Stripe en cours"
- ✅ "✅ Compte Stripe configuré - 100% des paiements"

---

## 📝 NOTES DE DÉVELOPPEMENT

### Points d'attention pour la V2
1. **Dépendance V1** : L'amicale doit avoir complété l'onboarding Stripe (V1) avant de pouvoir utiliser Tap to Pay (V2)
2. **Architecture existante** : Utiliser le pattern Repository et les services singleton déjà en place
3. **Gestion d'erreurs** : Utiliser `ApiException` pour tous les messages d'erreur
4. **Réactivité** : Utiliser `ValueListenableBuilder` avec les Box Hive
5. **Multi-environnement** : L'ApiService détecte automatiquement DEV/REC/PROD

### Conventions de code
- Noms de fichiers en snake_case
- Classes en PascalCase
- Variables et méthodes en camelCase
- Pas de Provider/Bloc, utiliser l'injection directe
- Tests unitaires obligatoires pour chaque service

### 🎯 Scope Stripe - Exclusivement logiciel
- **TAP TO PAY UNIQUEMENT** : Utilisation du NFC intégré du téléphone
- **PAS de terminaux physiques** : Pas de Bluetooth, USB ou Lightning
- **PAS de lecteurs externes** : Pas de WisePad, Reader M2, etc.
- **Futur** : Paiements Web via Stripe.js

### Ressources utiles
- [Documentation Stripe Terminal Flutter](https://stripe.com/docs/terminal/payments/setup-flutter)
- [Apple Tap to Pay Requirements](https://developer.apple.com/tap-to-pay/)
- [Flutter Hive Documentation](https://docs.hivedb.dev/)

---

## 🔄 DERNIÈRES MISES À JOUR

- **29/09/2025** : Clarification du scope et mise à jour complète
  - ✅ Scope : TAP TO PAY UNIQUEMENT (pas de terminaux physiques)
  - ✅ Suppression références Bluetooth et lecteurs externes
  - ✅ Réduction estimation : 1.5 semaine au lieu de 2-3 semaines
  - ✅ DeviceInfoService avec vérification API pour Android
  - ✅ Intégration endpoints `/stripe/devices/check-tap-to-pay`
  - ✅ Gestion batterie minimum 10%
  - ✅ Messages d'erreur détaillés selon le problème
  - ✅ Correction bug Tap to Pay sur web mobile
  - ✅ SDK Stripe Terminal 4.6.0 (compatible avec requirements)
- **28/09/2025** : Création du planning détaillé V2 avec 9 phases et 200+ TODO
- **01/09/2024** : V1 Stripe Connect complétée et opérationnelle
- **25/08/2024** : Début du développement V1

---

## 📞 CONTACTS PROJET

- **Product Owner** : À définir
- **Tech Lead Flutter** : À définir
- **Support Stripe** : support@stripe.com
- **Équipe Backend PHP** : À coordonner pour les endpoints API

---

*Document de planification V2 - Terminal Payments*
*Dernière révision : 28/09/2025*
  connectivity_plus: ^5.0.2  # Connectivité réseau
```

**STATUS**: Configuration Stripe Connect intégrée dans l'interface existante

```bash
cd app
flutter pub get
cd ios
pod install
```

#### ✅ Configuration iOS
```xml
<!-- ios/Runner/Info.plist -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Localisation nécessaire pour les paiements Stripe</string>
<!-- Pas de permissions Bluetooth requises pour Tap to Pay -->
<!-- Le NFC est géré nativement par le SDK Stripe -->
```

#### ✅ Configuration Android
```xml
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Déclaration de la fonctionnalité NFC (optionnelle pour ne pas exclure les appareils sans NFC) -->
<uses-feature android:name="android.hardware.nfc" android:required="false" />
```

```gradle
// android/app/build.gradle
android {
    defaultConfig {
        minSdkVersion 28  // Minimum requis pour Tap to Pay Android
        targetSdkVersion 33  // Ou plus récent
        compileSdkVersion 33
    }
}
```

### 🌆 Après-midi (4h)

#### ✅ Service de base StripeTerminalService
```dart
// lib/services/stripe_terminal_service.dart
import 'package:stripe_terminal/stripe_terminal.dart';

class StripeTerminalService {
  static final StripeTerminalService _instance = StripeTerminalService._internal();
  factory StripeTerminalService() => _instance;
  StripeTerminalService._internal();

  Terminal? _terminal;
  bool _isInitialized = false;

  Future<void> initialize() async {
    if (_isInitialized) return;

    _terminal = await Terminal.getInstance(
      fetchConnectionToken: _fetchConnectionToken,
    );
    
    _isInitialized = true;
  }

  Future<String> _fetchConnectionToken() async {
    final response = await ApiService().post('/terminal/connection-token');
    return response['secret'];
  }

  Future<bool> checkTapToPayCapability() async {
    if (!Platform.isIOS) return false;
    
    final deviceInfo = DeviceInfoPlugin();
    final iosInfo = await deviceInfo.iosInfo;
    
    // iPhone XS et ultérieurs
    final supportedModels = [
      'iPhone11,', // XS, XS Max
      'iPhone12,', // 11, 11 Pro
      'iPhone13,', // 12 series
      'iPhone14,', // 13 series
      'iPhone15,', // 14 series
      'iPhone16,', // 15 series
    ];
    
    final modelIdentifier = iosInfo.utsname.machine;
    final isSupported = supportedModels.any((model) => 
      modelIdentifier.startsWith(model)
    );
    
    // iOS 16.4 minimum
    final osVersion = iosInfo.systemVersion.split('.').map(int.parse).toList();
    final isOSSupported = osVersion[0] > 16 ||
      (osVersion[0] == 16 && osVersion.length > 1 && osVersion[1] >= 4);
    
    return isSupported && isOSSupported;
  }
}
```

---

## 📅 MARDI 26/08 - UI Paiement principal (8h)

### 🌅 Matin (4h)

#### ✅ Écran de vérification compatibilité
```dart
// lib/screens/payment/compatibility_check_screen.dart
class CompatibilityCheckScreen extends StatefulWidget {
  @override
  _CompatibilityCheckScreenState createState() => _CompatibilityCheckScreenState();
}

class _CompatibilityCheckScreenState extends State<CompatibilityCheckScreen> {
  bool? _isCompatible;
  String _deviceInfo = '';

  @override
  void initState() {
    super.initState();
    _checkCompatibility();
  }

  Future<void> _checkCompatibility() async {
    final service = StripeTerminalService();
    final compatible = await service.checkTapToPayCapability();
    
    final deviceInfo = DeviceInfoPlugin();
    final iosInfo = await deviceInfo.iosInfo;
    
    setState(() {
      _isCompatible = compatible;
      _deviceInfo = '${iosInfo.name} - iOS ${iosInfo.systemVersion}';
    });
    
    if (compatible) {
      await service.initialize();
      Navigator.pushReplacement(
        context, 
        MaterialPageRoute(builder: (_) => PaymentScreen())
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isCompatible == null) {
      return Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CircularProgressIndicator(),
              SizedBox(height: 20),
              Text('Vérification de compatibilité...'),
            ],
          ),
        ),
      );
    }

    if (!_isCompatible!) {
      return Scaffold(
        body: Center(
          child: Padding(
            padding: EdgeInsets.all(20),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.error_outline, size: 80, color: Colors.orange),
                SizedBox(height: 20),
                Text(
                  'Tap to Pay non disponible',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 10),
                Text(
                  'Votre appareil: $_deviceInfo',
                  style: TextStyle(color: Colors.grey),
                ),
                SizedBox(height: 20),
                Text(
                  'Tap to Pay nécessite un iPhone XS ou plus récent avec iOS 16.4+',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 16),
                ),
                SizedBox(height: 30),
                ElevatedButton(
                  onPressed: () => Navigator.pop(context),
                  child: Text('Retour'),
                ),
              ],
            ),
          ),
        ),
      );
    }

    return Container(); // Ne devrait jamais arriver ici
  }
}
```

### 🌆 Après-midi (4h)

#### ✅ Écran de paiement principal
```dart
// lib/screens/payment/payment_screen.dart
class PaymentScreen extends StatefulWidget {
  final Amicale amicale;
  
  PaymentScreen({required this.amicale});
  
  @override
  _PaymentScreenState createState() => _PaymentScreenState();
}

class _PaymentScreenState extends State<PaymentScreen> {
  final _amounts = [10.0, 20.0, 30.0, 50.0]; // en euros
  double _selectedAmount = 20.0;
  bool _isProcessing = false;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Paiement Calendrier'),
        backgroundColor: Colors.red.shade700,
      ),
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Info amicale
            Card(
              child: ListTile(
                leading: Icon(Icons.group, color: Colors.red),
                title: Text(widget.amicale.name),
                subtitle: Text('Calendrier ${DateTime.now().year}'),
              ),
            ),
            
            SizedBox(height: 30),
            
            // Sélection montant
            Text(
              'Sélectionnez le montant',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 15),
            
            Wrap(
              spacing: 10,
              runSpacing: 10,
              children: _amounts.map((amount) {
                final isSelected = _selectedAmount == amount;
                return ChoiceChip(
                  label: Text(
                    '${amount.toStringAsFixed(0)}€',
                    style: TextStyle(
                      fontSize: 18,
                      color: isSelected ? Colors.white : Colors.black,
                    ),
                  ),
                  selected: isSelected,
                  selectedColor: Colors.red.shade700,
                  onSelected: (selected) {
                    if (selected && !_isProcessing) {
                      setState(() => _selectedAmount = amount);
                    }
                  },
                );
              }).toList(),
            ),
            
            // Montant personnalisé
            SizedBox(height: 20),
            TextField(
              keyboardType: TextInputType.number,
              decoration: InputDecoration(
                labelText: 'Ou entrez un montant personnalisé',
                prefixText: '€ ',
                border: OutlineInputBorder(),
              ),
              onChanged: (value) {
                final amount = double.tryParse(value);
                if (amount != null && amount >= 1) {
                  setState(() => _selectedAmount = amount);
                }
              },
            ),
            
            Spacer(),
            
            // Bouton paiement
            ElevatedButton(
              onPressed: _isProcessing ? null : _startPayment,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red.shade700,
                padding: EdgeInsets.symmetric(vertical: 20),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              child: _isProcessing
                ? Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(
                          color: Colors.white,
                          strokeWidth: 2,
                        ),
                      ),
                      SizedBox(width: 10),
                      Text('Traitement...', style: TextStyle(fontSize: 18)),
                    ],
                  )
                : Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.contactless, size: 30),
                      SizedBox(width: 10),
                      Text(
                        'Payer ${_selectedAmount.toStringAsFixed(2)}€',
                        style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                      ),
                    ],
                  ),
            ),
            
            SizedBox(height: 10),
            
            Text(
              'Paiement sécurisé par Stripe',
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.grey, fontSize: 12),
            ),
          ],
        ),
      ),
    );
  }
  
  Future<void> _startPayment() async {
    setState(() => _isProcessing = true);
    
    try {
      // Naviguer vers l'écran Tap to Pay
      final result = await Navigator.push(
        context,
        MaterialPageRoute(
          builder: (_) => TapToPayScreen(
            amount: _selectedAmount,
            amicale: widget.amicale,
          ),
        ),
      );
      
      if (result != null && result['success']) {
        _showSuccessDialog(result['paymentIntentId']);
      }
    } catch (e) {
      _showErrorDialog(e.toString());
    } finally {
      setState(() => _isProcessing = false);
    }
  }
  
  void _showSuccessDialog(String paymentIntentId) {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: Row(
          children: [
            Icon(Icons.check_circle, color: Colors.green, size: 30),
            SizedBox(width: 10),
            Text('Paiement réussi !'),
          ],
        ),
        content: Text('Le paiement a été effectué avec succès.'),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
              Navigator.of(context).pop(); // Retour écran principal
            },
            child: Text('OK'),
          ),
        ],
      ),
    );
  }
  
  void _showErrorDialog(String error) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Erreur de paiement'),
        content: Text(error),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: Text('Réessayer'),
          ),
        ],
      ),
    );
  }
}
```

---

## 📅 MERCREDI 27/08 - Flux Tap to Pay (8h)

### 🌅 Matin (4h)

#### ✅ Écran Tap to Pay
```dart
// lib/screens/payment/tap_to_pay_screen.dart
class TapToPayScreen extends StatefulWidget {
  final double amount;
  final Amicale amicale;
  
  TapToPayScreen({required this.amount, required this.amicale});
  
  @override
  _TapToPayScreenState createState() => _TapToPayScreenState();
}

class _TapToPayScreenState extends State<TapToPayScreen> {
  final _terminalService = StripeTerminalService();
  PaymentIntent? _paymentIntent;
  String _status = 'Initialisation...';
  bool _isProcessing = true;
  
  @override
  void initState() {
    super.initState();
    _initializePayment();
  }
  
  Future<void> _initializePayment() async {
    try {
      setState(() => _status = 'Création du paiement...');
      
      // 1. Créer PaymentIntent via API
      final response = await ApiService().post('/payments/create-intent', {
        'amount': (widget.amount * 100).round(), // en centimes
        'amicale_id': widget.amicale.id,
      });
      
      setState(() => _status = 'Connexion au lecteur...');
      
      // 2. Initialiser le lecteur Tap to Pay local (le téléphone)
      await _terminalService.initializeLocalReader();
      // Pas de découverte de lecteurs externes - le téléphone EST le lecteur
      
      setState(() => _status = 'Prêt pour le paiement');
      
      // 3. Collecter le paiement
      _paymentIntent = await _terminalService.collectPaymentMethod(
        response['client_secret'],
      );
      
      setState(() => _status = 'Traitement du paiement...');
      
      // 4. Confirmer le paiement
      final confirmedIntent = await _terminalService.confirmPaymentIntent(
        _paymentIntent!,
      );
      
      if (confirmedIntent.status == PaymentIntentStatus.succeeded) {
        _onPaymentSuccess(confirmedIntent.id);
      } else {
        throw Exception('Paiement non confirmé');
      }
      
    } catch (e) {
      _onPaymentError(e.toString());
    }
  }
  
  void _onPaymentSuccess(String paymentIntentId) {
    Navigator.pop(context, {
      'success': true,
      'paymentIntentId': paymentIntentId,
    });
  }
  
  void _onPaymentError(String error) {
    setState(() {
      _isProcessing = false;
      _status = 'Erreur: $error';
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey.shade100,
      body: SafeArea(
        child: Column(
          children: [
            // Header
            Container(
              padding: EdgeInsets.all(20),
              color: Colors.white,
              child: Row(
                children: [
                  IconButton(
                    icon: Icon(Icons.close),
                    onPressed: _isProcessing ? null : () => Navigator.pop(context),
                  ),
                  Expanded(
                    child: Text(
                      'Paiement sans contact',
                      style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                      textAlign: TextAlign.center,
                    ),
                  ),
                  SizedBox(width: 48), // Pour équilibrer avec IconButton
                ],
              ),
            ),
            
            // Montant
            Container(
              margin: EdgeInsets.all(20),
              padding: EdgeInsets.all(30),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(15),
                boxShadow: [
                  BoxShadow(
                    color: Colors.grey.shade300,
                    blurRadius: 10,
                    offset: Offset(0, 5),
                  ),
                ],
              ),
              child: Column(
                children: [
                  Text(
                    '${widget.amount.toStringAsFixed(2)} €',
                    style: TextStyle(
                      fontSize: 48,
                      fontWeight: FontWeight.bold,
                      color: Colors.red.shade700,
                    ),
                  ),
                  SizedBox(height: 10),
                  Text(
                    widget.amicale.name,
                    style: TextStyle(fontSize: 16, color: Colors.grey),
                  ),
                ],
              ),
            ),
            
            // Animation Tap to Pay
            Expanded(
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    if (_status.contains('Prêt'))
                      TapToPayAnimation()
                    else if (_isProcessing)
                      CircularProgressIndicator(
                        size: 80,
                        strokeWidth: 8,
                        valueColor: AlwaysStoppedAnimation(Colors.red.shade700),
                      ),
                    
                    SizedBox(height: 30),
                    
                    Text(
                      _status,
                      style: TextStyle(fontSize: 18),
                      textAlign: TextAlign.center,
                    ),
                    
                    if (_status.contains('Prêt'))
                      Padding(
                        padding: EdgeInsets.only(top: 20),
                        child: Text(
                          'Approchez la carte du dos de l\'iPhone',
                          style: TextStyle(fontSize: 16, color: Colors.grey),
                        ),
                      ),
                  ],
                ),
              ),
            ),
            
            // Footer
            if (!_isProcessing && _status.contains('Erreur'))
              Padding(
                padding: EdgeInsets.all(20),
                child: ElevatedButton(
                  onPressed: _initializePayment,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red.shade700,
                    padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
                  ),
                  child: Text('Réessayer', style: TextStyle(fontSize: 16)),
                ),
              ),
          ],
        ),
      ),
    );
  }
}
```

### 🌆 Après-midi (4h)

#### ✅ Animation Tap to Pay
```dart
// lib/widgets/tap_to_pay_animation.dart
class TapToPayAnimation extends StatefulWidget {
  @override
  _TapToPayAnimationState createState() => _TapToPayAnimationState();
}

class _TapToPayAnimationState extends State<TapToPayAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _opacityAnimation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    )..repeat();
    
    _scaleAnimation = Tween<double>(
      begin: 0.8,
      end: 1.5,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOut,
    ));
    
    _opacityAnimation = Tween<double>(
      begin: 1.0,
      end: 0.0,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOut,
    ));
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      height: 200,
      child: Stack(
        alignment: Alignment.center,
        children: [
          // Ondes animées
          AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return Transform.scale(
                scale: _scaleAnimation.value,
                child: Opacity(
                  opacity: _opacityAnimation.value,
                  child: Container(
                    width: 150,
                    height: 150,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      border: Border.all(
                        color: Colors.red.shade700,
                        width: 3,
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
          
          // Icône centrale
          Container(
            width: 100,
            height: 100,
            decoration: BoxDecoration(
              color: Colors.red.shade700,
              shape: BoxShape.circle,
            ),
            child: Icon(
              Icons.contactless,
              size: 60,
              color: Colors.white,
            ),
          ),
        ],
      ),
    );
  }
}
```

---

## 📅 JEUDI 28/08 - Gestion offline et sync (8h)

### 🌅 Matin (4h)

#### ✅ Service de queue offline
```dart
// lib/services/offline_queue_service.dart
class OfflineQueueService {
  static const String _queueKey = 'offline_payment_queue';
  
  Future<void> addToQueue(PaymentData payment) async {
    final prefs = await SharedPreferences.getInstance();
    final queueJson = prefs.getString(_queueKey) ?? '[]';
    final queue = List<Map<String, dynamic>>.from(json.decode(queueJson));
    
    queue.add({
      'local_id': DateTime.now().millisecondsSinceEpoch.toString(),
      'amount': payment.amount,
      'amicale_id': payment.amicaleId,
      'pompier_id': payment.pompierId,
      'created_at': DateTime.now().toIso8601String(),
      'payment_method': 'card',
      'status': 'pending_sync',
    });
    
    await prefs.setString(_queueKey, json.encode(queue));
  }
  
  Future<List<Map<String, dynamic>>> getQueue() async {
    final prefs = await SharedPreferences.getInstance();
    final queueJson = prefs.getString(_queueKey) ?? '[]';
    return List<Map<String, dynamic>>.from(json.decode(queueJson));
  }
  
  Future<void> syncQueue() async {
    final queue = await getQueue();
    if (queue.isEmpty) return;
    
    final connectivity = await Connectivity().checkConnectivity();
    if (connectivity == ConnectivityResult.none) return;
    
    try {
      final response = await ApiService().post('/payments/batch-sync', {
        'transactions': queue,
      });
      
      // Clear queue after successful sync
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString(_queueKey, '[]');
      
    } catch (e) {
      print('Sync failed: $e');
    }
  }
}
```

### 🌆 Après-midi (4h)

#### ✅ Moniteur de connectivité
```dart
// lib/services/connectivity_monitor.dart
class ConnectivityMonitor {
  final _connectivity = Connectivity();
  StreamSubscription<ConnectivityResult>? _subscription;
  final _offlineQueue = OfflineQueueService();
  
  void startMonitoring() {
    _subscription = _connectivity.onConnectivityChanged.listen((result) {
      if (result != ConnectivityResult.none) {
        // Back online, try to sync
        _offlineQueue.syncQueue();
      }
    });
  }
  
  void stopMonitoring() {
    _subscription?.cancel();
  }
  
  Future<bool> isOnline() async {
    final result = await _connectivity.checkConnectivity();
    return result != ConnectivityResult.none;
  }
}
```

---

## 📅 VENDREDI 29/08 - Écran récap et reçu (8h)

### 🌅 Matin (4h)

#### ✅ Écran de récapitulatif post-paiement
```dart
// lib/screens/payment/payment_receipt_screen.dart
class PaymentReceiptScreen extends StatelessWidget {
  final String paymentIntentId;
  final double amount;
  final Amicale amicale;
  
  PaymentReceiptScreen({
    required this.paymentIntentId,
    required this.amount,
    required this.amicale,
  });
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.green.shade50,
      appBar: AppBar(
        title: Text('Paiement confirmé'),
        backgroundColor: Colors.green.shade700,
        automaticallyImplyLeading: false,
      ),
      body: SafeArea(
        child: Padding(
          padding: EdgeInsets.all(20),
          child: Column(
            children: [
              // Success icon
              Container(
                padding: EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.white,
                  shape: BoxShape.circle,
                ),
                child: Icon(
                  Icons.check_circle,
                  size: 80,
                  color: Colors.green.shade700,
                ),
              ),
              
              SizedBox(height: 30),
              
              Text(
                'Merci pour votre soutien !',
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                ),
              ),
              
              SizedBox(height: 20),
              
              // Détails de la transaction
              Container(
                padding: EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(15),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.grey.shade200,
                      blurRadius: 10,
                      offset: Offset(0, 5),
                    ),
                  ],
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    _buildReceiptRow('Montant', '${amount.toStringAsFixed(2)} €'),
                    Divider(),
                    _buildReceiptRow('Amicale', amicale.name),
                    Divider(),
                    _buildReceiptRow('Date', DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now())),
                    Divider(),
                    _buildReceiptRow('Référence', paymentIntentId.substring(0, 10).toUpperCase()),
                  ],
                ),
              ),
              
              SizedBox(height: 30),
              
              // Options de reçu
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  OutlinedButton.icon(
                    onPressed: () => _sendReceiptByEmail(context),
                    icon: Icon(Icons.email),
                    label: Text('Email'),
                    style: OutlinedButton.styleFrom(
                      padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
                    ),
                  ),
                  OutlinedButton.icon(
                    onPressed: () => _sendReceiptBySMS(context),
                    icon: Icon(Icons.sms),
                    label: Text('SMS'),
                    style: OutlinedButton.styleFrom(
                      padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
                    ),
                  ),
                ],
              ),
              
              Spacer(),
              
              // Bouton terminer
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).popUntil((route) => route.isFirst);
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.green.shade700,
                  padding: EdgeInsets.symmetric(horizontal: 50, vertical: 15),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10),
                  ),
                ),
                child: Text(
                  'Terminer',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
  
  Widget _buildReceiptRow(String label, String value) {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 8),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label, style: TextStyle(color: Colors.grey)),
          Text(value, style: TextStyle(fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }
  
  Future<void> _sendReceiptByEmail(BuildContext context) async {
    // Implémenter l'envoi par email via API
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Reçu envoyé par email')),
    );
  }
  
  Future<void> _sendReceiptBySMS(BuildContext context) async {
    // Implémenter l'envoi par SMS via API
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Reçu envoyé par SMS')),
    );
  }
}
```

### 🌆 Après-midi (4h)

#### ✅ Dashboard pompier
```dart
// lib/screens/dashboard/pompier_dashboard_screen.dart
class PompierDashboardScreen extends StatefulWidget {
  @override
  _PompierDashboardScreenState createState() => _PompierDashboardScreenState();
}

class _PompierDashboardScreenState extends State<PompierDashboardScreen> {
  Map<String, dynamic>? _stats;
  bool _isLoading = true;
  
  @override
  void initState() {
    super.initState();
    _loadStats();
  }
  
  Future<void> _loadStats() async {
    try {
      final response = await ApiService().get('/pompiers/me/stats');
      setState(() {
        _stats = response;
        _isLoading = false;
      });
    } catch (e) {
      setState(() => _isLoading = false);
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Mes ventes'),
        backgroundColor: Colors.red.shade700,
      ),
      body: _isLoading
        ? Center(child: CircularProgressIndicator())
        : RefreshIndicator(
            onRefresh: _loadStats,
            child: ListView(
              padding: EdgeInsets.all(20),
              children: [
                // Stats du jour
                _buildStatCard(
                  'Aujourd\'hui',
                  _stats?['today_count'] ?? 0,
                  _stats?['today_amount'] ?? 0,
                  Colors.blue,
                ),
                
                SizedBox(height: 15),
                
                // Stats de la semaine
                _buildStatCard(
                  'Cette semaine',
                  _stats?['week_count'] ?? 0,
                  _stats?['week_amount'] ?? 0,
                  Colors.orange,
                ),
                
                SizedBox(height: 15),
                
                // Stats totales
                _buildStatCard(
                  'Total',
                  _stats?['total_count'] ?? 0,
                  _stats?['total_amount'] ?? 0,
                  Colors.green,
                ),
                
                SizedBox(height: 30),
                
                // Dernières transactions
                Text(
                  'Dernières ventes',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                
                SizedBox(height: 10),
                
                ...(_stats?['recent_transactions'] ?? []).map((transaction) {
                  return Card(
                    child: ListTile(
                      leading: Icon(Icons.check_circle, color: Colors.green),
                      title: Text('${(transaction['amount'] / 100).toStringAsFixed(2)} €'),
                      subtitle: Text(DateFormat('dd/MM HH:mm').format(
                        DateTime.parse(transaction['created_at'])
                      )),
                      trailing: Icon(Icons.arrow_forward_ios, size: 16),
                    ),
                  );
                }).toList(),
              ],
            ),
          ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => CompatibilityCheckScreen()),
          );
        },
        backgroundColor: Colors.red.shade700,
        icon: Icon(Icons.contactless),
        label: Text('Nouveau paiement'),
      ),
    );
  }
  
  Widget _buildStatCard(String title, int count, int amount, Color color) {
    return Container(
      padding: EdgeInsets.all(20),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [color.withOpacity(0.8), color],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(15),
        boxShadow: [
          BoxShadow(
            color: color.withOpacity(0.3),
            blurRadius: 10,
            offset: Offset(0, 5),
          ),
        ],
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                title,
                style: TextStyle(color: Colors.white, fontSize: 16),
              ),
              SizedBox(height: 5),
              Text(
                '$count ventes',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
          Text(
            '${(amount / 100).toStringAsFixed(2)} €',
            style: TextStyle(
              color: Colors.white,
              fontSize: 28,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}
```

---

## 📅 LUNDI 01/09 - Intégration avec login (8h)

### 🌅 Matin (4h)

#### ✅ Vérification statut Stripe au login
```dart
// lib/screens/auth/login_screen.dart
class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _isLoading = false;
  
  Future<void> _login() async {
    setState(() => _isLoading = true);
    
    try {
      // 1. Authentification
      final authResponse = await ApiService().post('/auth/login', {
        'email': _emailController.text,
        'password': _passwordController.text,
      });
      
      // 2. Stocker token
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString('auth_token', authResponse['token']);
      await prefs.setString('user_id', authResponse['user']['id'].toString());
      
      // 3. Vérifier statut Stripe de l'amicale
      final amicaleResponse = await ApiService().get(
        '/amicales/${authResponse['user']['amicale_id']}/stripe-status'
      );
      
      if (!amicaleResponse['charges_enabled']) {
        // Amicale pas encore configurée pour Stripe
        Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (_) => StripeSetupPendingScreen()),
        );
      } else {
        // Tout est OK, aller au dashboard
        Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (_) => PompierDashboardScreen()),
        );
      }
      
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Erreur de connexion: ${e.toString()}')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Padding(
          padding: EdgeInsets.all(20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // Logo
              Image.asset('assets/logo_pompiers.png', height: 100),
              
              SizedBox(height: 40),
              
              Text(
                'Calendriers des Pompiers',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              
              SizedBox(height: 40),
              
              // Email field
              TextField(
                controller: _emailController,
                decoration: InputDecoration(
                  labelText: 'Email',
                  prefixIcon: Icon(Icons.email),
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.emailAddress,
              ),
              
              SizedBox(height: 15),
              
              // Password field
              TextField(
                controller: _passwordController,
                decoration: InputDecoration(
                  labelText: 'Mot de passe',
                  prefixIcon: Icon(Icons.lock),
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
              ),
              
              SizedBox(height: 30),
              
              // Login button
              ElevatedButton(
                onPressed: _isLoading ? null : _login,
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red.shade700,
                  padding: EdgeInsets.symmetric(horizontal: 50, vertical: 15),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10),
                  ),
                ),
                child: _isLoading
                  ? CircularProgressIndicator(color: Colors.white)
                  : Text(
                      'Se connecter',
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

### 🌆 Après-midi (4h)

#### ✅ Écran attente configuration Stripe
```dart
// lib/screens/stripe/stripe_setup_pending_screen.dart
class StripeSetupPendingScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: EdgeInsets.all(30),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                Icons.hourglass_empty,
                size: 80,
                color: Colors.orange,
              ),
              
              SizedBox(height: 30),
              
              Text(
                'Configuration en attente',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              
              SizedBox(height: 20),
              
              Text(
                'Votre amicale n\'a pas encore configuré son compte de paiement Stripe.',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16, color: Colors.grey),
              ),
              
              SizedBox(height: 10),
              
              Text(
                'Contactez votre responsable pour finaliser la configuration.',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 14, color: Colors.grey),
              ),
              
              SizedBox(height: 40),
              
              ElevatedButton.icon(
                onPressed: () async {
                  // Vérifier à nouveau le statut
                  final prefs = await SharedPreferences.getInstance();
                  final amicaleId = prefs.getString('amicale_id');
                  
                  final response = await ApiService().get(
                    '/amicales/$amicaleId/stripe-status'
                  );
                  
                  if (response['charges_enabled']) {
                    Navigator.pushReplacement(
                      context,
                      MaterialPageRoute(builder: (_) => PompierDashboardScreen()),
                    );
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('Configuration toujours en attente')),
                    );
                  }
                },
                icon: Icon(Icons.refresh),
                label: Text('Vérifier à nouveau'),
                style: ElevatedButton.styleFrom(
                  padding: EdgeInsets.symmetric(horizontal: 30, vertical: 12),
                ),
              ),
              
              TextButton(
                onPressed: () {
                  Navigator.pushReplacement(
                    context,
                    MaterialPageRoute(builder: (_) => LoginScreen()),
                  );
                },
                child: Text('Se déconnecter'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

---

## 📅 MARDI 02/09 - Tests unitaires (8h)

### 🌅 Matin (4h)

#### ✅ Tests du service Terminal
```dart
// test/services/stripe_terminal_service_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

void main() {
  group('StripeTerminalService', () {
    test('should detect Tap to Pay capability on compatible iPhone', () async {
      // Test avec iPhone 14
      final service = StripeTerminalService();
      // Mock device info
      final isCompatible = await service.checkTapToPayCapability();
      expect(isCompatible, true);
    });
    
    test('should reject old iPhone models', () async {
      // Test avec iPhone X
      final service = StripeTerminalService();
      // Mock device info pour iPhone X
      final isCompatible = await service.checkTapToPayCapability();
      expect(isCompatible, false);
    });
  });
}
```

### 🌆 Après-midi (4h)

#### ✅ Tests d'intégration
```dart
// test/integration/payment_flow_test.dart
void main() {
  testWidgets('Complete payment flow', (WidgetTester tester) async {
    // Test du flux complet de paiement
    await tester.pumpWidget(MyApp());
    
    // Login
    await tester.enterText(find.byType(TextField).first, 'test@pompiers.fr');
    await tester.enterText(find.byType(TextField).last, 'password');
    await tester.tap(find.text('Se connecter'));
    await tester.pumpAndSettle();
    
    // Navigate to payment
    await tester.tap(find.text('Nouveau paiement'));
    await tester.pumpAndSettle();
    
    // Select amount
    await tester.tap(find.text('20€'));
    await tester.tap(find.text('Payer 20.00€'));
    await tester.pumpAndSettle();
    
    // Verify Tap to Pay screen
    expect(find.text('Paiement sans contact'), findsOneWidget);
  });
}
```

---

## 📅 MERCREDI 03/09 - Optimisations et polish (8h)

### 🌅 Matin (4h)

#### ✅ Gestion des erreurs améliorée
```dart
// lib/utils/error_handler.dart
class ErrorHandler {
  static String getReadableError(dynamic error) {
    if (error is StripeException) {
      switch (error.code) {
        case 'card_declined':
          return 'Carte refusée';
        case 'insufficient_funds':
          return 'Solde insuffisant';
        case 'lost_card':
          return 'Carte perdue';
        case 'stolen_card':
          return 'Carte volée';
        default:
          return 'Erreur de paiement: ${error.message}';
      }
    }
    
    if (error.toString().contains('SocketException')) {
      return 'Pas de connexion internet';
    }
    
    return 'Une erreur est survenue. Veuillez réessayer.';
  }
}
```

### 🌆 Après-midi (4h)

#### ✅ Performance et cache
```dart
// lib/services/cache_service.dart
class CacheService {
  static const Duration _cacheValidity = Duration(minutes: 5);
  final Map<String, CacheEntry> _cache = {};
  
  T? get<T>(String key) {
    final entry = _cache[key];
    if (entry == null) return null;
    
    if (DateTime.now().difference(entry.timestamp) > _cacheValidity) {
      _cache.remove(key);
      return null;
    }
    
    return entry.data as T;
  }
  
  void set(String key, dynamic data) {
    _cache[key] = CacheEntry(data: data, timestamp: DateTime.now());
  }
}

class CacheEntry {
  final dynamic data;
  final DateTime timestamp;
  
  CacheEntry({required this.data, required this.timestamp});
}
```

---

## 📅 JEUDI 04/09 - Finalisation et déploiement (8h)

---

## 📅 VENDREDI 05/09 - Livraison et support (8h)

### 🌅 Matin (4h)

#### ✅ Build et livraison finale
```bash
# iOS - Build final
flutter build ios --release
# Upload vers TestFlight pour distribution beta
```

#### ✅ Tests finaux sur appareils réels
- [ ] Test complet sur iPhone XS
- [ ] Test complet sur iPhone 14/15
- [ ] Validation avec vraies cartes test Stripe
- [ ] Vérification mode offline/online

### 🌆 Après-midi (4h)

#### ✅ Support et formation
- [ ] Formation équipe support client
- [ ] Création guide utilisateur pompier
- [ ] Vidéo démo Tap to Pay
- [ ] Support hotline pour premiers utilisateurs
- [ ] Monitoring des premiers paiements en production

---

## 📊 RÉCAPITULATIF

- **Total heures** : 72h sur 10 jours
- **Écrans créés** : 8
- **Services** : 5
- **Tests** : 15+
- **Compatibilité** : iOS uniquement (iPhone XS+, iOS 15.4+)

## 🎯 LIVRABLES

1. **App Flutter iOS & Android** avec Tap to Pay
2. **Détection automatique** compatibilité appareil (iOS et Android)
3. **Vérification API** pour appareils Android certifiés
4. **Mode offline** avec synchronisation
5. **Dashboard** des ventes
6. **Reçus** par email/SMS
7. **Liste dynamique** des appareils Android compatibles

## ⚠️ POINTS D'ATTENTION

- [ ] Tester sur vrais iPhone XS, 11, 12, 13, 14, 15
- [ ] Valider avec cartes test Stripe
- [ ] Former utilisateurs pilotes
- [ ] Prévoir support jour J

---

## 🎯 BILAN DÉVELOPPEMENT FLUTTER (01/09/2024)

### ✅ INTÉGRATION STRIPE CONNECT RÉALISÉE

#### **Interface de Configuration Stripe**
- **Widget**: `amicale_form.dart` mis à jour
- **Fonctionnalités implémentées**:
  - Vérification statut compte Stripe automatique
  - Bouton "Configurer Stripe" pour amicales non configurées
  - Affichage statut : "✅ Compte Stripe configuré - 100% des paiements pour votre amicale"
  - Gestion des erreurs et états de chargement

#### **Service Stripe Connect**
- **Fichier**: `stripe_connect_service.dart` ✅
- **Méthodes implémentées**:
  ```dart
  Future<Map<String, dynamic>> createStripeAccount()      // Créer compte
  Future<Map<String, dynamic>> getAccountStatus(int id)   // Statut compte
  Future<String> createOnboardingLink(String accountId)   // Lien onboarding
  Future<Map<String, dynamic>> createLocation()          // Location Terminal
  ```
- **Intégration API**: Communication avec backend PHP via `ApiService`

#### **États et Interface Utilisateur**
- **Statut Stripe dynamique** avec codes couleur :
  - 🟠 Orange : Configuration en cours / Non configuré
  - 🟢 Vert : Compte configuré et opérationnel
- **Messages utilisateur** :
  - "💳 Activez les paiements par carte bancaire pour vos membres"
  - "⏳ Configuration Stripe en cours. Veuillez compléter le processus d'onboarding."
  - "✅ Compte Stripe configuré - 100% des paiements pour votre amicale"

### 🔧 ARCHITECTURE TECHNIQUE FLUTTER

#### **Pattern Repository**
- `AmicaleRepository`: Gestion des données amicale + intégration Stripe
- `ApiService`: Communication HTTP avec backend
- `HiveService`: Stockage local (pas utilisé pour Stripe - données sensibles)

#### **Gestion d'État**
- `ValueListenableBuilder`: Réactivité automatique UI
- `ChangeNotifier`: États de chargement Stripe
- Pas de stockage local des données Stripe (sécurité)

#### **Flow Utilisateur Implémenté**
1. **Amicale non configurée** → Bouton "Configurer Stripe" visible
2. **Clic configuration** → Appel API création compte
3. **Compte créé** → Redirection vers lien onboarding Stripe
4. **Onboarding complété** → Statut mise à jour automatiquement  
5. **Compte opérationnel** → Message "100% des paiements"

### 📱 INTERFACE UTILISATEUR

#### **Responsive Design**
- Adaptation mobile/desktop
- Cards Material Design
- Indicateurs visuels clairs (icônes, couleurs)
- Gestion des états d'erreur

#### **Messages Utilisateur**
- **Français uniquement** (conforme CLAUDE.md)
- Termes métier appropriés ("amicale", "membres")
- Messages d'erreur explicites
- Feedback temps réel

### 🚀 FONCTIONNALITÉS OPÉRATIONNELLES

#### **Stripe Connect V1** ✅
- Création comptes Stripe Express ✅
- Génération liens onboarding ✅ 
- Vérification statut en temps réel ✅
- Affichage information "0% commission plateforme" ✅

#### **Prêt pour V2 - Terminal Payments** 🔄
- Architecture préparée pour Tap to Pay
- Services Stripe prêts pour extension
- Interface utilisateur extensible

### ⚠️ LIMITATIONS ACTUELLES

#### **V1 - Connect Onboarding Uniquement**
- Pas encore de paiements Terminal implémentés
- Interface de configuration uniquement
- Tap to Pay prévu en V2

#### **Sécurité**
- Aucune donnée sensible stockée localement
- Clés Stripe uniquement côté backend
- Communication HTTPS obligatoire

### 🎯 PROCHAINES ÉTAPES FLUTTER

#### **V2 - Terminal Payments (À venir)**
1. **Packages Stripe Terminal** 
   - `stripe_terminal: ^3.2.0`
   - `stripe_ios: ^10.0.0` (iOS uniquement initialement)

2. **Écrans de Paiement**
   - Sélection montant
   - Interface Tap to Pay 
   - Confirmation et reçu

3. **Compatibilité Appareils**
   - Vérification iPhone XS+ / iOS 15.4+
   - Liste appareils Android certifiés (via API)

4. **Mode Offline**
   - Queue de synchronisation
   - Gestion connectivité réseau

#### **Tests et Validation**
- Tests widgets Stripe
- Tests d'intégration API
- Validation UX/UI sur vrais appareils

### 📊 MÉTRIQUES DÉVELOPPEMENT

- **Fichiers modifiés** : 2 (`amicale_form.dart`, `stripe_connect_service.dart`)
- **Lignes de code** : ~200 lignes ajoutées
- **APIs intégrées** : 4 endpoints Stripe
- **Tests** : Interface testée manuellement ✅
- **Statut** : V1 Connect opérationnelle ✅

---

*Document créé le 24/08/2024 - Dernière mise à jour : 01/09/2024*