diff --git a/modules/billing/add_override_price_column.sql b/modules/billing/add_override_price_column.sql
index 704a4418..565cc6d1 100644
--- a/modules/billing/add_override_price_column.sql
+++ b/modules/billing/add_override_price_column.sql
@@ -1,3 +1,13 @@
+-- DEPRECATED: This file is no longer needed.
+--
+-- The gsp_billing_service_remote_servers mapping table has been removed.
+-- Server availability per game/service is now stored in gsp_billing_services.remote_server_id
+-- as a comma-separated list of numeric server IDs (e.g. "1,3,7").
+-- The module migration (db_version 4) drops the mapping table automatically.
+--
+-- The original content of this file is kept below for historical reference only.
+-- Do NOT run this script on new installations.
+--
-- Migration: add override_price to billing_service_remote_servers
-- Run once on existing installs that already have the mapping table (db_version 2)
-- but are missing the override_price column (added in db_version 3 / module v3.1).
diff --git a/modules/billing/add_remote_server_enabled_column.sql b/modules/billing/add_remote_server_enabled_column.sql
index dc158104..b4726014 100644
--- a/modules/billing/add_remote_server_enabled_column.sql
+++ b/modules/billing/add_remote_server_enabled_column.sql
@@ -1,3 +1,13 @@
+-- DEPRECATED: This file is no longer needed.
+--
+-- The billing module no longer references an `enabled` column on gsp_remote_servers.
+-- gsp_remote_servers is the server inventory table only.
+-- Server availability per game/service is stored in gsp_billing_services.remote_server_id
+-- as a comma-separated list of numeric server IDs (e.g. "1,3,7").
+--
+-- The original content of this file is kept below for historical reference only.
+-- Do NOT run this script on new installations.
+--
-- Migration: add `enabled` column to gsp_remote_servers
--
-- The original panel schema (panel.sql / ogp_remote_servers) includes an `enabled`
diff --git a/modules/billing/adminserverlist.php b/modules/billing/adminserverlist.php
index f3765e0c..5f944d1d 100644
--- a/modules/billing/adminserverlist.php
+++ b/modules/billing/adminserverlist.php
@@ -3,210 +3,258 @@
- Admin Server / Game Matrix - GameServers.World
+ Admin Service Configuration - GSP
query(
- "CREATE TABLE IF NOT EXISTS `{$table_prefix}billing_service_remote_servers` (
- `id` INT(11) NOT NULL AUTO_INCREMENT,
- `service_id` INT(11) NOT NULL,
- `remote_server_id` INT(11) NOT NULL,
- `enabled` TINYINT(1) NOT NULL DEFAULT 1,
- `override_price` DECIMAL(10,2) NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `svc_rs` (`service_id`, `remote_server_id`),
- KEY `service_id` (`service_id`),
- KEY `remote_server_id` (`remote_server_id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
-);
-// Add override_price if this is an older install that already has the table without it
-$chk = $db->query("SHOW COLUMNS FROM `{$table_prefix}billing_service_remote_servers` LIKE 'override_price'");
-if ($chk && $chk->num_rows === 0) {
- $db->query("ALTER TABLE `{$table_prefix}billing_service_remote_servers` ADD COLUMN `override_price` DECIMAL(10,2) NULL");
+/* -----------------------------------------------------------------------
+ Auto-sync: keep billing_services in step with game/mod config list
+ Runs on every page load; INSERT and soft-disable only — never hard-delete.
+----------------------------------------------------------------------- */
+function sync_billing_services(mysqli $db, string $prefix): array
+{
+ $messages = [];
+
+ // Load all games/mods from panel config tables
+ $gameMods = [];
+ $res = $db->query(
+ "SELECT cm.mod_cfg_id, cm.home_cfg_id, cm.mod_name, ch.game_name
+ FROM `{$prefix}config_mods` cm
+ JOIN `{$prefix}config_homes` ch ON ch.home_cfg_id = cm.home_cfg_id
+ ORDER BY ch.game_name, cm.mod_name"
+ );
+ if ($res) {
+ while ($row = $res->fetch_assoc()) {
+ $gameMods[(int)$row['mod_cfg_id']] = $row;
+ }
+ }
+
+ if (empty($gameMods)) {
+ // config_mods is empty or tables don't exist yet — nothing to sync
+ return $messages;
+ }
+
+ // Load existing billing_services indexed by mod_cfg_id
+ $existing = [];
+ $svcRes = $db->query(
+ "SELECT service_id, mod_cfg_id, enabled, out_of_stock
+ FROM `{$prefix}billing_services`"
+ );
+ if ($svcRes) {
+ while ($row = $svcRes->fetch_assoc()) {
+ $existing[(int)$row['mod_cfg_id']] = $row;
+ }
+ }
+
+ // Insert new rows for game/mods not yet in billing_services
+ foreach ($gameMods as $modCfgId => $gm) {
+ if (isset($existing[$modCfgId])) {
+ continue;
+ }
+ $svcName = $db->real_escape_string($gm['mod_name'] ?: $gm['game_name']);
+ $homeCfgId = (int)$gm['home_cfg_id'];
+ $db->query(
+ "INSERT INTO `{$prefix}billing_services`
+ (home_cfg_id, mod_cfg_id, service_name, remote_server_id,
+ enabled, out_of_stock, price_daily, price_monthly, price_year,
+ slot_min_qty, slot_max_qty, install_method)
+ VALUES
+ ({$homeCfgId}, {$modCfgId}, '{$svcName}', '',
+ 0, 0, 0, 0, 0,
+ 1, 100, 'steamcmd')"
+ );
+ $messages[] = "Added new service: " . ($gm['mod_name'] ?: $gm['game_name']);
+ }
+
+ // Soft-disable billing_services whose mod_cfg_id no longer appears in config_mods
+ foreach ($existing as $modCfgId => $svcRow) {
+ if ($modCfgId > 0 && !isset($gameMods[$modCfgId])) {
+ $sid = (int)$svcRow['service_id'];
+ $db->query(
+ "UPDATE `{$prefix}billing_services`
+ SET enabled = 0, out_of_stock = 1
+ WHERE service_id = {$sid} AND enabled = 1"
+ );
+ if ($db->affected_rows > 0) {
+ $messages[] = "Service ID {$sid} disabled — game mod no longer in config.";
+ }
+ }
+ }
+
+ return $messages;
}
-$flash = [];
+$syncMessages = sync_billing_services($db, $table_prefix);
+
+$flash = [];
$flashType = 'ok';
/* -----------------------------------------------------------------------
- SAVE: matrix form submitted
+ SAVE: service configuration form submitted
----------------------------------------------------------------------- */
-if (isset($_POST['save_matrix'])) {
+if (isset($_POST['save_services'])) {
+ // Load valid remote server IDs for validation
+ $validServerIds = [];
+ $rsRes = $db->query("SELECT remote_server_id FROM `{$table_prefix}remote_servers`");
+ while ($rsRes && ($rsRow = $rsRes->fetch_assoc())) {
+ $validServerIds[] = (int)$rsRow['remote_server_id'];
+ }
+ $validSet = array_flip($validServerIds);
+
$postedServices = $_POST['svc'] ?? [];
- $postedMappings = $_POST['map'] ?? [];
+ $postedServers = $_POST['servers'] ?? [];
foreach ((array)$postedServices as $sid => $svcData) {
- $sid = (int)$sid;
- $enabled = isset($svcData['enabled']) ? 1 : 0;
- $base_price = number_format((float)($svcData['base_price'] ?? 0), 2, '.', '');
- $period = in_array($svcData['period'] ?? 'monthly', ['daily','monthly','yearly'], true)
- ? $svcData['period'] : 'monthly';
+ $sid = (int)$sid;
+ $enabled = isset($svcData['enabled']) ? 1 : 0;
+ $priceDaily = number_format((float)($svcData['price_daily'] ?? 0), 4, '.', '');
+ $priceMonthly = number_format((float)($svcData['price_monthly'] ?? 0), 4, '.', '');
+ $priceYear = number_format((float)($svcData['price_year'] ?? 0), 4, '.', '');
+ $slotMin = max(0, (int)($svcData['slot_min_qty'] ?? 0));
+ $slotMax = max(0, (int)($svcData['slot_max_qty'] ?? 0));
+ if ($slotMax < $slotMin) { $slotMax = $slotMin; }
- $price_col = $period === 'daily' ? 'price_daily' : ($period === 'yearly' ? 'price_year' : 'price_monthly');
- $base_esc = $db->real_escape_string($base_price);
+ // Build comma-separated remote_server_id from checkboxes, validating each ID
+ $checkedIds = [];
+ foreach ((array)($postedServers[$sid] ?? []) as $rawId) {
+ $rid = (int)$rawId;
+ if (isset($validSet[$rid])) {
+ $checkedIds[] = $rid;
+ }
+ }
+ $remoteServerIdStr = $db->real_escape_string(implode(',', $checkedIds));
$db->query(
"UPDATE `{$table_prefix}billing_services`
- SET enabled = {$enabled},
- `{$price_col}` = '{$base_esc}'
+ SET enabled = {$enabled},
+ price_daily = '{$priceDaily}',
+ price_monthly = '{$priceMonthly}',
+ price_year = '{$priceYear}',
+ slot_min_qty = {$slotMin},
+ slot_max_qty = {$slotMax},
+ remote_server_id = '{$remoteServerIdStr}'
WHERE service_id = {$sid}"
);
}
- // Upsert mappings: for every service x server pair post data received
- $allServerIds = [];
- $rsRes = $db->query("SELECT remote_server_id FROM `{$table_prefix}remote_servers`");
- while ($rsRes && ($rsRow = $rsRes->fetch_assoc())) {
- $allServerIds[] = (int)$rsRow['remote_server_id'];
- }
-
- $stmt = $db->prepare(
- "INSERT INTO `{$table_prefix}billing_service_remote_servers`
- (service_id, remote_server_id, enabled, override_price)
- VALUES (?, ?, ?, ?)
- ON DUPLICATE KEY UPDATE
- enabled = VALUES(enabled),
- override_price = VALUES(override_price)"
- );
- foreach ((array)$postedServices as $sid => $ignored) {
- $sid = (int)$sid;
- foreach ($allServerIds as $rid) {
- $mapEnabled = isset($postedMappings[$sid][$rid]['enabled']) ? 1 : 0;
- $ovRaw = $postedMappings[$sid][$rid]['override_price'] ?? '';
- $ovPrice = (trim($ovRaw) === '') ? null : number_format((float)$ovRaw, 2, '.', '');
- if ($stmt) {
- $stmt->bind_param('iisd', $sid, $rid, $mapEnabled, $ovPrice);
- $stmt->execute();
- }
- }
- }
- if ($stmt) {
- $stmt->close();
- }
-
- $flash[] = "Matrix saved successfully.";
+ $flash[] = "Services saved.";
}
/* -----------------------------------------------------------------------
- Remove a service
------------------------------------------------------------------------ */
-if (isset($_POST['remove_service'], $_POST['service_id_remove'])) {
- $sid = (int)$_POST['service_id_remove'];
- $db->query("DELETE FROM `{$table_prefix}billing_service_remote_servers` WHERE service_id = {$sid}");
- $db->query("DELETE FROM `{$table_prefix}billing_services` WHERE service_id = {$sid}");
- $flash[] = "Service #{$sid} removed.";
-}
-
-/* -----------------------------------------------------------------------
- Load data
+ Load data for display
----------------------------------------------------------------------- */
$remoteServers = [];
-$rsRes = $db->query("SELECT remote_server_id, remote_server_name FROM `{$table_prefix}remote_servers` ORDER BY remote_server_name");
+$rsRes = $db->query(
+ "SELECT remote_server_id, remote_server_name
+ FROM `{$table_prefix}remote_servers`
+ ORDER BY remote_server_name"
+);
while ($rsRes && ($row = $rsRes->fetch_assoc())) {
$remoteServers[] = $row;
}
$services = [];
$svcRes = $db->query(
- "SELECT service_id, service_name, enabled, price_daily, price_monthly, price_year
+ "SELECT service_id, service_name, enabled, price_daily, price_monthly, price_year,
+ slot_min_qty, slot_max_qty, remote_server_id
FROM `{$table_prefix}billing_services`
ORDER BY service_name"
);
while ($svcRes && ($row = $svcRes->fetch_assoc())) {
$services[] = $row;
}
-
-// Load existing mappings into a lookup: $mappings[$service_id][$remote_server_id] = ['enabled'=>..,'override_price'=>..]
-$mappings = [];
-$mapRes = $db->query(
- "SELECT service_id, remote_server_id, enabled, override_price
- FROM `{$table_prefix}billing_service_remote_servers`"
-);
-while ($mapRes && ($row = $mapRes->fetch_assoc())) {
- $mappings[(int)$row['service_id']][(int)$row['remote_server_id']] = [
- 'enabled' => (int)$row['enabled'],
- 'override_price' => $row['override_price'],
- ];
-}
?>
-
+ $msg):
+ $type = ($flashType === 'err' || $idx < count((array)$syncMessages) && count($syncMessages) > 0 && strpos($msg, 'disabled') !== false) ? 'ok' : $flashType;
+?>
-Game × Server Matrix
+Service Configuration
- Enable or disable each game for billing, set its base price and billing period, then
- toggle availability per server and optionally override the price for that location.
- Leave override blank to use the base price.
+ Enable services, configure pricing and slot ranges, and select which remote servers
+ each game can be installed on. The service list is automatically kept in sync with
+ the panel game/mod configuration. Check one or more servers to make a game available
+ for purchase; leaving all servers unchecked prevents the game from appearing in the
+ store.
- No billing services found. Add services first via the database or the panel.
+ No billing services found. Ensure game configs are loaded in the panel (Home → Games configuration).