# 📱 Intégration Flutter - Cartes Virtuelles TIM CASH

## 📋 Table des matières
1. [Configuration](#configuration)
2. [Service API](#service-api)
3. [Modèles de données](#modèles-de-données)
4. [Interface utilisateur](#interface-utilisateur)
5. [Gestion du paiement](#gestion-du-paiement)
6. [Deep Links](#deep-links)

---

## 🔧 Configuration

### 1. Dépendances à ajouter dans `pubspec.yaml`

```yaml
dependencies:
  flutter:
    sdk: flutter
  http: ^1.1.0
  webview_flutter: ^4.4.2
  url_launcher: ^6.2.1
  provider: ^6.1.1
  shared_preferences: ^2.2.2
```

### 2. Configuration de l'URL de l'API

```dart
// lib/config/api_config.dart
class ApiConfig {
  static const String baseUrl = 'http://localhost:8001'; // À remplacer en production
  static const String apiVersion = '/api';
  
  // Endpoints cartes virtuelles
  static const String virtualCardsColors = '$apiVersion/virtual-cards/colors';
  static const String virtualCardsPurchase = '$apiVersion/virtual-cards/purchase';
  static const String virtualCardsHistory = '$apiVersion/virtual-cards/history';
  static String virtualCardStatus(String purchaseId) => '$apiVersion/virtual-cards/purchase/$purchaseId';
}
```

---

## 🔌 Service API

### 1. Service de cartes virtuelles

```dart
// lib/services/virtual_card_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../config/api_config.dart';
import '../models/virtual_card_models.dart';

class VirtualCardService {
  final String _token;
  
  VirtualCardService(this._token);
  
  Map<String, String> get _headers => {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer $_token',
  };
  
  /// Récupérer les couleurs disponibles
  Future<AvailableColorsResponse> getAvailableColors() async {
    try {
      final response = await http.get(
        Uri.parse('${ApiConfig.baseUrl}${ApiConfig.virtualCardsColors}'),
        headers: _headers,
      );
      
      if (response.statusCode == 200) {
        return AvailableColorsResponse.fromJson(json.decode(response.body));
      } else {
        throw Exception('Erreur lors de la récupération des couleurs');
      }
    } catch (e) {
      throw Exception('Erreur réseau: $e');
    }
  }
  
  /// Initier un achat de carte virtuelle
  Future<VirtualCardPurchaseResponse> purchaseVirtualCard({
    required String cardColor,
    required Map<String, dynamic> deviceInfo,
    required String ipAddress,
  }) async {
    try {
      final body = {
        'card_color': cardColor,
        'device_info': deviceInfo,
        'ip_address': ipAddress,
      };
      
      final response = await http.post(
        Uri.parse('${ApiConfig.baseUrl}${ApiConfig.virtualCardsPurchase}'),
        headers: _headers,
        body: json.encode(body),
      );
      
      if (response.statusCode == 200) {
        return VirtualCardPurchaseResponse.fromJson(json.decode(response.body));
      } else {
        throw Exception('Erreur lors de l\'achat: ${response.body}');
      }
    } catch (e) {
      throw Exception('Erreur réseau: $e');
    }
  }
  
  /// Vérifier le statut d'un achat
  Future<VirtualCardStatusResponse> checkPurchaseStatus(String purchaseId) async {
    try {
      final response = await http.get(
        Uri.parse('${ApiConfig.baseUrl}${ApiConfig.virtualCardStatus(purchaseId)}'),
        headers: _headers,
      );
      
      if (response.statusCode == 200) {
        return VirtualCardStatusResponse.fromJson(json.decode(response.body));
      } else {
        throw Exception('Erreur lors de la vérification du statut');
      }
    } catch (e) {
      throw Exception('Erreur réseau: $e');
    }
  }
  
  /// Récupérer l'historique des achats
  Future<VirtualCardHistoryResponse> getPurchaseHistory() async {
    try {
      final response = await http.get(
        Uri.parse('${ApiConfig.baseUrl}${ApiConfig.virtualCardsHistory}'),
        headers: _headers,
      );
      
      if (response.statusCode == 200) {
        return VirtualCardHistoryResponse.fromJson(json.decode(response.body));
      } else {
        throw Exception('Erreur lors de la récupération de l\'historique');
      }
    } catch (e) {
      throw Exception('Erreur réseau: $e');
    }
  }
}
```

---

## 📦 Modèles de données

```dart
// lib/models/virtual_card_models.dart

/// Couleur de carte disponible
class VirtualCardColor {
  final String cardColor;
  final double basePrice;
  final double priceForUser;
  final double commissionAmount;
  final double totalAmount;
  final bool isAvailable;
  final bool isPremium;
  
  VirtualCardColor({
    required this.cardColor,
    required this.basePrice,
    required this.priceForUser,
    required this.commissionAmount,
    required this.totalAmount,
    required this.isAvailable,
    required this.isPremium,
  });
  
  factory VirtualCardColor.fromJson(Map<String, dynamic> json) {
    return VirtualCardColor(
      cardColor: json['card_color'],
      basePrice: json['base_price'].toDouble(),
      priceForUser: json['price_for_user'].toDouble(),
      commissionAmount: json['commission_amount'].toDouble(),
      totalAmount: json['total_amount'].toDouble(),
      isAvailable: json['is_available'],
      isPremium: json['is_premium'],
    );
  }
}

/// Réponse des couleurs disponibles
class AvailableColorsResponse {
  final List<VirtualCardColor> colors;
  final int totalColors;
  
  AvailableColorsResponse({
    required this.colors,
    required this.totalColors,
  });
  
  factory AvailableColorsResponse.fromJson(Map<String, dynamic> json) {
    return AvailableColorsResponse(
      colors: (json['colors'] as List)
          .map((color) => VirtualCardColor.fromJson(color))
          .toList(),
      totalColors: json['total_colors'],
    );
  }
}

/// Réponse d'achat de carte virtuelle
class VirtualCardPurchaseResponse {
  final bool success;
  final String message;
  final String purchaseId;
  final String? paymentUrl;
  final String? paymentToken;
  final double? amount;
  final double? totalAmount;
  final String status;
  
  VirtualCardPurchaseResponse({
    required this.success,
    required this.message,
    required this.purchaseId,
    this.paymentUrl,
    this.paymentToken,
    this.amount,
    this.totalAmount,
    required this.status,
  });
  
  factory VirtualCardPurchaseResponse.fromJson(Map<String, dynamic> json) {
    return VirtualCardPurchaseResponse(
      success: json['success'],
      message: json['message'],
      purchaseId: json['purchase_id'],
      paymentUrl: json['payment_url'],
      paymentToken: json['payment_token'],
      amount: json['amount']?.toDouble(),
      totalAmount: json['total_amount']?.toDouble(),
      status: json['status'],
    );
  }
}

/// Statut d'un achat
class VirtualCardStatusResponse {
  final String id;
  final String cardColor;
  final double totalAmount;
  final String status;
  final String paymentTransactionId;
  final String? cardId;
  final DateTime createdAt;
  
  VirtualCardStatusResponse({
    required this.id,
    required this.cardColor,
    required this.totalAmount,
    required this.status,
    required this.paymentTransactionId,
    this.cardId,
    required this.createdAt,
  });
  
  factory VirtualCardStatusResponse.fromJson(Map<String, dynamic> json) {
    return VirtualCardStatusResponse(
      id: json['id'],
      cardColor: json['card_color'],
      totalAmount: json['total_amount'].toDouble(),
      status: json['status'],
      paymentTransactionId: json['payment_transaction_id'],
      cardId: json['card_id'],
      createdAt: DateTime.parse(json['created_at']),
    );
  }
}

/// Historique des achats
class VirtualCardHistoryResponse {
  final List<VirtualCardStatusResponse> purchases;
  final int totalPurchases;
  final double totalSpent;
  
  VirtualCardHistoryResponse({
    required this.purchases,
    required this.totalPurchases,
    required this.totalSpent,
  });
  
  factory VirtualCardHistoryResponse.fromJson(Map<String, dynamic> json) {
    return VirtualCardHistoryResponse(
      purchases: (json['purchases'] as List)
          .map((purchase) => VirtualCardStatusResponse.fromJson(purchase))
          .toList(),
      totalPurchases: json['total_purchases'],
      totalSpent: json['total_spent'].toDouble(),
    );
  }
}
```

---

## 🎨 Interface utilisateur

### 1. Écran de sélection de couleur

```dart
// lib/screens/virtual_card_selection_screen.dart
import 'package:flutter/material.dart';
import '../services/virtual_card_service.dart';
import '../models/virtual_card_models.dart';
import 'payment_webview_screen.dart';

class VirtualCardSelectionScreen extends StatefulWidget {
  final String token;
  
  const VirtualCardSelectionScreen({Key? key, required this.token}) : super(key: key);
  
  @override
  State<VirtualCardSelectionScreen> createState() => _VirtualCardSelectionScreenState();
}

class _VirtualCardSelectionScreenState extends State<VirtualCardSelectionScreen> {
  late VirtualCardService _service;
  List<VirtualCardColor> _colors = [];
  bool _isLoading = true;
  String? _error;
  
  @override
  void initState() {
    super.initState();
    _service = VirtualCardService(widget.token);
    _loadColors();
  }
  
  Future<void> _loadColors() async {
    try {
      final response = await _service.getAvailableColors();
      setState(() {
        _colors = response.colors;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
        _isLoading = false;
      });
    }
  }
  
  Color _getCardGradientStart(String colorName) {
    switch (colorName.toLowerCase()) {
      case 'gold':
        return const Color(0xFFF59E0B);
      case 'amber':
        return const Color(0xFF84CC16);
      case 'green':
        return const Color(0xFF10B981);
      case 'infinite':
        return const Color(0xFF1F2937);
      default:
        return Colors.grey;
    }
  }
  
  Color _getCardGradientEnd(String colorName) {
    switch (colorName.toLowerCase()) {
      case 'gold':
        return const Color(0xFFD97706);
      case 'amber':
        return const Color(0xFFEAB308);
      case 'green':
        return const Color(0xFF059669);
      case 'infinite':
        return const Color(0xFF111827);
      default:
        return Colors.grey.shade700;
    }
  }
  
  String _getCardDisplayName(String colorName) {
    switch (colorName.toLowerCase()) {
      case 'gold':
        return 'Or Classique';
      case 'amber':
        return 'Ambre';
      case 'green':
        return 'Vert Pur';
      case 'infinite':
        return 'Infinite';
      default:
        return colorName;
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Choisir une carte virtuelle'),
        backgroundColor: const Color(0xFF667EEA),
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _error != null
              ? Center(child: Text('Erreur: $_error'))
              : GridView.builder(
                  padding: const EdgeInsets.all(16),
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    childAspectRatio: 0.75,
                    crossAxisSpacing: 16,
                    mainAxisSpacing: 16,
                  ),
                  itemCount: _colors.length,
                  itemBuilder: (context, index) {
                    final color = _colors[index];
                    return _buildCardOption(color);
                  },
                ),
    );
  }
  
  Widget _buildCardOption(VirtualCardColor color) {
    return GestureDetector(
      onTap: () => _onCardSelected(color),
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              _getCardGradientStart(color.cardColor),
              _getCardGradientEnd(color.cardColor),
            ],
          ),
          borderRadius: BorderRadius.circular(20),
          boxShadow: [
            BoxShadow(
              color: _getCardGradientStart(color.cardColor).withOpacity(0.3),
              blurRadius: 10,
              offset: const Offset(0, 5),
            ),
          ],
        ),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'TIM CASH',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const Spacer(),
              Text(
                _getCardDisplayName(color.cardColor),
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              Text(
                '${color.totalAmount.toStringAsFixed(0)} FCFA',
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                ),
              ),
              if (color.isPremium)
                Container(
                  margin: const EdgeInsets.only(top: 8),
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: Colors.white.withOpacity(0.3),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: const Text(
                    '⭐ PREMIUM',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 10,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
  
  Future<void> _onCardSelected(VirtualCardColor color) async {
    // Afficher confirmation
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Confirmer l\'achat'),
        content: Text(
          'Voulez-vous acheter la carte ${_getCardDisplayName(color.cardColor)} pour ${color.totalAmount.toStringAsFixed(0)} FCFA ?'
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('Annuler'),
          ),
          ElevatedButton(
            onPressed: () => Navigator.pop(context, true),
            child: const Text('Confirmer'),
          ),
        ],
      ),
    );
    
    if (confirmed != true) return;
    
    // Initier l'achat
    try {
      showDialog(
        context: context,
        barrierDismissible: false,
        builder: (context) => const Center(child: CircularProgressIndicator()),
      );
      
      final response = await _service.purchaseVirtualCard(
        cardColor: color.cardColor,
        deviceInfo: {
          'model': 'Flutter App',
          'os': 'Mobile',
        },
        ipAddress: '0.0.0.0',
      );
      
      Navigator.pop(context); // Fermer le loading
      
      if (response.success && response.paymentUrl != null) {
        // Ouvrir la WebView de paiement
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => PaymentWebViewScreen(
              paymentUrl: response.paymentUrl!,
              purchaseId: response.purchaseId,
              token: widget.token,
            ),
          ),
        );
      } else {
        _showError(response.message);
      }
    } catch (e) {
      Navigator.pop(context); // Fermer le loading
      _showError(e.toString());
    }
  }
  
  void _showError(String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Erreur'),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }
}
```

---

## 💳 Gestion du paiement

### WebView pour le paiement CinetPay

```dart
// lib/screens/payment_webview_screen.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../services/virtual_card_service.dart';
import 'dart:async';

class PaymentWebViewScreen extends StatefulWidget {
  final String paymentUrl;
  final String purchaseId;
  final String token;
  
  const PaymentWebViewScreen({
    Key? key,
    required this.paymentUrl,
    required this.purchaseId,
    required this.token,
  }) : super(key: key);
  
  @override
  State<PaymentWebViewScreen> createState() => _PaymentWebViewScreenState();
}

class _PaymentWebViewScreenState extends State<PaymentWebViewScreen> {
  late final WebViewController _controller;
  late VirtualCardService _service;
  Timer? _statusCheckTimer;
  bool _isLoading = true;
  
  @override
  void initState() {
    super.initState();
    _service = VirtualCardService(widget.token);
    
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            setState(() => _isLoading = true);
          },
          onPageFinished: (String url) {
            setState(() => _isLoading = false);
            
            // Vérifier si on est sur la page de retour
            if (url.contains('/virtual-cards/return')) {
              _startStatusCheck();
            }
          },
          onNavigationRequest: (NavigationRequest request) {
            // Intercepter les deep links
            if (request.url.startsWith('timcash://')) {
              _handleDeepLink(request.url);
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse(widget.paymentUrl));
  }
  
  void _startStatusCheck() {
    _statusCheckTimer?.cancel();
    _statusCheckTimer = Timer.periodic(const Duration(seconds: 3), (timer) async {
      try {
        final status = await _service.checkPurchaseStatus(widget.purchaseId);
        
        if (status.status == 'card_created') {
          timer.cancel();
          _showSuccess();
        } else if (status.status == 'failed') {
          timer.cancel();
          _showError('Le paiement a échoué');
        }
      } catch (e) {
        // Ignorer les erreurs de vérification
      }
    });
  }
  
  void _handleDeepLink(String url) {
    if (url.contains('success')) {
      _showSuccess();
    } else if (url.contains('failed')) {
      _showError('Le paiement a échoué');
    }
  }
  
  void _showSuccess() {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: const Text('✅ Succès'),
        content: const Text('Votre carte virtuelle a été créée avec succès !'),
        actions: [
          ElevatedButton(
            onPressed: () {
              Navigator.pop(context); // Fermer le dialog
              Navigator.pop(context); // Retour à l'écran précédent
            },
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }
  
  void _showError(String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('❌ Erreur'),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(context); // Fermer le dialog
              Navigator.pop(context); // Retour à l'écran précédent
            },
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }
  
  @override
  void dispose() {
    _statusCheckTimer?.cancel();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Paiement'),
        backgroundColor: const Color(0xFF667EEA),
      ),
      body: Stack(
        children: [
          WebViewWidget(controller: _controller),
          if (_isLoading)
            const Center(
              child: CircularProgressIndicator(),
            ),
        ],
      ),
    );
  }
}
```

---

## 🔗 Deep Links

### Configuration Android (`android/app/src/main/AndroidManifest.xml`)

```xml
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
        android:scheme="timcash"
        android:host="payment" />
</intent-filter>
```

### Configuration iOS (`ios/Runner/Info.plist`)

```xml
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>ci.timcash.app</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>timcash</string>
        </array>
    </dict>
</array>
```

---

## ✅ Checklist d'intégration

- [ ] Ajouter les dépendances dans `pubspec.yaml`
- [ ] Configurer l'URL de l'API
- [ ] Créer le service `VirtualCardService`
- [ ] Créer les modèles de données
- [ ] Implémenter l'écran de sélection de couleur
- [ ] Implémenter la WebView de paiement
- [ ] Configurer les deep links (Android + iOS)
- [ ] Tester le flux complet d'achat
- [ ] Gérer les erreurs et les cas limites
- [ ] Ajouter l'historique des achats

---

## 🎯 Flux utilisateur complet

1. **Utilisateur ouvre l'écran "Obtenir une carte virtuelle"**
2. **Affichage des 4 couleurs disponibles** (GOLD, AMBER, GREEN, INFINITE)
3. **Sélection d'une couleur** → Confirmation
4. **Appel API** `/api/virtual-cards/purchase`
5. **Ouverture WebView** avec l'URL CinetPay
6. **Paiement Mobile Money** (Wave, Orange, MTN, Moov)
7. **Redirection** vers la page de retour
8. **Vérification du statut** toutes les 3 secondes
9. **Affichage du succès** → Carte créée
10. **Retour à l'application** avec la nouvelle carte

---

## 📞 Support

Pour toute question ou problème d'intégration, contactez l'équipe backend TIM CASH.

