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:
@@ -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*
|
||||
343
api/docs/STRIPE-TAP-TO-PAY-FLOW.md
Normal file
343
api/docs/STRIPE-TAP-TO-PAY-FLOW.md
Normal 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
|
||||
197
api/docs/STRIPE-TAP-TO-PAY-REQUIREMENTS.md
Normal file
197
api/docs/STRIPE-TAP-TO-PAY-REQUIREMENTS.md
Normal 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*
|
||||
@@ -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
|
||||
|
||||
53
api/docs/create_table_user_devices.sql
Normal file
53
api/docs/create_table_user_devices.sql
Normal 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';
|
||||
166
api/docs/nouvelles-routes-session-refresh.txt
Normal file
166
api/docs/nouvelles-routes-session-refresh.txt
Normal 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.
|
||||
Reference in New Issue
Block a user