"ADD COLUMN `home_cfg_id` INT(11) NOT NULL DEFAULT 0", 'description' => "ADD COLUMN `description` VARCHAR(1000) NOT NULL DEFAULT ''", 'img_url' => "ADD COLUMN `img_url` VARCHAR(255) NOT NULL DEFAULT ''", 'out_of_stock' => "ADD COLUMN `out_of_stock` TINYINT(1) NOT NULL DEFAULT 0", 'slot_min_qty' => "ADD COLUMN `slot_min_qty` INT(11) NOT NULL DEFAULT 1", 'slot_max_qty' => "ADD COLUMN `slot_max_qty` INT(11) NOT NULL DEFAULT 100", 'price_daily' => "ADD COLUMN `price_daily` FLOAT(15,4) NOT NULL DEFAULT 0", 'price_monthly' => "ADD COLUMN `price_monthly` FLOAT(15,4) NOT NULL DEFAULT 0", 'price_year' => "ADD COLUMN `price_year` FLOAT(15,4) NOT NULL DEFAULT 0", 'remote_server_id' => "ADD COLUMN `remote_server_id` VARCHAR(255) NOT NULL DEFAULT ''", ]; foreach ($autoRepairCols as $col => $alterFragment) { if (!col_exists($db, $tableName, $col)) { if ($db->query("ALTER TABLE `{$tableName}` {$alterFragment}")) { $messages[] = "✔ Auto-repaired: added column '{$col}' to {$tableName}."; } else { $messages[] = "✖ Could not add column '{$col}' to {$tableName}: " . $db->error; } } } // If critical columns are still absent after repair, abort to avoid SQL errors. foreach (['service_name', 'home_cfg_id', 'enabled'] as $critical) { if (!col_exists($db, $tableName, $critical)) { $messages[] = "⚠ Critical column '{$critical}' missing from {$tableName}; skipping sync."; return $messages; } } // Load all game configs from config_homes — one entry per game XML. $configHomes = []; $res = $db->query( "SELECT home_cfg_id, game_name, home_cfg_file FROM `{$prefix}config_homes` ORDER BY game_name" ); if ($res) { while ($row = $res->fetch_assoc()) { $configHomes[(int)$row['home_cfg_id']] = $row; } } if (empty($configHomes)) { // config_homes is empty or the table does not exist yet — nothing to sync. return $messages; } // Load existing billing_services indexed by home_cfg_id. $existing = []; $svcRes = $db->query( "SELECT service_id, home_cfg_id, enabled, out_of_stock FROM `{$tableName}`" ); if ($svcRes) { while ($row = $svcRes->fetch_assoc()) { $hid = (int)$row['home_cfg_id']; if ($hid > 0) { $existing[$hid] = $row; } } } // Insert a new row for every config_homes entry not yet in billing_services. // Admin-editable fields (prices, slots, enabled, etc.) get safe defaults so // the service is visible to the admin but not yet live in the store. foreach ($configHomes as $homeCfgId => $ch) { if (isset($existing[$homeCfgId])) { continue; } $svcName = $db->real_escape_string($ch['game_name']); $db->query( "INSERT INTO `{$tableName}` (home_cfg_id, mod_cfg_id, service_name, description, remote_server_id, enabled, out_of_stock, price_daily, price_monthly, price_year, slot_min_qty, slot_max_qty, img_url, ftp, install_method, manual_url, access_rights) VALUES ({$homeCfgId}, 0, '{$svcName}', '{$svcName}', '', 0, 0, 0.00, 0.00, 0.00, 1, 100, '', '', 'steamcmd', '', '')" ); $messages[] = "Added new service: " . $ch['game_name']; } // Soft-disable billing_services whose home_cfg_id no longer appears in config_homes. foreach ($existing as $homeCfgId => $svcRow) { if (!isset($configHomes[$homeCfgId])) { $sid = (int)$svcRow['service_id']; $db->query( "UPDATE `{$tableName}` 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 config no longer in config_homes."; } } } return $messages; } $syncMessages = sync_billing_services($db, $table_prefix); $flash = []; $flashType = 'ok'; /* ----------------------------------------------------------------------- SAVE: service configuration form submitted Only admin-editable fields are updated; service_name and home_cfg_id are never overwritten here. ----------------------------------------------------------------------- */ 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'] ?? []; $postedServers = $_POST['servers'] ?? []; foreach ((array)$postedServices as $sid => $svcData) { $sid = (int)$sid; $enabled = isset($svcData['enabled']) ? 1 : 0; $outOfStock = isset($svcData['out_of_stock']) ? 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(1, (int)($svcData['slot_min_qty'] ?? 1)); $slotMax = max(1, (int)($svcData['slot_max_qty'] ?? 1)); if ($slotMax < $slotMin) { $slotMax = $slotMin; } $description = $db->real_escape_string(substr((string)($svcData['description'] ?? ''), 0, 1000)); $imgUrl = $db->real_escape_string(substr((string)($svcData['img_url'] ?? ''), 0, 255)); // 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}, out_of_stock = {$outOfStock}, price_daily = '{$priceDaily}', price_monthly = '{$priceMonthly}', price_year = '{$priceYear}', slot_min_qty = {$slotMin}, slot_max_qty = {$slotMax}, description = '{$description}', img_url = '{$imgUrl}', remote_server_id = '{$remoteServerIdStr}' WHERE service_id = {$sid}" ); } $flash[] = "Services saved."; } /* ----------------------------------------------------------------------- Load data for display — join config_homes to show the config XML filename ----------------------------------------------------------------------- */ $remoteServers = []; $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 bs.service_id, bs.service_name, bs.enabled, bs.out_of_stock, bs.price_daily, bs.price_monthly, bs.price_year, bs.slot_min_qty, bs.slot_max_qty, bs.remote_server_id, bs.description, bs.img_url, ch.home_cfg_file FROM `{$table_prefix}billing_services` bs LEFT JOIN `{$table_prefix}config_homes` ch ON ch.home_cfg_id = bs.home_cfg_id ORDER BY bs.service_name" ); while ($svcRes && ($row = $svcRes->fetch_assoc())) { $services[] = $row; } ?>

Service Configuration

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 configuration (config_homes). 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. Ensure game configs are loaded in the panel (Home → Games configuration).

Game Name Config XML Enabled Out of Stock Min Slots Max Slots Price / Day ($) Price / Month ($) Price / Year ($) Description Image URL Available Servers
ID:
—'; ?> > > No remote servers configured

Notes: