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:
@@ -124,8 +124,8 @@ if (amicale == null || amicale.id == 0) {
|
||||
"city": "Paris",
|
||||
"country": "FR"
|
||||
},
|
||||
"url": "https://app.geosector.fr/stripe/return",
|
||||
"refresh_url": "https://app.geosector.fr/stripe/refresh"
|
||||
"url": "https://app3.geosector.fr/stripe/return",
|
||||
"refresh_url": "https://app3.geosector.fr/stripe/refresh"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,8 +148,8 @@ $account = \Stripe\Account::create([
|
||||
// 2. Création du lien d'onboarding
|
||||
$onboardingLink = \Stripe\AccountLink::create([
|
||||
'account' => $account->id,
|
||||
'refresh_url' => 'https://app.geosector.fr/stripe/refresh',
|
||||
'return_url' => 'https://app.geosector.fr/stripe/return',
|
||||
'refresh_url' => 'https://app3.geosector.fr/stripe/refresh',
|
||||
'return_url' => 'https://app3.geosector.fr/stripe/return',
|
||||
'type' => 'account_onboarding'
|
||||
]);
|
||||
|
||||
@@ -529,6 +529,378 @@ $paymentIntent = \Stripe\PaymentIntent::create([
|
||||
|
||||
---
|
||||
|
||||
## 📱 FLOW PAIEMENT QR CODE (Web + Mobile)
|
||||
|
||||
### 🎯 Vue d'ensemble
|
||||
|
||||
Le paiement par QR Code permet aux clients de payer directement avec leur téléphone en scannant un code QR généré par l'application. Cette méthode fonctionne sur **Web et Mobile** et ne nécessite pas de matériel spécifique.
|
||||
|
||||
### 🔄 Diagramme de séquence complet
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ App Flutter │ │ API PHP │ │ Stripe │ │ Client │ │ Passage │
|
||||
└──────┬──────┘ └──────┬──────┘ └────┬─────┘ └────┬────┘ └────┬────┘
|
||||
│ │ │ │ │
|
||||
[1] │ Validation form │ │ │ │
|
||||
│ + montant CB │ │ │ │
|
||||
│ │ │ │ │
|
||||
[2] │ POST/PUT passage │ │ │ │
|
||||
│──────────────────>│ │ │ │
|
||||
│ │ │ │ │
|
||||
[3] │<──────────────────│ │ │ │
|
||||
│ Passage ID: 456 │ │ │ │
|
||||
│ │ │ │ │
|
||||
[4] │ Vérif Stripe │ │ │ │
|
||||
│ chkStripe=true │ │ │ │
|
||||
│ + stripeId rempli │ │ │ │
|
||||
│ │ │ │ │
|
||||
[5] │ Dialog Sélection │ │ │ │
|
||||
│ "Règlement CB" │ │ │ │
|
||||
│ [QRCode|TapToPay] │ │ │ │
|
||||
│ │ │ │ │
|
||||
[6] │ Clic "QR Code" │ │ │ │
|
||||
│ │ │ │ │
|
||||
[7] │ POST payment-links│ │ │ │
|
||||
│──────────────────>│ (passage_id: 456)│ │ │
|
||||
│ │ │ │ │
|
||||
[8] │ │ Create PaymentLink │ │
|
||||
│ │─────────────────>│ │ │
|
||||
│ │ │ │ │
|
||||
[9] │ │<─────────────────│ │ │
|
||||
│ │ link_id + url │ │ │
|
||||
│ │ │ │ │
|
||||
[10] │<──────────────────│ │ │ │
|
||||
│ PaymentLink data │ │ │ │
|
||||
│ │ │ │ │
|
||||
[11] │ Génération QR │ │ │ │
|
||||
│ avec URL Stripe │ │ │ │
|
||||
│ │ │ │ │
|
||||
[12] │ Affichage dialog │ │ │ │
|
||||
│ QR Code │ │ │ │
|
||||
│ ┌──────────────┐ │ │ │ │
|
||||
│ │ QR Code │ │ │ │ │
|
||||
│ │ ▓▓▓▓▓▓▓▓ │ │ │ │ │
|
||||
│ │ 20.00 € │ │ │ │ │
|
||||
│ └──────────────┘ │ │ │ │
|
||||
│ │ │ │ │
|
||||
[13] │ │ │ Scan QR Code │ │
|
||||
│ │ │<───────────────│ │
|
||||
│ │ │ │ │
|
||||
[14] │ │ │ Page paiement │ │
|
||||
│ │ │───────────────>│ │
|
||||
│ │ │ Stripe hosted │ │
|
||||
│ │ │ │ │
|
||||
[15] │ │ │ Saisie CB │ │
|
||||
│ │ │<───────────────│ │
|
||||
│ │ │ │ │
|
||||
[16] │ │ │ Validation │ │
|
||||
│ │ │───────────────>│ │
|
||||
│ │ │ │ │
|
||||
[17] │ │ Webhook │ │ │
|
||||
│ │<─────────────────│ │ │
|
||||
│ │ payment_succeeded│ │ │
|
||||
│ │ │ │ │
|
||||
[18] │ │ Update passage │ │ │
|
||||
│ │────────────────────────────────────────────────>│
|
||||
│ │ stripe_payment_id│ │ │
|
||||
│ │ │ │ │
|
||||
[19] │ │ │ Confirmation │ │
|
||||
│ │ │───────────────>│ │
|
||||
│ │ │ "Merci!" │ │
|
||||
```
|
||||
|
||||
### 📋 Détail des étapes
|
||||
|
||||
#### Étape 1-3 : SAUVEGARDE DU PASSAGE
|
||||
**Identique au flow Tap to Pay** - Le passage est toujours créé en premier pour obtenir un ID réel.
|
||||
|
||||
#### Étape 4 : VÉRIFICATION STRIPE
|
||||
**Acteur:** Application Flutter
|
||||
**Conditions vérifiées:**
|
||||
```dart
|
||||
final amicale = CurrentAmicaleService.instance.currentAmicale;
|
||||
final stripeEnabled = amicale?.chkStripe == true &&
|
||||
amicale?.stripeId != null &&
|
||||
amicale!.stripeId.isNotEmpty;
|
||||
|
||||
if (stripeEnabled && fkTypeReglement == 3 && montant > 0) {
|
||||
// Afficher dialog de sélection de méthode
|
||||
}
|
||||
```
|
||||
|
||||
#### Étape 5 : DIALOG DE SÉLECTION DE MÉTHODE
|
||||
**Widget:** `PaymentMethodSelectionDialog`
|
||||
**Interface affichée:**
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ Règlement CB │
|
||||
│ │
|
||||
│ 👤 Jean Dupont │
|
||||
│ 💰 20.00 € │
|
||||
│ │
|
||||
│ Sélectionnez une méthode : │
|
||||
│ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ 📱 Paiement par QR Code │ │
|
||||
│ │ Le client scanne le code │ │
|
||||
│ └──────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────┐ │ (Si compatible)
|
||||
│ │ 💳 Tap to Pay │ │
|
||||
│ │ Paiement sans contact │ │
|
||||
│ └──────────────────────────┘ │
|
||||
│ │
|
||||
│ 🔒 Sécurisé par Stripe │
|
||||
└────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Étape 6-10 : CRÉATION DU PAYMENT LINK
|
||||
**Requête:** `POST /api/stripe/payment-links`
|
||||
**Payload:**
|
||||
```json
|
||||
{
|
||||
"amount": 2000,
|
||||
"currency": "eur",
|
||||
"description": "Calendrier pompiers - Jean Dupont",
|
||||
"passage_id": 456,
|
||||
"metadata": {
|
||||
"passage_id": "456",
|
||||
"habitant_name": "Jean Dupont",
|
||||
"adresse": "10 Rue de la Paix, Paris"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"payment_link_id": "plink_1234567890",
|
||||
"url": "https://buy.stripe.com/test_xxxxxxxxxxxxx",
|
||||
"amount": 2000,
|
||||
"passage_id": 456
|
||||
}
|
||||
```
|
||||
|
||||
**Code PHP Backend:**
|
||||
```php
|
||||
$paymentLink = \Stripe\PaymentLink::create([
|
||||
'line_items' => [[
|
||||
'price_data' => [
|
||||
'currency' => 'eur',
|
||||
'product_data' => [
|
||||
'name' => 'Calendrier pompiers',
|
||||
],
|
||||
'unit_amount' => 2000,
|
||||
],
|
||||
'quantity' => 1,
|
||||
]],
|
||||
'metadata' => [
|
||||
'passage_id' => '456',
|
||||
'type' => 'qr_code_payment',
|
||||
],
|
||||
'after_completion' => [
|
||||
'type' => 'hosted_confirmation',
|
||||
'hosted_confirmation' => [
|
||||
'custom_message' => 'Merci pour votre paiement !',
|
||||
],
|
||||
],
|
||||
], [
|
||||
'stripe_account' => $amicale->stripe_id,
|
||||
]);
|
||||
```
|
||||
|
||||
#### Étape 11-12 : AFFICHAGE DU QR CODE
|
||||
**Widget:** `QRCodePaymentDialog`
|
||||
**Interface:**
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ Paiement par QR Code │
|
||||
│ │
|
||||
│ 💰 20.00 € │
|
||||
│ │
|
||||
│ ┌────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ▓▓▓▓▓ ▓▓▓▓▓▓▓ │ │
|
||||
│ │ ▓▓▓▓▓ ▓▓ ▓▓ │ │
|
||||
│ │ ▓▓▓▓▓ ▓▓▓▓▓▓▓ │ │
|
||||
│ │ QR CODE HERE │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────┘ │
|
||||
│ │
|
||||
│ Scannez ce QR code avec │
|
||||
│ votre téléphone │
|
||||
│ │
|
||||
│ Vous serez redirigé vers │
|
||||
│ une page sécurisée Stripe │
|
||||
│ │
|
||||
│ 🔒 Paiement sécurisé │
|
||||
│ │
|
||||
│ [Fermer] │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Étape 13-16 : PAIEMENT CLIENT
|
||||
**Acteur:** Client final
|
||||
**Actions:**
|
||||
1. Scan du QR Code avec son smartphone
|
||||
2. Ouverture automatique de l'URL Stripe dans le navigateur
|
||||
3. Affichage de la page de paiement Stripe hébergée
|
||||
4. Saisie des informations de carte bancaire
|
||||
5. Validation 3D Secure si nécessaire
|
||||
6. Confirmation du paiement
|
||||
|
||||
#### Étape 17-18 : WEBHOOK ET MISE À JOUR
|
||||
**Requête Webhook:** `POST /api/stripe/webhook`
|
||||
**Event:** `checkout.session.completed` ou `payment_intent.succeeded`
|
||||
**Payload Stripe:**
|
||||
```json
|
||||
{
|
||||
"type": "payment_intent.succeeded",
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "pi_1234567890",
|
||||
"amount": 2000,
|
||||
"metadata": {
|
||||
"passage_id": "456"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Action Backend:**
|
||||
```php
|
||||
// Récupérer le passage
|
||||
$passage = Passage::find($paymentIntent->metadata->passage_id);
|
||||
|
||||
// Mettre à jour avec le payment ID
|
||||
$passage->stripe_payment_id = $paymentIntent->id;
|
||||
$passage->save();
|
||||
|
||||
Log::info('Payment confirmed via QR Code', [
|
||||
'passage_id' => $passage->id,
|
||||
'payment_id' => $paymentIntent->id,
|
||||
]);
|
||||
```
|
||||
|
||||
### 🔄 Comparaison QR Code vs Tap to Pay
|
||||
|
||||
| Aspect | QR Code | Tap to Pay |
|
||||
|--------|---------|------------|
|
||||
| **Plateformes** | Web + Mobile | Mobile uniquement |
|
||||
| **Matériel requis** | Aucun | NFC (iPhone XS+ / Android certifié) |
|
||||
| **Client utilise** | Son propre téléphone | Le téléphone du pompier |
|
||||
| **payment_method_types** | `["card"]` | `["card_present"]` |
|
||||
| **Interface paiement** | Page Stripe hébergée | NFC sur l'app |
|
||||
| **Délai confirmation** | Immédiat (webhook) | Immédiat (SDK) |
|
||||
| **Expérience client** | Scan + saisie CB | Approche carte |
|
||||
| **Sans contact physique** | ✅ Oui (COVID-safe) | ❌ Non (proximité requise) |
|
||||
| **Montant minimum** | 0.50€ | 1.00€ |
|
||||
| **Cas d'usage idéal** | Client à distance | Face à face |
|
||||
|
||||
### ✅ Conditions d'éligibilité
|
||||
|
||||
#### Affichage du bouton "QR Code"
|
||||
```dart
|
||||
// Conditions cumulatives :
|
||||
final canShowQRCode =
|
||||
amicale.chkStripe == true && // Stripe activé
|
||||
amicale.stripeId.isNotEmpty && // Compte configuré
|
||||
fkTypeReglement == 3 && // CB sélectionnée
|
||||
montant > 0; // Montant valide
|
||||
```
|
||||
|
||||
#### Différences avec Tap to Pay
|
||||
- **Pas besoin de** `stripeLocationId` (spécifique Terminal)
|
||||
- **Pas de vérification** device (fonctionne partout)
|
||||
- **Pas de batterie** minimum requise
|
||||
|
||||
### 🎨 Widgets créés
|
||||
|
||||
#### 1. PaymentMethodSelectionDialog
|
||||
**Fichier:** `lib/presentation/widgets/payment_method_selection_dialog.dart`
|
||||
**Rôle:** Choisir entre QR Code et Tap to Pay
|
||||
**Props:**
|
||||
```dart
|
||||
PaymentMethodSelectionDialog({
|
||||
required PassageModel passage,
|
||||
required double amount,
|
||||
required String habitantName,
|
||||
required StripeConnectService stripeConnectService,
|
||||
VoidCallback? onTapToPaySelected,
|
||||
})
|
||||
```
|
||||
|
||||
#### 2. QRCodePaymentDialog
|
||||
**Fichier:** `lib/presentation/widgets/qr_code_payment_dialog.dart`
|
||||
**Rôle:** Afficher le QR Code généré
|
||||
**Props:**
|
||||
```dart
|
||||
QRCodePaymentDialog({
|
||||
required PaymentLinkResult paymentLink,
|
||||
VoidCallback? onClose,
|
||||
})
|
||||
```
|
||||
|
||||
### 🔧 Services modifiés
|
||||
|
||||
#### StripeConnectService
|
||||
**Nouvelle méthode:**
|
||||
```dart
|
||||
Future<PaymentLinkResult?> createPaymentLink({
|
||||
required int amountInCents,
|
||||
required int passageId,
|
||||
String? description,
|
||||
Map<String, dynamic>? metadata,
|
||||
})
|
||||
```
|
||||
|
||||
### 📊 Modèles créés
|
||||
|
||||
#### PaymentLinkResult
|
||||
**Fichier:** `lib/core/data/models/payment_link_result.dart`
|
||||
```dart
|
||||
class PaymentLinkResult {
|
||||
final String paymentLinkId;
|
||||
final String url;
|
||||
final int amount;
|
||||
final int? passageId;
|
||||
}
|
||||
```
|
||||
|
||||
### 🔐 Sécurité
|
||||
|
||||
#### Validation Backend
|
||||
```php
|
||||
// Vérifications obligatoires
|
||||
$request->validate([
|
||||
'amount' => 'required|integer|min:50',
|
||||
'passage_id' => 'required|integer|exists:ope_pass,id',
|
||||
]);
|
||||
|
||||
// Vérifier Stripe activé
|
||||
if (!$amicale->chk_stripe || empty($amicale->stripe_id)) {
|
||||
return response()->json(['error' => 'Stripe non activé'], 403);
|
||||
}
|
||||
|
||||
// Créer sur le compte Connect de l'amicale
|
||||
\Stripe\PaymentLink::create([...], [
|
||||
'stripe_account' => $amicale->stripe_id,
|
||||
]);
|
||||
```
|
||||
|
||||
### 📱 Package ajouté
|
||||
|
||||
**pubspec.yaml:**
|
||||
```yaml
|
||||
dependencies:
|
||||
qr_flutter: ^4.1.0 # Génération de QR codes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 FLOW PAIEMENT WEB
|
||||
|
||||
### 🔄 Principales différences avec Tap to Pay
|
||||
@@ -846,8 +1218,9 @@ Log::info('PaymentIntent created', [
|
||||
| 1.0 | 28/09/2025 | Création documentation initiale |
|
||||
| 1.1 | 28/09/2025 | Ajout flow complet Tap to Pay |
|
||||
| 1.2 | 28/09/2025 | Intégration passage_id et metadata |
|
||||
| 1.3 | 05/11/2025 | Ajout flow paiement par QR Code |
|
||||
|
||||
---
|
||||
|
||||
*Document technique - Flow Stripe GEOSECTOR*
|
||||
*Dernière mise à jour : 28 septembre 2025*
|
||||
*Dernière mise à jour : 5 novembre 2025*
|
||||
Reference in New Issue
Block a user