# 🔧 Migration Backend Stripe - Option A (Tout en 1) ## 📋 Objectif Optimiser la création de compte Stripe Connect en **1 seule requête** côté Flutter qui crée : 1. Le compte Stripe Connect 2. La Location Terminal 3. Le lien d'onboarding --- ## 🗄️ 1. Modification de la base de données ### **Ajouter la colonne `stripe_location_id`** ```sql ALTER TABLE amicales ADD COLUMN stripe_location_id VARCHAR(255) NULL AFTER stripe_id; ``` **Vérification** : ```sql DESCRIBE amicales; ``` Doit afficher : ``` +-------------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------------+--------------+------+-----+---------+-------+ | stripe_id | varchar(255) | YES | | NULL | | | stripe_location_id| varchar(255) | YES | | NULL | | +-------------------+--------------+------+-----+---------+-------+ ``` --- ## 🔧 2. Modification de l'endpoint `POST /stripe/accounts` ### **Fichier** : `app/Http/Controllers/StripeController.php` (ou similaire) ### **Méthode** : `createAccount()` ou `store()` ### **Code proposé** : ```php validate([ 'fk_entite' => 'required|integer|exists:amicales,id', 'return_url' => 'required|string|url', 'refresh_url' => 'required|string|url', ]); $fkEntite = $request->fk_entite; $amicale = Amicale::findOrFail($fkEntite); // Vérifier si un compte existe déjà if (!empty($amicale->stripe_id)) { return $this->handleExistingAccount($amicale, $request); } DB::beginTransaction(); try { // Configurer la clé Stripe (selon environnement) \Stripe\Stripe::setApiKey(config('services.stripe.secret')); // 1️⃣ Créer le compte Stripe Connect Express $account = \Stripe\Account::create([ 'type' => 'express', 'country' => 'FR', 'email' => $amicale->email, 'business_type' => 'non_profit', // ou 'company' selon le cas 'business_profile' => [ 'name' => $amicale->name, 'url' => config('app.url'), ], 'capabilities' => [ 'card_payments' => ['requested' => true], 'transfers' => ['requested' => true], ], ]); \Log::info('Stripe account created', [ 'amicale_id' => $amicale->id, 'account_id' => $account->id, ]); // 2️⃣ Créer la Location Terminal pour Tap to Pay $location = \Stripe\Terminal\Location::create([ 'display_name' => $amicale->name, 'address' => [ 'line1' => $amicale->adresse1 ?: 'Non renseigné', 'line2' => $amicale->adresse2, 'city' => $amicale->ville ?: 'Non renseigné', 'postal_code' => $amicale->code_postal ?: '00000', 'country' => 'FR', ], ], [ 'stripe_account' => $account->id, // ← Important : Connect account ]); \Log::info('Stripe Terminal Location created', [ 'amicale_id' => $amicale->id, 'location_id' => $location->id, ]); // 3️⃣ Créer le lien d'onboarding $accountLink = \Stripe\AccountLink::create([ 'account' => $account->id, 'refresh_url' => $request->refresh_url, 'return_url' => $request->return_url, 'type' => 'account_onboarding', ]); \Log::info('Stripe onboarding link created', [ 'amicale_id' => $amicale->id, 'account_id' => $account->id, ]); // 4️⃣ Sauvegarder TOUT en base de données $amicale->stripe_id = $account->id; $amicale->stripe_location_id = $location->id; $amicale->chk_stripe = true; $amicale->save(); DB::commit(); // 5️⃣ Retourner TOUTES les informations return response()->json([ 'success' => true, 'account_id' => $account->id, 'location_id' => $location->id, 'onboarding_url' => $accountLink->url, 'charges_enabled' => $account->charges_enabled, 'payouts_enabled' => $account->payouts_enabled, 'existing' => false, 'message' => 'Compte Stripe Connect créé avec succès', ], 201); } catch (\Stripe\Exception\ApiErrorException $e) { DB::rollBack(); \Log::error('Stripe API error', [ 'amicale_id' => $amicale->id, 'error' => $e->getMessage(), 'type' => get_class($e), ]); return response()->json([ 'success' => false, 'message' => 'Erreur Stripe : ' . $e->getMessage(), ], 500); } catch (\Exception $e) { DB::rollBack(); \Log::error('Stripe account creation failed', [ 'amicale_id' => $amicale->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); return response()->json([ 'success' => false, 'message' => 'Erreur lors de la création du compte Stripe', ], 500); } } /** * Gérer le cas d'un compte Stripe existant */ private function handleExistingAccount(Amicale $amicale, Request $request) { try { \Stripe\Stripe::setApiKey(config('services.stripe.secret')); // Récupérer les infos du compte existant $account = \Stripe\Account::retrieve($amicale->stripe_id); // Si pas de location_id, la créer maintenant if (empty($amicale->stripe_location_id)) { $location = \Stripe\Terminal\Location::create([ 'display_name' => $amicale->name, 'address' => [ 'line1' => $amicale->adresse1 ?: 'Non renseigné', 'city' => $amicale->ville ?: 'Non renseigné', 'postal_code' => $amicale->code_postal ?: '00000', 'country' => 'FR', ], ], [ 'stripe_account' => $amicale->stripe_id, ]); $amicale->stripe_location_id = $location->id; $amicale->save(); \Log::info('Location created for existing account', [ 'amicale_id' => $amicale->id, 'location_id' => $location->id, ]); } // Si le compte est déjà complètement configuré if ($account->charges_enabled && $account->payouts_enabled) { return response()->json([ 'success' => true, 'account_id' => $amicale->stripe_id, 'location_id' => $amicale->stripe_location_id, 'onboarding_url' => null, 'charges_enabled' => true, 'payouts_enabled' => true, 'existing' => true, 'message' => 'Compte Stripe déjà configuré et actif', ]); } // Compte existant mais configuration incomplète : générer un nouveau lien $accountLink = \Stripe\AccountLink::create([ 'account' => $amicale->stripe_id, 'refresh_url' => $request->refresh_url, 'return_url' => $request->return_url, 'type' => 'account_onboarding', ]); return response()->json([ 'success' => true, 'account_id' => $amicale->stripe_id, 'location_id' => $amicale->stripe_location_id, 'onboarding_url' => $accountLink->url, 'charges_enabled' => $account->charges_enabled, 'payouts_enabled' => $account->payouts_enabled, 'existing' => true, 'message' => 'Compte existant, configuration à finaliser', ]); } catch (\Exception $e) { \Log::error('Error handling existing account', [ 'amicale_id' => $amicale->id, 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'message' => 'Erreur lors de la vérification du compte existant', ], 500); } } ``` --- ## 📡 3. Modification de l'endpoint `GET /stripe/accounts/{id}/status` Ajouter `location_id` dans la réponse : ```php public function checkAccountStatus($amicaleId) { $amicale = Amicale::findOrFail($amicaleId); if (empty($amicale->stripe_id)) { return response()->json([ 'has_account' => false, 'account_id' => null, 'location_id' => null, 'charges_enabled' => false, 'payouts_enabled' => false, 'onboarding_completed' => false, ]); } try { \Stripe\Stripe::setApiKey(config('services.stripe.secret')); $account = \Stripe\Account::retrieve($amicale->stripe_id); return response()->json([ 'has_account' => true, 'account_id' => $amicale->stripe_id, 'location_id' => $amicale->stripe_location_id, // ← Ajouté 'charges_enabled' => $account->charges_enabled, 'payouts_enabled' => $account->payouts_enabled, 'onboarding_completed' => $account->details_submitted, ]); } catch (\Exception $e) { return response()->json([ 'has_account' => false, 'error' => $e->getMessage(), ], 500); } } ``` --- ## 🗑️ 4. Endpoint à SUPPRIMER (devenu inutile) ### **❌ `POST /stripe/locations`** Cet endpoint n'est plus nécessaire car la Location est créée automatiquement dans `POST /stripe/accounts`. **Option 1** : Supprimer complètement **Option 2** : Le garder pour compatibilité temporaire (si utilisé ailleurs) --- ## 📝 5. Modification du modèle Eloquent ### **Fichier** : `app/Models/Amicale.php` Ajouter le champ `stripe_location_id` : ```php protected $fillable = [ // ... autres champs 'stripe_id', 'stripe_location_id', // ← Ajouté 'chk_stripe', ]; protected $casts = [ 'chk_stripe' => 'boolean', ]; ``` --- ## ✅ 6. Tests à effectuer ### **Test 1 : Nouvelle amicale** ```bash curl -X POST http://localhost/api/stripe/accounts \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {token}" \ -d '{ "fk_entite": 123, "return_url": "https://app.geosector.fr/stripe/success", "refresh_url": "https://app.geosector.fr/stripe/refresh" }' ``` **Réponse attendue** : ```json { "success": true, "account_id": "acct_xxxxxxxxxxxxx", "location_id": "tml_xxxxxxxxxxxxx", "onboarding_url": "https://connect.stripe.com/setup/...", "charges_enabled": false, "payouts_enabled": false, "existing": false, "message": "Compte Stripe Connect créé avec succès" } ``` ### **Test 2 : Amicale avec compte existant** ```bash curl -X POST http://localhost/api/stripe/accounts \ -H "Content-Type: application/json" \ -H "Authorization: Bearer {token}" \ -d '{ "fk_entite": 456, "return_url": "https://app.geosector.fr/stripe/success", "refresh_url": "https://app.geosector.fr/stripe/refresh" }' ``` **Réponse attendue** : ```json { "success": true, "account_id": "acct_xxxxxxxxxxxxx", "location_id": "tml_xxxxxxxxxxxxx", "onboarding_url": null, "charges_enabled": true, "payouts_enabled": true, "existing": true, "message": "Compte Stripe déjà configuré et actif" } ``` ### **Test 3 : Vérifier la BDD** ```sql SELECT id, name, stripe_id, stripe_location_id, chk_stripe FROM amicales WHERE id = 123; ``` **Résultat attendu** : ``` +-----+------------------+-------------------+-------------------+------------+ | id | name | stripe_id | stripe_location_id| chk_stripe | +-----+------------------+-------------------+-------------------+------------+ | 123 | Pompiers Paris15 | acct_xxxxxxxxxxxxx| tml_xxxxxxxxxxxxx | 1 | +-----+------------------+-------------------+-------------------+------------+ ``` --- ## 🚀 7. Déploiement ### **Étapes** : 1. ✅ Appliquer la migration SQL 2. ✅ Déployer le code Backend modifié 3. ✅ Tester avec Postman/curl 4. ✅ Déployer le code Flutter modifié 5. ✅ Tester le flow complet depuis l'app --- ## 📊 Comparaison Avant/Après | Aspect | Avant | Après | |--------|-------|-------| | **Appels API Flutter → Backend** | 3 | 1 | | **Appels Backend → Stripe** | 3 | 3 (mais atomiques) | | **Latence totale** | ~3-5s | ~1-2s | | **Gestion erreurs** | Complexe | Simplifié avec transaction | | **Atomicité** | ❌ Non | ✅ Oui (DB transaction) | | **Location ID sauvegardé** | ❌ Non | ✅ Oui | --- ## 🎯 Bénéfices 1. ✅ **Performance** : Latence divisée par 2-3 2. ✅ **Fiabilité** : Transaction BDD garantit la cohérence 3. ✅ **Simplicité** : Code Flutter plus simple 4. ✅ **Maintenance** : Moins de code à maintenir 5. ✅ **Traçabilité** : Logs centralisés côté Backend 6. ✅ **Tap to Pay prêt** : `location_id` disponible immédiatement --- ## ⚠️ Points d'attention 1. **Rollback** : Si la transaction échoue, rien n'est sauvegardé (bon comportement) 2. **Logs** : Bien logger chaque étape pour le debug 3. **Stripe Connect limitations** : Respecter les rate limits Stripe 4. **Tests** : Tester avec des comptes Stripe de test d'abord --- ## 📚 Ressources - [Stripe Connect Express Accounts](https://stripe.com/docs/connect/express-accounts) - [Stripe Terminal Locations](https://stripe.com/docs/terminal/fleet/locations) - [Stripe Account Links](https://stripe.com/docs/connect/account-links)