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:
464
api/docs/STRIPE-BACKEND-MIGRATION.md
Normal file
464
api/docs/STRIPE-BACKEND-MIGRATION.md
Normal 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)
|
||||
Reference in New Issue
Block a user