feat: Version 3.6.2 - Correctifs tâches #17-20
- #17: Amélioration gestion des secteurs et statistiques - #18: Optimisation services API et logs - #19: Corrections Flutter widgets et repositories - #20: Fix création passage - détection automatique ope_users.id vs users.id Suppression dossier web/ (migration vers app Flutter) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,30 +94,7 @@ Ce dossier contient les scripts automatisés de maintenance et de traitement pou
|
||||
|
||||
---
|
||||
|
||||
### 5. `update_stripe_devices.php`
|
||||
|
||||
**Fonction** : Met à jour la liste des appareils Android certifiés pour Tap to Pay
|
||||
|
||||
**Caractéristiques** :
|
||||
|
||||
- Liste de 95+ devices intégrée
|
||||
- Ajoute les nouveaux appareils certifiés
|
||||
- Met à jour les versions Android minimales
|
||||
- Désactive les appareils obsolètes
|
||||
- Notification email si changements importants
|
||||
- Possibilité de personnaliser via `/data/stripe_certified_devices.json`
|
||||
|
||||
**Fréquence recommandée** : Hebdomadaire le dimanche à 3h
|
||||
|
||||
**Ligne crontab** :
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. `sync_databases.php`
|
||||
### 5. `sync_databases.php`
|
||||
|
||||
**Fonction** : Synchronise les bases de données entre environnements
|
||||
|
||||
@@ -175,9 +152,6 @@ crontab -e
|
||||
|
||||
# Rotation des logs événements (mensuel le 1er à 3h)
|
||||
0 3 1 * * /usr/bin/php /var/www/geosector/api/scripts/cron/rotate_event_logs.php >> /var/www/geosector/api/logs/rotation_events.log 2>&1
|
||||
|
||||
# Mise à jour des devices Stripe (hebdomadaire dimanche à 3h)
|
||||
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
|
||||
```
|
||||
|
||||
### 4. Vérifier que les CRONs sont actifs
|
||||
@@ -203,7 +177,6 @@ Tous les logs CRON sont stockés dans `/var/www/geosector/api/logs/` :
|
||||
- `cleanup_security.log` : Nettoyage des données de sécurité
|
||||
- `cleanup_logs.log` : Nettoyage des anciens fichiers logs
|
||||
- `rotation_events.log` : Rotation des logs événements JSONL
|
||||
- `stripe_devices.log` : Mise à jour des devices Tap to Pay
|
||||
|
||||
### Vérification de l'exécution
|
||||
|
||||
@@ -216,9 +189,6 @@ tail -n 50 /var/www/geosector/api/logs/cleanup_logs.log
|
||||
|
||||
# Voir les dernières rotations des logs événements
|
||||
tail -n 50 /var/www/geosector/api/logs/rotation_events.log
|
||||
|
||||
# Voir les dernières mises à jour Stripe
|
||||
tail -n 50 /var/www/geosector/api/logs/stripe_devices.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
456
api/scripts/cron/aggregate_event_stats.php
Executable file
456
api/scripts/cron/aggregate_event_stats.php
Executable file
@@ -0,0 +1,456 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Script CRON pour agrégation des statistiques d'événements
|
||||
*
|
||||
* Parse les fichiers JSONL et agrège les données dans event_stats_daily
|
||||
*
|
||||
* Usage:
|
||||
* php aggregate_event_stats.php # Agrège J-1
|
||||
* php aggregate_event_stats.php --date=2025-12-20 # Agrège une date spécifique
|
||||
* php aggregate_event_stats.php --from=2025-12-01 --to=2025-12-21 # Rattrapage plage
|
||||
*
|
||||
* À exécuter quotidiennement via crontab (1h du matin) :
|
||||
* 0 1 * * * /usr/bin/php /var/www/geosector/api/scripts/cron/aggregate_event_stats.php >> /var/www/geosector/api/logs/aggregate_stats.log 2>&1
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Configuration
|
||||
define('LOCK_FILE', '/tmp/aggregate_event_stats.lock');
|
||||
define('EVENT_LOG_DIR', __DIR__ . '/../../logs/events');
|
||||
|
||||
// Empêcher l'exécution multiple simultanée
|
||||
if (file_exists(LOCK_FILE)) {
|
||||
$lockTime = filemtime(LOCK_FILE);
|
||||
if (time() - $lockTime > 3600) {
|
||||
unlink(LOCK_FILE);
|
||||
} else {
|
||||
die("[" . date('Y-m-d H:i:s') . "] Le processus est déjà en cours d'exécution\n");
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents(LOCK_FILE, (string) getmypid());
|
||||
|
||||
register_shutdown_function(function () {
|
||||
if (file_exists(LOCK_FILE)) {
|
||||
unlink(LOCK_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
// Simuler l'environnement web pour AppConfig en CLI
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$hostname = gethostname();
|
||||
if (strpos($hostname, 'pra') !== false) {
|
||||
$_SERVER['SERVER_NAME'] = 'app3.geosector.fr';
|
||||
} elseif (strpos($hostname, 'rca') !== false) {
|
||||
$_SERVER['SERVER_NAME'] = 'rapp.geosector.fr';
|
||||
} else {
|
||||
$_SERVER['SERVER_NAME'] = 'dapp.geosector.fr';
|
||||
}
|
||||
|
||||
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'];
|
||||
$_SERVER['REMOTE_ADDR'] = $_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';
|
||||
|
||||
use App\Services\LogService;
|
||||
|
||||
/**
|
||||
* Parse les arguments CLI
|
||||
*/
|
||||
function parseArgs(array $argv): array
|
||||
{
|
||||
$args = [
|
||||
'date' => null,
|
||||
'from' => null,
|
||||
'to' => null,
|
||||
];
|
||||
|
||||
foreach ($argv as $arg) {
|
||||
if (strpos($arg, '--date=') === 0) {
|
||||
$args['date'] = substr($arg, 7);
|
||||
} elseif (strpos($arg, '--from=') === 0) {
|
||||
$args['from'] = substr($arg, 7);
|
||||
} elseif (strpos($arg, '--to=') === 0) {
|
||||
$args['to'] = substr($arg, 5);
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère la liste des dates à traiter
|
||||
*/
|
||||
function getDatesToProcess(array $args): array
|
||||
{
|
||||
$dates = [];
|
||||
|
||||
if ($args['date']) {
|
||||
$dates[] = $args['date'];
|
||||
} elseif ($args['from'] && $args['to']) {
|
||||
$current = new DateTime($args['from']);
|
||||
$end = new DateTime($args['to']);
|
||||
while ($current <= $end) {
|
||||
$dates[] = $current->format('Y-m-d');
|
||||
$current->modify('+1 day');
|
||||
}
|
||||
} else {
|
||||
// Par défaut : J-1
|
||||
$dates[] = date('Y-m-d', strtotime('-1 day'));
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse un fichier JSONL et retourne les événements
|
||||
*/
|
||||
function parseJsonlFile(string $filePath): array
|
||||
{
|
||||
$events = [];
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return $events;
|
||||
}
|
||||
|
||||
$handle = fopen($filePath, 'r');
|
||||
if (!$handle) {
|
||||
return $events;
|
||||
}
|
||||
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$event = json_decode($line, true);
|
||||
if ($event && isset($event['event'])) {
|
||||
$events[] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrège les événements par entity_id et event_type
|
||||
*/
|
||||
function aggregateEvents(array $events): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
foreach ($events as $event) {
|
||||
$entityId = $event['entity_id'] ?? null;
|
||||
$eventType = $event['event'] ?? 'unknown';
|
||||
$userId = $event['user_id'] ?? null;
|
||||
|
||||
// Clé d'agrégation : entity_id peut être NULL (stats globales)
|
||||
$key = ($entityId ?? 'NULL') . '|' . $eventType;
|
||||
|
||||
if (!isset($stats[$key])) {
|
||||
$stats[$key] = [
|
||||
'entity_id' => $entityId,
|
||||
'event_type' => $eventType,
|
||||
'count' => 0,
|
||||
'sum_amount' => 0.0,
|
||||
'user_ids' => [],
|
||||
'metadata' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$stats[$key]['count']++;
|
||||
|
||||
// Collecter les user_ids pour unique_users
|
||||
if ($userId !== null) {
|
||||
$stats[$key]['user_ids'][$userId] = true;
|
||||
}
|
||||
|
||||
// Somme des montants pour les passages et paiements Stripe
|
||||
if (in_array($eventType, ['passage_created', 'passage_updated'])) {
|
||||
$amount = $event['amount'] ?? 0;
|
||||
$stats[$key]['sum_amount'] += (float) $amount;
|
||||
} elseif (in_array($eventType, ['stripe_payment_success', 'stripe_payment_created'])) {
|
||||
// Montant en centimes -> euros
|
||||
$amount = ($event['amount'] ?? 0) / 100;
|
||||
$stats[$key]['sum_amount'] += (float) $amount;
|
||||
}
|
||||
|
||||
// Collecter metadata spécifiques
|
||||
collectMetadata($stats[$key], $event);
|
||||
}
|
||||
|
||||
// Convertir user_ids en count
|
||||
foreach ($stats as &$stat) {
|
||||
$stat['unique_users'] = count($stat['user_ids']);
|
||||
unset($stat['user_ids']);
|
||||
|
||||
// Finaliser les metadata
|
||||
$stat['metadata'] = finalizeMetadata($stat['metadata'], $stat['event_type']);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collecte les métadonnées spécifiques par type d'événement
|
||||
*/
|
||||
function collectMetadata(array &$stat, array $event): void
|
||||
{
|
||||
$eventType = $event['event'] ?? '';
|
||||
|
||||
switch ($eventType) {
|
||||
case 'login_failed':
|
||||
$reason = $event['reason'] ?? 'unknown';
|
||||
$stat['metadata']['reasons'][$reason] = ($stat['metadata']['reasons'][$reason] ?? 0) + 1;
|
||||
break;
|
||||
|
||||
case 'passage_created':
|
||||
$sectorId = $event['sector_id'] ?? null;
|
||||
if ($sectorId) {
|
||||
$stat['metadata']['sectors'][$sectorId] = ($stat['metadata']['sectors'][$sectorId] ?? 0) + 1;
|
||||
}
|
||||
$paymentType = $event['payment_type'] ?? 'unknown';
|
||||
$stat['metadata']['payment_types'][$paymentType] = ($stat['metadata']['payment_types'][$paymentType] ?? 0) + 1;
|
||||
break;
|
||||
|
||||
case 'stripe_payment_failed':
|
||||
$errorCode = $event['error_code'] ?? 'unknown';
|
||||
$stat['metadata']['error_codes'][$errorCode] = ($stat['metadata']['error_codes'][$errorCode] ?? 0) + 1;
|
||||
break;
|
||||
|
||||
case 'stripe_terminal_error':
|
||||
$errorCode = $event['error_code'] ?? 'unknown';
|
||||
$stat['metadata']['error_codes'][$errorCode] = ($stat['metadata']['error_codes'][$errorCode] ?? 0) + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalise les métadonnées (top 5, tri, etc.)
|
||||
*/
|
||||
function finalizeMetadata(array $metadata, string $eventType): ?array
|
||||
{
|
||||
if (empty($metadata)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
// Top 5 secteurs
|
||||
if (isset($metadata['sectors'])) {
|
||||
arsort($metadata['sectors']);
|
||||
$result['top_sectors'] = array_slice($metadata['sectors'], 0, 5, true);
|
||||
}
|
||||
|
||||
// Raisons d'échec login
|
||||
if (isset($metadata['reasons'])) {
|
||||
arsort($metadata['reasons']);
|
||||
$result['failure_reasons'] = $metadata['reasons'];
|
||||
}
|
||||
|
||||
// Types de paiement
|
||||
if (isset($metadata['payment_types'])) {
|
||||
arsort($metadata['payment_types']);
|
||||
$result['payment_types'] = $metadata['payment_types'];
|
||||
}
|
||||
|
||||
// Codes d'erreur
|
||||
if (isset($metadata['error_codes'])) {
|
||||
arsort($metadata['error_codes']);
|
||||
$result['error_codes'] = $metadata['error_codes'];
|
||||
}
|
||||
|
||||
return empty($result) ? null : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insère ou met à jour les stats dans la base de données
|
||||
*/
|
||||
function upsertStats(PDO $db, string $date, array $stats): int
|
||||
{
|
||||
$upsertedCount = 0;
|
||||
|
||||
$sql = "
|
||||
INSERT INTO event_stats_daily
|
||||
(stat_date, entity_id, event_type, count, sum_amount, unique_users, metadata)
|
||||
VALUES
|
||||
(:stat_date, :entity_id, :event_type, :count, :sum_amount, :unique_users, :metadata)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
count = VALUES(count),
|
||||
sum_amount = VALUES(sum_amount),
|
||||
unique_users = VALUES(unique_users),
|
||||
metadata = VALUES(metadata),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
";
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
foreach ($stats as $stat) {
|
||||
try {
|
||||
$stmt->execute([
|
||||
'stat_date' => $date,
|
||||
'entity_id' => $stat['entity_id'],
|
||||
'event_type' => $stat['event_type'],
|
||||
'count' => $stat['count'],
|
||||
'sum_amount' => $stat['sum_amount'],
|
||||
'unique_users' => $stat['unique_users'],
|
||||
'metadata' => $stat['metadata'] ? json_encode($stat['metadata'], JSON_UNESCAPED_UNICODE) : null,
|
||||
]);
|
||||
$upsertedCount++;
|
||||
} catch (PDOException $e) {
|
||||
echo " ERREUR insertion {$stat['event_type']}: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $upsertedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère également les stats globales (entity_id = NULL)
|
||||
*/
|
||||
function generateGlobalStats(array $statsByEntity): array
|
||||
{
|
||||
$globalStats = [];
|
||||
|
||||
foreach ($statsByEntity as $stat) {
|
||||
$eventType = $stat['event_type'];
|
||||
|
||||
if (!isset($globalStats[$eventType])) {
|
||||
$globalStats[$eventType] = [
|
||||
'entity_id' => null,
|
||||
'event_type' => $eventType,
|
||||
'count' => 0,
|
||||
'sum_amount' => 0.0,
|
||||
'unique_users' => 0,
|
||||
'metadata' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$globalStats[$eventType]['count'] += $stat['count'];
|
||||
$globalStats[$eventType]['sum_amount'] += $stat['sum_amount'];
|
||||
$globalStats[$eventType]['unique_users'] += $stat['unique_users'];
|
||||
}
|
||||
|
||||
return array_values($globalStats);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MAIN
|
||||
// ============================================================
|
||||
|
||||
try {
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Démarrage de l'agrégation des statistiques\n";
|
||||
|
||||
// Initialisation
|
||||
$appConfig = AppConfig::getInstance();
|
||||
$config = $appConfig->getFullConfig();
|
||||
$environment = $appConfig->getEnvironment();
|
||||
|
||||
echo "Environnement: {$environment}\n";
|
||||
|
||||
Database::init($config['database']);
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Parser les arguments
|
||||
$args = parseArgs($argv);
|
||||
$dates = getDatesToProcess($args);
|
||||
|
||||
echo "Dates à traiter: " . implode(', ', $dates) . "\n\n";
|
||||
|
||||
$totalStats = 0;
|
||||
$totalEvents = 0;
|
||||
|
||||
foreach ($dates as $date) {
|
||||
$jsonlFile = EVENT_LOG_DIR . '/' . $date . '.jsonl';
|
||||
|
||||
echo "--- Traitement de {$date} ---\n";
|
||||
|
||||
if (!file_exists($jsonlFile)) {
|
||||
echo " Fichier non trouvé: {$jsonlFile}\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$fileSize = filesize($jsonlFile);
|
||||
echo " Fichier: " . basename($jsonlFile) . " (" . number_format($fileSize / 1024, 2) . " KB)\n";
|
||||
|
||||
// Parser le fichier
|
||||
$events = parseJsonlFile($jsonlFile);
|
||||
$eventCount = count($events);
|
||||
echo " Événements parsés: {$eventCount}\n";
|
||||
|
||||
if ($eventCount === 0) {
|
||||
echo " Aucun événement à agréger\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalEvents += $eventCount;
|
||||
|
||||
// Agréger par entity/event_type
|
||||
$stats = aggregateEvents($events);
|
||||
echo " Agrégations par entité: " . count($stats) . "\n";
|
||||
|
||||
// Générer les stats globales
|
||||
$globalStats = generateGlobalStats($stats);
|
||||
echo " Agrégations globales: " . count($globalStats) . "\n";
|
||||
|
||||
// Fusionner stats entités + globales
|
||||
$allStats = array_merge(array_values($stats), $globalStats);
|
||||
|
||||
// Insérer en base
|
||||
$upserted = upsertStats($db, $date, $allStats);
|
||||
echo " Stats insérées/mises à jour: {$upserted}\n";
|
||||
|
||||
$totalStats += $upserted;
|
||||
}
|
||||
|
||||
// Résumé
|
||||
echo "\n=== RÉSUMÉ ===\n";
|
||||
echo "Dates traitées: " . count($dates) . "\n";
|
||||
echo "Événements traités: {$totalEvents}\n";
|
||||
echo "Stats agrégées: {$totalStats}\n";
|
||||
|
||||
// Log
|
||||
LogService::log('Agrégation des statistiques terminée', [
|
||||
'level' => 'info',
|
||||
'script' => 'aggregate_event_stats.php',
|
||||
'environment' => $environment,
|
||||
'dates_count' => count($dates),
|
||||
'events_count' => $totalEvents,
|
||||
'stats_count' => $totalStats,
|
||||
]);
|
||||
|
||||
echo "\n[" . date('Y-m-d H:i:s') . "] Agrégation terminée avec succès\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
$errorMsg = 'Erreur lors de l\'agrégation: ' . $e->getMessage();
|
||||
|
||||
LogService::log($errorMsg, [
|
||||
'level' => 'error',
|
||||
'script' => 'aggregate_event_stats.php',
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
echo "\n❌ ERREUR: {$errorMsg}\n";
|
||||
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
@@ -1,444 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Script CRON pour mettre à jour la liste des appareils certifiés Stripe Tap to Pay
|
||||
*
|
||||
* Ce script récupère et met à jour la liste des appareils Android certifiés
|
||||
* pour Tap to Pay en France dans la table stripe_android_certified_devices
|
||||
*
|
||||
* À exécuter hebdomadairement via crontab :
|
||||
* Exemple: 0 3 * * 0 /usr/bin/php /path/to/api/scripts/cron/update_stripe_devices.php
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Configuration
|
||||
define('LOCK_FILE', '/tmp/update_stripe_devices.lock');
|
||||
define('DEVICES_JSON_URL', 'https://raw.githubusercontent.com/stripe/stripe-terminal-android/master/tap-to-pay/certified-devices.json');
|
||||
define('DEVICES_LOCAL_FILE', __DIR__ . '/../../data/stripe_certified_devices.json');
|
||||
|
||||
// Empêcher l'exécution multiple simultanée
|
||||
if (file_exists(LOCK_FILE)) {
|
||||
$lockTime = filemtime(LOCK_FILE);
|
||||
if (time() - $lockTime > 3600) { // Lock de plus d'1 heure = processus bloqué
|
||||
unlink(LOCK_FILE);
|
||||
} else {
|
||||
die("[" . date('Y-m-d H:i:s') . "] Le processus est déjà en cours d'exécution\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Créer le fichier de lock
|
||||
file_put_contents(LOCK_FILE, getmypid());
|
||||
|
||||
// Enregistrer un handler pour supprimer le lock en cas d'arrêt
|
||||
register_shutdown_function(function() {
|
||||
if (file_exists(LOCK_FILE)) {
|
||||
unlink(LOCK_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
// Simuler l'environnement web pour AppConfig en CLI
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$hostname = gethostname();
|
||||
if (strpos($hostname, 'prod') !== false || strpos($hostname, 'pra') !== false) {
|
||||
$_SERVER['SERVER_NAME'] = 'app3.geosector.fr';
|
||||
} elseif (strpos($hostname, 'rec') !== false || strpos($hostname, 'rca') !== false) {
|
||||
$_SERVER['SERVER_NAME'] = 'rapp.geosector.fr';
|
||||
} else {
|
||||
$_SERVER['SERVER_NAME'] = 'dapp.geosector.fr'; // DVA par défaut
|
||||
}
|
||||
$_SERVER['REQUEST_URI'] = '/cron/update_stripe_devices';
|
||||
$_SERVER['REQUEST_METHOD'] = 'CLI';
|
||||
$_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'];
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
|
||||
// Définir getallheaders si elle n'existe pas (CLI)
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Charger l'environnement
|
||||
require_once dirname(dirname(__DIR__)) . '/vendor/autoload.php';
|
||||
require_once dirname(dirname(__DIR__)) . '/src/Config/AppConfig.php';
|
||||
require_once dirname(dirname(__DIR__)) . '/src/Core/Database.php';
|
||||
require_once dirname(dirname(__DIR__)) . '/src/Services/LogService.php';
|
||||
|
||||
use App\Services\LogService;
|
||||
|
||||
try {
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Début de la mise à jour des devices Stripe certifiés\n";
|
||||
|
||||
// Initialiser la configuration et la base de données
|
||||
$appConfig = AppConfig::getInstance();
|
||||
$dbConfig = $appConfig->getDatabaseConfig();
|
||||
Database::init($dbConfig);
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Logger le début
|
||||
LogService::log("Début de la mise à jour des devices Stripe certifiés", [
|
||||
'source' => 'cron',
|
||||
'script' => 'update_stripe_devices.php'
|
||||
]);
|
||||
|
||||
// Étape 1: Récupérer la liste des devices
|
||||
$devicesData = fetchCertifiedDevices();
|
||||
|
||||
if (empty($devicesData)) {
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Aucune donnée de devices récupérée\n";
|
||||
LogService::log("Aucune donnée de devices récupérée", ['level' => 'warning']);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Étape 2: Traiter et mettre à jour la base de données
|
||||
$stats = updateDatabase($db, $devicesData);
|
||||
|
||||
// Étape 3: Logger les résultats
|
||||
$message = sprintf(
|
||||
"Mise à jour terminée : %d ajoutés, %d modifiés, %d désactivés, %d inchangés",
|
||||
$stats['added'],
|
||||
$stats['updated'],
|
||||
$stats['disabled'],
|
||||
$stats['unchanged']
|
||||
);
|
||||
|
||||
echo "[" . date('Y-m-d H:i:s') . "] $message\n";
|
||||
|
||||
LogService::log($message, [
|
||||
'source' => 'cron',
|
||||
'stats' => $stats
|
||||
]);
|
||||
|
||||
// Étape 4: Envoyer une notification si changements significatifs
|
||||
if ($stats['added'] > 0 || $stats['disabled'] > 0) {
|
||||
sendNotification($stats);
|
||||
}
|
||||
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Mise à jour terminée avec succès\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
$errorMsg = "Erreur lors de la mise à jour des devices: " . $e->getMessage();
|
||||
echo "[" . date('Y-m-d H:i:s') . "] $errorMsg\n";
|
||||
LogService::log($errorMsg, [
|
||||
'level' => 'error',
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la liste des devices certifiés
|
||||
* Essaie d'abord depuis une URL externe, puis depuis un fichier local en fallback
|
||||
*/
|
||||
function fetchCertifiedDevices(): array {
|
||||
// Liste maintenue manuellement des devices certifiés en France
|
||||
// Source: Documentation Stripe Terminal et tests confirmés
|
||||
$frenchCertifiedDevices = [
|
||||
// Samsung Galaxy S Series
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S21', 'model_identifier' => 'SM-G991B', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S21+', 'model_identifier' => 'SM-G996B', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S21 Ultra', 'model_identifier' => 'SM-G998B', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S21 FE', 'model_identifier' => 'SM-G990B', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S22', 'model_identifier' => 'SM-S901B', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S22+', 'model_identifier' => 'SM-S906B', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S22 Ultra', 'model_identifier' => 'SM-S908B', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S23', 'model_identifier' => 'SM-S911B', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S23+', 'model_identifier' => 'SM-S916B', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S23 Ultra', 'model_identifier' => 'SM-S918B', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S23 FE', 'model_identifier' => 'SM-S711B', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S24', 'model_identifier' => 'SM-S921B', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S24+', 'model_identifier' => 'SM-S926B', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy S24 Ultra', 'model_identifier' => 'SM-S928B', 'min_android_version' => 14],
|
||||
|
||||
// Samsung Galaxy Note
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Note 20', 'model_identifier' => 'SM-N980F', 'min_android_version' => 10],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Note 20 Ultra', 'model_identifier' => 'SM-N986B', 'min_android_version' => 10],
|
||||
|
||||
// Samsung Galaxy Z Fold
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Fold3', 'model_identifier' => 'SM-F926B', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Fold4', 'model_identifier' => 'SM-F936B', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Fold5', 'model_identifier' => 'SM-F946B', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Fold6', 'model_identifier' => 'SM-F956B', 'min_android_version' => 14],
|
||||
|
||||
// Samsung Galaxy Z Flip
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Flip3', 'model_identifier' => 'SM-F711B', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Flip4', 'model_identifier' => 'SM-F721B', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Flip5', 'model_identifier' => 'SM-F731B', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy Z Flip6', 'model_identifier' => 'SM-F741B', 'min_android_version' => 14],
|
||||
|
||||
// Samsung Galaxy A Series (haut de gamme)
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy A54', 'model_identifier' => 'SM-A546B', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Samsung', 'model' => 'Galaxy A73', 'model_identifier' => 'SM-A736B', 'min_android_version' => 12],
|
||||
|
||||
// Google Pixel
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 6', 'model_identifier' => 'oriole', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 6 Pro', 'model_identifier' => 'raven', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 6a', 'model_identifier' => 'bluejay', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 7', 'model_identifier' => 'panther', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 7 Pro', 'model_identifier' => 'cheetah', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 7a', 'model_identifier' => 'lynx', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 8', 'model_identifier' => 'shiba', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 8 Pro', 'model_identifier' => 'husky', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 8a', 'model_identifier' => 'akita', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 9', 'model_identifier' => 'tokay', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 9 Pro', 'model_identifier' => 'caiman', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel 9 Pro XL', 'model_identifier' => 'komodo', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel Fold', 'model_identifier' => 'felix', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Google', 'model' => 'Pixel Tablet', 'model_identifier' => 'tangorpro', 'min_android_version' => 13],
|
||||
|
||||
// OnePlus
|
||||
['manufacturer' => 'OnePlus', 'model' => '9', 'model_identifier' => 'LE2113', 'min_android_version' => 11],
|
||||
['manufacturer' => 'OnePlus', 'model' => '9 Pro', 'model_identifier' => 'LE2123', 'min_android_version' => 11],
|
||||
['manufacturer' => 'OnePlus', 'model' => '10 Pro', 'model_identifier' => 'NE2213', 'min_android_version' => 12],
|
||||
['manufacturer' => 'OnePlus', 'model' => '10T', 'model_identifier' => 'CPH2413', 'min_android_version' => 12],
|
||||
['manufacturer' => 'OnePlus', 'model' => '11', 'model_identifier' => 'CPH2449', 'min_android_version' => 13],
|
||||
['manufacturer' => 'OnePlus', 'model' => '11R', 'model_identifier' => 'CPH2487', 'min_android_version' => 13],
|
||||
['manufacturer' => 'OnePlus', 'model' => '12', 'model_identifier' => 'CPH2581', 'min_android_version' => 14],
|
||||
['manufacturer' => 'OnePlus', 'model' => '12R', 'model_identifier' => 'CPH2585', 'min_android_version' => 14],
|
||||
['manufacturer' => 'OnePlus', 'model' => 'Open', 'model_identifier' => 'CPH2551', 'min_android_version' => 13],
|
||||
|
||||
// Xiaomi
|
||||
['manufacturer' => 'Xiaomi', 'model' => 'Mi 11', 'model_identifier' => 'M2011K2G', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Xiaomi', 'model' => 'Mi 11 Ultra', 'model_identifier' => 'M2102K1G', 'min_android_version' => 11],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '12', 'model_identifier' => '2201123G', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '12 Pro', 'model_identifier' => '2201122G', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '12T Pro', 'model_identifier' => '2207122MC', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '13', 'model_identifier' => '2211133G', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '13 Pro', 'model_identifier' => '2210132G', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '13T Pro', 'model_identifier' => '23078PND5G', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '14', 'model_identifier' => '23127PN0CG', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '14 Pro', 'model_identifier' => '23116PN5BG', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Xiaomi', 'model' => '14 Ultra', 'model_identifier' => '24030PN60G', 'min_android_version' => 14],
|
||||
|
||||
// OPPO
|
||||
['manufacturer' => 'OPPO', 'model' => 'Find X3 Pro', 'model_identifier' => 'CPH2173', 'min_android_version' => 11],
|
||||
['manufacturer' => 'OPPO', 'model' => 'Find X5 Pro', 'model_identifier' => 'CPH2305', 'min_android_version' => 12],
|
||||
['manufacturer' => 'OPPO', 'model' => 'Find X6 Pro', 'model_identifier' => 'CPH2449', 'min_android_version' => 13],
|
||||
['manufacturer' => 'OPPO', 'model' => 'Find N2', 'model_identifier' => 'CPH2399', 'min_android_version' => 13],
|
||||
['manufacturer' => 'OPPO', 'model' => 'Find N3', 'model_identifier' => 'CPH2499', 'min_android_version' => 13],
|
||||
|
||||
// Realme
|
||||
['manufacturer' => 'Realme', 'model' => 'GT 2 Pro', 'model_identifier' => 'RMX3301', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Realme', 'model' => 'GT 3', 'model_identifier' => 'RMX3709', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Realme', 'model' => 'GT 5 Pro', 'model_identifier' => 'RMX3888', 'min_android_version' => 14],
|
||||
|
||||
// Honor
|
||||
['manufacturer' => 'Honor', 'model' => 'Magic5 Pro', 'model_identifier' => 'PGT-N19', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Honor', 'model' => 'Magic6 Pro', 'model_identifier' => 'BVL-N49', 'min_android_version' => 14],
|
||||
['manufacturer' => 'Honor', 'model' => '90', 'model_identifier' => 'REA-NX9', 'min_android_version' => 13],
|
||||
|
||||
// ASUS
|
||||
['manufacturer' => 'ASUS', 'model' => 'Zenfone 9', 'model_identifier' => 'AI2202', 'min_android_version' => 12],
|
||||
['manufacturer' => 'ASUS', 'model' => 'Zenfone 10', 'model_identifier' => 'AI2302', 'min_android_version' => 13],
|
||||
['manufacturer' => 'ASUS', 'model' => 'ROG Phone 7', 'model_identifier' => 'AI2205', 'min_android_version' => 13],
|
||||
|
||||
// Nothing
|
||||
['manufacturer' => 'Nothing', 'model' => 'Phone (1)', 'model_identifier' => 'A063', 'min_android_version' => 12],
|
||||
['manufacturer' => 'Nothing', 'model' => 'Phone (2)', 'model_identifier' => 'A065', 'min_android_version' => 13],
|
||||
['manufacturer' => 'Nothing', 'model' => 'Phone (2a)', 'model_identifier' => 'A142', 'min_android_version' => 14],
|
||||
];
|
||||
|
||||
// Essayer de charger depuis un fichier JSON local si présent
|
||||
if (file_exists(DEVICES_LOCAL_FILE)) {
|
||||
$localData = json_decode(file_get_contents(DEVICES_LOCAL_FILE), true);
|
||||
if (!empty($localData)) {
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Données chargées depuis le fichier local\n";
|
||||
return array_merge($frenchCertifiedDevices, $localData);
|
||||
}
|
||||
}
|
||||
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Utilisation de la liste intégrée des devices certifiés\n";
|
||||
return $frenchCertifiedDevices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la base de données avec les nouvelles données
|
||||
*/
|
||||
function updateDatabase($db, array $devices): array {
|
||||
$stats = [
|
||||
'added' => 0,
|
||||
'updated' => 0,
|
||||
'disabled' => 0,
|
||||
'unchanged' => 0,
|
||||
'total' => 0
|
||||
];
|
||||
|
||||
// Récupérer tous les devices existants
|
||||
$stmt = $db->prepare("SELECT * FROM stripe_android_certified_devices WHERE country = 'FR'");
|
||||
$stmt->execute();
|
||||
$existingDevices = [];
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$key = $row['manufacturer'] . '|' . $row['model'] . '|' . $row['model_identifier'];
|
||||
$existingDevices[$key] = $row;
|
||||
}
|
||||
|
||||
// Marquer tous les devices pour tracking
|
||||
$processedKeys = [];
|
||||
|
||||
// Traiter chaque device de la nouvelle liste
|
||||
foreach ($devices as $device) {
|
||||
$key = $device['manufacturer'] . '|' . $device['model'] . '|' . $device['model_identifier'];
|
||||
$processedKeys[$key] = true;
|
||||
|
||||
if (isset($existingDevices[$key])) {
|
||||
// Le device existe, vérifier s'il faut le mettre à jour
|
||||
$existing = $existingDevices[$key];
|
||||
|
||||
// Vérifier si des champs ont changé
|
||||
$needsUpdate = false;
|
||||
if ($existing['min_android_version'] != $device['min_android_version']) {
|
||||
$needsUpdate = true;
|
||||
}
|
||||
if ($existing['tap_to_pay_certified'] != 1) {
|
||||
$needsUpdate = true;
|
||||
}
|
||||
|
||||
if ($needsUpdate) {
|
||||
$stmt = $db->prepare("
|
||||
UPDATE stripe_android_certified_devices
|
||||
SET min_android_version = :min_version,
|
||||
tap_to_pay_certified = 1,
|
||||
last_verified = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE manufacturer = :manufacturer
|
||||
AND model = :model
|
||||
AND model_identifier = :model_identifier
|
||||
AND country = 'FR'
|
||||
");
|
||||
$stmt->execute([
|
||||
'min_version' => $device['min_android_version'],
|
||||
'manufacturer' => $device['manufacturer'],
|
||||
'model' => $device['model'],
|
||||
'model_identifier' => $device['model_identifier']
|
||||
]);
|
||||
$stats['updated']++;
|
||||
|
||||
LogService::log("Device mis à jour", [
|
||||
'device' => $device['manufacturer'] . ' ' . $device['model']
|
||||
]);
|
||||
} else {
|
||||
// Juste mettre à jour last_verified
|
||||
$stmt = $db->prepare("
|
||||
UPDATE stripe_android_certified_devices
|
||||
SET last_verified = NOW()
|
||||
WHERE manufacturer = :manufacturer
|
||||
AND model = :model
|
||||
AND model_identifier = :model_identifier
|
||||
AND country = 'FR'
|
||||
");
|
||||
$stmt->execute([
|
||||
'manufacturer' => $device['manufacturer'],
|
||||
'model' => $device['model'],
|
||||
'model_identifier' => $device['model_identifier']
|
||||
]);
|
||||
$stats['unchanged']++;
|
||||
}
|
||||
} else {
|
||||
// Nouveau device, l'ajouter
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO stripe_android_certified_devices
|
||||
(manufacturer, model, model_identifier, tap_to_pay_certified,
|
||||
certification_date, min_android_version, country, notes, last_verified)
|
||||
VALUES
|
||||
(:manufacturer, :model, :model_identifier, 1,
|
||||
NOW(), :min_version, 'FR', 'Ajouté automatiquement via CRON', NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
'manufacturer' => $device['manufacturer'],
|
||||
'model' => $device['model'],
|
||||
'model_identifier' => $device['model_identifier'],
|
||||
'min_version' => $device['min_android_version']
|
||||
]);
|
||||
$stats['added']++;
|
||||
|
||||
LogService::log("Nouveau device ajouté", [
|
||||
'device' => $device['manufacturer'] . ' ' . $device['model']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Désactiver les devices qui ne sont plus dans la liste
|
||||
foreach ($existingDevices as $key => $existing) {
|
||||
if (!isset($processedKeys[$key]) && $existing['tap_to_pay_certified'] == 1) {
|
||||
$stmt = $db->prepare("
|
||||
UPDATE stripe_android_certified_devices
|
||||
SET tap_to_pay_certified = 0,
|
||||
notes = CONCAT(IFNULL(notes, ''), ' | Désactivé le ', NOW(), ' (non présent dans la mise à jour)'),
|
||||
updated_at = NOW()
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute(['id' => $existing['id']]);
|
||||
$stats['disabled']++;
|
||||
|
||||
LogService::log("Device désactivé", [
|
||||
'device' => $existing['manufacturer'] . ' ' . $existing['model'],
|
||||
'reason' => 'Non présent dans la liste mise à jour'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$stats['total'] = count($devices);
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie une notification email aux administrateurs si changements importants
|
||||
*/
|
||||
function sendNotification(array $stats): void {
|
||||
try {
|
||||
// Récupérer la configuration
|
||||
$appConfig = AppConfig::getInstance();
|
||||
$emailConfig = $appConfig->getEmailConfig();
|
||||
|
||||
if (empty($emailConfig['admin_email'])) {
|
||||
return; // Pas d'email admin configuré
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Préparer le contenu de l'email
|
||||
$subject = "Mise à jour des devices Stripe Tap to Pay";
|
||||
$body = "Bonjour,\n\n";
|
||||
$body .= "La mise à jour automatique de la liste des appareils certifiés Stripe Tap to Pay a été effectuée.\n\n";
|
||||
$body .= "Résumé des changements :\n";
|
||||
$body .= "- Nouveaux appareils ajoutés : " . $stats['added'] . "\n";
|
||||
$body .= "- Appareils mis à jour : " . $stats['updated'] . "\n";
|
||||
$body .= "- Appareils désactivés : " . $stats['disabled'] . "\n";
|
||||
$body .= "- Appareils inchangés : " . $stats['unchanged'] . "\n";
|
||||
$body .= "- Total d'appareils traités : " . $stats['total'] . "\n\n";
|
||||
|
||||
if ($stats['added'] > 0) {
|
||||
$body .= "Les nouveaux appareils ont été automatiquement ajoutés à la base de données.\n";
|
||||
}
|
||||
|
||||
if ($stats['disabled'] > 0) {
|
||||
$body .= "Certains appareils ont été désactivés car ils ne sont plus certifiés.\n";
|
||||
}
|
||||
|
||||
$body .= "\nConsultez les logs pour plus de détails.\n";
|
||||
$body .= "\nCordialement,\nLe système GeoSector";
|
||||
|
||||
// Insérer dans la queue d'emails
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO email_queue
|
||||
(to_email, subject, body, status, created_at, attempts)
|
||||
VALUES
|
||||
(:to_email, :subject, :body, 'pending', NOW(), 0)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
'to_email' => $emailConfig['admin_email'],
|
||||
'subject' => $subject,
|
||||
'body' => $body
|
||||
]);
|
||||
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Notification ajoutée à la queue d'emails\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Ne pas faire échouer le script si l'email ne peut pas être envoyé
|
||||
echo "[" . date('Y-m-d H:i:s') . "] Impossible d'envoyer la notification: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user