- 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>
465 lines
14 KiB
Markdown
465 lines
14 KiB
Markdown
# 🔧 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
|
||
<?php
|
||
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
use App\Models\Amicale;
|
||
|
||
/**
|
||
* Créer un compte Stripe Connect avec Location Terminal et lien d'onboarding
|
||
*
|
||
* @param Request $request
|
||
* @return \Illuminate\Http\JsonResponse
|
||
*/
|
||
public function createStripeAccount(Request $request)
|
||
{
|
||
$request->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)
|