diff --git a/modules/billing/create_servers.php b/modules/billing/create_servers.php index ea09ff4a..7a6398ca 100644 --- a/modules/billing/create_servers.php +++ b/modules/billing/create_servers.php @@ -296,8 +296,9 @@ function exec_ogp_module() //WEBHOOK Discord======================================================================================= - $webhookurl = "https://discord.com/api/webhooks/710275918274363412/g5Tr-EUdEnLfFryOlscxJ6FuPiSJuE6EMKRYmh9UGMiqTUxU5-y9CQrBlDJW7znr0Tol"; - //$settings['webhookurl']; + $webhookurl = !empty($settings['webhookurl']) ? $settings['webhookurl'] : ''; + + if (!empty($webhookurl)) { $msg = "A new server, ". $home_name ." ID #". $home_id . ", has just been created."; @@ -314,6 +315,7 @@ function exec_ogp_module() //If you need to debug, or find out why you can't send message uncomment line below, and execute script. //echo $response; //end WEBHOOK Discord + } } // END EMAIL diff --git a/modules/billing/cron-shop.php b/modules/billing/cron-shop.php index 9884944d..7b229c8b 100644 --- a/modules/billing/cron-shop.php +++ b/modules/billing/cron-shop.php @@ -108,7 +108,7 @@ $due_for_invoice = $db->resultQuery(" AND sh.next_invoice_date IS NOT NULL AND sh.next_invoice_date <= NOW() AND NOT EXISTS ( - SELECT 1 FROM {$table_prefix}invoices inv + SELECT 1 FROM {$table_prefix}billing_invoices inv WHERE inv.home_id = sh.home_id AND inv.billing_status = 'Invoiced' ) ORDER BY sh.home_id ASC @@ -142,7 +142,7 @@ if (is_array($due_for_invoice)) { // Guard: skip if an invoice for this exact period already exists $exists = $db->resultQuery(" - SELECT invoice_id FROM {$table_prefix}invoices + SELECT invoice_id FROM {$table_prefix}billing_invoices WHERE home_id = {$home_id} AND due_date = '" . $db->realEscapeSingle($due_date) . "' LIMIT 1 @@ -152,11 +152,11 @@ if (is_array($due_for_invoice)) { continue; } - // Create renewal invoice in {prefix}invoices + // Create renewal invoice in {prefix}billing_invoices $db->query(" - INSERT INTO {$table_prefix}invoices + INSERT INTO {$table_prefix}billing_invoices (home_id, user_id, due_date, billing_status, rate_type, - price_per_player, player_slots, quantity, subtotal, total_due) + rate_per_player, players, qty, subtotal, total_due) VALUES ( {$home_id}, {$user_id}, '" . $db->realEscapeSingle($due_date) . "', @@ -219,10 +219,10 @@ $past_due = $db->resultQuery(" AND ( sh.last_invoice_id IS NULL OR EXISTS ( - SELECT 1 FROM {$table_prefix}invoices inv + SELECT 1 FROM {$table_prefix}billing_invoices inv WHERE inv.invoice_id = sh.last_invoice_id AND inv.billing_status = 'Invoiced' - AND inv.paid_at IS NULL + AND inv.paid_date IS NULL ) ) ORDER BY sh.home_id ASC @@ -243,11 +243,11 @@ if (is_array($past_due)) { // Mark matching invoice Expired (if still unpaid) if ($last_invoice_id > 0) { $db->query(" - UPDATE {$table_prefix}invoices + UPDATE {$table_prefix}billing_invoices SET billing_status = 'Expired' WHERE invoice_id = {$last_invoice_id} AND billing_status = 'Invoiced' - AND paid_at IS NULL + AND paid_date IS NULL "); } @@ -358,9 +358,9 @@ if (is_array($to_delete)) { WHERE home_id = '{$home_id}' "); - // Mark any open gsp_invoices for this home as Expired + // Mark any open billing_invoices for this home as Expired $db->query(" - UPDATE {$table_prefix}invoices + UPDATE {$table_prefix}billing_invoices SET billing_status = 'Expired' WHERE home_id = {$home_id} AND billing_status = 'Invoiced' @@ -394,11 +394,11 @@ $db->logger("BILLING-CRON: --- Step D: Paid invoice safety-net ---"); $paid_invoices = $db->resultQuery(" SELECT inv.invoice_id, inv.home_id, inv.rate_type, sh.billing_status - FROM {$table_prefix}invoices inv + FROM {$table_prefix}billing_invoices inv INNER JOIN {$table_prefix}server_homes sh ON sh.home_id = inv.home_id WHERE inv.billing_status = 'Invoiced' AND sh.billing_status = 'Invoiced' - AND (inv.paid_at IS NOT NULL OR inv.payment_id IS NOT NULL) + AND (inv.paid_date IS NOT NULL OR inv.payment_txid IS NOT NULL) ORDER BY inv.invoice_id ASC "); @@ -413,7 +413,7 @@ if (is_array($paid_invoices)) { $next_invoice_date = date('Y-m-d H:i:s', strtotime($period_map[$rate_type] ?? '+1 month')); $db->query(" - UPDATE {$table_prefix}invoices + UPDATE {$table_prefix}billing_invoices SET billing_status = 'Active' WHERE invoice_id = {$invoice_id} "); diff --git a/modules/billing/module.php b/modules/billing/module.php index c7e456a8..e4a7c409 100644 --- a/modules/billing/module.php +++ b/modules/billing/module.php @@ -25,7 +25,7 @@ // Module general information $module_title = "billing"; $module_version = "3.2"; -$db_version = 1; +$db_version = 2; $module_required = FALSE; // Module description $module_description = "Billing storefront / provisioning integration. Public ordering runs as a standalone site; panel pages provide provisioning and admin order management."; @@ -36,12 +36,14 @@ $module_menus = array(); $install_queries = array(); -// Baseline schema — all billing tables with their final column set. -// This is the single source of truth for fresh installs. +// ----------------------------------------------------------------------- +// db_version 1 — Baseline schema for fresh installs. // All CREATE TABLE statements use IF NOT EXISTS so they are safe to re-run. -// Existing installs at any previous db_version already have these tables and columns, -// so no incremental ALTER chains are needed here. -$install_queries[0] = array( +// NOTE: The panel updater runs $install_queries[$i+1] when upgrading a +// module from db_version $i. A module installed fresh at db_version 0 +// therefore runs $install_queries[1], not $install_queries[0]. +// ----------------------------------------------------------------------- +$install_queries[1] = array( // Billing Services — available game server packages "CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."billing_services` ( `service_id` INT(11) NOT NULL AUTO_INCREMENT, @@ -78,6 +80,7 @@ $install_queries[0] = array( `invoice_duration` VARCHAR(16) NOT NULL DEFAULT 'month', `max_players` INT(11) NOT NULL DEFAULT 0, `price` FLOAT(15,2) NOT NULL DEFAULT 0, + `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00, `remote_control_password` VARCHAR(255) NULL, `ftp_password` VARCHAR(255) NULL, `home_id` VARCHAR(255) NOT NULL DEFAULT '0', @@ -95,11 +98,13 @@ $install_queries[0] = array( // Billing Invoices — created on cart add, paid after payment capture. // home_id is 0 until the service is provisioned after payment. + // billing_status tracks the renewal lifecycle (Active / Invoiced / Expired) + // independently of payment_status which tracks the payment state. "CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."billing_invoices` ( `invoice_id` INT(11) NOT NULL AUTO_INCREMENT, `order_id` INT(11) NOT NULL DEFAULT 0, `user_id` INT(11) NOT NULL, - `service_id` INT(11) NOT NULL, + `service_id` INT(11) NOT NULL DEFAULT 0, `home_id` INT(11) NOT NULL DEFAULT 0, `home_name` VARCHAR(255) NOT NULL DEFAULT '', `ip` INT(11) NOT NULL DEFAULT 0, @@ -109,8 +114,10 @@ $install_queries[0] = array( `customer_name` VARCHAR(255) NOT NULL DEFAULT '', `customer_email` VARCHAR(255) NOT NULL DEFAULT '', `amount` FLOAT(15,2) NOT NULL DEFAULT 0, + `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00, `currency` VARCHAR(3) NOT NULL DEFAULT 'USD', `status` VARCHAR(16) NOT NULL DEFAULT 'due', + `billing_status` VARCHAR(16) NOT NULL DEFAULT 'due', `invoice_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `due_date` DATETIME NULL, `paid_date` DATETIME NULL, @@ -127,12 +134,15 @@ $install_queries[0] = array( `total_due` DECIMAL(15,2) NOT NULL DEFAULT 0, `payment_status` ENUM('unpaid','paid','cancelled','refunded') NOT NULL DEFAULT 'unpaid', `qty` INT(11) NOT NULL DEFAULT 1, + `coupon_id` INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (`invoice_id`), - KEY `order_id` (`order_id`), - KEY `user_id` (`user_id`), - KEY `status` (`status`), - KEY `due_date` (`due_date`), - KEY `service_id` (`service_id`) + KEY `order_id` (`order_id`), + KEY `user_id` (`user_id`), + KEY `home_id` (`home_id`), + KEY `status` (`status`), + KEY `due_date` (`due_date`), + KEY `service_id` (`service_id`), + KEY `coupon_id` (`coupon_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;", // Billing Transactions — immutable payment audit trail @@ -157,8 +167,163 @@ $install_queries[0] = array( KEY `payment_method` (`payment_method`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", + // Billing Coupons — discount codes + "CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."billing_coupons` ( + `coupon_id` INT(11) NOT NULL AUTO_INCREMENT, + `code` VARCHAR(50) NOT NULL, + `name` VARCHAR(255) NOT NULL DEFAULT '', + `description` TEXT NULL, + `discount_percent` DECIMAL(5,2) NOT NULL DEFAULT 0.00, + `usage_type` ENUM('one_time','permanent') NOT NULL DEFAULT 'one_time', + `game_filter_type` ENUM('all_games','specific_games') NOT NULL DEFAULT 'all_games', + `game_filter_list` TEXT NULL, + `max_uses` INT(11) NULL, + `current_uses` INT(11) NOT NULL DEFAULT 0, + `expires` DATETIME NULL, + `created_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` INT(11) NULL, + `is_active` TINYINT(1) NOT NULL DEFAULT 1, + PRIMARY KEY (`coupon_id`), + UNIQUE KEY `idx_code` (`code`), + KEY `idx_active_expires` (`is_active`,`expires`), + KEY `idx_created_by` (`created_by`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;", + + // Billing Config — global and per-game-key billing settings used by cron + "CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."billing_config` ( + `config_id` INT(11) NOT NULL AUTO_INCREMENT, + `game_key` VARCHAR(100) NULL DEFAULT NULL, + `enabled` TINYINT(1) NOT NULL DEFAULT 1, + `grace_days` INT(11) NOT NULL DEFAULT 0, + `delete_after_expired_days` INT(11) NOT NULL DEFAULT 7, + `rate_type` ENUM('daily','monthly','yearly') NOT NULL DEFAULT 'monthly', + `price_per_player` DECIMAL(10,4) NOT NULL DEFAULT 0.0000, + PRIMARY KEY (`config_id`), + KEY `game_key` (`game_key`), + KEY `enabled` (`enabled`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;", + // Drop legacy mapping table if it still exists from older installs "DROP TABLE IF EXISTS `".OGP_DB_PREFIX."billing_service_remote_servers`" ); +// ----------------------------------------------------------------------- +// db_version 2 — Safe idempotent column migrations for existing installs. +// Each callable checks whether the column already exists before running +// ALTER TABLE, so this migration can be re-run without errors. +// ----------------------------------------------------------------------- +$install_queries[2] = array( + // billing_orders: add discount_amount if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_orders` ADD `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER `price`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add home_id if missing (needed by cron-shop.php join) + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `home_id` INT(11) NOT NULL DEFAULT 0 AFTER `service_id`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add discount_amount if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER `amount`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add billing_status (lifecycle: Active/Invoiced/Expired) if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `billing_status` VARCHAR(16) NOT NULL DEFAULT 'due' AFTER `status`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add rate_type enum if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `rate_type` ENUM('daily','monthly','yearly') NOT NULL DEFAULT 'monthly' AFTER `invoice_duration`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add rate_per_player if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `rate_per_player` DECIMAL(15,4) NOT NULL DEFAULT 0 AFTER `rate_type`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add players if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `players` INT(11) NOT NULL DEFAULT 0 AFTER `rate_per_player`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add subtotal if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `subtotal` DECIMAL(15,2) NOT NULL DEFAULT 0 AFTER `players`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add total_due if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `total_due` DECIMAL(15,2) NOT NULL DEFAULT 0 AFTER `subtotal`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add payment_status enum if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `payment_status` ENUM('unpaid','paid','cancelled','refunded') NOT NULL DEFAULT 'unpaid' AFTER `currency`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoices: add coupon_id if missing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXbilling_invoices` ADD `coupon_id` INT(11) NOT NULL DEFAULT 0 AFTER `qty`")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // Create billing_config table for cron-shop settings if missing + "CREATE TABLE IF NOT EXISTS `OGP_DB_PREFIXbilling_config` ( + `config_id` INT(11) NOT NULL AUTO_INCREMENT, + `game_key` VARCHAR(100) NULL DEFAULT NULL, + `enabled` TINYINT(1) NOT NULL DEFAULT 1, + `grace_days` INT(11) NOT NULL DEFAULT 0, + `delete_after_expired_days` INT(11) NOT NULL DEFAULT 7, + `rate_type` ENUM('daily','monthly','yearly') NOT NULL DEFAULT 'monthly', + `price_per_player` DECIMAL(10,4) NOT NULL DEFAULT 0.0000, + PRIMARY KEY (`config_id`), + KEY `game_key` (`game_key`), + KEY `enabled` (`enabled`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;", + // Create billing_coupons table if missing (older installs using panel.sql may not have it) + "CREATE TABLE IF NOT EXISTS `OGP_DB_PREFIXbilling_coupons` ( + `coupon_id` INT(11) NOT NULL AUTO_INCREMENT, + `code` VARCHAR(50) NOT NULL, + `name` VARCHAR(255) NOT NULL DEFAULT '', + `description` TEXT NULL, + `discount_percent` DECIMAL(5,2) NOT NULL DEFAULT 0.00, + `usage_type` ENUM('one_time','permanent') NOT NULL DEFAULT 'one_time', + `game_filter_type` ENUM('all_games','specific_games') NOT NULL DEFAULT 'all_games', + `game_filter_list` TEXT NULL, + `max_uses` INT(11) NULL, + `current_uses` INT(11) NOT NULL DEFAULT 0, + `expires` DATETIME NULL, + `created_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_by` INT(11) NULL, + `is_active` TINYINT(1) NOT NULL DEFAULT 1, + PRIMARY KEY (`coupon_id`), + UNIQUE KEY `idx_code` (`code`), + KEY `idx_active_expires` (`is_active`,`expires`), + KEY `idx_created_by` (`created_by`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;" +); + ?> diff --git a/modules/billing/normalize_billing_order_status.sql b/modules/billing/normalize_billing_order_status.sql index 1e6ed1fe..511cfef1 100644 --- a/modules/billing/normalize_billing_order_status.sql +++ b/modules/billing/normalize_billing_order_status.sql @@ -19,23 +19,23 @@ -- 'Expired') are left unchanged. -- -- Compatible with MySQL 5.7+ and MariaDB 10.2+. --- Table prefix is hardcoded to gsp_ (standalone billing module context). +-- IMPORTANT: Replace below with your table prefix (e.g. gsp_ or ogp_) (standalone billing module context). -- Run ONCE on an existing installation; safe to run again (no-op on clean data). -- 'installed' → 'Active' -UPDATE `gsp_billing_orders` +UPDATE `billing_orders` SET `status` = 'Active' WHERE `status` = 'installed'; -- 'paid' → 'Active' -UPDATE `gsp_billing_orders` +UPDATE `billing_orders` SET `status` = 'Active' WHERE `status` = 'paid'; -- 'suspended' → 'Invoiced' -- These rows had an open renewal invoice; cron-shop Step B will move them to -- 'Expired' on the next run if the invoice remains unpaid. -UPDATE `gsp_billing_orders` +UPDATE `billing_orders` SET `status` = 'Invoiced' WHERE `status` = 'suspended'; @@ -43,7 +43,7 @@ UPDATE `gsp_billing_orders` -- Expected result: only rows with status IN ('Active','Invoiced','Expired', -- 'in-cart','cancelled','refunded') should appear. SELECT `status`, COUNT(*) AS `count` - FROM `gsp_billing_orders` + FROM `billing_orders` GROUP BY `status` ORDER BY `status`; @@ -56,8 +56,8 @@ SELECT o.`order_id`, o.`home_id` AS missing_home_id, o.`status`, o.`end_date` - FROM `gsp_billing_orders` o - LEFT JOIN `gsp_server_homes` sh ON sh.`home_id` = o.`home_id` + FROM `billing_orders` o + LEFT JOIN `server_homes` sh ON sh.`home_id` = o.`home_id` WHERE o.`home_id` != '0' AND o.`home_id` != '' AND sh.`home_id` IS NULL diff --git a/modules/billing/sql/002_billing_checkout_fixes.sql b/modules/billing/sql/002_billing_checkout_fixes.sql index 74e985e4..36612bf3 100644 --- a/modules/billing/sql/002_billing_checkout_fixes.sql +++ b/modules/billing/sql/002_billing_checkout_fixes.sql @@ -3,18 +3,18 @@ -- Idempotent migration: adds columns required by the billing checkout fixes. -- Safe to run multiple times (uses IF-based prepared statements). -- Run against the panel database after deploying the updated PHP files. --- Table prefix is hardcoded to gsp_. +-- IMPORTANT: Replace with your actual table prefix (e.g. gsp_ or ogp_). -- ============================================================================= SET @db = DATABASE(); -SET @tbl = 'gsp_billing_invoices'; +SET @tbl = 'billing_invoices'; -- 1) coupon_id — tracks which coupon was applied to an invoice SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND COLUMN_NAME = 'coupon_id'; SET @sql = IF(@cnt = 0, - 'ALTER TABLE `gsp_billing_invoices` ADD COLUMN `coupon_id` INT(11) NOT NULL DEFAULT 0 AFTER `qty`', + 'ALTER TABLE `billing_invoices` ADD COLUMN `coupon_id` INT(11) NOT NULL DEFAULT 0 AFTER `qty`', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; @@ -23,7 +23,7 @@ SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND COLUMN_NAME = 'discount_amount'; SET @sql = IF(@cnt = 0, - 'ALTER TABLE `gsp_billing_invoices` ADD COLUMN `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER `amount`', + 'ALTER TABLE `billing_invoices` ADD COLUMN `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER `amount`', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; @@ -33,14 +33,14 @@ SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND COLUMN_NAME = 'payment_status'; SET @sql = IF(@cnt = 0, - "ALTER TABLE `gsp_billing_invoices` ADD COLUMN `payment_status` ENUM('unpaid','paid','cancelled','refunded') NOT NULL DEFAULT 'unpaid' AFTER `currency`", + "ALTER TABLE `billing_invoices` ADD COLUMN `payment_status` ENUM('unpaid','paid','cancelled','refunded') NOT NULL DEFAULT 'unpaid' AFTER `currency`", 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- Backfill payment_status for existing rows: -- 'paid' → payment_status = 'paid' -- anything else → payment_status = 'unpaid' -UPDATE `gsp_billing_invoices` +UPDATE `billing_invoices` SET `payment_status` = 'paid' WHERE `status` = 'paid' AND `payment_status` <> 'paid'; @@ -49,7 +49,7 @@ SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND COLUMN_NAME = 'subtotal'; SET @sql = IF(@cnt = 0, - 'ALTER TABLE `gsp_billing_invoices` ADD COLUMN `subtotal` DECIMAL(15,2) NOT NULL DEFAULT 0 AFTER `discount_amount`', + 'ALTER TABLE `billing_invoices` ADD COLUMN `subtotal` DECIMAL(15,2) NOT NULL DEFAULT 0 AFTER `discount_amount`', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; @@ -58,7 +58,7 @@ SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND COLUMN_NAME = 'total_due'; SET @sql = IF(@cnt = 0, - 'ALTER TABLE `gsp_billing_invoices` ADD COLUMN `total_due` DECIMAL(15,2) NOT NULL DEFAULT 0 AFTER `subtotal`', + 'ALTER TABLE `billing_invoices` ADD COLUMN `total_due` DECIMAL(15,2) NOT NULL DEFAULT 0 AFTER `subtotal`', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; @@ -67,21 +67,21 @@ SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND INDEX_NAME = 'coupon_id'; SET @sql = IF(@cnt = 0, - 'ALTER TABLE `gsp_billing_invoices` ADD KEY `coupon_id` (`coupon_id`)', + 'ALTER TABLE `billing_invoices` ADD KEY `coupon_id` (`coupon_id`)', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ------------------------- -- billing_orders additions -- ------------------------- -SET @tbl = 'gsp_billing_orders'; +SET @tbl = 'billing_orders'; -- 7) coupon_id on billing_orders (already in baseline schema but guard for older installs) SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND COLUMN_NAME = 'coupon_id'; SET @sql = IF(@cnt = 0, - 'ALTER TABLE `gsp_billing_orders` ADD COLUMN `coupon_id` INT(11) NOT NULL DEFAULT 0 AFTER `paid_ts`', + 'ALTER TABLE `billing_orders` ADD COLUMN `coupon_id` INT(11) NOT NULL DEFAULT 0 AFTER `paid_ts`', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; @@ -90,7 +90,7 @@ SET @cnt = 0; SELECT COUNT(*) INTO @cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = @tbl AND COLUMN_NAME = 'discount_amount'; SET @sql = IF(@cnt = 0, - 'ALTER TABLE `gsp_billing_orders` ADD COLUMN `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER `price`', + 'ALTER TABLE `billing_orders` ADD COLUMN `discount_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER `price`', 'SELECT 1'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; diff --git a/modules/billing/sql/normalize_billing_order_status.sql b/modules/billing/sql/normalize_billing_order_status.sql index bda6d6cf..a291b865 100644 --- a/modules/billing/sql/normalize_billing_order_status.sql +++ b/modules/billing/sql/normalize_billing_order_status.sql @@ -17,19 +17,19 @@ -- ============================================================ -- Map old 'installed' to 'Active' -UPDATE `gsp_billing_orders` +UPDATE `billing_orders` SET `status` = 'Active' WHERE `status` = 'installed'; -- Map old 'paid' to 'Active' -- (Orders that were paid but not yet provisioned should be provisioned -- via the admin orders panel after this migration.) -UPDATE `gsp_billing_orders` +UPDATE `billing_orders` SET `status` = 'Active' WHERE `status` = 'paid'; -- Map old 'suspended' to 'Expired' -UPDATE `gsp_billing_orders` +UPDATE `billing_orders` SET `status` = 'Expired' WHERE `status` = 'suspended'; diff --git a/modules/gamemanager/module.php b/modules/gamemanager/module.php index d5cd61b6..c83a7e9d 100644 --- a/modules/gamemanager/module.php +++ b/modules/gamemanager/module.php @@ -25,7 +25,7 @@ // Module general information $module_title = "Game manager"; $module_version = "1.33"; -$db_version = 1; +$db_version = 2; $module_required = TRUE; $module_menus = array( array( 'subpage' => 'game_monitor', 'name'=>'Game Monitor', 'group'=>'user' ) ); $module_access_rights = array('u' => 'allow_updates', 'p' => 'allow_parameter_usage', 'e' => 'allow_extra_params', 'c' => 'allow_custom_fields'); @@ -87,4 +87,75 @@ $install_queries[0] = array( `server_status_cache` longtext NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1;" ); + +// ----------------------------------------------------------------------- +// db_version 2 — Add billing lifecycle columns to server_homes. +// Each callable is idempotent: it checks whether the column already exists +// and treats a "Duplicate column name" error as success (not a real failure). +// ----------------------------------------------------------------------- +$install_queries[2] = array( + // billing_status: current lifecycle state (Active / Invoiced / Expired) + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `billing_status` VARCHAR(16) NOT NULL DEFAULT 'Active'")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_enabled: whether this server participates in billing automation + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `billing_enabled` TINYINT(1) NOT NULL DEFAULT 0")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // next_invoice_date: when cron-shop should generate the next renewal invoice + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `next_invoice_date` DATETIME NULL DEFAULT NULL")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // last_invoice_id: FK to billing_invoices.invoice_id (most recent renewal) + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `last_invoice_id` INT(11) NOT NULL DEFAULT 0")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_expires_at: canonical billing expiration date (DATETIME) + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `billing_expires_at` DATETIME NULL DEFAULT NULL")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_price: price stored at provisioning time for renewals + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `billing_price` DECIMAL(15,4) NOT NULL DEFAULT 0.0000")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_rate_type: 'daily' / 'monthly' / 'yearly' + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `billing_rate_type` ENUM('daily','monthly','yearly') NOT NULL DEFAULT 'monthly'")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_players: slot count used to calculate per-player pricing + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `billing_players` INT(11) NOT NULL DEFAULT 0")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, + // billing_invoice_sent_at: timestamp of last renewal invoice email + function($db) { + if (!$db->query("ALTER TABLE `OGP_DB_PREFIXserver_homes` ADD `billing_invoice_sent_at` DATETIME NULL DEFAULT NULL")) { + return (stripos((string)$db->getError(), 'Duplicate column') !== false); + } + return true; + }, +); ?> \ No newline at end of file