From 2f62bd32c95b134d50030ce05bf3b6f8e783556f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 15:55:39 +0000 Subject: [PATCH] fix: standardize billing order status values and fix expiration lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - billing_integration.php: admin-created servers now use status='Active' (was 'installed') - home_handling_functions.php: expiration query uses status IN ('Active','Invoiced') only - my_account.php: renewable_statuses includes canonical 'active'/'invoiced'; legacy labels updated - admin_orders.php: add orphaned home_id diagnostics section - normalize_billing_order_status.sql: new migration to convert installed/paid→Active, suspended→Invoiced Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/c56f04bb-ecce-4f1b-9bbd-c5f83107da1d Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com> --- modules/billing/admin_orders.php | 40 ++++++++++++ modules/billing/my_account.php | 4 +- .../normalize_billing_order_status.sql | 64 +++++++++++++++++++ .../gamemanager/home_handling_functions.php | 16 ++--- modules/user_games/billing_integration.php | 6 +- 5 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 modules/billing/normalize_billing_order_status.sql diff --git a/modules/billing/admin_orders.php b/modules/billing/admin_orders.php index c502565f..e726b4ff 100644 --- a/modules/billing/admin_orders.php +++ b/modules/billing/admin_orders.php @@ -197,5 +197,45 @@ function exec_ogp_module() echo ""; echo ""; + + // Orphaned home_id diagnostics ————————————————————————————————————————— + // Find billing_orders rows where home_id != 0 but no matching gsp_server_homes + // record exists. These indicate provisioning failures or stale data, and they + // are the reason the game monitor may show "No expiration date found". + $orphans = $db->resultQuery( + "SELECT o.order_id, o.user_id, o.home_name, o.home_id, o.status, o.end_date + FROM OGP_DB_PREFIXbilling_orders o + LEFT JOIN OGP_DB_PREFIXserver_homes sh ON sh.home_id = o.home_id + WHERE o.home_id != '0' + AND o.home_id != '' + AND sh.home_id IS NULL + ORDER BY o.order_id ASC" + ); + + echo "
"; + echo "

Orphaned home_id Diagnostics

"; + echo "

Billing orders that reference a home_id which no longer exists in gsp_server_homes. "; + echo "These orders will not show an expiration date on the game monitor. "; + echo "Reset home_id to 0 or re-provision these orders to fix them. "; + echo "Run normalize_billing_order_status.sql to standardise any legacy status values.

"; + + if (empty($orphans)) { + echo "

✓ No orphaned billing orders found.

"; + } else { + echo ""; + echo ""; + foreach ($orphans as $row) { + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + echo "
Order IDUser IDServer Namehome_id (missing)StatusEnd Date
".intval($row['order_id'])."".intval($row['user_id'])."".htmlspecialchars($row['home_name'] ?? '')."".htmlspecialchars($row['home_id'] ?? '')."".htmlspecialchars($row['status'] ?? '')."".htmlspecialchars($row['end_date'] ?? 'NULL')."
"; + } + echo "
"; } ?> diff --git a/modules/billing/my_account.php b/modules/billing/my_account.php index 6d1ac48b..5c670726 100644 --- a/modules/billing/my_account.php +++ b/modules/billing/my_account.php @@ -210,7 +210,7 @@ $status_config = [ 'paid' => ['label' => 'Paid Invoices', 'class' => 'paid'], 'completed' => ['label' => 'Completed Invoices', 'class' => 'paid'], 'in-cart' => ['label' => 'In Cart', 'class' => 'pending'], - 'installed' => ['label' => 'Installed/Active', 'class' => 'paid'], + 'installed' => ['label' => 'Active', 'class' => 'paid'], 'expired' => ['label' => 'Expired Invoices', 'class' => 'expired'], 'cancelled' => ['label' => 'Cancelled Invoices', 'class' => 'expired'], ]; @@ -338,7 +338,7 @@ $status_config = [
Renew diff --git a/modules/billing/normalize_billing_order_status.sql b/modules/billing/normalize_billing_order_status.sql new file mode 100644 index 00000000..b366e58f --- /dev/null +++ b/modules/billing/normalize_billing_order_status.sql @@ -0,0 +1,64 @@ +-- normalize_billing_order_status.sql +-- +-- One-time migration: standardise gsp_billing_orders.status to the canonical +-- three-value set used by cron-shop.php, create_servers.php, and the game +-- monitor expiration lookup: +-- +-- Active – server provisioned and billing current +-- Invoiced – renewal invoice open; service still running +-- Expired – invoice unpaid past grace period; server suspended/awaiting deletion +-- +-- Legacy → canonical mapping applied by this script: +-- 'installed' → 'Active' (provisioned via old invoice-first flow) +-- 'paid' → 'Active' (payment captured but before explicit provisioning step) +-- 'suspended' → 'Invoiced' (overdue; renewal invoice was open — maps to Invoiced +-- so cron Step B will expire them on the next run if +-- still unpaid, rather than silently treating them as Active) +-- +-- All other statuses ('in-cart', 'cancelled', 'refunded', 'Active', 'Invoiced', +-- 'Expired') are left unchanged. +-- +-- Compatible with MySQL 5.7+ and MariaDB 10.2+. +-- Table prefix is hardcoded to gsp_ (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` + SET `status` = 'Active' + WHERE `status` = 'installed'; + +-- 'paid' → 'Active' +UPDATE `gsp_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` + SET `status` = 'Invoiced' + WHERE `status` = 'suspended'; + +-- Diagnostic: show any remaining non-canonical status values after migration. +-- 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` + GROUP BY `status` + ORDER BY `status`; + +-- Diagnostic: billing_orders whose home_id references a non-existent server home. +-- These orders will show "No expiration date found" on the game monitor until +-- home_id is corrected (set to the real home_id or to 0 if the server is gone). +SELECT o.`order_id`, + o.`user_id`, + o.`home_name`, + 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` + WHERE o.`home_id` != '0' + AND o.`home_id` != '' + AND sh.`home_id` IS NULL + ORDER BY o.`order_id`; diff --git a/modules/gamemanager/home_handling_functions.php b/modules/gamemanager/home_handling_functions.php index fd58c309..d968971d 100644 --- a/modules/gamemanager/home_handling_functions.php +++ b/modules/gamemanager/home_handling_functions.php @@ -514,15 +514,13 @@ function get_server_billing_expiration_html(int $home_id): string // 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 + // Only canonical active statuses are considered: + // 'Active' – provisioned and current (set by payment capture or admin creation) + // 'Invoiced' – renewal invoice generated; service still running while unpaid // - // Terminal statuses ('Expired', 'in-cart', 'cancelled', 'refunded') are excluded - // because they represent orders with no active service period. + // Terminal statuses ('Expired', 'in-cart', 'cancelled', 'refunded') and legacy + // statuses ('installed', 'paid', 'suspended') are not matched here. + // Run normalize_billing_order_status.sql to migrate any legacy rows first. // // OGP_DB_PREFIX is replaced at runtime by the panel DB wrapper (str_replace). $rows = $db->resultQuery( @@ -530,7 +528,7 @@ function get_server_billing_expiration_html(int $home_id): string FROM OGP_DB_PREFIXbilling_orders WHERE home_id = '" . intval($home_id) . "' AND home_id != '0' - AND status IN ('Active','Invoiced','installed','paid','suspended') + AND status IN ('Active','Invoiced') ORDER BY end_date DESC LIMIT 1" ); diff --git a/modules/user_games/billing_integration.php b/modules/user_games/billing_integration.php index 48ba2b8b..bc931b95 100644 --- a/modules/user_games/billing_integration.php +++ b/modules/user_games/billing_integration.php @@ -5,7 +5,7 @@ * Shared helper for recording admin-created game servers in the billing tables, * so they are treated identically to FREE website orders: * billing_invoices (status='paid', amount=0) - * billing_orders (status='installed', price=0) + * billing_orders (status='Active', price=0) * * This does NOT re-provision the server — the caller (add_home.php) already * created the server via the panel DB layer. We only write the billing ledger @@ -113,7 +113,7 @@ if (!function_exists('admin_register_server_in_billing')) { } // ------------------------------------------------------------------ // - // 4. Insert billing_order (status='installed', already provisioned). // + // 4. Insert billing_order (status='Active', already provisioned). // // ------------------------------------------------------------------ // $order_fields = array( 'user_id' => intval($user_id), @@ -128,7 +128,7 @@ if (!function_exists('admin_register_server_in_billing')) { 'remote_control_password' => '', 'ftp_password' => '', 'home_id' => intval($home_id), - 'status' => 'installed', + 'status' => 'Active', 'order_date' => $now, 'end_date' => $end_date, 'payment_txid' => 'admin-created',