# PLANNING STRIPE - DÉVELOPPEUR BACKEND PHP
## API PHP 8.3 - Intégration Stripe Tap to Pay (Mobile uniquement)
### Période : 25/08/2024 - 05/09/2024
### Mise à jour : Janvier 2025 - Simplification architecture

---

## 📅 LUNDI 25/08 - Setup et architecture (8h)

### 🌅 Matin (4h)
```bash
# Installation Stripe PHP SDK
cd api
composer require stripe/stripe-php
```

#### ✅ Configuration environnement
- [x] Créer configuration Stripe dans `AppConfig.php` avec clés TEST
- [x] Ajouter variables de configuration :
  ```php
  'stripe' => [
    'public_key_test' => 'pk_test_51QwoVN00pblGEgsXkf8qlXm...',
    'secret_key_test' => 'sk_test_51QwoVN00pblGEgsXnvqi8qf...',
    'webhook_secret_test' => 'whsec_test_...',
    'api_version' => '2024-06-20',
    'application_fee_percent' => 0, // DECISION: 0% commission
    'mode' => 'test'
  ]
  ```
- [x] Créer service `StripeService.php` singleton
- [x] Configurer authentification Session-based API

#### ✅ Base de données
```sql
-- Modification de la table ope_pass existante (JANVIER 2025)
ALTER TABLE `ope_pass`
DROP COLUMN IF EXISTS `is_striped`,
ADD COLUMN `stripe_payment_id` VARCHAR(50) DEFAULT NULL COMMENT 'ID du PaymentIntent Stripe (pi_xxx)',
ADD INDEX `idx_stripe_payment` (`stripe_payment_id`);

-- Tables à créer (simplifiées)
CREATE TABLE stripe_accounts (
    id INT PRIMARY KEY AUTO_INCREMENT,
    amicale_id INT NOT NULL,
    stripe_account_id VARCHAR(255) UNIQUE,
    charges_enabled BOOLEAN DEFAULT FALSE,
    payouts_enabled BOOLEAN DEFAULT FALSE,
    onboarding_completed BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (amicale_id) REFERENCES amicales(id)
);

-- NOTE: Table payment_intents SUPPRIMÉE - on utilise directement stripe_payment_id dans ope_pass
-- NOTE: Table terminal_readers SUPPRIMÉE - Tap to Pay uniquement, pas de terminaux externes

CREATE TABLE android_certified_devices (
    id INT PRIMARY KEY AUTO_INCREMENT,
    manufacturer VARCHAR(100),
    model VARCHAR(200),
    model_identifier VARCHAR(200),
    tap_to_pay_certified BOOLEAN DEFAULT FALSE,
    certification_date DATE,
    min_android_version INT,
    country VARCHAR(2) DEFAULT 'FR',
    last_verified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_manufacturer_model (manufacturer, model)
);
```

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

#### ✅ Endpoints Connect - Onboarding (RÉALISÉS)
```php
// POST /api/stripe/accounts - IMPLEMENTED
public function createAccount() {
    $amicale = Amicale::find($amicaleId);
    
    $account = \Stripe\Account::create([
        'type' => 'express',
        'country' => 'FR',
        'email' => $amicale->email,
        'capabilities' => [
            'card_payments' => ['requested' => true],
            'transfers' => ['requested' => true],
        ],
        'business_type' => 'non_profit',
        'business_profile' => [
            'name' => $amicale->name,
            'product_description' => 'Vente de calendriers des pompiers',
        ],
    ]);
    
    // Sauvegarder stripe_account_id
    return $account;
}

// GET /api/amicales/{id}/onboarding-link
public function getOnboardingLink($amicaleId) {
    $accountLink = \Stripe\AccountLink::create([
        'account' => $amicale->stripe_account_id,
        'refresh_url' => config('app.url') . '/stripe/refresh',
        'return_url' => config('app.url') . '/stripe/success',
        'type' => 'account_onboarding',
    ]);
    
    return ['url' => $accountLink->url];
}
```

---

## 📅 MARDI 26/08 - Webhooks et Terminal (8h)

### 🌅 Matin (4h)

#### ✅ Webhooks handler
```php
// POST /api/webhooks/stripe
public function handleWebhook(Request $request) {
    $payload = $request->getContent();
    $sig_header = $request->header('Stripe-Signature');
    
    try {
        $event = \Stripe\Webhook::constructEvent(
            $payload, $sig_header, config('stripe.webhook_secret')
        );
    } catch(\Exception $e) {
        return response('Invalid signature', 400);
    }
    
    switch ($event->type) {
        case 'account.updated':
            $this->handleAccountUpdated($event->data->object);
            break;
        case 'account.application.authorized':
            $this->handleAccountAuthorized($event->data->object);
            break;
        case 'payment_intent.succeeded':
            $this->handlePaymentSucceeded($event->data->object);
            break;
    }
    
    return response('Webhook handled', 200);
}
```

#### ✅ Configuration Tap to Pay
```php
// POST /api/stripe/tap-to-pay/init
public function initTapToPay(Request $request) {
    $userId = Session::getUserId();
    $entityId = Session::getEntityId();

    // Vérifier que l'entité a un compte Stripe
    $account = $this->getStripeAccount($entityId);

    return [
        'stripe_account_id' => $account->stripe_account_id,
        'tap_to_pay_enabled' => true
    ];
}
```

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

#### ✅ Vérification compatibilité Device
```php
// POST /api/stripe/devices/check-tap-to-pay
public function checkTapToPayCapability(Request $request) {
    $platform = $request->input('platform');
    $model = $request->input('device_model');
    $osVersion = $request->input('os_version');

    if ($platform === 'iOS') {
        // iPhone XS et ultérieurs avec iOS 16.4+
        $supported = $this->checkiOSCompatibility($model, $osVersion);
    } else {
        // Android certifié pour la France
        $supported = $this->checkAndroidCertification($model);
    }

    return [
        'tap_to_pay_supported' => $supported,
        'message' => $supported ?
            'Tap to Pay disponible' :
            'Appareil non compatible'
    ];
}
```

---

## 📅 MERCREDI 27/08 - Paiements et fees (8h)

### 🌅 Matin (4h)

#### ✅ Création PaymentIntent avec association au passage
```php
// POST /api/payments/create-intent
public function createPaymentIntent(Request $request) {
    $validated = $request->validate([
        'amount' => 'required|integer|min:100', // en centimes
        'passage_id' => 'required|integer', // ID du passage ope_pass
        'entity_id' => 'required|integer',
    ]);

    $userId = Session::getUserId();
    $entity = $this->getEntity($validated['entity_id']);

    // Commission à 0% (décision client)
    $applicationFee = 0;

    $paymentIntent = \Stripe\PaymentIntent::create([
        'amount' => $validated['amount'],
        'currency' => 'eur',
        'payment_method_types' => ['card_present'],
        'capture_method' => 'automatic',
        'application_fee_amount' => $applicationFee,
        'transfer_data' => [
            'destination' => $entity->stripe_account_id,
        ],
        'metadata' => [
            'passage_id' => $validated['passage_id'],
            'user_id' => $userId,
            'entity_id' => $entity->id,
            'year' => date('Y'),
        ],
    ]);

    // Mise à jour directe dans ope_pass
    $this->db->prepare("
        UPDATE ope_pass
        SET stripe_payment_id = :stripe_id,
            date_modified = NOW()
        WHERE id = :passage_id
    ")->execute([
        ':stripe_id' => $paymentIntent->id,
        ':passage_id' => $validated['passage_id']
    ]);

    return [
        'client_secret' => $paymentIntent->client_secret,
        'payment_intent_id' => $paymentIntent->id,
    ];
}
```

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

#### ✅ Capture et confirmation
```php
// POST /api/payments/{id}/capture
public function capturePayment($paymentIntentId) {
    // Récupérer le passage depuis ope_pass
    $stmt = $this->db->prepare("
        SELECT id, stripe_payment_id, montant
        FROM ope_pass
        WHERE stripe_payment_id = :stripe_id
    ");
    $stmt->execute([':stripe_id' => $paymentIntentId]);
    $passage = $stmt->fetch();

    $paymentIntent = \Stripe\PaymentIntent::retrieve($paymentIntentId);

    if ($paymentIntent->status === 'requires_capture') {
        $paymentIntent->capture();
    }

    // Mettre à jour le statut dans ope_pass si nécessaire
    if ($paymentIntent->status === 'succeeded' && $passage) {
        $this->db->prepare("
            UPDATE ope_pass
            SET date_stripe_validated = NOW()
            WHERE id = :passage_id
        ")->execute([':passage_id' => $passage['id']]);

        // Envoyer email reçu si configuré
        $this->sendReceipt($passage['id']);
    }

    return $paymentIntent;
}

// GET /api/passages/{id}/stripe-status
public function getPassageStripeStatus($passageId) {
    $stmt = $this->db->prepare("
        SELECT stripe_payment_id, montant, date_creat
        FROM ope_pass
        WHERE id = :id
    ");
    $stmt->execute([':id' => $passageId]);
    $passage = $stmt->fetch();

    if (!$passage['stripe_payment_id']) {
        return ['status' => 'no_stripe_payment'];
    }

    // Récupérer le statut depuis Stripe
    $paymentIntent = \Stripe\PaymentIntent::retrieve($passage['stripe_payment_id']);

    return [
        'stripe_payment_id' => $passage['stripe_payment_id'],
        'status' => $paymentIntent->status,
        'amount' => $paymentIntent->amount,
        'currency' => $paymentIntent->currency,
        'created_at' => $passage['date_creat']
    ];
}
```

---

## 📅 JEUDI 28/08 - Reporting et Android compatibility (8h)

### 🌅 Matin (4h)

#### ✅ Gestion appareils Android certifiés
```php
// POST /api/devices/check-tap-to-pay
public function checkTapToPayCapability(Request $request) {
    $validated = $request->validate([
        'platform' => 'required|in:ios,android',
        'manufacturer' => 'required_if:platform,android',
        'model' => 'required_if:platform,android',
        'os_version' => 'required',
    ]);
    
    if ($validated['platform'] === 'ios') {
        // iPhone XS et ultérieurs avec iOS 15.4+
        $supportedModels = ['iPhone11,', 'iPhone12,', 'iPhone13,', 'iPhone14,', 'iPhone15,', 'iPhone16,'];
        $modelSupported = false;
        
        foreach ($supportedModels as $prefix) {
            if (str_starts_with($validated['model'], $prefix)) {
                $modelSupported = true;
                break;
            }
        }
        
        $osVersion = explode('.', $validated['os_version']);
        $osSupported = $osVersion[0] > 15 || 
            ($osVersion[0] == 15 && isset($osVersion[1]) && $osVersion[1] >= 4);
        
        return [
            'tap_to_pay_supported' => $modelSupported && $osSupported,
            'message' => $modelSupported && $osSupported ? 
                'Tap to Pay disponible' : 
                'iPhone XS ou ultérieur avec iOS 15.4+ requis'
        ];
    }
    
    // Android - vérifier dans la base de données
    $device = DB::table('android_certified_devices')
        ->where('manufacturer', $validated['manufacturer'])
        ->where('model', $validated['model'])
        ->where('tap_to_pay_certified', true)
        ->first();
    
    return [
        'tap_to_pay_supported' => $device !== null,
        'message' => $device ? 
            'Tap to Pay disponible sur cet appareil' : 
            'Appareil non certifié pour Tap to Pay en France',
        'alternative' => !$device ? 'Utilisez un iPhone compatible' : null
    ];
}

// GET /api/devices/certified-android
public function getCertifiedAndroidDevices() {
    return DB::table('android_certified_devices')
        ->where('tap_to_pay_certified', true)
        ->where('country', 'FR')
        ->orderBy('manufacturer')
        ->orderBy('model')
        ->get();
}
```

#### ✅ Seeder pour appareils certifiés
```php
// database/seeders/AndroidCertifiedDevicesSeeder.php
public function run() {
    $devices = [
        // Samsung
        ['manufacturer' => 'Samsung', 'model' => 'Galaxy S21', 'model_identifier' => 'SM-G991B', 'min_android_version' => 11],
        ['manufacturer' => 'Samsung', 'model' => 'Galaxy S21+', 'model_identifier' => 'SM-G996B', 'min_android_version' => 11],
        ['manufacturer' => 'Samsung', 'model' => 'Galaxy S21 Ultra', 'model_identifier' => 'SM-G998B', 'min_android_version' => 11],
        ['manufacturer' => 'Samsung', 'model' => 'Galaxy S22', 'model_identifier' => 'SM-S901B', 'min_android_version' => 12],
        ['manufacturer' => 'Samsung', 'model' => 'Galaxy S23', 'model_identifier' => 'SM-S911B', 'min_android_version' => 13],
        ['manufacturer' => 'Samsung', 'model' => 'Galaxy S24', 'model_identifier' => 'SM-S921B', 'min_android_version' => 14],
        // Google Pixel
        ['manufacturer' => 'Google', 'model' => 'Pixel 6', 'model_identifier' => 'oriole', 'min_android_version' => 12],
        ['manufacturer' => 'Google', 'model' => 'Pixel 6 Pro', 'model_identifier' => 'raven', 'min_android_version' => 12],
        ['manufacturer' => 'Google', 'model' => 'Pixel 7', 'model_identifier' => 'panther', 'min_android_version' => 13],
        ['manufacturer' => 'Google', 'model' => 'Pixel 8', 'model_identifier' => 'shiba', 'min_android_version' => 14],
    ];
    
    foreach ($devices as $device) {
        DB::table('android_certified_devices')->insert([
            'manufacturer' => $device['manufacturer'],
            'model' => $device['model'],
            'model_identifier' => $device['model_identifier'],
            'tap_to_pay_certified' => true,
            'certification_date' => now(),
            'min_android_version' => $device['min_android_version'],
            'country' => 'FR',
        ]);
    }
}
```

#### ✅ Endpoints statistiques
```php
// GET /api/amicales/{id}/stats
public function getAmicaleStats($amicaleId) {
    $stats = DB::table('payment_intents')
        ->where('amicale_id', $amicaleId)
        ->where('status', 'succeeded')
        ->selectRaw('
            COUNT(*) as total_ventes,
            SUM(amount) as total_montant,
            SUM(application_fee) as total_commissions,
            DATE(created_at) as date
        ')
        ->groupBy('date')
        ->get();
    
    return $stats;
}

// GET /api/pompiers/{id}/ventes
public function getPompierVentes($pompierId) {
    return PaymentIntent::where('pompier_id', $pompierId)
        ->where('status', 'succeeded')
        ->orderBy('created_at', 'desc')
        ->paginate(20);
}
```

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

#### ✅ Gestion des remboursements
```php
// POST /api/payments/{id}/refund
public function refundPayment($paymentIntentId, Request $request) {
    $validated = $request->validate([
        'amount' => 'integer|min:100', // optionnel, remboursement partiel
        'reason' => 'string|in:duplicate,fraudulent,requested_by_customer',
    ]);
    
    $payment = PaymentIntent::where('stripe_payment_intent_id', $paymentIntentId)->first();
    
    $refund = \Stripe\Refund::create([
        'payment_intent' => $paymentIntentId,
        'amount' => $validated['amount'] ?? null, // null = remboursement total
        'reason' => $validated['reason'] ?? 'requested_by_customer',
        'reverse_transfer' => true, // Important pour Connect
        'refund_application_fee' => true, // Rembourser aussi la commission
    ]);
    
    $payment->update(['status' => 'refunded']);
    
    return $refund;
}
```

---

## 📅 VENDREDI 29/08 - Mode offline et sync (8h)

### 🌅 Matin (4h)

#### ✅ Queue de synchronisation
```php
// POST /api/payments/batch-sync
public function batchSync(Request $request) {
    $validated = $request->validate([
        'transactions' => 'required|array',
        'transactions.*.local_id' => 'required|string',
        'transactions.*.amount' => 'required|integer',
        'transactions.*.created_at' => 'required|date',
        'transactions.*.payment_method' => 'required|in:card,cash',
    ]);
    
    $results = [];
    
    foreach ($validated['transactions'] as $transaction) {
        if ($transaction['payment_method'] === 'cash') {
            // Enregistrer paiement cash uniquement en DB
            $results[] = $this->recordCashPayment($transaction);
        } else {
            // Créer PaymentIntent a posteriori (si possible)
            $results[] = $this->createOfflinePayment($transaction);
        }
    }
    
    return ['synced' => $results];
}
```

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

#### ✅ Tests unitaires critiques
```php
class StripePaymentTest extends TestCase {
    public function test_create_payment_intent_with_fees() {
        // Test création PaymentIntent avec commission
    }
    
    public function test_webhook_signature_validation() {
        // Test sécurité webhook
    }
    
    public function test_refund_reverses_transfer() {
        // Test remboursement avec annulation virement
    }
}
```

---

## 📅 LUNDI 01/09 - Sécurité et optimisations (8h)

### 🌅 Matin (4h)

#### ✅ Rate limiting et sécurité
```php
// Middleware RateLimiter pour endpoints sensibles
Route::middleware(['throttle:10,1'])->group(function () {
    Route::post('/payments/create-intent', 'PaymentController@createIntent');
});

// Validation des montants
public function validateAmount($amount) {
    if ($amount < 100 || $amount > 50000) { // 1€ - 500€
        throw new ValidationException('Montant invalide');
    }
}
```

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

#### ✅ Logs et monitoring
```php
// Logger tous les événements Stripe
Log::channel('stripe')->info('Payment created', [
    'payment_intent_id' => $paymentIntent->id,
    'amount' => $paymentIntent->amount,
    'pompier_id' => $pompier->id,
]);
```

---

## 📅 MARDI 02/09 - Documentation API (4h)

#### ✅ Documentation OpenAPI/Swagger
```yaml
/api/payments/create-intent:
  post:
    summary: Créer une intention de paiement
    parameters:
      - name: amount
        type: integer
        required: true
        description: Montant en centimes
    responses:
      200:
        description: PaymentIntent créé
```

---

## 📅 MERCREDI 03/09 - Tests d'intégration (8h)

#### ✅ Tests end-to-end
- [ ] Parcours complet onboarding amicale
- [ ] Création paiement → capture → confirmation
- [ ] Test remboursement complet et partiel
- [ ] Test webhooks avec ngrok

---

## 📅 JEUDI 04/09 - Mise en production (8h)

---

## 📅 VENDREDI 05/09 - Support et livraison finale (8h)

### 🌅 Matin (4h)

#### ✅ Déploiement final
- [ ] Migration DB production
- [ ] Variables environnement LIVE
- [ ] Smoke tests production
- [ ] Vérification des webhooks en production

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

#### ✅ Support et monitoring
- [ ] Monitoring des premiers paiements réels
- [ ] Support hotline pour équipes terrain
- [ ] Documentation de passation
- [ ] Réunion de clôture et retour d'expérience

---

## 📊 RÉCAPITULATIF

- **Total heures** : 72h sur 10 jours
- **Endpoints créés** : 15
- **Tables DB** : 3
- **Tests** : 20+

## 🔧 DÉPENDANCES

```json
{
    "require": {
        "php": "^8.3",
        "stripe/stripe-php": "^13.0",
        "laravel/framework": "^10.0"
    }
}
```

## ⚠️ CHECKLIST SÉCURITÉ

- [ ] ❌ JAMAIS logger les clés secrètes
- [ ] ✅ TOUJOURS valider signature webhooks
- [ ] ✅ TOUJOURS utiliser HTTPS
- [ ] ✅ Rate limiting sur endpoints paiement
- [ ] ✅ Logs détaillés pour audit

---

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

### ✅ ENDPOINTS IMPLÉMENTÉS (TAP TO PAY UNIQUEMENT)

#### **Stripe Connect - Comptes**
- **POST /api/stripe/accounts** ✅
  - Création compte Stripe Express pour amicales
  - Gestion déchiffrement données (encrypted_email, encrypted_name)
  - Support des comptes existants

- **GET /api/stripe/accounts/:entityId/status** ✅
  - Récupération statut complet du compte
  - Vérification charges_enabled et payouts_enabled
  - Retour JSON avec informations détaillées

- **POST /api/stripe/accounts/:accountId/onboarding-link** ✅
  - Génération liens d'onboarding Stripe
  - URLs de retour configurées
  - Gestion des erreurs et timeouts

#### **Configuration et Utilitaires**
- **GET /api/stripe/config** ✅
  - Configuration publique Stripe
  - Clés publiques et paramètres client
  - Adaptation par environnement

- **POST /api/stripe/webhook** ✅
  - Réception événements Stripe
  - Vérification signatures webhook
  - Traitement des événements Connect

### 🔧 CORRECTIONS TECHNIQUES RÉALISÉES

#### **StripeController.php**
- Fixed `Database::getInstance()` → `$this->db`
- Fixed `$db->prepare()` → `$this->db->prepare()`
- Removed `details_submitted` column from SQL UPDATE
- Added proper exit statements after JSON responses
- Commented out Logger class calls (class not found)

#### **StripeService.php** 
- Added proper Stripe SDK imports (`use Stripe\Account`)
- Fixed `Account::retrieve()` → `$this->stripe->accounts->retrieve()`
- **CRUCIAL**: Added data decryption support:
  ```php
  $nom = !empty($entite['encrypted_name']) ? 
    \ApiService::decryptData($entite['encrypted_name']) : '';
  $email = !empty($entite['encrypted_email']) ? 
    \ApiService::decryptSearchableData($entite['encrypted_email']) : null;
  ```
- Fixed address mapping (adresse1, adresse2 vs adresse)
- **REMOVED commission calculation - set to 0%**

#### **Router.php**
- Commented out excessive debug logging causing nginx 502 errors:
  ```php
  // error_log("Recherche de route pour: méthode=$method, uri=$uri");
  // error_log("Test pattern: $pattern contre uri: $uri");
  ```

#### **AppConfig.php**
- Set `application_fee_percent` to 0 (was 2.5)
- Set `application_fee_minimum` to 0 (was 50)
- **Policy**: 100% of payments go to amicales

### 📊 TESTS ET VALIDATION

#### **Tests Réussis**
1. **POST /api/stripe/accounts** → 200 OK (Compte créé: acct_1S2YfNP63A07c33Y)
2. **GET /api/stripe/accounts/5/status** → 200 OK (charges_enabled: true)
3. **POST /api/stripe/locations** → 200 OK (Location: tml_GLJ21w7KCYX4Wj)
4. **POST /api/stripe/accounts/.../onboarding-link** → 200 OK (Link generated)
5. **Onboarding Stripe** → Completed successfully by user

#### **Erreurs Résolues**
- ❌ 500 "Class App\Controllers\Database not found" → ✅ Fixed
- ❌ 400 "Invalid email address: " → ✅ Fixed (decryption added)
- ❌ 502 "upstream sent too big header" → ✅ Fixed (logs removed)
- ❌ SQL "Column not found: details_submitted" → ✅ Fixed

### 🚀 ARCHITECTURE TECHNIQUE

#### **Services Implémentés**
- **StripeService**: Singleton pour interactions Stripe API
- **StripeController**: Endpoints REST avec gestion sessions
- **StripeWebhookController**: Handler événements webhook
- **ApiService**: Déchiffrement données encrypted fields

#### **Sécurité**
- Validation signatures webhook Stripe
- Authentification session-based pour APIs privées
- Public endpoints: webhook uniquement
- Pas de stockage clés secrètes en base

#### **Base de données (MISE À JOUR JANVIER 2025)**
- **Modification table `ope_pass`** : `stripe_payment_id` VARCHAR(50) remplace `is_striped`
- **Table `payment_intents` supprimée** : Intégration directe dans `ope_pass`
- Utilisation tables existantes (entites)
- Champs encrypted_email et encrypted_name supportés
- Déchiffrement automatique avant envoi Stripe

### 🎯 PROCHAINES ÉTAPES API
1. **Tests paiements réels** avec PaymentIntents
2. **Endpoints statistiques** pour dashboard amicales  
3. **Webhooks production** avec clés live
4. **Monitoring et logs** des transactions
5. **Rate limiting** sur endpoints sensibles

---

## 📱 FLOW TAP TO PAY SIMPLIFIÉ (Janvier 2025)

### Architecture
```
Flutter App (Tap to Pay) ↔ API PHP ↔ Stripe API
```

### Étape 1: Création PaymentIntent
**Flutter → API**
```json
POST /api/stripe/payments/create-intent
{
    "amount": 1500,
    "passage_id": 123,
    "entity_id": 5
}
```

**API → Stripe → Base de données**
```php
// 1. Créer le PaymentIntent
$paymentIntent = Stripe\PaymentIntent::create([...]);

// 2. Sauvegarder dans ope_pass
UPDATE ope_pass SET stripe_payment_id = 'pi_xxx' WHERE id = 123;
```

**API → Flutter**
```json
{
    "client_secret": "pi_xxx_secret_yyy",
    "payment_intent_id": "pi_xxx"
}
```

### Étape 2: Collecte du paiement (Flutter)
- L'app Flutter utilise le SDK Stripe Terminal
- Le téléphone devient le terminal de paiement (Tap to Pay)
- Utilise le client_secret pour collecter le paiement

### Étape 3: Confirmation (Webhook)
**Stripe → API**
- Event: `payment_intent.succeeded`
- Met à jour le statut dans la base de données

### Tables nécessaires
- ✅ `ope_pass.stripe_payment_id` - Association passage/paiement
- ✅ `stripe_accounts` - Comptes Connect des amicales
- ✅ `android_certified_devices` - Vérification compatibilité
- ❌ ~~`stripe_payment_intents`~~ - Supprimée
- ❌ ~~`terminal_readers`~~ - Pas de terminaux externes

### Endpoints essentiels
1. `POST /api/stripe/payments/create-intent` - Créer PaymentIntent
2. `POST /api/stripe/devices/check-tap-to-pay` - Vérifier compatibilité
3. `POST /api/stripe/webhook` - Recevoir confirmations
4. `GET /api/passages/{id}/stripe-status` - Vérifier statut

---

## 📝 CHANGELOG

### Janvier 2025 - Refactoring base de données
- **Suppression** de la table `payment_intents` (non nécessaire)
- **Migration** : `is_striped` → `stripe_payment_id` VARCHAR(50) dans `ope_pass`
- **Simplification** : Association directe PaymentIntent ↔ Passage
- **Avantage** : Traçabilité directe sans table intermédiaire

---

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