Files
geo/api/scripts/cron/update_stripe_devices.php
pierre 570a1fa1f0 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>
2025-10-05 20:11:15 +02:00

442 lines
22 KiB
PHP

#!/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'] = '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";
}
}