Files
geo/app/docs/FLOW-STRIPE.md
pierre 2f5946a184 feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 18:26:27 +01:00

46 KiB

FLOW STRIPE - DOCUMENTATION TECHNIQUE COMPLÈTE

🎯 Vue d'ensemble

Ce document détaille le flow complet des paiements Stripe dans l'application GEOSECTOR, incluant la création des comptes Stripe Connect pour les amicales, les paiements web et Tap to Pay via l'application Flutter.


🏛️ FLOW STRIPE CONNECT - CRÉATION COMPTE AMICALE

🔄 Processus de création et configuration

Le système utilise Stripe Connect pour permettre à chaque amicale de recevoir directement ses paiements sur son propre compte bancaire.

📋 Prérequis et conditions

Configuration requise

  • Plateforme : Web uniquement (pas disponible sur mobile)
  • Rôle utilisateur : Admin amicale (rôle ≥ 2) minimum
  • Statut amicale : Amicale existante avec données complètes

Vérifications automatiques

// Contrôles avant activation Stripe
if (!kIsWeb) {
  // Afficher dialog "Configuration Web requise"
  return;
}

if (userRole < 2) {
  // Seuls les admins d'amicale peuvent configurer Stripe
  return;
}

if (amicale == null || amicale.id == 0) {
  // L'amicale doit exister en base
  return;
}

🔄 Diagramme de séquence - Onboarding Stripe Connect

┌─────────────────┐    ┌─────────────┐    ┌──────────────┐    ┌─────────────┐
│   Admin Web     │    │   App Web   │    │   API PHP    │    │   Stripe    │
└─────────┬───────┘    └──────┬──────┘    └──────┬───────┘    └──────┬──────┘
          │                   │                  │                   │
      [1] │ Coche "CB accepté"│                  │                   │
          │──────────────────>│                  │                   │
          │                   │                  │                   │
      [2] │ Clic "Configurer" │                  │                   │
          │──────────────────>│                  │                   │
          │                   │                  │                   │
      [3] │                   │ POST /stripe/create-account         │
          │                   │─────────────────>│                   │
          │                   │ (amicale_data)   │                   │
          │                   │                  │                   │
      [4] │                   │                  │ Create Account    │
          │                   │                  │──────────────────>│
          │                   │                  │                   │
      [5] │                   │                  │<──────────────────│
          │                   │                  │ account_id        │
          │                   │                  │                   │
      [6] │                   │                  │ Create Onboarding │
          │                   │                  │──────────────────>│
          │                   │                  │                   │
      [7] │                   │                  │<──────────────────│
          │                   │                  │ onboarding_url    │
          │                   │                  │                   │
      [8] │                   │<─────────────────│                   │
          │                   │ onboarding_url   │                   │
          │                   │                  │                   │
      [9] │<──────────────────│                   │                   │
          │ Redirection Stripe│                   │                   │
          │                   │                  │                   │
     [10] │ STRIPE ONBOARDING │                   │                   │
          │ ================== │                   │                   │
          │ • Infos entreprise │                   │                   │
          │ • Infos bancaires  │                   │                   │
          │ • Vérifications    │                   │                   │
          │ ================== │                   │                   │
          │                   │                  │                   │
     [11] │ Retour application │                  │                   │
          │──────────────────>│                  │                   │
          │                   │                  │                   │
     [12] │                   │ GET /stripe/status│                   │
          │                   │─────────────────>│                   │
          │                   │                  │                   │
     [13] │                   │                  │ Retrieve Account  │
          │                   │                  │──────────────────>│
          │                   │                  │                   │
     [14] │                   │                  │<──────────────────│
          │                   │                  │ account_status    │
          │                   │                  │                   │
     [15] │                   │<─────────────────│                   │
          │                   │ status_response  │                   │
          │                   │                  │                   │
     [16] │<──────────────────│                   │                   │
          │ Affichage statut  │                   │                   │

📋 Détail des étapes

Étape 1-2 : ACTIVATION INTERFACE

Acteur: Admin amicale sur interface web Actions:

  • Activation de la checkbox "Accepte les règlements en CB"
  • Clic sur le bouton "Configurer Stripe"
  • Affichage dialog de confirmation avec informations sur le processus

Étape 3 : CRÉATION DU COMPTE STRIPE

Requête: POST /api/stripe/create-account Payload:

{
  "amicale_id": 45,
  "business_name": "Amicale des Pompiers de Paris",
  "business_type": "non_profit",
  "email": "contact@pompiers-paris.fr",
  "phone": "0145123456",
  "address": {
    "line1": "123 Rue de la Caserne",
    "postal_code": "75001",
    "city": "Paris",
    "country": "FR"
  },
  "url": "https://app3.geosector.fr/stripe/return",
  "refresh_url": "https://app3.geosector.fr/stripe/refresh"
}

Étape 4-7 : ONBOARDING STRIPE

Processus côté API:

// 1. Création du compte Stripe Connect
$account = \Stripe\Account::create([
    'type' => 'express',
    'country' => 'FR',
    'business_type' => 'non_profit',
    'company' => [
        'name' => $amicale->name,
        'phone' => $amicale->phone,
        'address' => [...],
    ],
    'email' => $amicale->email
]);

// 2. Création du lien d'onboarding
$onboardingLink = \Stripe\AccountLink::create([
    'account' => $account->id,
    'refresh_url' => 'https://app3.geosector.fr/stripe/refresh',
    'return_url' => 'https://app3.geosector.fr/stripe/return',
    'type' => 'account_onboarding'
]);

// 3. Sauvegarde en base
$amicale->stripe_id = $account->id;
$amicale->save();

return ['onboarding_url' => $onboardingLink->url];

Étape 8-11 : ONBOARDING UTILISATEUR

Processus côté Stripe:

  1. Redirection vers l'interface Stripe dédiée
  2. Collecte informations :
    • Informations légales de l'amicale
    • Coordonnées bancaires (IBAN français)
    • Documents justificatifs si nécessaire
    • Vérification d'identité du représentant légal
  3. Validation automatique ou manuelle par Stripe
  4. Retour vers l'application GEOSECTOR

Étape 12-16 : VÉRIFICATION STATUT

Requête: GET /api/stripe/status/{amicale_id} Réponse:

{
  "account_id": "acct_1234567890",
  "onboarding_completed": true,
  "can_accept_payments": true,
  "capabilities": {
    "card_payments": "active",
    "transfers": "active"
  },
  "requirements": {
    "currently_due": [],
    "pending_verification": []
  },
  "status_message": "Compte actif - Prêt pour les paiements",
  "status_color": "#4CAF50"
}

🎮 Interface utilisateur et états

États possibles du compte Stripe

État Description Interface Actions
Non configuré Checkbox décochée Gris Cocher la case
En cours de config Onboarding incomplet Orange + Compléter sur Stripe
Actif Prêt pour paiements Vert + Aucune action requise
En attente Vérifications Stripe Orange + ⚠️ Attendre validation
Rejeté Compte refusé Rouge + Contacter support

Affichage dynamique

1. CONFIGURATION NON DÉMARRÉE

☐ Accepte les règlements en CB
   [Configurer Stripe]
   💳 Activez les paiements par carte bancaire pour vos membres

2. CONFIGURATION EN COURS

☑ Accepte les règlements en CB
   [⏳ Configuration en cours]  [⚠️ Tooltip: "Veuillez compléter..."]
   ⏳ Configuration Stripe en cours. Veuillez compléter le processus d'onboarding.

3. COMPTE ACTIF

☑ Accepte les règlements en CB
   [✅ Compte actif]  [✅ Tooltip: "Compte configuré"]
   ✅ Compte Stripe configuré - 100% des paiements pour votre amicale

🔐 Sécurité et conformité

Conformité Stripe Connect

  • PCI DSS : Stripe gère la conformité PCI
  • KYC/AML : Vérifications d'identité automatiques
  • Comptes séparés : Chaque amicale a son propre compte
  • Fonds isolés : Pas de commingling des fonds

Validation côté serveur

// Vérifications obligatoires
if (!$user->canManageAmicale($amicaleId)) {
    throw new UnauthorizedException();
}

if (!$amicale->isComplete()) {
    throw new ValidationException('Amicale incomplète');
}

if ($amicale->stripe_id && $this->stripeService->accountExists($amicale->stripe_id)) {
    throw new ConflictException('Compte déjà existant');
}

📊 Suivi et monitoring

Métriques importantes

  • Taux de completion de l'onboarding (objectif > 85%)
  • Temps moyen de configuration (< 10 minutes)
  • Taux d'approbation Stripe (> 95%)
  • Délai d'activation des comptes

Logs et audit

Log::info('Stripe onboarding started', [
    'amicale_id' => $amicaleId,
    'user_id' => $userId,
    'account_id' => $accountId
]);

Log::info('Stripe account activated', [
    'amicale_id' => $amicaleId,
    'account_id' => $accountId,
    'capabilities' => $capabilities
]);

📱 FLOW TAP TO PAY (Application Flutter)

🔄 Diagramme de séquence complet

┌─────────────┐     ┌─────────────┐     ┌──────────┐     ┌─────────┐
│ App Flutter │     │   API PHP   │     │  Stripe  │     │  Carte  │
└──────┬──────┘     └──────┬──────┘     └────┬─────┘     └────┬────┘
       │                   │                  │                │
   [1] │ Validation form   │                  │                │
       │ + montant CB      │                  │                │
       │                   │                  │                │
   [2] │ POST/PUT passage  │                  │                │
       │──────────────────>│                  │                │
       │                   │                  │                │
   [3] │<──────────────────│                  │                │
       │ Passage ID: 456   │                  │                │
       │                   │                  │                │
   [4] │ POST create-intent│                  │                │
       │──────────────────>│ (avec passage_id: 456)           │
       │                   │                  │                │
   [5] │                   │ Create PaymentIntent              │
       │                   │─────────────────>│                │
       │                   │                  │                │
   [6] │                   │<─────────────────│                │
       │                   │ pi_xxx + secret  │                │
       │                   │                  │                │
   [7] │<──────────────────│                  │                │
       │ PaymentIntent ID  │                  │                │
       │                   │                  │                │
   [8] │ SDK Terminal Init │                  │                │
       │ "Approchez carte" │                  │                │
       │                   │                  │                │
   [9] │<──────────────────────────────────────────────────────│
       │        NFC : Lecture carte sans contact               │
       │                   │                  │                │
  [10] │ Process Payment   │                  │                │
       │───────────────────────────────────>│                │
       │                   │                  │                │
  [11] │<───────────────────────────────────│                │
       │     Payment Success                 │                │
       │                   │                  │                │
  [12] │ POST confirm      │                  │                │
       │──────────────────>│                  │                │
       │                   │                  │                │
  [13] │ PUT passage/456   │                  │                │
       │──────────────────>│ (ajout stripe_payment_id)        │
       │                   │                  │                │
  [14] │<──────────────────│                  │                │
       │  Passage updated  │                  │                │
       │                   │                  │                │

🎮 Gestion du Terminal de Paiement

États du Terminal

Le terminal de paiement reste affiché jusqu'à la réponse définitive de Stripe. Il gère plusieurs états :

État Description Actions disponibles
confirming Demande confirmation utilisateur Annuler / Lancer paiement
initializing Initialisation du SDK Aucune (attente)
awaiting_tap Attente carte NFC Annuler uniquement
processing Traitement paiement Aucune (bloqué)
success Paiement réussi Fermeture auto (2s)
error Échec paiement Annuler / Réessayer

Interface utilisateur

1. ATTENTE CARTE

┌──────────────────────┐
│   Présentez la carte  │
│      📱               │
│   [===========]       │ ← Barre de progression
│   Montant: 20.00€     │
│                       │
│   [Annuler]          │ ← Seul bouton disponible
└──────────────────────┘

2. TRAITEMENT

┌──────────────────────┐
│  Traitement...        │
│      ⟳                │ ← Spinner
│  Ne pas retirer       │
│  la carte             │
│                       │ ← Pas de bouton
└──────────────────────┘

3. RÉSULTAT

  • Succès : Message de confirmation + fermeture automatique après 2 secondes
  • Erreur : Message d'erreur + options Annuler/Réessayer

Points importants

  • Dialog non-dismissible : barrierDismissible: false empêche la fermeture accidentelle
  • Timeout : 60 secondes pour présenter la carte, 30 secondes pour le traitement
  • Persistence : Le terminal reste ouvert jusqu'à réponse définitive de Stripe
  • Gestion d'erreur : Possibilité de réessayer sans perdre le contexte

📋 Détail des étapes

Étape 1 : VALIDATION DU FORMULAIRE

Acteur: Application Flutter Actions:

  • L'utilisateur remplit le formulaire de passage complet
  • Saisie du montant du don
  • Sélection du mode de paiement "Carte Bancaire"
  • Validation de tous les champs obligatoires

Étape 2 : SAUVEGARDE DU PASSAGE

Requête: POST /api/passages (nouveau) ou PUT /api/passages/{id} (modification) Payload:

{
  "numero": "10",
  "rue": "Rue de la Paix",
  "ville": "Paris",
  "montant": "20.00",
  "fk_type_reglement": 3,  // CB
  "fk_type": 1,            // Effectué
  // ... autres champs sans stripe_payment_id
}

Réponse:

{
  "id": 456,  // ID réel du passage créé/modifié
  "status": "created"
}

Note: Le passage est TOUJOURS sauvegardé en premier pour obtenir un ID réel.

Étape 3 : DEMANDE DE PAYMENT INTENT

Requête: POST /api/stripe/payments/create-intent Payload envoyé par l'app:

{
  "amount": 2000,                    // Montant en centimes (20€)
  "currency": "eur",
  "payment_method_types": ["card_present"],  // Pour Tap to Pay
  "passage_id": 456,                 // ID RÉEL du passage sauvegardé
  "amicale_id": 45,                  // ID de l'amicale
  "member_id": 67,                   // ID du membre pompier
  "stripe_account": "acct_1234",     // Compte Stripe Connect
  "location_id": "loc_xyz",          // Location Terminal (optionnel)
  "metadata": {
    "passage_id": "456",              // ID réel, jamais 0
    "amicale_name": "Pompiers de Paris",
    "member_name": "Jean Dupont",
    "type": "tap_to_pay"
  }
}

Étape 4 : CRÉATION CÔTÉ STRIPE

Acteur: API PHP → Stripe Actions de l'API:

  1. Validation des données reçues
  2. Vérification des permissions utilisateur
  3. Appel Stripe API :
$paymentIntent = \Stripe\PaymentIntent::create([
    'amount' => 2000,
    'currency' => 'eur',
    'payment_method_types' => ['card_present'],
    'capture_method' => 'automatic',
    'metadata' => [
        'passage_id' => '123',
        'amicale_id' => '45',
        'member_id' => '67'
    ]
], ['stripe_account' => 'acct_1234']);

Étape 5 : RETOUR DU PAYMENT INTENT

Réponse API → App:

{
  "success": true,
  "payment_intent_id": "pi_3O123abc",
  "client_secret": "pi_3O123abc_secret_xyz",
  "amount": 2000,
  "status": "requires_payment_method"
}

Étape 6 : COLLECTE NFC

Acteur: Application Flutter (SDK Stripe Terminal) Actions:

  1. Initialisation du Terminal SDK
  2. Activation du NFC
  3. Affichage interface "Approchez la carte"
  4. Lecture des données de la carte
  5. Animation visuelle pendant la lecture

Étape 7 : TRAITEMENT STRIPE

Acteur: SDK → Stripe Actions automatiques:

  • Envoi sécurisé des données carte
  • Vérification 3D Secure si nécessaire
  • Autorisation bancaire
  • Capture automatique du paiement
  • Retour du statut à l'application

Étape 8 : CONFIRMATION

Requête: POST /api/stripe/payments/confirm Payload:

{
  "payment_intent_id": "pi_3O123abc",
  "status": "succeeded",
  "amount": 2000,
  "amicale_id": 45,
  "member_id": 67
}

Note importante: Cette confirmation est envoyée AVANT la sauvegarde du passage. Elle permet à l'API de :

  • Tracker la tentative de paiement
  • Vérifier la cohérence avec Stripe
  • Enregistrer le succès/échec indépendamment du passage

Étape 9 : MISE À JOUR DU PASSAGE

Requête: PUT /api/passages/456 Payload:

{
  "id": 456,
  "stripe_payment_id": "pi_3O123abc",  // Ajout du payment ID
  // ... autres champs inchangés
}

Note: Seul le stripe_payment_id est ajouté au passage déjà existant.

Étape 10 : CONFIRMATION FINALE

Réponse API → App:

{
  "success": true,
  "passage": {
    "id": 123,
    "stripe_payment_id": "pi_3O123abc",
    "status": "completed"
  }
}

📱 FLOW PAIEMENT QR CODE (Web + Mobile)

🎯 Vue d'ensemble

Le paiement par QR Code permet aux clients de payer directement avec leur téléphone en scannant un code QR généré par l'application. Cette méthode fonctionne sur Web et Mobile et ne nécessite pas de matériel spécifique.

🔄 Diagramme de séquence complet

┌─────────────┐     ┌─────────────┐     ┌──────────┐     ┌─────────┐     ┌─────────┐
│ App Flutter │     │   API PHP   │     │  Stripe  │     │ Client  │     │ Passage │
└──────┬──────┘     └──────┬──────┘     └────┬─────┘     └────┬────┘     └────┬────┘
       │                   │                  │                │               │
   [1] │ Validation form   │                  │                │               │
       │ + montant CB      │                  │                │               │
       │                   │                  │                │               │
   [2] │ POST/PUT passage  │                  │                │               │
       │──────────────────>│                  │                │               │
       │                   │                  │                │               │
   [3] │<──────────────────│                  │                │               │
       │ Passage ID: 456   │                  │                │               │
       │                   │                  │                │               │
   [4] │ Vérif Stripe      │                  │                │               │
       │ chkStripe=true    │                  │                │               │
       │ + stripeId rempli │                  │                │               │
       │                   │                  │                │               │
   [5] │ Dialog Sélection  │                  │                │               │
       │ "Règlement CB"    │                  │                │               │
       │ [QRCode|TapToPay] │                  │                │               │
       │                   │                  │                │               │
   [6] │ Clic "QR Code"    │                  │                │               │
       │                   │                  │                │               │
   [7] │ POST payment-links│                  │                │               │
       │──────────────────>│ (passage_id: 456)│                │               │
       │                   │                  │                │               │
   [8] │                   │ Create PaymentLink                │               │
       │                   │─────────────────>│                │               │
       │                   │                  │                │               │
   [9] │                   │<─────────────────│                │               │
       │                   │ link_id + url    │                │               │
       │                   │                  │                │               │
  [10] │<──────────────────│                  │                │               │
       │ PaymentLink data  │                  │                │               │
       │                   │                  │                │               │
  [11] │ Génération QR     │                  │                │               │
       │ avec URL Stripe   │                  │                │               │
       │                   │                  │                │               │
  [12] │ Affichage dialog  │                  │                │               │
       │ QR Code           │                  │                │               │
       │ ┌──────────────┐  │                  │                │               │
       │ │   QR Code    │  │                  │                │               │
       │ │   ▓▓▓▓▓▓▓▓   │  │                  │                │               │
       │ │   20.00 €    │  │                  │                │               │
       │ └──────────────┘  │                  │                │               │
       │                   │                  │                │               │
  [13] │                   │                  │   Scan QR Code │               │
       │                   │                  │<───────────────│               │
       │                   │                  │                │               │
  [14] │                   │                  │ Page paiement  │               │
       │                   │                  │───────────────>│               │
       │                   │                  │ Stripe hosted  │               │
       │                   │                  │                │               │
  [15] │                   │                  │   Saisie CB    │               │
       │                   │                  │<───────────────│               │
       │                   │                  │                │               │
  [16] │                   │                  │  Validation    │               │
       │                   │                  │───────────────>│               │
       │                   │                  │                │               │
  [17] │                   │  Webhook         │                │               │
       │                   │<─────────────────│                │               │
       │                   │ payment_succeeded│                │               │
       │                   │                  │                │               │
  [18] │                   │ Update passage   │                │               │
       │                   │────────────────────────────────────────────────>│
       │                   │ stripe_payment_id│                │               │
       │                   │                  │                │               │
  [19] │                   │                  │ Confirmation   │               │
       │                   │                  │───────────────>│               │
       │                   │                  │ "Merci!"       │               │

📋 Détail des étapes

Étape 1-3 : SAUVEGARDE DU PASSAGE

Identique au flow Tap to Pay - Le passage est toujours créé en premier pour obtenir un ID réel.

Étape 4 : VÉRIFICATION STRIPE

Acteur: Application Flutter Conditions vérifiées:

final amicale = CurrentAmicaleService.instance.currentAmicale;
final stripeEnabled = amicale?.chkStripe == true &&
                     amicale?.stripeId != null &&
                     amicale!.stripeId.isNotEmpty;

if (stripeEnabled && fkTypeReglement == 3 && montant > 0) {
  // Afficher dialog de sélection de méthode
}

Étape 5 : DIALOG DE SÉLECTION DE MÉTHODE

Widget: PaymentMethodSelectionDialog Interface affichée:

┌────────────────────────────────┐
│     Règlement CB               │
│                                │
│  👤 Jean Dupont                │
│  💰 20.00 €                    │
│                                │
│  Sélectionnez une méthode :    │
│                                │
│  ┌──────────────────────────┐  │
│  │ 📱 Paiement par QR Code  │  │
│  │ Le client scanne le code │  │
│  └──────────────────────────┘  │
│                                │
│  ┌──────────────────────────┐  │ (Si compatible)
│  │ 💳 Tap to Pay            │  │
│  │ Paiement sans contact    │  │
│  └──────────────────────────┘  │
│                                │
│  🔒 Sécurisé par Stripe        │
└────────────────────────────────┘

Requête: POST /api/stripe/payment-links Payload:

{
  "amount": 2000,
  "currency": "eur",
  "description": "Calendrier pompiers - Jean Dupont",
  "passage_id": 456,
  "metadata": {
    "passage_id": "456",
    "habitant_name": "Jean Dupont",
    "adresse": "10 Rue de la Paix, Paris"
  }
}

Réponse:

{
  "success": true,
  "payment_link_id": "plink_1234567890",
  "url": "https://buy.stripe.com/test_xxxxxxxxxxxxx",
  "amount": 2000,
  "passage_id": 456
}

Code PHP Backend:

$paymentLink = \Stripe\PaymentLink::create([
    'line_items' => [[
        'price_data' => [
            'currency' => 'eur',
            'product_data' => [
                'name' => 'Calendrier pompiers',
            ],
            'unit_amount' => 2000,
        ],
        'quantity' => 1,
    ]],
    'metadata' => [
        'passage_id' => '456',
        'type' => 'qr_code_payment',
    ],
    'after_completion' => [
        'type' => 'hosted_confirmation',
        'hosted_confirmation' => [
            'custom_message' => 'Merci pour votre paiement !',
        ],
    ],
], [
    'stripe_account' => $amicale->stripe_id,
]);

Étape 11-12 : AFFICHAGE DU QR CODE

Widget: QRCodePaymentDialog Interface:

┌──────────────────────────────┐
│  Paiement par QR Code        │
│                              │
│  💰 20.00 €                  │
│                              │
│  ┌────────────────────────┐  │
│  │                        │  │
│  │    ▓▓▓▓▓  ▓▓▓▓▓▓▓     │  │
│  │    ▓▓▓▓▓  ▓▓   ▓▓     │  │
│  │    ▓▓▓▓▓  ▓▓▓▓▓▓▓     │  │
│  │    QR CODE HERE        │  │
│  │                        │  │
│  └────────────────────────┘  │
│                              │
│  Scannez ce QR code avec     │
│  votre téléphone             │
│                              │
│  Vous serez redirigé vers    │
│  une page sécurisée Stripe   │
│                              │
│  🔒 Paiement sécurisé        │
│                              │
│         [Fermer]             │
└──────────────────────────────┘

Étape 13-16 : PAIEMENT CLIENT

Acteur: Client final Actions:

  1. Scan du QR Code avec son smartphone
  2. Ouverture automatique de l'URL Stripe dans le navigateur
  3. Affichage de la page de paiement Stripe hébergée
  4. Saisie des informations de carte bancaire
  5. Validation 3D Secure si nécessaire
  6. Confirmation du paiement

Étape 17-18 : WEBHOOK ET MISE À JOUR

Requête Webhook: POST /api/stripe/webhook Event: checkout.session.completed ou payment_intent.succeeded Payload Stripe:

{
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_1234567890",
      "amount": 2000,
      "metadata": {
        "passage_id": "456"
      }
    }
  }
}

Action Backend:

// Récupérer le passage
$passage = Passage::find($paymentIntent->metadata->passage_id);

// Mettre à jour avec le payment ID
$passage->stripe_payment_id = $paymentIntent->id;
$passage->save();

Log::info('Payment confirmed via QR Code', [
    'passage_id' => $passage->id,
    'payment_id' => $paymentIntent->id,
]);

🔄 Comparaison QR Code vs Tap to Pay

Aspect QR Code Tap to Pay
Plateformes Web + Mobile Mobile uniquement
Matériel requis Aucun NFC (iPhone XS+ / Android certifié)
Client utilise Son propre téléphone Le téléphone du pompier
payment_method_types ["card"] ["card_present"]
Interface paiement Page Stripe hébergée NFC sur l'app
Délai confirmation Immédiat (webhook) Immédiat (SDK)
Expérience client Scan + saisie CB Approche carte
Sans contact physique Oui (COVID-safe) Non (proximité requise)
Montant minimum 0.50€ 1.00€
Cas d'usage idéal Client à distance Face à face

Conditions d'éligibilité

Affichage du bouton "QR Code"

// Conditions cumulatives :
final canShowQRCode =
    amicale.chkStripe == true &&           // Stripe activé
    amicale.stripeId.isNotEmpty &&         // Compte configuré
    fkTypeReglement == 3 &&                // CB sélectionnée
    montant > 0;                           // Montant valide

Différences avec Tap to Pay

  • Pas besoin de stripeLocationId (spécifique Terminal)
  • Pas de vérification device (fonctionne partout)
  • Pas de batterie minimum requise

🎨 Widgets créés

1. PaymentMethodSelectionDialog

Fichier: lib/presentation/widgets/payment_method_selection_dialog.dart Rôle: Choisir entre QR Code et Tap to Pay Props:

PaymentMethodSelectionDialog({
  required PassageModel passage,
  required double amount,
  required String habitantName,
  required StripeConnectService stripeConnectService,
  VoidCallback? onTapToPaySelected,
})

2. QRCodePaymentDialog

Fichier: lib/presentation/widgets/qr_code_payment_dialog.dart Rôle: Afficher le QR Code généré Props:

QRCodePaymentDialog({
  required PaymentLinkResult paymentLink,
  VoidCallback? onClose,
})

🔧 Services modifiés

StripeConnectService

Nouvelle méthode:

Future<PaymentLinkResult?> createPaymentLink({
  required int amountInCents,
  required int passageId,
  String? description,
  Map<String, dynamic>? metadata,
})

📊 Modèles créés

PaymentLinkResult

Fichier: lib/core/data/models/payment_link_result.dart

class PaymentLinkResult {
  final String paymentLinkId;
  final String url;
  final int amount;
  final int? passageId;
}

🔐 Sécurité

Validation Backend

// Vérifications obligatoires
$request->validate([
    'amount' => 'required|integer|min:50',
    'passage_id' => 'required|integer|exists:ope_pass,id',
]);

// Vérifier Stripe activé
if (!$amicale->chk_stripe || empty($amicale->stripe_id)) {
    return response()->json(['error' => 'Stripe non activé'], 403);
}

// Créer sur le compte Connect de l'amicale
\Stripe\PaymentLink::create([...], [
    'stripe_account' => $amicale->stripe_id,
]);

📱 Package ajouté

pubspec.yaml:

dependencies:
  qr_flutter: ^4.1.0  # Génération de QR codes

💻 FLOW PAIEMENT WEB

🔄 Principales différences avec Tap to Pay

Aspect Web Tap to Pay
payment_method_types ["card"] ["card_present"]
SDK Stripe.js dans navigateur Stripe Terminal SDK natif
Interface paiement Formulaire carte web NFC téléphone
capture_method manual ou automatic Toujours automatic
Metadata type "web" "tap_to_pay"
Client secret usage Pour Stripe Elements Pour Terminal SDK

📋 Flow Web simplifié

1. Utilisateur remplit formulaire web avec montant
2. POST /api/stripe/payments/create-intent
   - payment_method_types: ["card"]
   - metadata.type: "web"
3. API crée PaymentIntent et retourne client_secret
4. Frontend utilise Stripe.js pour afficher formulaire carte
5. Utilisateur saisit données carte
6. Stripe.js confirme le paiement
7. Webhook Stripe notifie l'API du succès
8. API met à jour le passage en base

📱 VALIDATION ET CONTRÔLES CÔTÉ APP

Vérifications avant affichage du Terminal

L'application effectue une série de vérifications avant d'afficher le terminal de paiement :

1. Dans le formulaire de passage

void _handleSubmit() {
  // ✅ Validation des champs du formulaire
  if (!_formKey.currentState!.validate()) return;

  // ✅ Vérification CB sélectionnée + montant > 0
  if (_fkTypeReglement == 3 && montant > 0) {
    await _attemptTapToPay();  // Lance le flow
  }
}

2. Dans le service StripeTapToPayService

initialize() {
  // ✅ User connecté
  if (!CurrentUserService.instance.isLoggedIn) return false;

  // ✅ Amicale avec Stripe activé
  if (!amicale.chkStripe || amicale.stripeId.isEmpty) return false;

  // ✅ Appareil compatible (iPhone XS+, iOS 16.4+)
  if (!DeviceInfoService.instance.canUseTapToPay()) return false;

  // ✅ Configuration Stripe récupérée
  await _fetchConfiguration();
}

3. Dans le Dialog Tap to Pay

_startPayment() {
  // ✅ Service initialisé ou initialisation réussie
  if (!initialized) throw Exception('Impossible d\'initialiser');

  // ✅ Prêt pour paiements (toutes conditions remplies)
  if (!isReadyForPayments()) throw Exception('Appareil non prêt');

  // Création PaymentIntent et collecte NFC...
}

Flow de sauvegarde et paiement

Le nouveau flow garantit que le passage existe TOUJOURS avant le paiement :

// 1. SAUVEGARDE DU PASSAGE EN PREMIER
Future<void> _savePassage() {
  // Créer ou modifier le passage
  PassageModel? savedPassage;
  if (widget.passage == null) {
    // Création avec retour de l'ID
    savedPassage = await passageRepository.createPassageWithReturn(passageData);
  } else {
    // Modification
    savedPassage = passageData;
  }

  // 2. SI CB SÉLECTIONNÉE, LANCER TAP TO PAY
  if (typeReglement == CB && montant > 0) {
    await _attemptTapToPayWithPassage(savedPassage, montant);
  }
}

// 3. PAIEMENT AVEC ID RÉEL
_attemptTapToPayWithPassage(PassageModel passage, double montant) {
  _TapToPayFlowDialog(
    passageId: passage.id,  // ← ID réel, jamais 0
    onSuccess: (paymentIntentId) {
      // 4. MISE À JOUR DU PASSAGE
      final updated = passage.copyWith(
        stripePaymentId: paymentIntentId
      );
      passageRepository.updatePassage(updated);
    }
  );
}

🔐 SÉCURITÉ ET BONNES PRATIQUES

🛡️ Principes de sécurité

  1. Jamais de données carte en clair - Toujours via SDK Stripe
  2. HTTPS obligatoire - Toutes communications chiffrées
  3. Validation côté serveur - Ne jamais faire confiance au client
  4. Tokens temporaires - Connection tokens à durée limitée
  5. Logs sans données sensibles - Pas de numéros carte dans les logs

Validations requises

Côté App Flutter:

  • Vérifier compatibilité appareil (iPhone XS+, iOS 16.4+)
  • Valider montant (min 1€, max 999€)
  • Vérifier connexion internet avant paiement
  • Gérer timeouts réseau

Côté API:

  • Authentification utilisateur obligatoire
  • Vérification appartenance à l'amicale
  • Validation montants et devises
  • Vérification compte Stripe actif
  • Rate limiting sur endpoints

📊 DOUBLE CONFIRMATION API

Pourquoi deux appels distincts ?

Le système utilise deux endpoints séparés pour une meilleure traçabilité :

1. Confirmation du paiement (/api/stripe/payments/confirm)

POST /api/stripe/payments/confirm
{
  "payment_intent_id": "pi_xxx",
  "status": "succeeded",  // ou "failed"
  "amount": 2000
}

Rôle : Notifier l'API du résultat Stripe (succès/échec)

2. Sauvegarde du passage (/api/passages)

POST/PUT /api/passages
{
  "stripe_payment_id": "pi_xxx",
  "montant": "20.00",
  "fk_type_reglement": 3  // CB
}

Rôle : Sauvegarder le passage uniquement si paiement réussi

Avantages du nouveau flow

Aspect Bénéfice
Passage toujours créé Même si le paiement échoue, le passage existe
ID réel dans Stripe Les metadata contiennent toujours le vrai passage_id
Traçabilité complète Liaison bidirectionnelle garantie (passage → Stripe et Stripe → passage)
Gestion d'erreur robuste Si paiement échoue, le passage reste sans stripe_payment_id
Mode offline Le passage peut être créé localement avec ID temporaire

🔄 GESTION DES ERREURS

📱 Erreurs Tap to Pay

Code erreur Description Action utilisateur
device_not_compatible iPhone non compatible Afficher message explicatif
nfc_disabled NFC désactivé Demander activation dans réglages
card_declined Carte refusée Essayer autre carte
insufficient_funds Solde insuffisant Essayer autre carte
network_error Erreur réseau Réessayer ou mode offline
timeout Timeout lecture carte Rapprocher carte et réessayer

🔄 Flow de retry

1. Erreur détectée
2. Message utilisateur explicite
3. Option "Réessayer" proposée
4. Conservation du montant et contexte
5. Nouveau PaymentIntent si nécessaire
6. Maximum 3 tentatives

📊 MONITORING ET LOGS

📈 Métriques à suivre

  1. Taux de succès des paiements (objectif > 95%)
  2. Temps moyen de transaction (< 15 secondes)
  3. Types d'erreurs les plus fréquentes
  4. Appareils utilisés (modèles iPhone)
  5. Montants moyens des transactions

📝 Logs essentiels

App Flutter:

debugPrint('🚀 PaymentIntent créé: $paymentIntentId');
debugPrint('💳 Collecte NFC démarrée');
debugPrint('✅ Paiement confirmé: $amount €');
debugPrint('❌ Erreur paiement: $errorCode');

API PHP:

Log::info('PaymentIntent created', [
    'id' => $paymentIntent->id,
    'amount' => $amount,
    'amicale_id' => $amicaleId
]);

🚀 OPTIMISATIONS ET PERFORMANCES

Optimisations implémentées

  1. Cache Box Hive - Éviter accès répétés
  2. Batch API calls - Grouper les requêtes
  3. Lazy loading - Charger données à la demande
  4. Connection pooling - Réutiliser connexions HTTP
  5. Queue offline - File d'attente locale

🎯 Points d'amélioration

  • Pré-création PaymentIntent pendant saisie montant
  • Cache des configurations Stripe
  • Compression des payloads API
  • Optimisation animations NFC
  • Réduction taille APK/IPA

📱 COMPATIBILITÉ APPAREILS

🍎 iOS - Tap to Pay

Appareils compatibles:

  • iPhone XS, XS Max, XR
  • iPhone 11, 11 Pro, 11 Pro Max
  • iPhone 12, 12 mini, 12 Pro, 12 Pro Max
  • iPhone 13, 13 mini, 13 Pro, 13 Pro Max
  • iPhone 14, 14 Plus, 14 Pro, 14 Pro Max
  • iPhone 15, 15 Plus, 15 Pro, 15 Pro Max
  • iPhone 16 (tous modèles)

Prérequis:

  • iOS 16.4 minimum
  • NFC activé
  • Bluetooth activé (pour certains cas)

🤖 Android - Tap to Pay (V2.2+)

À venir - Liste dynamique via API

  • Appareils certifiés Google Pay
  • Android 9.0+ (API 28+)
  • NFC requis

🔗 RESSOURCES ET DOCUMENTATION

📚 Documentation officielle

🛠️ Outils de test

  • Cartes de test Stripe: 4242 4242 4242 4242
  • iPhone Simulator: Ne supporte pas NFC
  • Stripe CLI: Pour webhooks locaux
  • Postman: Collection API fournie

📞 Support

  • Stripe Support: support@stripe.com
  • Équipe Backend: API PHP GEOSECTOR
  • Équipe Mobile: Flutter GEOSECTOR

📅 HISTORIQUE DES VERSIONS

Version Date Modifications
1.0 28/09/2025 Création documentation initiale
1.1 28/09/2025 Ajout flow complet Tap to Pay
1.2 28/09/2025 Intégration passage_id et metadata
1.3 05/11/2025 Ajout flow paiement par QR Code

Document technique - Flow Stripe GEOSECTOR Dernière mise à jour : 5 novembre 2025