feat: Version 3.3.5 - Optimisations pages, améliorations ergonomie et affichages dynamiques stats
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -90,16 +90,41 @@ INSERT INTO ope_pass_backup (
|
||||
- Index sur les tables volumineuses
|
||||
- Pagination optimisée
|
||||
|
||||
#### 4. Sécurisation des clés Stripe par environnement
|
||||
**Objectif :** Étudier une approche plus sécurisée pour stocker les clés Stripe
|
||||
|
||||
**Problème actuel :**
|
||||
- Toutes les clés (DEV, REC, PROD) sont dans un seul fichier `AppConfig.php`
|
||||
- Les clés PRODUCTION sont visibles dans le code DEV/REC
|
||||
- Risque si accès au container DEV → exposition des clés PROD
|
||||
|
||||
**Solutions à étudier :**
|
||||
1. **Variables d'environnement** (`.env` par container)
|
||||
- Fichier `.env.dev`, `.env.rec`, `.env.prod`
|
||||
- Chargement dynamique selon l'environnement
|
||||
- Exclusion des `.env` du versionning Git
|
||||
|
||||
2. **Fichiers de config séparés**
|
||||
- `config/stripe.dev.php`, `config/stripe.rec.php`, `config/stripe.prod.php`
|
||||
- Déploiement sélectif selon l'environnement
|
||||
- Non versionnés (ajoutés au .gitignore)
|
||||
|
||||
3. **Secrets management** (avancé)
|
||||
- HashiCorp Vault, AWS Secrets Manager, etc.
|
||||
- API de récupération sécurisée des secrets
|
||||
|
||||
**Recommandation :** Approche #1 (variables d'environnement) pour équilibre sécurité/simplicité
|
||||
|
||||
---
|
||||
|
||||
### 🟢 PRIORITÉ BASSE
|
||||
|
||||
#### 4. Documentation API
|
||||
#### 5. Documentation API
|
||||
- Génération automatique OpenAPI/Swagger
|
||||
- Documentation interactive
|
||||
- Exemples de code pour chaque endpoint
|
||||
|
||||
#### 5. Tests automatisés
|
||||
#### 6. Tests automatisés
|
||||
- Tests unitaires pour les services critiques
|
||||
- Tests d'intégration pour les endpoints
|
||||
- Tests de charge
|
||||
|
||||
@@ -981,19 +981,52 @@ Content-Type: application/json
|
||||
|
||||
### Configuration environnement
|
||||
|
||||
#### Variables Stripe par environnement :
|
||||
#### Architecture des clés Stripe
|
||||
|
||||
| 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 |
|
||||
Depuis janvier 2025, les clés Stripe sont **séparées par environnement** dans `src/Config/AppConfig.php` :
|
||||
|
||||
#### Comptes Connect :
|
||||
| Environnement | URL | Mode | Clés utilisées | Status |
|
||||
|---------------|-----|------|----------------|--------|
|
||||
| **DEV** | https://dapp.geosector.fr | `test` | Clés TEST Pierre (dev plateforme) | ✅ Opérationnel |
|
||||
| **RECETTE** | https://rapp.geosector.fr | `test` | Clés TEST du client | ⏳ À configurer |
|
||||
| **PRODUCTION** | https://app.geosector.fr | `live` | Clés LIVE du client | ⏳ À configurer |
|
||||
|
||||
**Emplacement dans le code :**
|
||||
- **DEV** : `AppConfig.php` lignes 175-187 (section `dapp.geosector.fr`)
|
||||
- **RECETTE** : `AppConfig.php` lignes 150-162 (section `rapp.geosector.fr`)
|
||||
- **PRODUCTION** : `AppConfig.php` lignes 126-138 (section `app.geosector.fr`)
|
||||
|
||||
#### Configuration des clés client
|
||||
|
||||
Pour configurer les clés Stripe du client :
|
||||
|
||||
1. **Récupérer les clés depuis le Dashboard Stripe du client**
|
||||
- Se connecter sur https://dashboard.stripe.com
|
||||
- Aller dans **Développeurs → Clés API**
|
||||
- Pour les clés TEST : Mode Test activé
|
||||
- Pour les clés LIVE : Mode Live activé
|
||||
|
||||
2. **Remplacer les placeholders dans AppConfig.php**
|
||||
- **RECETTE** (ligne 152-153) : Remplacer `CLIENT_PK_TEST_A_REMPLACER` et `CLIENT_SK_TEST_A_REMPLACER`
|
||||
- **PRODUCTION** (ligne 130-131) : Remplacer `CLIENT_PK_LIVE_A_REMPLACER` et `CLIENT_SK_LIVE_A_REMPLACER`
|
||||
|
||||
3. **Déployer selon l'environnement**
|
||||
```bash
|
||||
# Déployer en RECETTE
|
||||
./deploy-api.sh rca
|
||||
|
||||
# Déployer en PRODUCTION
|
||||
./deploy-api.sh pra
|
||||
```
|
||||
|
||||
**⚠️ Sécurité :** Voir `TODO-API.md` section "Sécurisation des clés Stripe" pour étudier une approche plus sécurisée (variables d'environnement, fichiers séparés).
|
||||
|
||||
#### Comptes Connect
|
||||
- Type : Express (simplifié pour les associations)
|
||||
- Pays : France (FR)
|
||||
- Devise : Euro (EUR)
|
||||
- Frais : Standard Stripe Connect
|
||||
- Pas de commission plateforme (100% pour l'amicale)
|
||||
|
||||
### Gestion des appareils certifiés Tap to Pay
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ CREATE TABLE `email_queue` (
|
||||
`headers` text DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||
`status` enum('pending','sent','failed') DEFAULT 'pending',
|
||||
`sent_at` timestamp NULL DEFAULT NULL,
|
||||
`attempts` int(10) unsigned DEFAULT 0,
|
||||
`error_message` text DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
|
||||
@@ -324,7 +324,9 @@ CREATE TABLE `email_queue` (
|
||||
`headers` text COLLATE utf8mb4_unicode_ci,
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`status` enum('pending','sent','failed') COLLATE utf8mb4_unicode_ci DEFAULT 'pending',
|
||||
`sent_at` timestamp NULL DEFAULT NULL,
|
||||
`attempts` int unsigned DEFAULT '0',
|
||||
`error_message` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
165
api/scripts/config/update_php_fpm_settings.sh
Normal file
165
api/scripts/config/update_php_fpm_settings.sh
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/bin/bash
|
||||
|
||||
##############################################################################
|
||||
# Script de mise à jour des paramètres PHP-FPM pour GeoSector
|
||||
#
|
||||
# Usage:
|
||||
# ./update_php_fpm_settings.sh dev # Pour DVA
|
||||
# ./update_php_fpm_settings.sh rec # Pour RCA
|
||||
# ./update_php_fpm_settings.sh prod # Pour PRA
|
||||
##############################################################################
|
||||
|
||||
set -e
|
||||
|
||||
# Couleurs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Déterminer l'environnement
|
||||
ENV=${1:-dev}
|
||||
|
||||
case $ENV in
|
||||
dev)
|
||||
CONTAINER="dva-geo"
|
||||
TIMEOUT=180
|
||||
MAX_REQUESTS=1000
|
||||
MEMORY_LIMIT=512M
|
||||
;;
|
||||
rec)
|
||||
CONTAINER="rca-geo"
|
||||
TIMEOUT=120
|
||||
MAX_REQUESTS=2000
|
||||
MEMORY_LIMIT=256M
|
||||
;;
|
||||
prod)
|
||||
CONTAINER="pra-geo"
|
||||
TIMEOUT=120
|
||||
MAX_REQUESTS=2000
|
||||
MEMORY_LIMIT=256M
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Erreur: Environnement invalide '$ENV'${NC}"
|
||||
echo "Usage: $0 [dev|rec|prod]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo -e "${GREEN}=== Mise à jour PHP-FPM pour $ENV ($CONTAINER) ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Vérifier que le container existe
|
||||
if ! incus list | grep -q "$CONTAINER"; then
|
||||
echo -e "${RED}Erreur: Container $CONTAINER non trouvé${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Trouver le fichier de configuration
|
||||
echo "Recherche du fichier de configuration PHP-FPM..."
|
||||
POOL_FILE=$(incus exec $CONTAINER -- find /etc/php* -name "www.conf" 2>/dev/null | grep fpm/pool | head -1)
|
||||
|
||||
if [ -z "$POOL_FILE" ]; then
|
||||
echo -e "${RED}Erreur: Fichier pool PHP-FPM non trouvé${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Fichier trouvé: $POOL_FILE${NC}"
|
||||
echo ""
|
||||
|
||||
# Sauvegarder le fichier original
|
||||
BACKUP_FILE="${POOL_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Création d'une sauvegarde..."
|
||||
incus exec $CONTAINER -- cp "$POOL_FILE" "$BACKUP_FILE"
|
||||
echo -e "${GREEN}✓ Sauvegarde créée: $BACKUP_FILE${NC}"
|
||||
echo ""
|
||||
|
||||
# Afficher les valeurs actuelles
|
||||
echo "Valeurs actuelles:"
|
||||
incus exec $CONTAINER -- grep -E "^(request_terminate_timeout|pm.max_requests|memory_limit)" "$POOL_FILE" || echo " (non définies)"
|
||||
echo ""
|
||||
|
||||
# Créer un fichier temporaire avec les nouvelles valeurs
|
||||
TMP_FILE="/tmp/php_fpm_update_$$.conf"
|
||||
|
||||
cat > $TMP_FILE << EOF
|
||||
; === Configuration GeoSector - Modifié le $(date +%Y-%m-%d) ===
|
||||
|
||||
; Timeout des requêtes
|
||||
request_terminate_timeout = ${TIMEOUT}s
|
||||
|
||||
; Nombre max de requêtes avant recyclage du worker
|
||||
pm.max_requests = ${MAX_REQUESTS}
|
||||
|
||||
; Limite mémoire PHP
|
||||
php_admin_value[memory_limit] = ${MEMORY_LIMIT}
|
||||
|
||||
; Log des requêtes lentes
|
||||
slowlog = /var/log/php8.3-fpm-slow.log
|
||||
request_slowlog_timeout = 10s
|
||||
EOF
|
||||
|
||||
echo "Nouvelles valeurs à appliquer:"
|
||||
cat $TMP_FILE
|
||||
echo ""
|
||||
|
||||
# Demander confirmation
|
||||
read -p "Appliquer ces modifications ? (y/N) " -n 1 -r
|
||||
echo ""
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Annulé."
|
||||
rm $TMP_FILE
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Supprimer les anciennes valeurs si présentes
|
||||
echo "Suppression des anciennes valeurs..."
|
||||
incus exec $CONTAINER -- sed -i '/^request_terminate_timeout/d' "$POOL_FILE"
|
||||
incus exec $CONTAINER -- sed -i '/^pm.max_requests/d' "$POOL_FILE"
|
||||
incus exec $CONTAINER -- sed -i '/^php_admin_value\[memory_limit\]/d' "$POOL_FILE"
|
||||
incus exec $CONTAINER -- sed -i '/^slowlog/d' "$POOL_FILE"
|
||||
incus exec $CONTAINER -- sed -i '/^request_slowlog_timeout/d' "$POOL_FILE"
|
||||
|
||||
# Ajouter les nouvelles valeurs à la fin du fichier
|
||||
echo "Ajout des nouvelles valeurs..."
|
||||
incus file push $TMP_FILE $CONTAINER/tmp/php_fpm_settings.conf
|
||||
incus exec $CONTAINER -- bash -c "cat /tmp/php_fpm_settings.conf >> $POOL_FILE"
|
||||
incus exec $CONTAINER -- rm /tmp/php_fpm_settings.conf
|
||||
|
||||
rm $TMP_FILE
|
||||
|
||||
echo -e "${GREEN}✓ Configuration mise à jour${NC}"
|
||||
echo ""
|
||||
|
||||
# Tester la configuration
|
||||
echo "Test de la configuration PHP-FPM..."
|
||||
if incus exec $CONTAINER -- php-fpm8.3 -t; then
|
||||
echo -e "${GREEN}✓ Configuration valide${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Configuration invalide !${NC}"
|
||||
echo "Restauration de la sauvegarde..."
|
||||
incus exec $CONTAINER -- cp "$BACKUP_FILE" "$POOL_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Redémarrage de PHP-FPM..."
|
||||
incus exec $CONTAINER -- rc-service php-fpm8.3 restart
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PHP-FPM redémarré avec succès${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Erreur lors du redémarrage${NC}"
|
||||
echo "Restauration de la sauvegarde..."
|
||||
incus exec $CONTAINER -- cp "$BACKUP_FILE" "$POOL_FILE"
|
||||
incus exec $CONTAINER -- rc-service php-fpm8.3 restart
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}=== Mise à jour terminée avec succès ===${NC}"
|
||||
echo ""
|
||||
echo "Vérification des nouvelles valeurs:"
|
||||
incus exec $CONTAINER -- grep -E "^(request_terminate_timeout|pm.max_requests|php_admin_value\[memory_limit\])" "$POOL_FILE"
|
||||
echo ""
|
||||
echo "Sauvegarde disponible: $BACKUP_FILE"
|
||||
57
api/scripts/migrations/add_email_queue_fields.sql
Normal file
57
api/scripts/migrations/add_email_queue_fields.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
-- Migration : Ajout des champs manquants dans email_queue
|
||||
-- Date : 2025-01-06
|
||||
-- Description : Ajoute sent_at et error_message pour le bon fonctionnement du CRON
|
||||
|
||||
USE geo_app;
|
||||
|
||||
-- Vérifier si les champs existent déjà avant de les ajouter
|
||||
SET @db_name = DATABASE();
|
||||
SET @table_name = 'email_queue';
|
||||
|
||||
-- Ajouter sent_at si n'existe pas
|
||||
SET @column_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = @table_name
|
||||
AND COLUMN_NAME = 'sent_at'
|
||||
);
|
||||
|
||||
SET @sql = IF(@column_exists = 0,
|
||||
'ALTER TABLE email_queue ADD COLUMN sent_at TIMESTAMP NULL DEFAULT NULL AFTER status',
|
||||
'SELECT "Column sent_at already exists" AS message'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Ajouter error_message si n'existe pas
|
||||
SET @column_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = @table_name
|
||||
AND COLUMN_NAME = 'error_message'
|
||||
);
|
||||
|
||||
SET @sql = IF(@column_exists = 0,
|
||||
'ALTER TABLE email_queue ADD COLUMN error_message TEXT NULL DEFAULT NULL AFTER attempts',
|
||||
'SELECT "Column error_message already exists" AS message'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Vérifier le résultat
|
||||
SELECT
|
||||
'Migration terminée' AS status,
|
||||
COLUMN_NAME,
|
||||
COLUMN_TYPE,
|
||||
IS_NULLABLE,
|
||||
COLUMN_DEFAULT
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db_name
|
||||
AND TABLE_NAME = @table_name
|
||||
AND COLUMN_NAME IN ('sent_at', 'error_message');
|
||||
112
api/scripts/test/generate_receipt_manual.php
Normal file
112
api/scripts/test/generate_receipt_manual.php
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Script de test pour générer manuellement un reçu
|
||||
* Usage: php generate_receipt_manual.php <passage_id>
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Simuler l'environnement web pour AppConfig en CLI
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$_SERVER['SERVER_NAME'] = 'dapp.geosector.fr'; // DEV
|
||||
$_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'];
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chargement de l'environnement
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../../src/Config/AppConfig.php';
|
||||
require_once __DIR__ . '/../../src/Core/Database.php';
|
||||
require_once __DIR__ . '/../../src/Services/LogService.php';
|
||||
require_once __DIR__ . '/../../src/Services/ReceiptService.php';
|
||||
|
||||
// Vérifier qu'un ID de passage est fourni
|
||||
if ($argc < 2) {
|
||||
echo "Usage: php generate_receipt_manual.php <passage_id>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$passageId = (int)$argv[1];
|
||||
|
||||
try {
|
||||
echo "=== Test de génération de reçu ===\n";
|
||||
echo "Passage ID: $passageId\n\n";
|
||||
|
||||
// Initialisation de la configuration
|
||||
$appConfig = AppConfig::getInstance();
|
||||
$dbConfig = $appConfig->getDatabaseConfig();
|
||||
|
||||
// Initialiser la base de données
|
||||
Database::init($dbConfig);
|
||||
$db = Database::getInstance();
|
||||
|
||||
echo "✓ Connexion à la base de données OK\n";
|
||||
|
||||
// Vérifier le passage
|
||||
$stmt = $db->prepare('SELECT id, fk_type, encrypted_email, nom_recu FROM ope_pass WHERE id = ?');
|
||||
$stmt->execute([$passageId]);
|
||||
$passage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$passage) {
|
||||
echo "✗ Passage $passageId non trouvé\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "✓ Passage trouvé\n";
|
||||
echo " - fk_type: " . $passage['fk_type'] . "\n";
|
||||
echo " - encrypted_email: " . (!empty($passage['encrypted_email']) ? 'OUI' : 'NON') . "\n";
|
||||
echo " - nom_recu: " . ($passage['nom_recu'] ?: 'vide') . "\n\n";
|
||||
|
||||
// Déchiffrer l'email
|
||||
if (!empty($passage['encrypted_email'])) {
|
||||
$email = \ApiService::decryptSearchableData($passage['encrypted_email']);
|
||||
echo " - Email déchiffré: $email\n";
|
||||
echo " - Email valide: " . (filter_var($email, FILTER_VALIDATE_EMAIL) ? 'OUI' : 'NON') . "\n\n";
|
||||
} else {
|
||||
echo "✗ Aucun email chiffré trouvé\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Générer le reçu
|
||||
echo "Génération du reçu...\n";
|
||||
$receiptService = new \App\Services\ReceiptService();
|
||||
$result = $receiptService->generateReceiptForPassage($passageId);
|
||||
|
||||
if ($result) {
|
||||
echo "✓ Reçu généré avec succès !\n\n";
|
||||
|
||||
// Vérifier l'email dans la queue
|
||||
$stmt = $db->prepare('SELECT id, to_email, status, created_at FROM email_queue WHERE fk_pass = ? ORDER BY created_at DESC LIMIT 1');
|
||||
$stmt->execute([$passageId]);
|
||||
$queueEmail = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($queueEmail) {
|
||||
echo "✓ Email ajouté à la queue\n";
|
||||
echo " - Queue ID: " . $queueEmail['id'] . "\n";
|
||||
echo " - Destinataire: " . $queueEmail['to_email'] . "\n";
|
||||
echo " - Status: " . $queueEmail['status'] . "\n";
|
||||
echo " - Créé: " . $queueEmail['created_at'] . "\n";
|
||||
} else {
|
||||
echo "✗ Aucun email trouvé dans la queue\n";
|
||||
}
|
||||
} else {
|
||||
echo "✗ Échec de la génération du reçu\n";
|
||||
echo "Consultez /var/www/geosector/api/logs/api.log pour plus de détails\n";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "✗ ERREUR: " . $e->getMessage() . "\n";
|
||||
echo $e->getTraceAsString() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\n=== Fin du test ===\n";
|
||||
exit(0);
|
||||
@@ -63,18 +63,10 @@ class AppConfig {
|
||||
'mapbox' => [
|
||||
'api_key' => '', // À remplir avec la clé API Mapbox
|
||||
],
|
||||
'stripe' => [
|
||||
'public_key_test' => 'pk_test_51QwoVN00pblGEgsXkf8qlXmLGEpxDQcG0KLRpjrGLjJHd7AVZ4Iwd6ChgdjO0w0n3vRqwNCEW8KnHUe5eh3uIlkV00k07kCBmd', // À remplacer par votre clé publique TEST
|
||||
'secret_key_test' => 'sk_test_51QwoVN00pblGEgsXnvqi8qfYpzHtesWWclvK3lzQjPNoHY0dIyOpJmxIkoLqsbmRMEUZpKS5MQ7iFDRlSqVyTo9c006yWetbsd', // À remplacer par votre clé secrète TEST
|
||||
'public_key_live' => 'pk_live_XXXXXXXXXXXX', // À remplacer par votre clé publique LIVE
|
||||
'secret_key_live' => 'sk_live_XXXXXXXXXXXX', // À remplacer par votre clé secrète LIVE
|
||||
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX', // À remplacer après création webhook TEST
|
||||
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX', // À remplacer après création webhook LIVE
|
||||
'api_version' => '2024-06-20',
|
||||
'application_fee_percent' => 0, // Pas de commission plateforme
|
||||
'application_fee_minimum' => 0, // Pas de commission minimum
|
||||
'mode' => 'test', // 'test' ou 'live'
|
||||
],
|
||||
// NOTE : La configuration Stripe est définie par environnement (voir plus bas)
|
||||
// - DEV : Clés TEST Pierre (développement)
|
||||
// - REC : Clés TEST Client (recette)
|
||||
// - PROD : Clés LIVE Client (production)
|
||||
'sms' => [
|
||||
'provider' => 'ovh', // Comme mentionné dans le cahier des charges
|
||||
'api_key' => '', // À remplir avec la clé API SMS OVH
|
||||
@@ -103,6 +95,19 @@ class AppConfig {
|
||||
'username' => 'adr_geo_user',
|
||||
'password' => 'd66,AdrGeo.User',
|
||||
],
|
||||
// Configuration Stripe PRODUCTION - Clés LIVE du CLIENT
|
||||
'stripe' => [
|
||||
'public_key_test' => 'pk_test_XXXXXX', // Non utilisé en PROD
|
||||
'secret_key_test' => 'sk_test_XXXXXX', // Non utilisé en PROD
|
||||
'public_key_live' => 'CLIENT_PK_LIVE_A_REMPLACER', // ← À REMPLACER avec pk_live_...
|
||||
'secret_key_live' => 'CLIENT_SK_LIVE_A_REMPLACER', // ← À REMPLACER avec sk_live_...
|
||||
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX',
|
||||
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX',
|
||||
'api_version' => '2024-06-20',
|
||||
'application_fee_percent' => 0,
|
||||
'application_fee_minimum' => 0,
|
||||
'mode' => 'live', // ← MODE LIVE pour la production
|
||||
],
|
||||
]);
|
||||
|
||||
// Configuration RECETTE
|
||||
@@ -127,7 +132,19 @@ class AppConfig {
|
||||
'username' => 'adr_geo_user',
|
||||
'password' => 'd66,AdrGeoRec.User',
|
||||
],
|
||||
// Vous pouvez remplacer d'autres paramètres spécifiques à l'environnement de recette ici
|
||||
// Configuration Stripe RECETTE - Clés TEST du CLIENT
|
||||
'stripe' => [
|
||||
'public_key_test' => 'pk_test_51S5oMd1tQE0jBEomd1u28D1bUujOcl87ASuGf9xulcz4rY27QfHrLBtQj20MVlWta4AGXsX0YMfeOJFE66AlGlkz00vG30U8Rr',
|
||||
'secret_key_test' => 'sk_test_51S5oMd1tQE0jBEomAhzPBvUcCf0HX9ydK0xq7DagKnidp3JsovbQoVaTj24TKSUPvujQA3PP7IpIS8iWzAd15Rte00TETmbimh',
|
||||
'public_key_live' => 'pk_live_XXXXXX', // Non utilisé en REC
|
||||
'secret_key_live' => 'sk_live_XXXXXX', // Non utilisé en REC
|
||||
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX',
|
||||
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX',
|
||||
'api_version' => '2024-06-20',
|
||||
'application_fee_percent' => 0,
|
||||
'application_fee_minimum' => 0,
|
||||
'mode' => 'test', // ← MODE TEST pour la recette
|
||||
],
|
||||
]);
|
||||
|
||||
// Configuration DÉVELOPPEMENT
|
||||
@@ -152,6 +169,19 @@ class AppConfig {
|
||||
'username' => 'adr_geo_user',
|
||||
'password' => 'd66,AdrGeoDev.User',
|
||||
],
|
||||
// Configuration Stripe DÉVELOPPEMENT - Clés TEST de Pierre (plateforme de test existante)
|
||||
'stripe' => [
|
||||
'public_key_test' => 'pk_test_51QwoVN00pblGEgsXkf8qlXmLGEpxDQcG0KLRpjrGLjJHd7AVZ4Iwd6ChgdjO0w0n3vRqwNCEW8KnHUe5eh3uIlkV00k07kCBmd',
|
||||
'secret_key_test' => 'sk_test_51QwoVN00pblGEgsXnvqi8qfYpzHtesWWclvK3lzQjPNoHY0dIyOpJmxIkoLqsbmRMEUZpKS5MQ7iFDRlSqVyTo9c006yWetbsd',
|
||||
'public_key_live' => 'pk_live_XXXXXX', // Non utilisé en DEV
|
||||
'secret_key_live' => 'sk_live_XXXXXX', // Non utilisé en DEV
|
||||
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX',
|
||||
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX',
|
||||
'api_version' => '2024-06-20',
|
||||
'application_fee_percent' => 0,
|
||||
'application_fee_minimum' => 0,
|
||||
'mode' => 'test', // ← MODE TEST pour le développement
|
||||
],
|
||||
// Vous pouvez activer des fonctionnalités de débogage en développement
|
||||
'debug' => true,
|
||||
]);
|
||||
|
||||
@@ -576,6 +576,43 @@ class PassageController {
|
||||
'operationId' => $operationId
|
||||
]);
|
||||
|
||||
// Enregistrer la génération du reçu dans shutdown_function pour garantir son exécution
|
||||
// Même si le worker FPM est tué après fastcgi_finish_request()
|
||||
$fkType = isset($data['fk_type']) ? (int)$data['fk_type'] : 0;
|
||||
if ($fkType === 1 || $fkType === 5) {
|
||||
// Vérifier si un email a été fourni
|
||||
$hasEmail = false;
|
||||
if (!empty($data['email'])) {
|
||||
$hasEmail = filter_var($data['email'], FILTER_VALIDATE_EMAIL) !== false;
|
||||
} elseif (!empty($encryptedEmail)) {
|
||||
$hasEmail = true;
|
||||
}
|
||||
|
||||
if ($hasEmail) {
|
||||
$capturedPassageId = $passageId; // Capturer pour la closure
|
||||
|
||||
register_shutdown_function(function() use ($capturedPassageId) {
|
||||
try {
|
||||
$receiptService = new \App\Services\ReceiptService();
|
||||
$receiptGenerated = $receiptService->generateReceiptForPassage($capturedPassageId);
|
||||
|
||||
if ($receiptGenerated) {
|
||||
LogService::log('Reçu généré automatiquement pour le passage', [
|
||||
'level' => 'info',
|
||||
'passageId' => $capturedPassageId
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de la génération automatique du reçu', [
|
||||
'level' => 'warning',
|
||||
'error' => $e->getMessage(),
|
||||
'passageId' => $capturedPassageId
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Envoyer la réponse immédiatement pour éviter les timeouts
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
@@ -583,51 +620,19 @@ class PassageController {
|
||||
'passage_id' => $passageId,
|
||||
'receipt_generated' => false // On va générer le reçu en arrière-plan
|
||||
], 201);
|
||||
|
||||
|
||||
// Flush la sortie pour s'assurer que la réponse est envoyée
|
||||
if (ob_get_level()) {
|
||||
ob_end_flush();
|
||||
}
|
||||
flush();
|
||||
|
||||
|
||||
// Fermer la connexion HTTP mais continuer le traitement
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
fastcgi_finish_request();
|
||||
}
|
||||
|
||||
// Générer automatiquement un reçu si c'est un don (fk_type = 1) avec email valide
|
||||
if (isset($data['fk_type']) && (int)$data['fk_type'] === 1) {
|
||||
// Vérifier si un email a été fourni
|
||||
$hasEmail = false;
|
||||
if (!empty($data['email'])) {
|
||||
$hasEmail = filter_var($data['email'], FILTER_VALIDATE_EMAIL) !== false;
|
||||
} elseif (!empty($encryptedEmail)) {
|
||||
// L'email a déjà été validé lors du chiffrement
|
||||
$hasEmail = true;
|
||||
}
|
||||
|
||||
if ($hasEmail) {
|
||||
try {
|
||||
$receiptService = new \App\Services\ReceiptService();
|
||||
$receiptGenerated = $receiptService->generateReceiptForPassage($passageId);
|
||||
|
||||
if ($receiptGenerated) {
|
||||
LogService::log('Reçu généré automatiquement pour le passage', [
|
||||
'level' => 'info',
|
||||
'passageId' => $passageId
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de la génération automatique du reçu', [
|
||||
'level' => 'warning',
|
||||
'error' => $e->getMessage(),
|
||||
'passageId' => $passageId
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return; // Fin de la méthode, éviter d'exécuter le code après
|
||||
|
||||
return; // Fin de la méthode
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de la création du passage', [
|
||||
'level' => 'error',
|
||||
@@ -792,65 +797,72 @@ class PassageController {
|
||||
'passageId' => $passageId
|
||||
]);
|
||||
|
||||
// Enregistrer la génération du reçu dans shutdown_function pour garantir son exécution
|
||||
// Même si le worker FPM est tué après fastcgi_finish_request()
|
||||
$capturedPassageId = $passageId;
|
||||
$capturedDb = $this->db;
|
||||
|
||||
register_shutdown_function(function() use ($capturedPassageId, $capturedDb) {
|
||||
try {
|
||||
// Récupérer les données actualisées du passage
|
||||
$stmt = $capturedDb->prepare('SELECT fk_type, encrypted_email, nom_recu FROM ope_pass WHERE id = ?');
|
||||
$stmt->execute([$capturedPassageId]);
|
||||
$updatedPassage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($updatedPassage) {
|
||||
// Générer un reçu si :
|
||||
// - C'est un don (fk_type = 1) ou un lot (fk_type = 5)
|
||||
// - Il y a un email valide
|
||||
// - Il n'y a pas encore de reçu (nom_recu est vide ou null)
|
||||
$fkType = (int)$updatedPassage['fk_type'];
|
||||
if (($fkType === 1 || $fkType === 5) &&
|
||||
!empty($updatedPassage['encrypted_email']) &&
|
||||
empty($updatedPassage['nom_recu'])) {
|
||||
|
||||
// Vérifier que l'email est valide en le déchiffrant
|
||||
$email = ApiService::decryptSearchableData($updatedPassage['encrypted_email']);
|
||||
|
||||
if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$receiptService = new \App\Services\ReceiptService();
|
||||
$receiptGenerated = $receiptService->generateReceiptForPassage($capturedPassageId);
|
||||
|
||||
if ($receiptGenerated) {
|
||||
LogService::log('Reçu généré automatiquement après mise à jour du passage', [
|
||||
'level' => 'info',
|
||||
'passageId' => $capturedPassageId
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de la génération automatique du reçu après mise à jour', [
|
||||
'level' => 'warning',
|
||||
'error' => $e->getMessage(),
|
||||
'passageId' => $capturedPassageId
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Envoyer la réponse immédiatement pour éviter les timeouts
|
||||
Response::json([
|
||||
'status' => 'success',
|
||||
'message' => 'Passage mis à jour avec succès',
|
||||
'receipt_generated' => false // On va générer le reçu en arrière-plan
|
||||
], 200);
|
||||
|
||||
|
||||
// Flush la sortie pour s'assurer que la réponse est envoyée
|
||||
if (ob_get_level()) {
|
||||
ob_end_flush();
|
||||
}
|
||||
flush();
|
||||
|
||||
|
||||
// Fermer la connexion HTTP mais continuer le traitement
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
fastcgi_finish_request();
|
||||
}
|
||||
|
||||
// Maintenant générer le reçu en arrière-plan après avoir envoyé la réponse
|
||||
try {
|
||||
// Récupérer les données actualisées du passage
|
||||
$stmt = $this->db->prepare('SELECT fk_type, encrypted_email, nom_recu FROM ope_pass WHERE id = ?');
|
||||
$stmt->execute([$passageId]);
|
||||
$updatedPassage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($updatedPassage) {
|
||||
// Générer un reçu si :
|
||||
// - C'est un don (fk_type = 1)
|
||||
// - Il y a un email valide
|
||||
// - Il n'y a pas encore de reçu (nom_recu est vide ou null)
|
||||
if ((int)$updatedPassage['fk_type'] === 1 &&
|
||||
!empty($updatedPassage['encrypted_email']) &&
|
||||
empty($updatedPassage['nom_recu'])) {
|
||||
|
||||
// Vérifier que l'email est valide en le déchiffrant
|
||||
$email = ApiService::decryptSearchableData($updatedPassage['encrypted_email']);
|
||||
|
||||
if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$receiptService = new \App\Services\ReceiptService();
|
||||
$receiptGenerated = $receiptService->generateReceiptForPassage($passageId);
|
||||
|
||||
if ($receiptGenerated) {
|
||||
LogService::log('Reçu généré automatiquement après mise à jour du passage', [
|
||||
'level' => 'info',
|
||||
'passageId' => $passageId
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de la génération automatique du reçu après mise à jour', [
|
||||
'level' => 'warning',
|
||||
'error' => $e->getMessage(),
|
||||
'passageId' => $passageId
|
||||
]);
|
||||
}
|
||||
|
||||
return; // Fin de la méthode, éviter d'exécuter le code après
|
||||
|
||||
return; // Fin de la méthode
|
||||
} catch (Exception $e) {
|
||||
LogService::log('Erreur lors de la mise à jour du passage', [
|
||||
'level' => 'error',
|
||||
|
||||
@@ -51,9 +51,10 @@ class ReceiptService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier que c'est un don effectué (fk_type = 1) avec email valide
|
||||
if ((int)$passageData['fk_type'] !== 1) {
|
||||
return false; // Pas un don, pas de reçu
|
||||
// Vérifier que c'est un don effectué (fk_type = 1) ou un lot (fk_type = 5) avec email valide
|
||||
$fkType = (int)$passageData['fk_type'];
|
||||
if ($fkType !== 1 && $fkType !== 5) {
|
||||
return false; // Ni don ni lot, pas de reçu
|
||||
}
|
||||
|
||||
// Déchiffrer et vérifier l'email
|
||||
|
||||
Reference in New Issue
Block a user