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>
This commit is contained in:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

View File

@@ -0,0 +1,464 @@
# 🔧 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)