diff --git a/modules/gamemanager/home_handling_functions.php b/modules/gamemanager/home_handling_functions.php index af34295b..fd58c309 100644 --- a/modules/gamemanager/home_handling_functions.php +++ b/modules/gamemanager/home_handling_functions.php @@ -491,12 +491,7 @@ function get_monitor_buttons($server_home, $server_xml) * Returns an HTML-formatted expiration label for the given server home_id. * * Source of truth: billing_orders.end_date (DATETIME, NULL means no date set). - * The most recent active billing order for the home is resolved via a LEFT JOIN - * from server_homes to billing_orders — the same relationship used by the billing - * cron (cron-shop.php) and order-provisioning logic (create_servers.php). - * - * billing_orders.home_id is VARCHAR(255); server_homes.home_id is INT. - * MySQL handles the implicit cast in the JOIN condition automatically. + * billing_orders.home_id is the direct FK to server_homes.home_id. * Only rows where home_id != '0' are considered (home_id = '0' means not yet * provisioned). * @@ -515,33 +510,38 @@ function get_server_billing_expiration_html(int $home_id): string { global $db; - // Use a LEFT JOIN from server_homes to billing_orders — the same join pattern - // used throughout the billing module (cron-shop.php, create_servers.php). - // billing_orders.home_id is VARCHAR; server_homes.home_id is INT. MySQL - // performs the implicit cast for the equality comparison. - // We exclude billing_orders rows where home_id = '0' (not yet provisioned). + // Query billing_orders directly by home_id — the canonical FK relationship. + // billing_orders.home_id is VARCHAR(255), so we quote the value in the query. + // We exclude rows where home_id = '0' (not yet provisioned). + // + // Status whitelist — covers all active/paid states regardless of billing flow: + // 'Active' – set by payment_processor.php after PayPal capture + // 'Invoiced' – renewal invoice generated by cron-shop.php + // 'installed'– used by the invoice-first provisioning flow + // 'paid' – used by the invoice-first provisioning flow + // 'suspended'– overdue but not yet fully expired; date still meaningful + // + // Terminal statuses ('Expired', 'in-cart', 'cancelled', 'refunded') are excluded + // because they represent orders with no active service period. + // // OGP_DB_PREFIX is replaced at runtime by the panel DB wrapper (str_replace). $rows = $db->resultQuery( - "SELECT bo.end_date - FROM OGP_DB_PREFIXserver_homes sh - LEFT JOIN OGP_DB_PREFIXbilling_orders bo - ON bo.home_id = sh.home_id - AND bo.home_id != '0' - AND bo.status IN ('Active','Invoiced') - WHERE sh.home_id = " . intval($home_id) . " - ORDER BY bo.end_date DESC + "SELECT end_date + FROM OGP_DB_PREFIXbilling_orders + WHERE home_id = '" . intval($home_id) . "' + AND home_id != '0' + AND status IN ('Active','Invoiced','installed','paid','suspended') + ORDER BY end_date DESC LIMIT 1" ); - // If the server_homes row itself does not exist, or the query failed, bail out. - // empty($rows) is true when resultQuery returns FALSE (0 rows or error). + // resultQuery returns FALSE both on error and when 0 rows are found. + // Either way, there is no billing order for this server. if ($rows === false) { - // Query error — billing_orders table may be missing or schema mismatch. return "No expiration date found"; } - // A LEFT JOIN row always comes back (sh row exists), but bo.end_date may be NULL - // when there is no matching billing_orders record for this server. + // end_date may be NULL (e.g. order created but payment not yet captured). if (empty($rows[0]['end_date'])) { return "No expiration date found"; }