PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC) ); } catch (Exception $e) { http_response_code(500); echo "DB connection error."; exit; } /* ---------- AJAX UPDATE ---------- */ if (isset($_GET['action']) && $_GET['action'] === 'update' && $_SERVER['REQUEST_METHOD'] === 'POST') { header('Content-Type: application/json; charset=utf-8'); // Champs autorisés (whitelist) $allowed = array( 'country_name', 'country_code', 'price_month', 'price_year', 'checked', 'remise_pourcentage', 'remise_pourcentage_date_debut', 'remise_pourcentage_date_fin', 'price_month_local', 'price_year_local', 'disponible_sur_stripe', 'currency', 'price_month_local_checked', 'devise_monetaire_symbole', 'zero_decimal' ); $id = isset($_POST['id']) ? (int)$_POST['id'] : 0; if ($id <= 0) { echo json_encode(array('ok'=>false,'error'=>'Invalid id')); exit; } // Construction dynamique du UPDATE $sets = array(); $vals = array(); foreach ($allowed as $k) { if (array_key_exists($k, $_POST)) { $val = $_POST[$k]; // Normalisations simples if (in_array($k, array('checked','disponible_sur_stripe','price_month_local_checked','zero_decimal'), true)) { $val = ($val === '1' || $val === 1 || $val === 'true' || $val === 'on') ? 1 : 0; } // Dates : accepter '' pour NULL if (in_array($k, array('remise_pourcentage_date_debut','remise_pourcentage_date_fin'), true)) { $val = trim($val); if ($val === '') $val = null; } // Numériques if (in_array($k, array('price_month','price_year','price_month_local','price_year_local','remise_pourcentage'), true)) { $val = ($val === '' ? null : str_replace(',', '.', $val)); if (!is_null($val) && !is_numeric($val)) { echo json_encode(array('ok'=>false,'error'=>"Invalid numeric for $k")); exit; } } $sets[] = "`$k` = :$k"; $vals[":$k"] = $val; } } if (empty($sets)) { echo json_encode(array('ok'=>false,'error'=>'No updatable fields received')); exit; } $vals[':id'] = $id; try { $sql = "UPDATE price_by_country SET ".implode(', ', $sets)." WHERE id = :id LIMIT 1"; $st = $pdo->prepare($sql); $st->execute($vals); echo json_encode(array('ok'=>true)); } catch (Exception $e) { echo json_encode(array('ok'=>false,'error'=>'DB error: '.$e->getMessage())); } exit; } /* ---------- LISTING (GET) ---------- */ $perPage = 50; $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $offset = ($page - 1) * $perPage; $q = isset($_GET['q']) ? trim($_GET['q']) : ''; $where = " WHERE 1=1 "; $params = array(); if ($q !== '') { $where .= " AND (pc.country_name LIKE :q OR pc.country_code LIKE :q) "; $params[':q'] = '%'.$q.'%'; } /* exchange_rates: base EUR Colonne rate_per_eur = combien d’unités de la devise pour 1 EUR (ex: USD ~ 1.07, JPY ~ 160). Conversion vers EUR : EUR = local / rate_per_eur (si currency != 'EUR') */ $countSql = "SELECT COUNT(*) FROM price_by_country pc ".$where; $stc = $pdo->prepare($countSql); $stc->execute($params); $totalRows = (int)$stc->fetchColumn(); $sql = " SELECT pc.*, er.rate_per_eur, CASE WHEN pc.currency = 'EUR' THEN pc.price_month_local WHEN er.rate_per_eur IS NULL OR er.rate_per_eur = 0 THEN NULL ELSE pc.price_month_local / er.rate_per_eur END AS price_month_eur, CASE WHEN pc.currency = 'EUR' THEN pc.price_year_local WHEN er.rate_per_eur IS NULL OR er.rate_per_eur = 0 THEN NULL ELSE pc.price_year_local / er.rate_per_eur END AS price_year_eur, er.last_updated AS fx_last_updated FROM price_by_country pc LEFT JOIN exchange_rates er ON er.currency_code = pc.currency ".$where." ORDER BY pc.country_name ASC, pc.id ASC LIMIT :limit OFFSET :offset "; $st = $pdo->prepare($sql); foreach ($params as $k=>$v) $st->bindValue($k, $v, PDO::PARAM_STR); $st->bindValue(':limit', $perPage, PDO::PARAM_INT); $st->bindValue(':offset', $offset, PDO::PARAM_INT); $st->execute(); $rows = $st->fetchAll(); $pages = max(1, (int)ceil($totalRows / $perPage)); ?> Price by Country — Listing

Price by Country rows

Clear
ID Country Name Code Currency Symbol Checked Zero-decimal Stripe enabled Price Month (USD/EUR/… local) Price Month (EUR) Price Year (local) Price Year (EUR) Price Month (global EUR) Price Year (global EUR) Local checked Remise % Remise début Remise fin Actions FX last update