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 242a90720e
commit b6584c83fa
1625 changed files with 145669 additions and 51249 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*