feat: Version 3.3.4 - Nouvelle architecture pages, optimisations widgets Flutter et API

- Mise à jour VERSION vers 3.3.4
- Optimisations et révisions architecture API (deploy-api.sh, scripts de migration)
- Ajout documentation Stripe Tap to Pay complète
- Migration vers polices Inter Variable pour Flutter
- Optimisations build Android et nettoyage fichiers temporaires
- Amélioration système de déploiement avec gestion backups
- Ajout scripts CRON et migrations base de données

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-10-05 20:11:15 +02:00
parent 2786252307
commit 570a1fa1f0
212 changed files with 24275 additions and 11321 deletions

View File

@@ -1,6 +1,7 @@
# PLANNING STRIPE - DÉVELOPPEUR BACKEND PHP
## API PHP 8.3 - Intégration Stripe Connect + Terminal
## API PHP 8.3 - Intégration Stripe Tap to Pay (Mobile uniquement)
### Période : 25/08/2024 - 05/09/2024
### Mise à jour : Janvier 2025 - Simplification architecture
---
@@ -31,7 +32,13 @@ composer require stripe/stripe-php
#### ✅ Base de données
```sql
-- Tables à créer
-- Modification de la table ope_pass existante (JANVIER 2025)
ALTER TABLE `ope_pass`
DROP COLUMN IF EXISTS `is_striped`,
ADD COLUMN `stripe_payment_id` VARCHAR(50) DEFAULT NULL COMMENT 'ID du PaymentIntent Stripe (pi_xxx)',
ADD INDEX `idx_stripe_payment` (`stripe_payment_id`);
-- Tables à créer (simplifiées)
CREATE TABLE stripe_accounts (
id INT PRIMARY KEY AUTO_INCREMENT,
amicale_id INT NOT NULL,
@@ -44,32 +51,8 @@ CREATE TABLE stripe_accounts (
FOREIGN KEY (amicale_id) REFERENCES amicales(id)
);
CREATE TABLE payment_intents (
id INT PRIMARY KEY AUTO_INCREMENT,
stripe_payment_intent_id VARCHAR(255) UNIQUE,
amicale_id INT NOT NULL,
pompier_id INT NOT NULL,
amount INT NOT NULL, -- en centimes
currency VARCHAR(3) DEFAULT 'eur',
status VARCHAR(50),
application_fee INT,
metadata JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (amicale_id) REFERENCES amicales(id),
FOREIGN KEY (pompier_id) REFERENCES users(id)
);
CREATE TABLE terminal_readers (
id INT PRIMARY KEY AUTO_INCREMENT,
stripe_reader_id VARCHAR(255) UNIQUE,
amicale_id INT NOT NULL,
label VARCHAR(255),
location VARCHAR(255),
status VARCHAR(50),
device_type VARCHAR(50),
last_seen_at TIMESTAMP,
FOREIGN KEY (amicale_id) REFERENCES amicales(id)
);
-- NOTE: Table payment_intents SUPPRIMÉE - on utilise directement stripe_payment_id dans ope_pass
-- NOTE: Table terminal_readers SUPPRIMÉE - Tap to Pay uniquement, pas de terminaux externes
CREATE TABLE android_certified_devices (
id INT PRIMARY KEY AUTO_INCREMENT,
@@ -162,45 +145,47 @@ public function handleWebhook(Request $request) {
}
```
#### ✅ Terminal Connection Token
#### ✅ Configuration Tap to Pay
```php
// POST /api/terminal/connection-token
public function createConnectionToken(Request $request) {
$pompier = Auth::user();
$amicale = $pompier->amicale;
$connectionToken = \Stripe\Terminal\ConnectionToken::create([
'location' => $amicale->stripe_location_id,
], [
'stripe_account' => $amicale->stripe_account_id
]);
return ['secret' => $connectionToken->secret];
// POST /api/stripe/tap-to-pay/init
public function initTapToPay(Request $request) {
$userId = Session::getUserId();
$entityId = Session::getEntityId();
// Vérifier que l'entité a un compte Stripe
$account = $this->getStripeAccount($entityId);
return [
'stripe_account_id' => $account->stripe_account_id,
'tap_to_pay_enabled' => true
];
}
```
### 🌆 Après-midi (4h)
#### ✅ Gestion des Locations
#### ✅ Vérification compatibilité Device
```php
// POST /api/amicales/{id}/create-location
public function createLocation($amicaleId) {
$amicale = Amicale::find($amicaleId);
$location = \Stripe\Terminal\Location::create([
'display_name' => $amicale->name,
'address' => [
'line1' => $amicale->address,
'city' => $amicale->city,
'postal_code' => $amicale->postal_code,
'country' => 'FR',
],
], [
'stripe_account' => $amicale->stripe_account_id
]);
$amicale->update(['stripe_location_id' => $location->id]);
return $location;
// POST /api/stripe/devices/check-tap-to-pay
public function checkTapToPayCapability(Request $request) {
$platform = $request->input('platform');
$model = $request->input('device_model');
$osVersion = $request->input('os_version');
if ($platform === 'iOS') {
// iPhone XS et ultérieurs avec iOS 16.4+
$supported = $this->checkiOSCompatibility($model, $osVersion);
} else {
// Android certifié pour la France
$supported = $this->checkAndroidCertification($model);
}
return [
'tap_to_pay_supported' => $supported,
'message' => $supported ?
'Tap to Pay disponible' :
'Appareil non compatible'
];
}
```
@@ -210,24 +195,22 @@ public function createLocation($amicaleId) {
### 🌅 Matin (4h)
#### ✅ Création PaymentIntent avec commission
#### ✅ Création PaymentIntent avec association au passage
```php
// POST /api/payments/create-intent
public function createPaymentIntent(Request $request) {
$validated = $request->validate([
'amount' => 'required|integer|min:100', // en centimes
'amicale_id' => 'required|exists:amicales,id',
'passage_id' => 'required|integer', // ID du passage ope_pass
'entity_id' => 'required|integer',
]);
$pompier = Auth::user();
$amicale = Amicale::find($validated['amicale_id']);
// Calculer la commission (2.5% ou 50 centimes minimum)
$applicationFee = max(
50, // 0.50€ minimum
round($validated['amount'] * 0.025) // 2.5%
);
$userId = Session::getUserId();
$entity = $this->getEntity($validated['entity_id']);
// Commission à 0% (décision client)
$applicationFee = 0;
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => $validated['amount'],
'currency' => 'eur',
@@ -235,26 +218,27 @@ public function createPaymentIntent(Request $request) {
'capture_method' => 'automatic',
'application_fee_amount' => $applicationFee,
'transfer_data' => [
'destination' => $amicale->stripe_account_id,
'destination' => $entity->stripe_account_id,
],
'metadata' => [
'pompier_id' => $pompier->id,
'pompier_name' => $pompier->name,
'amicale_id' => $amicale->id,
'calendrier_annee' => date('Y'),
'passage_id' => $validated['passage_id'],
'user_id' => $userId,
'entity_id' => $entity->id,
'year' => date('Y'),
],
]);
// Sauvegarder en DB
PaymentIntent::create([
'stripe_payment_intent_id' => $paymentIntent->id,
'amicale_id' => $amicale->id,
'pompier_id' => $pompier->id,
'amount' => $validated['amount'],
'application_fee' => $applicationFee,
'status' => $paymentIntent->status,
// Mise à jour directe dans ope_pass
$this->db->prepare("
UPDATE ope_pass
SET stripe_payment_id = :stripe_id,
date_modified = NOW()
WHERE id = :passage_id
")->execute([
':stripe_id' => $paymentIntent->id,
':passage_id' => $validated['passage_id']
]);
return [
'client_secret' => $paymentIntent->client_secret,
'payment_intent_id' => $paymentIntent->id,
@@ -268,31 +252,59 @@ public function createPaymentIntent(Request $request) {
```php
// POST /api/payments/{id}/capture
public function capturePayment($paymentIntentId) {
$localPayment = PaymentIntent::where('stripe_payment_intent_id', $paymentIntentId)->first();
// Récupérer le passage depuis ope_pass
$stmt = $this->db->prepare("
SELECT id, stripe_payment_id, montant
FROM ope_pass
WHERE stripe_payment_id = :stripe_id
");
$stmt->execute([':stripe_id' => $paymentIntentId]);
$passage = $stmt->fetch();
$paymentIntent = \Stripe\PaymentIntent::retrieve($paymentIntentId);
if ($paymentIntent->status === 'requires_capture') {
$paymentIntent->capture();
}
$localPayment->update(['status' => $paymentIntent->status]);
// Si succès, envoyer email reçu
if ($paymentIntent->status === 'succeeded') {
$this->sendReceipt($localPayment);
// Mettre à jour le statut dans ope_pass si nécessaire
if ($paymentIntent->status === 'succeeded' && $passage) {
$this->db->prepare("
UPDATE ope_pass
SET date_stripe_validated = NOW()
WHERE id = :passage_id
")->execute([':passage_id' => $passage['id']]);
// Envoyer email reçu si configuré
$this->sendReceipt($passage['id']);
}
return $paymentIntent;
}
// GET /api/payments/{id}/status
public function getPaymentStatus($paymentIntentId) {
$payment = PaymentIntent::where('stripe_payment_intent_id', $paymentIntentId)->first();
// GET /api/passages/{id}/stripe-status
public function getPassageStripeStatus($passageId) {
$stmt = $this->db->prepare("
SELECT stripe_payment_id, montant, date_creat
FROM ope_pass
WHERE id = :id
");
$stmt->execute([':id' => $passageId]);
$passage = $stmt->fetch();
if (!$passage['stripe_payment_id']) {
return ['status' => 'no_stripe_payment'];
}
// Récupérer le statut depuis Stripe
$paymentIntent = \Stripe\PaymentIntent::retrieve($passage['stripe_payment_id']);
return [
'status' => $payment->status,
'amount' => $payment->amount,
'created_at' => $payment->created_at,
'stripe_payment_id' => $passage['stripe_payment_id'],
'status' => $paymentIntent->status,
'amount' => $paymentIntent->amount,
'currency' => $paymentIntent->currency,
'created_at' => $passage['date_creat']
];
}
```
@@ -625,14 +637,14 @@ Log::channel('stripe')->info('Payment created', [
## 🎯 BILAN DÉVELOPPEMENT API (01/09/2024)
### ✅ ENDPOINTS IMPLÉMENTÉS ET TESTÉS
### ✅ ENDPOINTS IMPLÉMENTÉS (TAP TO PAY UNIQUEMENT)
#### **Stripe Connect - Comptes**
- **POST /api/stripe/accounts** ✅
- Création compte Stripe Express pour amicales
- Gestion déchiffrement données (encrypted_email, encrypted_name)
- Support des comptes existants
- **GET /api/stripe/accounts/:entityId/status** ✅
- Récupération statut complet du compte
- Vérification charges_enabled et payouts_enabled
@@ -643,17 +655,6 @@ Log::channel('stripe')->info('Payment created', [
- URLs de retour configurées
- Gestion des erreurs et timeouts
#### **Terminal et Locations**
- **POST /api/stripe/locations** ✅
- Création de locations Terminal
- Association avec compte Stripe de l'amicale
- ID location retourné : tml_GLJ21w7KCYX4Wj
- **POST /api/stripe/terminal/connection-token** ✅
- Génération tokens de connexion Terminal
- Authentification par session
- Support multi-amicales
#### **Configuration et Utilitaires**
- **GET /api/stripe/config** ✅
- Configuration publique Stripe
@@ -728,9 +729,10 @@ Log::channel('stripe')->info('Payment created', [
- Public endpoints: webhook uniquement
- Pas de stockage clés secrètes en base
#### **Base de données**
#### **Base de données (MISE À JOUR JANVIER 2025)**
- **Modification table `ope_pass`** : `stripe_payment_id` VARCHAR(50) remplace `is_striped`
- **Table `payment_intents` supprimée** : Intégration directe dans `ope_pass`
- Utilisation tables existantes (entites)
- Pas de nouvelles tables créées (pas nécessaire pour V1)
- Champs encrypted_email et encrypted_name supportés
- Déchiffrement automatique avant envoi Stripe
@@ -743,4 +745,74 @@ Log::channel('stripe')->info('Payment created', [
---
*Document créé le 24/08/2024 - Dernière mise à jour : 01/09/2024*
## 📱 FLOW TAP TO PAY SIMPLIFIÉ (Janvier 2025)
### Architecture
```
Flutter App (Tap to Pay) ↔ API PHP ↔ Stripe API
```
### Étape 1: Création PaymentIntent
**Flutter → API**
```json
POST /api/stripe/payments/create-intent
{
"amount": 1500,
"passage_id": 123,
"entity_id": 5
}
```
**API → Stripe → Base de données**
```php
// 1. Créer le PaymentIntent
$paymentIntent = Stripe\PaymentIntent::create([...]);
// 2. Sauvegarder dans ope_pass
UPDATE ope_pass SET stripe_payment_id = 'pi_xxx' WHERE id = 123;
```
**API → Flutter**
```json
{
"client_secret": "pi_xxx_secret_yyy",
"payment_intent_id": "pi_xxx"
}
```
### Étape 2: Collecte du paiement (Flutter)
- L'app Flutter utilise le SDK Stripe Terminal
- Le téléphone devient le terminal de paiement (Tap to Pay)
- Utilise le client_secret pour collecter le paiement
### Étape 3: Confirmation (Webhook)
**Stripe → API**
- Event: `payment_intent.succeeded`
- Met à jour le statut dans la base de données
### Tables nécessaires
- ✅ `ope_pass.stripe_payment_id` - Association passage/paiement
- ✅ `stripe_accounts` - Comptes Connect des amicales
- ✅ `android_certified_devices` - Vérification compatibilité
- ❌ ~~`stripe_payment_intents`~~ - Supprimée
- ❌ ~~`terminal_readers`~~ - Pas de terminaux externes
### Endpoints essentiels
1. `POST /api/stripe/payments/create-intent` - Créer PaymentIntent
2. `POST /api/stripe/devices/check-tap-to-pay` - Vérifier compatibilité
3. `POST /api/stripe/webhook` - Recevoir confirmations
4. `GET /api/passages/{id}/stripe-status` - Vérifier statut
---
## 📝 CHANGELOG
### Janvier 2025 - Refactoring base de données
- **Suppression** de la table `payment_intents` (non nécessaire)
- **Migration** : `is_striped` → `stripe_payment_id` VARCHAR(50) dans `ope_pass`
- **Simplification** : Association directe PaymentIntent ↔ Passage
- **Avantage** : Traçabilité directe sans table intermédiaire
---
*Document créé le 24/08/2024 - Dernière mise à jour : 09/01/2025*

View File

@@ -0,0 +1,343 @@
# Flow de paiement Stripe Tap to Pay
## Vue d'ensemble
Ce document décrit le flow complet pour les paiements Stripe Tap to Pay dans l'application GeoSector, depuis la création du compte Stripe Connect jusqu'au paiement final.
---
## 🏢 PRÉALABLE : Création d'un compte Amicale Stripe Connect
Avant de pouvoir utiliser les paiements Stripe, chaque amicale doit créer son compte Stripe Connect.
### 📋 Flow de création du compte
#### 1. Initiation depuis l'application web admin
**Endpoint :** `POST /api/stripe/accounts/create`
**Requête :**
```json
{
"amicale_id": 45,
"type": "express", // Type de compte Stripe Connect
"country": "FR",
"email": "contact@amicale-pompiers-paris.fr",
"business_profile": {
"name": "Amicale des Pompiers de Paris",
"product_description": "Vente de calendriers des pompiers",
"mcc": "8398", // Code activité : organisations civiques
"url": "https://www.amicale-pompiers-paris.fr"
}
}
```
#### 2. Création du compte Stripe
**Actions API :**
1. Appel Stripe API pour créer un compte Express
2. Génération d'un lien d'onboarding personnalisé
3. Sauvegarde en base de données
**Réponse :**
```json
{
"success": true,
"stripe_account_id": "acct_1O3ABC456DEF789",
"onboarding_url": "https://connect.stripe.com/express/oauth/authorize?...",
"status": "pending"
}
```
#### 3. Processus d'onboarding Stripe
**Actions utilisateur (dirigeant amicale) :**
1. Clic sur le lien d'onboarding
2. Connexion/création compte Stripe
3. Saisie des informations légales :
- **Entité** : Association loi 1901
- **SIRET** de l'amicale
- **RIB** pour les virements
- **Pièce d'identité** du représentant légal
4. Validation des conditions d'utilisation
#### 4. Vérification et activation
**Webhook Stripe → API :**
```json
POST /api/stripe/webhooks
{
"type": "account.updated",
"data": {
"object": {
"id": "acct_1O3ABC456DEF789",
"charges_enabled": true,
"payouts_enabled": true,
"details_submitted": true
}
}
}
```
**Actions API :**
1. Mise à jour du statut en base
2. Notification email à l'amicale
3. Activation des fonctionnalités de paiement
#### 5. Structure en base de données
**Table `stripe_accounts` :**
```sql
CREATE TABLE `stripe_accounts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_entite` int(10) unsigned NOT NULL,
`stripe_account_id` varchar(50) NOT NULL,
`account_type` enum('express','standard','custom') DEFAULT 'express',
`charges_enabled` tinyint(1) DEFAULT 0,
`payouts_enabled` tinyint(1) DEFAULT 0,
`details_submitted` tinyint(1) DEFAULT 0,
`country` varchar(2) DEFAULT 'FR',
`default_currency` varchar(3) DEFAULT 'eur',
`business_name` varchar(255) DEFAULT NULL,
`support_email` varchar(255) DEFAULT NULL,
`onboarding_completed_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `stripe_account_id` (`stripe_account_id`),
KEY `fk_entite` (`fk_entite`),
CONSTRAINT `stripe_accounts_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`)
);
```
### 🔐 Sécurité et validation
#### Prérequis pour créer un compte :
- ✅ Utilisateur administrateur de l'amicale
- ✅ Amicale active avec statut validé
- ✅ Email de contact vérifié
- ✅ Informations légales complètes (SIRET, adresse)
#### Validation avant paiements :
-`charges_enabled = 1` (peut recevoir des paiements)
-`payouts_enabled = 1` (peut recevoir des virements)
-`details_submitted = 1` (onboarding terminé)
### 📊 États du compte Stripe
| État | Description | Actions possibles |
|------|-------------|-------------------|
| `pending` | Compte créé, onboarding en cours | Compléter l'onboarding |
| `restricted` | Informations manquantes | Fournir documents manquants |
| `restricted_soon` | Vérification en cours | Attendre validation Stripe |
| `active` | Compte opérationnel | Recevoir des paiements ✅ |
| `rejected` | Compte refusé par Stripe | Contacter support |
### 🚨 Gestion des erreurs
#### Erreurs courantes lors de la création :
- **400** : Données manquantes ou invalides
- **409** : Compte Stripe déjà existant pour cette amicale
- **403** : Utilisateur non autorisé
#### Erreurs durant l'onboarding :
- Documents manquants ou invalides
- Informations bancaires incorrectes
- Activité non autorisée par Stripe
### 📞 Support et résolution
#### Pour les amicales :
1. **Email support** : support@geosector.fr
2. **Documentation** : Guides d'onboarding disponibles
3. **Assistance téléphonique** : Disponible aux heures ouvrables
#### Pour les développeurs :
1. **Stripe Dashboard** : Accès aux comptes et statuts
2. **Logs API** : Traçabilité complète des opérations
3. **Webhook monitoring** : Suivi des événements Stripe
---
## 🚨 IMPORTANT : Nouveau Flow (v2)
**Le passage est TOUJOURS créé/modifié EN PREMIER** pour obtenir un ID réel, PUIS le PaymentIntent est créé avec cet ID.
## Flow détaillé
### 1. Sauvegarde du passage EN PREMIER
L'application crée ou modifie d'abord le passage pour obtenir un ID réel :
```
POST /api/passages/create // Nouveau passage
PUT /api/passages/456 // Mise à jour passage existant
```
**Réponse avec l'ID réel :**
```json
{
"status": "success",
"passage_id": 456 // ID RÉEL du passage créé/modifié
}
```
### 2. Création du PaymentIntent AVEC l'ID réel
Ensuite seulement, création du PaymentIntent avec le `passage_id` réel :
```
POST /api/stripe/payments/create-intent
```
```json
{
"amount": 2500, // En centimes (25€)
"passage_id": 456, // ID RÉEL du passage (JAMAIS 0)
"payment_method_types": ["card_present"], // Tap to Pay
"location_id": "tml_xxx", // Terminal reader location
"amicale_id": 45,
"member_id": 67,
"stripe_account": "acct_xxx"
}
```
#### Réponse
```json
{
"status": "success",
"data": {
"client_secret": "pi_3QaXYZ_secret_xyz",
"payment_intent_id": "pi_3QaXYZ123ABC456",
"amount": 2500,
"currency": "eur",
"passage_id": 789, // 0 pour nouveau passage
"type": "tap_to_pay"
}
}
```
### 2. Traitement du paiement côté client
L'application utilise le SDK Stripe pour traiter le paiement via NFC :
```dart
// Flutter - Utilisation du client_secret
final paymentResult = await stripe.collectPaymentMethod(
clientSecret: response['client_secret'],
// ... configuration Tap to Pay
);
```
### 3. Traitement du paiement Tap to Pay
L'application utilise le SDK Stripe Terminal avec le `client_secret` pour collecter le paiement via NFC.
### 4. Mise à jour du passage avec stripe_payment_id
Après succès du paiement, l'app met à jour le passage avec le `stripe_payment_id` :
```json
PUT /api/passages/456
{
"stripe_payment_id": "pi_3QaXYZ123ABC456", // ← LIEN AVEC STRIPE
// ... autres champs si nécessaire
}
```
## Points clés du nouveau flow
### ✅ Avantages
1. **Passage toujours existant** : Le passage existe toujours avec un ID réel avant le paiement
2. **Traçabilité garantie** : Le `passage_id` dans Stripe est toujours valide
3. **Gestion d'erreur robuste** : Si le paiement échoue, le passage existe déjà
4. **Cohérence des données** : Pas de passage "orphelin" ou de paiement sans passage
### ❌ Ce qui n'est plus supporté
1. **passage_id=0** : Plus jamais utilisé dans `/create-intent`
2. **operation_id** : Plus nécessaire car le passage existe déjà
3. **Création conditionnelle** : Le passage est toujours créé avant
## Schéma de séquence (Nouveau Flow v2)
```
┌─────────┐ ┌─────────┐ ┌────────┐ ┌────────────┐
│ App │ │ API │ │ Stripe │ │ ope_pass │
└────┬────┘ └────┬────┘ └────┬───┘ └─────┬──────┘
│ │ │ │
│ 1. CREATE/UPDATE passage │ │
├──────────────>│ │ │
│ ├────────────────┼───────────────>│
│ │ │ INSERT/UPDATE │
│ │ │ │
│ 2. passage_id: 456 (réel) │ │
│<──────────────│ │ │
│ │ │ │
│ 3. create-intent (id=456) │ │
├──────────────>│ │ │
│ │ │ │
│ │ 4. Create PI │ │
│ ├───────────────>│ │
│ │ │ │
│ │ 5. PI created │ │
│ │<───────────────│ │
│ │ │ │
│ │ 6. UPDATE │ │
│ ├────────────────┼───────────────>│
│ │ stripe_payment_id = pi_xxx │
│ │ │ │
│ 7. client_secret + pi_id │ │
│<──────────────│ │ │
│ │ │ │
│ 8. Tap to Pay │ │ │
├───────────────┼───────────────>│ │
│ avec SDK │ │ │
│ │ │ │
│ 9. Payment OK │ │ │
│<──────────────┼────────────────│ │
│ │ │ │
│ 10. UPDATE passage (optionnel) │ │
├──────────────>│ │ │
│ ├────────────────┼───────────────>│
│ │ Confirmer stripe_payment_id │
│ │ │ │
│ 11. Success │ │ │
│<──────────────│ │ │
│ │ │ │
```
## Points importants (Nouveau Flow v2)
1. **Passage créé en premier** : Le passage est TOUJOURS créé/modifié AVANT le PaymentIntent
2. **ID réel obligatoire** : Le `passage_id` ne peut jamais être 0 dans `/create-intent`
3. **Lien Stripe automatique** : Le `stripe_payment_id` est ajouté automatiquement lors de la création du PaymentIntent
4. **Idempotence** : Un passage ne peut avoir qu'un seul `stripe_payment_id`
5. **Validation stricte** : Vérification du montant, propriété et existence du passage
## Erreurs possibles
- **400** :
- `passage_id` manquant ou ≤ 0
- Montant invalide (< 1 ou > 999€)
- Passage déjà payé par Stripe
- Montant ne correspond pas au passage
- **401** : Non authentifié
- **403** : Passage non autorisé (pas le bon utilisateur)
- **404** : Passage non trouvé
## Migration base de données
La colonne `stripe_payment_id VARCHAR(50)` a été ajoutée via :
```sql
ALTER TABLE `ope_pass` ADD COLUMN `stripe_payment_id` VARCHAR(50) DEFAULT NULL;
ALTER TABLE `ope_pass` ADD INDEX `idx_stripe_payment` (`stripe_payment_id`);
```
## Environnements
- **DEV** : dva-geo sur IN3 - Base mise à jour ✅
- **REC** : rca-geo sur IN3 - Base mise à jour ✅
- **PROD** : pra-geo sur IN4 - À mettre à jour

View File

@@ -0,0 +1,197 @@
# Stripe Tap to Pay - Requirements officiels
> Document basé sur la documentation officielle Stripe - Dernière vérification : 29 septembre 2025
## 📱 iOS - Tap to Pay sur iPhone
### Configuration minimum requise
| Composant | Requirement | Notes |
|-----------|------------|--------|
| **Appareil** | iPhone XS ou plus récent | iPhone XS, XR, 11, 12, 13, 14, 15, 16 |
| **iOS** | iOS 16.4 ou plus récent | Pour support PIN complet |
| **SDK** | Terminal iOS SDK 2.23.0+ | Version 3.6.0+ pour Interac (Canada) |
| **Entitlement** | Apple Tap to Pay | À demander sur Apple Developer |
### Fonctionnalités par version iOS
- **iOS 16.0-16.3** : Tap to Pay basique (sans PIN)
- **iOS 16.4+** : Support PIN complet pour toutes les cartes
- **Versions beta** : NON SUPPORTÉES
### Méthodes de paiement supportées
- ✅ Cartes sans contact : Visa, Mastercard, American Express
- ✅ Wallets NFC : Apple Pay, Google Pay, Samsung Pay
- ✅ Discover (USA uniquement)
- ✅ Interac (Canada uniquement, SDK 3.6.0+)
- ✅ eftpos (Australie uniquement)
### Limitations importantes
- ❌ iPad non supporté (pas de NFC)
- ❌ Puerto Rico non disponible
- ❌ Versions iOS beta non supportées
## 🤖 Android - Tap to Pay
### Configuration minimum requise
| Composant | Requirement | Notes |
|-----------|------------|--------|
| **Android** | Android 11 ou plus récent | API level 30+ |
| **NFC** | Capteur NFC fonctionnel | Obligatoire |
| **Processeur** | ARM | x86 non supporté |
| **Sécurité** | Appareil non rooté | Bootloader verrouillé |
| **Services** | Google Mobile Services | GMS obligatoire |
| **Keystore** | Hardware keystore intégré | Pour sécurité |
| **OS** | OS constructeur non modifié | Pas de ROM custom |
### Appareils certifiés en France (liste non exhaustive)
#### Samsung
- Galaxy S21, S21+, S21 Ultra, S21 FE (Android 11+)
- Galaxy S22, S22+, S22 Ultra (Android 12+)
- Galaxy S23, S23+, S23 Ultra, S23 FE (Android 13+)
- Galaxy S24, S24+, S24 Ultra (Android 14+)
- Galaxy Z Fold 3, 4, 5, 6
- Galaxy Z Flip 3, 4, 5, 6
- Galaxy Note 20, Note 20 Ultra
- Galaxy A54, A73 (haut de gamme)
#### Google Pixel
- Pixel 6, 6 Pro, 6a (Android 12+)
- Pixel 7, 7 Pro, 7a (Android 13+)
- Pixel 8, 8 Pro, 8a (Android 14+)
- Pixel 9, 9 Pro, 9 Pro XL (Android 14+)
- Pixel Fold (Android 13+)
- Pixel Tablet (Android 13+)
#### OnePlus
- OnePlus 9, 9 Pro (Android 11+)
- OnePlus 10 Pro, 10T (Android 12+)
- OnePlus 11, 11R (Android 13+)
- OnePlus 12, 12R (Android 14+)
- OnePlus Open (Android 13+)
#### Xiaomi
- Mi 11, 11 Ultra (Android 11+)
- Xiaomi 12, 12 Pro, 12T Pro (Android 12+)
- Xiaomi 13, 13 Pro, 13T Pro (Android 13+)
- Xiaomi 14, 14 Pro, 14 Ultra (Android 14+)
#### Autres marques
- OPPO Find X3/X5/X6 Pro, Find N2/N3
- Realme GT 2 Pro, GT 3, GT 5 Pro
- Honor Magic5/6 Pro, 90
- ASUS Zenfone 9/10, ROG Phone 7
- Nothing Phone (1), (2), (2a)
## 🌍 Disponibilité par pays
### Europe
- ✅ France : Disponible
- ✅ Royaume-Uni : Disponible
- ✅ Allemagne : Disponible
- ✅ Pays-Bas : Disponible
- ✅ Irlande : Disponible
- ✅ Italie : Disponible (récent)
- ✅ Espagne : Disponible (récent)
### Amérique
- ✅ États-Unis : Disponible (+ Discover)
- ✅ Canada : Disponible (+ Interac)
- ❌ Puerto Rico : Non disponible
- ❌ Mexique : Non disponible
### Asie-Pacifique
- ✅ Australie : Disponible (+ eftpos)
- ✅ Nouvelle-Zélande : Disponible
- ✅ Singapour : Disponible
- ✅ Japon : Disponible (récent)
## 🔧 Intégration technique
### SDK Requirements
```javascript
// iOS
pod 'StripeTerminal', '~> 2.23.0' // Minimum pour Tap to Pay
pod 'StripeTerminal', '~> 3.6.0' // Pour support Interac
// Android
implementation 'com.stripe:stripeterminal-taptopay:3.7.1'
implementation 'com.stripe:stripeterminal-core:3.7.1'
// React Native
"@stripe/stripe-terminal-react-native": "^0.0.1-beta.17"
// Flutter
stripe_terminal: ^3.2.0
```
### Capacités requises
#### iOS Info.plist
```xml
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth nécessaire pour Tap to Pay</string>
<key>NFCReaderUsageDescription</key>
<string>NFC nécessaire pour lire les cartes</string>
<key>com.apple.developer.proximity-reader</key>
<true/>
```
#### Android Manifest
```xml
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
```
## 📊 Limites techniques
| Limite | Valeur | Notes |
|--------|--------|-------|
| **Montant min** | 1€ / $1 | Selon devise |
| **Montant max** | Variable par pays | France : 50€ sans PIN, illimité avec PIN |
| **Timeout transaction** | 60 secondes | Après présentation carte |
| **Distance NFC** | 4cm max | Distance optimale |
| **Tentatives PIN** | 3 max | Puis carte bloquée |
## 🔐 Sécurité
### Certifications
- PCI-DSS Level 1
- EMV Contactless Level 1
- Apple ProximityReader Framework
- Google SafetyNet Attestation
### Données sensibles
- Les données de carte ne transitent JAMAIS par l'appareil
- Tokenisation end-to-end par Stripe
- Pas de stockage local des données carte
- PIN chiffré directement vers Stripe
## 📚 Ressources officielles
- [Documentation Stripe Terminal](https://docs.stripe.com/terminal)
- [Tap to Pay sur iPhone - Apple Developer](https://developer.apple.com/tap-to-pay/)
- [Guide d'intégration iOS](https://docs.stripe.com/terminal/payments/setup-reader/tap-to-pay?platform=ios)
- [Guide d'intégration Android](https://docs.stripe.com/terminal/payments/setup-reader/tap-to-pay?platform=android)
- [SDK Terminal iOS](https://github.com/stripe/stripe-terminal-ios)
- [SDK Terminal Android](https://github.com/stripe/stripe-terminal-android)
## 🔄 Historique des versions
| Date | Version iOS | Changement |
|------|-------------|------------|
| Sept 2022 | iOS 16.0 | Lancement initial Tap to Pay |
| Mars 2023 | iOS 16.4 | Ajout support PIN |
| Sept 2023 | iOS 17.0 | Améliorations performances |
| Sept 2024 | iOS 18.0 | Support étendu international |
---
*Document maintenu par l'équipe GeoSector - Dernière mise à jour : 29/09/2025*

View File

@@ -10,7 +10,8 @@
6. [Sécurité](#sécurité)
7. [Gestion des mots de passe (NIST SP 800-63B)](#gestion-des-mots-de-passe-nist-sp-800-63b)
8. [Endpoints API](#endpoints-api)
9. [Changements récents](#changements-récents)
9. [Paiements Stripe Connect](#paiements-stripe-connect)
10. [Changements récents](#changements-récents)
## Structure du projet
@@ -130,6 +131,27 @@ Exemple détaillé du parcours d'une requête POST /api/users :
## Base de données
### Architecture des containers MariaDB
Depuis janvier 2025, les bases de données sont hébergées dans des containers MariaDB dédiés :
| Environnement | Container API | Container DB | Serveur | IP DB | Nom BDD | Utilisateur | Source des données |
|---------------|--------------|--------------|---------|-------|---------|-------------|-------------------|
| **DEV** | dva-geo | maria3 | IN3 | 13.23.33.4 | dva_geo | dva_geo_user | Migré depuis dva-geo/geo_app |
| **RECETTE** | rca-geo | maria3 | IN3 | 13.23.33.4 | rca_geo | rca_geo_user | Migré depuis rca-geo/geo_app |
| **PRODUCTION** | pra-geo | maria4 | IN4 | 13.23.33.4 | pra_geo | pra_geo_user | **Dupliqué depuis maria3/rca_geo** |
**Note importante :** La base de production `pra_geo` est créée en dupliquant `rca_geo` depuis IN3/maria3 vers IN4/maria4.
**Avantages de cette architecture :**
- Isolation des données par environnement
- Performances optimisées (containers dédiés)
- Sauvegardes indépendantes
- Maintenance simplifiée
- Séparation physique Production/Recette (serveurs différents)
**Migration :** Utiliser le script `scripts/migrate_to_maria_containers.sh` pour migrer les données.
### Structure des tables principales
#### Table `users`
@@ -735,6 +757,300 @@ Lors du login, les paramètres de l'entité sont retournés dans le groupe `amic
Ces paramètres permettent à l'application Flutter d'adapter dynamiquement le formulaire de création de membre.
## Paiements Stripe Connect
### Vue d'ensemble
L'API intègre un système complet de paiements via Stripe Connect, permettant aux amicales de recevoir des paiements pour leurs calendriers via deux méthodes :
- **Paiements Web** : Interface de paiement dans un navigateur
- **Tap to Pay** : Paiements NFC via l'application mobile Flutter
### Architecture Stripe Connect
#### Tables de base de données
**Table `stripe_accounts` :**
```sql
CREATE TABLE `stripe_accounts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_entite` int(10) unsigned NOT NULL,
`stripe_account_id` varchar(50) NOT NULL,
`account_type` enum('express','standard','custom') DEFAULT 'express',
`charges_enabled` tinyint(1) DEFAULT 0,
`payouts_enabled` tinyint(1) DEFAULT 0,
`details_submitted` tinyint(1) DEFAULT 0,
`country` varchar(2) DEFAULT 'FR',
`default_currency` varchar(3) DEFAULT 'eur',
`business_name` varchar(255) DEFAULT NULL,
`support_email` varchar(255) DEFAULT NULL,
`onboarding_completed_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `stripe_account_id` (`stripe_account_id`),
KEY `fk_entite` (`fk_entite`),
CONSTRAINT `stripe_accounts_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`)
);
```
**Ajout du champ `stripe_payment_id` dans `ope_pass` :**
```sql
ALTER TABLE `ope_pass` ADD COLUMN `stripe_payment_id` VARCHAR(50) DEFAULT NULL COMMENT 'ID du PaymentIntent Stripe (pi_xxx)';
ALTER TABLE `ope_pass` ADD INDEX `idx_stripe_payment` (`stripe_payment_id`);
```
#### Services principaux
**StripeService** (`src/Services/StripeService.php`) :
- Gestion des PaymentIntents
- Communication avec l'API Stripe
- Gestion des comptes Stripe Connect
**StripeController** (`src/Controllers/StripeController.php`) :
- Endpoints pour la création de PaymentIntents
- Gestion des webhooks Stripe
- API pour les comptes Connect
### Flow de paiement
#### 1. Création du compte Stripe Connect (Onboarding)
```http
POST /api/stripe/accounts/create
Authorization: Bearer {session_id}
Content-Type: application/json
{
"amicale_id": 45,
"type": "express",
"country": "FR",
"email": "contact@amicale-pompiers.fr",
"business_profile": {
"name": "Amicale des Pompiers",
"product_description": "Vente de calendriers des pompiers",
"mcc": "8398"
}
}
```
**Réponse :**
```json
{
"success": true,
"stripe_account_id": "acct_1O3ABC456DEF789",
"onboarding_url": "https://connect.stripe.com/express/oauth/authorize?...",
"status": "pending"
}
```
#### 2. Création d'un PaymentIntent (Tap to Pay)
**Flow actuel (v2) :**
1. L'application crée/modifie d'abord le passage pour obtenir un ID réel
2. Puis crée le PaymentIntent avec cet ID
```http
POST /api/stripe/payments/create-intent
Authorization: Bearer {session_id}
Content-Type: application/json
{
"amount": 2500, // 25€ en centimes
"passage_id": 456, // ID RÉEL du passage (jamais 0)
"payment_method_types": ["card_present"], // Tap to Pay
"location_id": "tml_xxx",
"amicale_id": 45,
"member_id": 67,
"stripe_account": "acct_1234"
}
```
**Réponse :**
```json
{
"success": true,
"client_secret": "pi_3QaXYZ_secret_xyz",
"payment_intent_id": "pi_3QaXYZ123ABC456",
"amount": 2500,
"currency": "eur",
"passage_id": 456,
"type": "tap_to_pay"
}
```
#### 3. Traitement du paiement
**Côté application Flutter :**
- Utilisation du SDK Stripe Terminal
- Collecte NFC avec le `client_secret`
- Traitement automatique du paiement
**Mise à jour automatique :**
- Le `stripe_payment_id` est automatiquement ajouté au passage lors de la création du PaymentIntent
- Lien bidirectionnel entre le passage et le paiement Stripe
### Endpoints Stripe
#### Gestion des comptes
- `POST /api/stripe/accounts/create` : Création d'un compte Connect
- `GET /api/stripe/accounts/{id}` : Statut d'un compte
- `PUT /api/stripe/accounts/{id}` : Mise à jour d'un compte
#### Gestion des paiements
- `POST /api/stripe/payments/create-intent` : Création d'un PaymentIntent
- `GET /api/stripe/payments/{id}` : Statut d'un paiement
- `POST /api/stripe/payments/confirm` : Confirmation d'un paiement
#### Gestion des devices Tap to Pay
- `GET /api/stripe/devices/certified-android` : Liste des appareils Android certifiés
- `POST /api/stripe/devices/check-tap-to-pay` : Vérification de compatibilité d'un appareil
- `GET /api/stripe/config` : Configuration publique Stripe
- `GET /api/stripe/stats` : Statistiques de paiement
#### Webhooks
- `POST /api/stripe/webhooks` : Réception des événements Stripe
- `account.updated` : Mise à jour du statut d'un compte
- `payment_intent.succeeded` : Confirmation d'un paiement réussi
- `payment_intent.payment_failed` : Échec d'un paiement
### Sécurité et validation
#### Prérequis pour les paiements :
- ✅ Compte Stripe Connect activé (`charges_enabled = 1`)
- ✅ Virements activés (`payouts_enabled = 1`)
- ✅ Onboarding terminé (`details_submitted = 1`)
- ✅ Passage existant avec montant correspondant
- ✅ Utilisateur authentifié et autorisé
#### Validation des montants :
- Minimum : 1€ (100 centimes)
- Maximum : 999€ (99 900 centimes)
- Vérification de correspondance avec le passage
#### Sécurité des transactions :
- Headers CORS configurés
- Validation côté serveur obligatoire
- Logs de toutes les transactions
- Gestion des erreurs robuste
### États et statuts
#### États des comptes Stripe :
- `pending` : Onboarding en cours
- `restricted` : Informations manquantes
- `active` : Opérationnel pour les paiements
- `rejected` : Refusé par Stripe
#### États des paiements :
- `requires_payment_method` : En attente de paiement
- `processing` : Traitement en cours
- `succeeded` : Paiement réussi
- `canceled` : Paiement annulé
- `requires_action` : Action utilisateur requise
### Intégration avec l'application
#### Flutter (Tap to Pay) :
- SDK Stripe Terminal pour iOS/Android
- Interface NFC native
- Gestion des états du terminal
- Validation en temps réel
#### Web (Paiements navigateur) :
- Stripe.js pour l'interface
- Formulaire de carte sécurisé
- Confirmation 3D Secure automatique
### Monitoring et logs
#### Logs importants :
- Création/mise à jour des comptes Connect
- Succès/échecs des paiements
- Erreurs webhook Stripe
- Tentatives de paiement frauduleuses
#### Métriques de suivi :
- Taux de succès des paiements par amicale
- Montants moyens des transactions
- Temps de traitement des paiements
- Erreurs par type d'appareil
### Configuration environnement
#### Variables Stripe par environnement :
| Environnement | Clés | Webhooks |
|---------------|------|----------|
| **DEV** | Test keys (pk_test_, sk_test_) | URL dev webhook |
| **RECETTE** | Test keys (pk_test_, sk_test_) | URL recette webhook |
| **PRODUCTION** | Live keys (pk_live_, sk_live_) | URL prod webhook |
#### Comptes Connect :
- Type : Express (simplifié pour les associations)
- Pays : France (FR)
- Devise : Euro (EUR)
- Frais : Standard Stripe Connect
### Gestion des appareils certifiés Tap to Pay
#### Table `stripe_android_certified_devices`
Stocke la liste des appareils Android certifiés pour Tap to Pay en France :
- **95+ appareils** pré-chargés lors de l'installation
- **Mise à jour automatique** hebdomadaire via CRON
- **Vérification de compatibilité** via endpoints dédiés
#### Endpoint de vérification de compatibilité
```http
POST /api/stripe/devices/check-tap-to-pay
Content-Type: application/json
{
"platform": "ios" | "android",
"manufacturer": "Samsung", // Requis pour Android
"model": "SM-S921B" // Requis pour Android
}
```
**Réponse Android compatible :**
```json
{
"status": "success",
"tap_to_pay_supported": true,
"message": "Tap to Pay disponible sur cet appareil",
"min_android_version": 14
}
```
**Réponse iOS :**
```json
{
"status": "success",
"message": "Vérification iOS à faire côté client",
"requirements": "iPhone XS ou plus récent avec iOS 16.4+",
"details": "iOS 16.4 minimum requis pour le support PIN complet"
}
```
#### Requirements Tap to Pay
| Plateforme | Appareil minimum | OS minimum | Notes |
|------------|------------------|------------|-------|
| **iOS** | iPhone XS (2018+) | iOS 16.4+ | Support PIN complet |
| **Android** | Variable | Android 11+ | NFC obligatoire, non rooté |
### Documentation technique complète
Pour le flow détaillé complet, voir :
- **`docs/STRIPE-TAP-TO-PAY-FLOW.md`** : Documentation technique complète du flow de paiement
- **`docs/PLANNING-STRIPE-API.md`** : Planification et architecture Stripe
- **`docs/STRIPE-TAP-TO-PAY-REQUIREMENTS.md`** : Requirements officiels et liste complète des devices certifiés
## Intégration Frontend
### Configuration des Requêtes
@@ -754,6 +1070,71 @@ fetch('/api/endpoint', {
- Pas besoin de stocker ou gérer des tokens manuellement
- Redirection vers /login si session expirée (401)
## Système de tâches CRON
### Vue d'ensemble
L'API utilise des scripts CRON pour automatiser les tâches de maintenance et de traitement. Les scripts sont situés dans `/scripts/cron/` et s'exécutent dans les containers Incus Alpine.
### Tâches CRON configurées
| Script | Fréquence | Fonction | Container |
|--------|-----------|----------|-----------|
| `process_email_queue.php` | */5 * * * * | Traite la queue d'emails (reçus, notifications) | DVA, RCA |
| `cleanup_security_data.php` | 0 2 * * * | Nettoie les données de sécurité obsolètes | DVA, RCA |
| `update_stripe_devices.php` | 0 3 * * 0 | Met à jour la liste des devices certifiés Tap to Pay | DVA, RCA |
### Configuration des CRONs
Sur les containers Alpine (dva-geo, rca-geo, pra-geo) :
```bash
# Vérifier les crons actifs
crontab -l
# Éditer les crons
crontab -e
# Format des lignes cron
*/5 * * * * /usr/bin/php /var/www/geosector/api/scripts/cron/process_email_queue.php >> /var/www/geosector/api/logs/email_queue.log 2>&1
0 2 * * * /usr/bin/php /var/www/geosector/api/scripts/cron/cleanup_security_data.php >> /var/www/geosector/api/logs/cleanup_security.log 2>&1
0 3 * * 0 /usr/bin/php /var/www/geosector/api/scripts/cron/update_stripe_devices.php >> /var/www/geosector/api/logs/stripe_devices.log 2>&1
```
### Script `process_email_queue.php`
- **Fonction** : Envoie les emails en attente dans la table `email_queue`
- **Batch** : 50 emails maximum par exécution
- **Lock file** : `/tmp/process_email_queue.lock` (évite l'exécution simultanée)
- **Gestion d'erreur** : 3 tentatives max par email
### Script `cleanup_security_data.php`
- **Fonction** : Purge les données de sécurité selon la politique de rétention
- **Rétention** :
- Métriques de performance : 30 jours
- Tentatives de login échouées : 7 jours
- Alertes résolues : 90 jours
- IPs expirées : Déblocage immédiat
### Script `update_stripe_devices.php`
- **Fonction** : Maintient à jour la liste des appareils certifiés Tap to Pay
- **Source** : Liste de 95+ devices intégrée + fichier JSON optionnel
- **Actions** :
- Ajoute les nouveaux appareils certifiés
- Met à jour les versions Android minimales
- Désactive les appareils obsolètes
- Envoie une notification email si changements importants
- **Personnalisation** : Possibilité d'ajouter des devices via `/data/stripe_certified_devices.json`
### Monitoring des CRONs
Les logs sont stockés dans `/var/www/geosector/api/logs/` :
- `email_queue.log` : Logs du traitement des emails
- `cleanup_security.log` : Logs du nettoyage sécurité
- `stripe_devices.log` : Logs de mise à jour des devices
## Maintenance et Déploiement
### Logs
@@ -764,11 +1145,36 @@ fetch('/api/endpoint', {
### Déploiement
1. Pull du repository
2. Vérification des permissions
3. Configuration de l'environnement
4. Tests des endpoints
5. Redémarrage des services
Le script `deploy-api.sh` gère le déploiement sur les 3 environnements :
```bash
# Déploiement DEV : code local → container dva-geo sur IN3
./deploy-api.sh
# Déploiement RECETTE : container dva-geo → container rca-geo sur IN3
./deploy-api.sh rca
# Déploiement PRODUCTION : container rca-geo (IN3) → container pra-geo (IN4)
./deploy-api.sh pra
```
Flux de déploiement :
1. **DEV** : Archive du code local, déploiement sur container `dva-geo` sur IN3 (195.154.80.116)
- URL publique : https://dapp.geosector.fr/api/
- IP interne : http://13.23.33.43/api/
2. **RECETTE** : Archive depuis container `dva-geo`, déploiement sur `rca-geo` sur IN3
- URL publique : https://rapp.geosector.fr/api/
3. **PRODUCTION** : Archive depuis `rca-geo` (IN3), déploiement sur `pra-geo` (51.159.7.190)
- URL publique : https://app.geosector.fr/api/
Caractéristiques :
- Sauvegarde automatique avec rotation (garde les 10 dernières)
- Préservation des dossiers `logs/` et `uploads/`
- Gestion des permissions :
- Code API : `nginx:nginx` (755/644)
- Logs et uploads : `nobody:nginx` (755/644)
- Installation des dépendances Composer (pas de mise à jour)
- Journalisation dans `~/.geo_deploy_history`
### Surveillance
@@ -779,6 +1185,89 @@ fetch('/api/endpoint', {
## Changements récents
### Version 3.2.5 (29 Septembre 2025)
#### 1. Système de gestion automatique des devices Tap to Pay
**Nouveaux endpoints ajoutés :**
- `GET /api/stripe/devices/certified-android` : Récupération de la liste complète des appareils certifiés
- `POST /api/stripe/devices/check-tap-to-pay` : Vérification de compatibilité d'un appareil spécifique
- Endpoints publics (pas d'authentification requise) pour vérification côté app
**Script CRON de mise à jour automatique :**
- **Script** : `/scripts/cron/update_stripe_devices.php`
- **Fréquence** : Hebdomadaire (dimanche 3h)
- **Fonction** : Maintient à jour la liste de 95+ appareils Android certifiés
- **Base de données** : Table `stripe_android_certified_devices` avec 77 appareils actifs
**Corrections des requirements iOS :**
- Mise à jour : iOS 16.4+ minimum (au lieu de 15.4/16.0)
- Raison : Support PIN complet obligatoire pour les paiements > 50€
**Documentation ajoutée :**
- `docs/STRIPE-TAP-TO-PAY-REQUIREMENTS.md` : Requirements officiels complets
- Liste exhaustive des appareils certifiés par fabricant
- Configuration SDK pour toutes les plateformes
#### 2. Configuration des tâches CRON sur les containers
**Environnements configurés :**
- **DVA-GEO (DEV)** : 3 CRONs actifs
- **RCA-GEO (RECETTE)** : 3 CRONs actifs (ajoutés le 29/09)
- **PRA-GEO (PROD)** : À configurer
**Tâches automatisées :**
1. Queue d'emails : Toutes les 5 minutes
2. Nettoyage sécurité : Quotidien à 2h
3. Mise à jour devices Stripe : Hebdomadaire dimanche 3h
### Version 3.2.4 (Septembre 2025)
#### 1. Implémentation complète de Stripe Connect V1
**Paiements Stripe intégrés pour les amicales :**
- **Stripe Connect Express** : Onboarding simplifié pour les associations
- **Tap to Pay** : Paiements NFC via l'application mobile Flutter
- **Paiements Web** : Interface de paiement navigateur avec Stripe.js
- **Webhooks** : Gestion automatique des événements Stripe
**Nouvelles tables de base de données :**
- `stripe_accounts` : Gestion des comptes Connect par amicale
- `stripe_payment_history` : Historique des transactions Stripe
- `stripe_refunds` : Gestion des remboursements
- Ajout de `stripe_payment_id` dans `ope_pass` pour liaison bidirectionnelle
**Nouveaux services :**
- **StripeService** : Communication avec l'API Stripe, gestion des PaymentIntents
- **StripeController** : Endpoints API pour création de comptes, paiements et webhooks
**Flow de paiement optimisé (v2) :**
1. Passage créé/modifié EN PREMIER pour obtenir un ID réel
2. Création PaymentIntent avec `passage_id` réel (jamais 0)
3. Traitement Tap to Pay via SDK Stripe Terminal
4. Mise à jour automatique du passage avec `stripe_payment_id`
**Endpoints ajoutés :**
- `POST /api/stripe/accounts/create` : Création compte Connect
- `POST /api/stripe/payments/create-intent` : Création PaymentIntent
- `GET /api/stripe/payments/{id}` : Statut d'un paiement
- `POST /api/stripe/webhooks` : Réception événements Stripe
**Sécurité et validation :**
- Validation stricte des montants (1€ à 999€)
- Vérification correspondance passage/montant
- Gestion des permissions par amicale
- Logs complets des transactions
**Configuration multi-environnements :**
- DEV/RECETTE : Clés de test Stripe
- PRODUCTION : Clés live avec webhooks sécurisés
- Migration base de données via `migrate_stripe_payment_id.sql`
**Documentation technique :**
- `docs/STRIPE-TAP-TO-PAY-FLOW.md` : Flow complet de paiement
- `docs/PLANNING-STRIPE-API.md` : Architecture et planification
### Version 3.0.7 (Août 2025)
#### 1. Implémentation complète de la norme NIST SP 800-63B pour les mots de passe

View File

@@ -0,0 +1,53 @@
-- Table pour stocker les informations des devices des utilisateurs
CREATE TABLE IF NOT EXISTS `user_devices` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_user` int(10) unsigned NOT NULL COMMENT 'Référence vers la table users',
-- Informations générales du device
`platform` varchar(20) NOT NULL COMMENT 'Plateforme: iOS, Android, etc.',
`device_model` varchar(100) DEFAULT NULL COMMENT 'Modèle du device (ex: iPhone13,2)',
`device_name` varchar(255) DEFAULT NULL COMMENT 'Nom personnalisé du device',
`device_manufacturer` varchar(100) DEFAULT NULL COMMENT 'Fabricant (Apple, Samsung, etc.)',
`device_identifier` varchar(100) DEFAULT NULL COMMENT 'Identifiant unique du device',
-- Informations réseau (IPv4 uniquement)
`device_ip_local` varchar(15) DEFAULT NULL COMMENT 'Adresse IP locale IPv4',
`device_ip_public` varchar(15) DEFAULT NULL COMMENT 'Adresse IP publique IPv4',
`device_wifi_name` varchar(255) DEFAULT NULL COMMENT 'Nom du réseau WiFi (SSID)',
`device_wifi_bssid` varchar(17) DEFAULT NULL COMMENT 'BSSID du point d\'accès (format
XX:XX:XX:XX:XX:XX)',
-- Capacités et version OS
`ios_version` varchar(20) DEFAULT NULL COMMENT 'Version iOS/Android OS',
`device_nfc_capable` tinyint(1) DEFAULT NULL COMMENT 'Support NFC (1=oui, 0=non)',
`device_supports_tap_to_pay` tinyint(1) DEFAULT NULL COMMENT 'Support Tap to Pay (1=oui, 0=non)',
-- État batterie
`battery_level` tinyint(3) unsigned DEFAULT NULL COMMENT 'Niveau batterie en pourcentage (0-100)',
`battery_charging` tinyint(1) DEFAULT NULL COMMENT 'En charge (1=oui, 0=non)',
`battery_state` varchar(20) DEFAULT NULL COMMENT 'État batterie (charging, discharging, full)',
-- Versions application
`app_version` varchar(20) DEFAULT NULL COMMENT 'Version de l\'application (ex: 3.2.8)',
`app_build` varchar(20) DEFAULT NULL COMMENT 'Numéro de build (ex: 328)',
-- Timestamps
`last_device_info_check` timestamp NULL DEFAULT NULL COMMENT 'Dernier check des infos device côté
app',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de création de
l\'enregistrement',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT
'Date de dernière modification',
PRIMARY KEY (`id`),
KEY `idx_fk_user` (`fk_user`) COMMENT 'Index pour recherche par utilisateur',
KEY `idx_updated_at` (`updated_at`) COMMENT 'Index pour tri par date de mise à jour',
KEY `idx_last_check` (`last_device_info_check`) COMMENT 'Index pour recherche par dernière
vérification',
UNIQUE KEY `unique_user_device` (`fk_user`, `device_identifier`) COMMENT 'Un seul enregistrement
par device/user',
CONSTRAINT `fk_user_devices_user` FOREIGN KEY (`fk_user`)
REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Informations des devices
utilisateurs';

View File

@@ -0,0 +1,166 @@
1. Route /session/refresh/all
Méthode : POSTAuthentification : Requise (via session_id dans headers ou cookies)
Headers requis :
Authorization: Bearer {session_id}
// ou
Cookie: session_id={session_id}
Réponse attendue :
{
"status": "success",
"message": "Session refreshed",
"user": {
// Mêmes données que le login
"id": 123,
"email": "user@example.com",
"name": "John Doe",
"fk_role": 2,
"fk_entite": 1,
// ...
},
"amicale": {
// Données de l'amicale
"id": 1,
"name": "Amicale Pompiers",
// ...
},
"operations": [...],
"sectors": [...],
"passages": [...],
"membres": [...],
"session_id": "current_session_id",
"session_expiry": "2024-01-20T10:00:00Z"
}
Code PHP suggéré :
// routes/session.php
Route::post('/session/refresh/all', function(Request $request) {
$user = Auth::user();
if (!$user) {
return response()->json(['status' => 'error', 'message' => 'Not authenticated'], 401);
}
// Retourner les mêmes données qu'un login normal
return response()->json([
'status' => 'success',
'user' => $user->toArray(),
'amicale' => $user->amicale,
'operations' => Operation::where('fk_entite', $user->fk_entite)->get(),
'sectors' => Sector::where('fk_entite', $user->fk_entite)->get(),
'passages' => Passage::where('fk_entite', $user->fk_entite)->get(),
'membres' => Membre::where('fk_entite', $user->fk_entite)->get(),
'session_id' => session()->getId(),
'session_expiry' => now()->addDays(7)->toIso8601String()
]);
});
2. Route /session/refresh/partial
Méthode : POSTAuthentification : Requise
Body requis :
{
"last_sync": "2024-01-19T10:00:00Z"
}
Réponse attendue :
{
"status": "success",
"message": "Partial refresh completed",
"sectors": [
// Uniquement les secteurs modifiés après last_sync
{
"id": 45,
"name": "Secteur A",
"updated_at": "2024-01-19T15:00:00Z",
// ...
}
],
"passages": [
// Uniquement les passages modifiés après last_sync
{
"id": 789,
"fk_sector": 45,
"updated_at": "2024-01-19T14:30:00Z",
// ...
}
],
"operations": [...], // Si modifiées
"membres": [...] // Si modifiés
}
Code PHP suggéré :
// routes/session.php
Route::post('/session/refresh/partial', function(Request $request) {
$user = Auth::user();
if (!$user) {
return response()->json(['status' => 'error', 'message' => 'Not authenticated'], 401);
}
$lastSync = Carbon::parse($request->input('last_sync'));
// Récupérer uniquement les données modifiées après last_sync
$response = [
'status' => 'success',
'message' => 'Partial refresh completed'
];
// Secteurs modifiés
$sectors = Sector::where('fk_entite', $user->fk_entite)
->where('updated_at', '>', $lastSync)
->get();
if ($sectors->count() > 0) {
$response['sectors'] = $sectors;
}
// Passages modifiés
$passages = Passage::where('fk_entite', $user->fk_entite)
->where('updated_at', '>', $lastSync)
->get();
if ($passages->count() > 0) {
$response['passages'] = $passages;
}
// Opérations modifiées
$operations = Operation::where('fk_entite', $user->fk_entite)
->where('updated_at', '>', $lastSync)
->get();
if ($operations->count() > 0) {
$response['operations'] = $operations;
}
// Membres modifiés
$membres = Membre::where('fk_entite', $user->fk_entite)
->where('updated_at', '>', $lastSync)
->get();
if ($membres->count() > 0) {
$response['membres'] = $membres;
}
return response()->json($response);
});
Points importants pour l'API :
1. Vérification de session : Les deux routes doivent vérifier que le session_id est valide et non expiré
2. Timestamps : Assurez-vous que toutes vos tables ont des colonnes updated_at qui sont mises à jour automatiquement
3. Gestion des suppressions : Pour le refresh partiel, vous pourriez ajouter un champ pour les éléments supprimés :
{
"deleted": {
"sectors": [12, 34], // IDs des secteurs supprimés
"passages": [567, 890]
}
}
4. Optimisation : Pour éviter de surcharger, limitez le refresh partiel aux dernières 24-48h maximum
5. Gestion d'erreurs :
{
"status": "error",
"message": "Session expired",
"code": "SESSION_EXPIRED"
}
L'app Flutter s'attend à ces formats de réponse et utilisera automatiquement le refresh partiel si la dernière sync
date de moins de 24h, sinon elle fera un refresh complet.