#!/usr/bin/env php 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'] = 'app.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'; 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"; } }