Manage Servers & Services
Invoice History
Edit Site Config
diff --git a/modules/billing/cart.php b/modules/billing/cart.php
index 893e9523..a4798f0b 100644
--- a/modules/billing/cart.php
+++ b/modules/billing/cart.php
@@ -58,7 +58,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['create_free_for']))
$orderId = (int)$_POST['create_free_for'];
if ($orderId > 0) {
// load order to verify ownership/price
- $stmt = $db->prepare("SELECT user_id, price, status, qty, invoice_duration FROM ogp_billing_orders WHERE order_id = ? LIMIT 1");
+ $stmt = $db->prepare("SELECT user_id, price, status, qty, invoice_duration FROM " . $table_prefix . "billing_orders WHERE order_id = ? LIMIT 1");
if ($stmt) {
$stmt->bind_param('i', $orderId);
$stmt->execute();
@@ -106,15 +106,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['create_free_for']))
$finish_date = date('Y-m-d H:i:s');
}
- // Check if finish_date column exists
- $finish_col_exists = false;
- $col_check = mysqli_query($db, "SHOW COLUMNS FROM ogp_billing_orders LIKE 'finish_date'");
- if ($col_check && mysqli_num_rows($col_check) > 0) $finish_col_exists = true;
+ // Check if finish_date column exists (use table prefix)
+ $finish_col_exists = false;
+ $col_check = mysqli_query($db, "SHOW COLUMNS FROM " . $table_prefix . "billing_orders LIKE 'finish_date'");
+ if ($col_check && mysqli_num_rows($col_check) > 0) $finish_col_exists = true;
// Perform update and log results. Use prepared statements when available and fallback to direct query on error.
$updated_rows = 0;
if ($finish_col_exists) {
- $upd = $db->prepare("UPDATE ogp_billing_orders SET status = 'paid', finish_date = ? WHERE order_id = ? LIMIT 1");
+ $upd = $db->prepare("UPDATE " . $table_prefix . "billing_orders SET status = 'paid', finish_date = ? WHERE order_id = ? LIMIT 1");
if ($upd) {
$upd->bind_param('si', $finish_date, $orderId);
$ok = $upd->execute();
@@ -124,13 +124,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['create_free_for']))
} else {
// fallback
$safe_fd = mysqli_real_escape_string($db, $finish_date);
- $q = "UPDATE ogp_billing_orders SET status = 'paid', finish_date = '$safe_fd' WHERE order_id = " . intval($orderId) . " LIMIT 1";
+ $q = "UPDATE " . $table_prefix . "billing_orders SET status = 'paid', finish_date = '$safe_fd' WHERE order_id = " . intval($orderId) . " LIMIT 1";
$resq = mysqli_query($db, $q);
if (!$resq) site_log_warn('free_create_update_failed_query', ['error'=>mysqli_error($db), 'sql'=>$q]);
else $updated_rows = mysqli_affected_rows($db);
}
} else {
- $upd = $db->prepare("UPDATE ogp_billing_orders SET status = 'paid' WHERE order_id = ? LIMIT 1");
+ $upd = $db->prepare("UPDATE " . $table_prefix . "billing_orders SET status = 'paid' WHERE order_id = ? LIMIT 1");
if ($upd) {
$upd->bind_param('i', $orderId);
$ok = $upd->execute();
@@ -138,7 +138,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['create_free_for']))
$updated_rows = $upd->affected_rows;
$upd->close();
} else {
- $q = "UPDATE ogp_billing_orders SET status = 'paid' WHERE order_id = " . intval($orderId) . " LIMIT 1";
+ $q = "UPDATE " . $table_prefix . "billing_orders SET status = 'paid' WHERE order_id = " . intval($orderId) . " LIMIT 1";
$resq = mysqli_query($db, $q);
if (!$resq) site_log_warn('free_create_update_failed_query', ['error'=>mysqli_error($db), 'sql'=>$q]);
else $updated_rows = mysqli_affected_rows($db);
@@ -248,20 +248,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_single'])) {
$stmt->bind_param("ii", $order_id, $user_id);
$stmt->execute();
$stmt->bind_result($status);
- if ($stmt->fetch() && strtolower($status) === 'renew') {
- $stmt->close();
- // Set status to 'expired' if currently 'renew'
- $update = $db->prepare("UPDATE ogp_billing_orders SET status = 'expired' WHERE order_id = ? AND user_id = ?");
- $update->bind_param("ii", $order_id, $user_id);
- $update->execute();
- $update->close();
+ if ($stmt->fetch() && strtolower($status) === 'renew') {
+ $stmt->close();
+ // If user removes a renewal from their cart, revert the order back to 'installed'
+ $update = $db->prepare("UPDATE ogp_billing_orders SET status = 'installed' WHERE order_id = ? AND user_id = ?");
+ $update->bind_param("ii", $order_id, $user_id);
+ $update->execute();
+ // Log revert action to panel logger
+ if (isset($db) && method_exists($db, 'logger')) {
+ $db->logger("USER-CART: User " . intval($user_id) . " reverted renew for order " . intval($order_id));
+ }
+ $update->close();
} else {
$stmt->close();
// Otherwise, delete the order
$delete = $db->prepare("DELETE FROM ogp_billing_orders WHERE order_id = ? AND user_id = ?");
$delete->bind_param("ii", $order_id, $user_id);
- $delete->execute();
- $delete->close();
+ $delete->execute();
+ // Log deletion to panel logger
+ if (isset($db) && method_exists($db, 'logger')) {
+ $db->logger("USER-CART: User " . intval($user_id) . " deleted order " . intval($order_id));
+ }
+ $delete->close();
}
}
}
@@ -318,6 +326,15 @@ if ($db){
$ |
|
+ isset($row['home_id']) ? (string)$row['home_id'] : ('order'.$row['order_id']),
+ 'amount' => number_format($rowtotal, 2, '.', ''),
+ 'order_id' => intval($row['order_id'])
+ ];
+ ?>
-
Admin: force-create a paid record for testing.
+
Admin: force-create a paid record for testing.
@@ -401,6 +418,11 @@ $invoiceId = 'INV-' . date('Ymd-His') . '-' . bin2hex(random_bytes(3));
// A short custom reference derived from your line items (<= 127 chars for PayPal)
$customHash = substr(strtoupper(sha1(json_encode($invoice))), 0, 16);
$customId = "INVREF-$customHash";
+// If the cart contains a single order, set custom_id to the numeric order id so webhooks
+// can match the order directly (payment_success matches numeric custom -> order_id).
+if (is_array($invoice) && count($invoice) === 1 && !empty($invoice[0]['order_id'])) {
+ $customId = (string) intval($invoice[0]['order_id']);
+}
// Text on the PayPal side
$description = 'Game server order (' . count($lineItems) . ' item' . (count($lineItems)===1?'': 's') . ')';
diff --git a/modules/billing/cron-shop.php b/modules/billing/cron-shop.php
index c26d981d..b9103ac2 100644
--- a/modules/billing/cron-shop.php
+++ b/modules/billing/cron-shop.php
@@ -20,6 +20,19 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
+ *
+ *
+
+Complete Status Flow:
+in-cart - User added to cart, not yet paid
+renew - Renewal order in cart
+paid - Payment received, awaiting server creation
+installed - ✅ Active/Running (server provisioned and operational)
+invoiced - Invoice generated, payment due (7 days before expiration)
+suspended - Server stopped, payment overdue
+deleted - Server permanently removed (7 days after suspension)
+expired - Order has expired
+unknown - Error/undefined state
*/
chdir(realpath(dirname(__FILE__))); /* Change to the current file path */
@@ -68,7 +81,16 @@ else
foreach($user_homes as $user_home)
{
- $user_id = $user_home['user_id'];
+ // Developer note:
+ // In future we may want to change the renewal/invoice strategy so that a
+ // new order record is created for the renewal (leaving the original order
+ // intact) instead of mutating the existing order's status/finish_date.
+ // Creating a separate renewal order gives a clearer, immutable purchase
+ // history and simplifies auditing. For now this cron job continues to
+ // update the existing order (change status/finish_date) as implemented
+ // below.
+
+ $user_id = $user_home['user_id'];
$home_id = $user_home['home_id'];
diff --git a/modules/billing/css/header.css b/modules/billing/css/header.css
index af64f4df..610e7418 100644
--- a/modules/billing/css/header.css
+++ b/modules/billing/css/header.css
@@ -90,6 +90,21 @@ input, textarea, select, button { color: #fff; background: #11141f; border: 1px
#gsw-site a.gsw-btn:hover,
#gsw-site button.gsw-btn:hover{transform:translateY(-2px);color:#fff !important;text-decoration:none;}
+/* Renew button: slightly smaller but matching gradient, used on My Account cards */
+#gsw-site .renew-btn, #gsw-site a.renew-btn, #gsw-site button.renew-btn{
+ display:inline-block;
+ padding:8px 14px;
+ background:linear-gradient(135deg,#f59e0b 0%,#ef4444 100%) !important;
+ color:#fff !important;
+ text-decoration:none;
+ border-radius:8px;
+ font-weight:700;
+ transition:transform 0.12s;
+ border:none;
+ cursor:pointer;
+}
+#gsw-site .renew-btn:hover, #gsw-site a.renew-btn:hover, #gsw-site button.renew-btn:hover{transform:translateY(-2px);}
+
#gsw-site .gsw-btn-secondary,
#gsw-site a.gsw-btn-secondary{display:inline-block;padding:10px 16px;background:rgba(255,255,255,0.06);color:#fff !important;text-decoration:none;border-radius:8px;border:1px solid rgba(255,255,255,0.06);cursor:pointer;}
#gsw-site .gsw-btn-secondary:hover,
@@ -152,3 +167,135 @@ input, textarea, select, button { color: #fff; background: #11141f; border: 1px
.ml-8{margin-left:8px}
.flex-row-gap{display:flex;gap:8px;align-items:center}
+/* Account page styles */
+.account-container{max-width:1000px;margin:20px auto;padding:20px}
+.account-section{background:rgba(0,0,0,0.25);padding:20px;border-radius:8px;margin-bottom:20px}
+.account-section h2{margin:0 0 15px 0;font-size:1.3rem;color:#fff;border-bottom:2px solid rgba(255,255,255,0.1);padding-bottom:10px}
+.account-info-grid{display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-bottom:15px}
+.account-info-item{padding:10px;background:rgba(255,255,255,0.03);border-radius:6px}
+.account-info-label{font-weight:600;color:rgba(255,255,255,0.7);font-size:0.9rem;margin-bottom:5px}
+.account-info-value{color:#fff;font-size:1rem}
+.account-edit-summary{cursor:pointer;color:#667eea;font-weight:600;margin-top:10px}
+
+/* Form styles */
+.form-group{margin-bottom:15px}
+.form-group label{display:block;margin-bottom:5px;color:#fff;font-weight:500}
+.form-group input{width:100%;padding:10px;border:1px solid rgba(255,255,255,0.1);border-radius:6px;background:rgba(0,0,0,0.3);color:#fff}
+
+/* Alert messages */
+.alert{padding:12px 16px;border-radius:8px;margin-bottom:20px;font-size:0.95rem}
+.alert-error{background-color:rgba(255,0,0,0.2);border:1px solid rgba(255,0,0,0.3);color:#ffcccc}
+.alert-success{background-color:rgba(0,255,0,0.2);border:1px solid rgba(0,255,0,0.3);color:#ccffcc}
+
+/* Server item cards */
+.server-item{background:rgba(255,255,255,0.03);padding:15px;border-radius:6px;margin-bottom:10px;border-left:3px solid #667eea}
+.server-name{font-size:1.1rem;font-weight:600;color:#fff;margin-bottom:8px}
+.server-details{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:10px}
+.server-detail{font-size:0.9rem}
+.server-detail-label{color:rgba(255,255,255,0.6)}
+.server-detail-value{color:#fff;font-weight:500}
+
+/* Invoice items */
+.invoice-item{background:rgba(255,255,255,0.03);padding:12px 15px;border-radius:6px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center}
+.invoice-id{font-weight:600;color:#fff}
+.invoice-amount{color:#10b981;font-weight:600}
+.invoice-status{padding:4px 10px;border-radius:4px;font-size:0.85rem;font-weight:600}
+.invoice-status-paid{background:rgba(16,185,129,0.2);color:#10b981}
+.invoice-status-pending{background:rgba(245,158,11,0.2);color:#f59e0b}
+.invoice-date{color:rgba(255,255,255,0.6);font-size:0.9rem}
+
+/* Login placeholder for non-logged-in users */
+.login-placeholder{padding:12px;background:rgba(255,255,255,0.03);border-radius:8px;color:#fff}
+.login-placeholder a{color:#cfe6ff;text-decoration:underline}
+
+/* No data state */
+.no-data{text-align:center;padding:30px;color:rgba(255,255,255,0.6)}
+
+/* Service description text */
+.service-desc{color:gray;width:230px}
+.service-desc-wide{color:gray;width:280px}
+.service-textarea{resize:none;width:230px;height:132px}
+
+/* Admin helpers */
+.admin-note{font-size:11px;color:#666;margin-top:4px}
+.admin-flex-wrap{display:flex;gap:12px;flex-wrap:wrap;margin-top:12px}
+
+@media (max-width:768px){
+ .account-info-grid{grid-template-columns:1fr}
+}
+
+/* Server status and utility classes */
+#gsw-site .text-success {
+ color: #10b981 !important;
+ font-weight: 600 !important;
+}
+
+#gsw-site .text-danger {
+ color: #ef4444 !important;
+ font-weight: 600 !important;
+}
+
+#gsw-site .text-muted {
+ color: rgba(255,255,255,0.7) !important;
+}
+
+#gsw-site .text-center {
+ text-align: center !important;
+}
+
+#gsw-site .mb-20 {
+ margin-bottom: 20px !important;
+}
+
+#gsw-site .server-notes {
+ padding-left: 40px !important;
+ font-size: 0.9rem !important;
+ color: rgba(255,255,255,0.7) !important;
+}
+
+/* Status badges */
+#gsw-site .status-badge {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-size: 0.85rem;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+#gsw-site .status-online {
+ background-color: rgba(16, 185, 129, 0.2);
+ color: #10b981;
+}
+
+#gsw-site .status-offline {
+ background-color: rgba(239, 68, 68, 0.2);
+ color: #ef4444;
+}
+
+#gsw-site .status-maintenance {
+ background-color: rgba(251, 191, 36, 0.2);
+ color: #fbbf24;
+}
+
+#gsw-site .status-unknown {
+ background-color: rgba(156, 163, 175, 0.2);
+ color: #9ca3af;
+}
+
+/* Form radio labels in renewal page */
+#gsw-site .form-group label {
+ display: block;
+ margin-bottom: 10px;
+ cursor: pointer;
+ padding: 12px;
+ border: 2px solid #e1e8ed;
+ border-radius: 8px;
+ background: rgba(255,255,255,0.05);
+ transition: background 0.2s ease;
+}
+
+#gsw-site .form-group label:hover {
+ background: rgba(255,255,255,0.1);
+}
+
diff --git a/modules/billing/site.zip b/modules/billing/files.zip
similarity index 98%
rename from modules/billing/site.zip
rename to modules/billing/files.zip
index 4c360fbb..e5dccf60 100644
Binary files a/modules/billing/site.zip and b/modules/billing/files.zip differ
diff --git a/modules/billing/includes/config.inc.php b/modules/billing/includes/config.inc.php
deleted file mode 100644
index 4401518c..00000000
--- a/modules/billing/includes/config.inc.php
+++ /dev/null
@@ -1,35 +0,0 @@
-
diff --git a/modules/billing/includes/menu.php b/modules/billing/includes/menu.php
index 0ad4b635..a8c6d168 100644
--- a/modules/billing/includes/menu.php
+++ b/modules/billing/includes/menu.php
@@ -71,16 +71,12 @@ if ($is_logged_in) {
diff --git a/modules/billing/my_account.php b/modules/billing/my_account.php
index 0e2656e5..31863b3d 100644
--- a/modules/billing/my_account.php
+++ b/modules/billing/my_account.php
@@ -4,186 +4,28 @@
-
b before a
+ if ($bId === null) return -1; // a has id -> a before b
+ return $bId - $aId; // numeric desc
+ }
+
+ // Fallback: newest timestamp first
return strtotime($b['ts'] ?? 0) - strtotime($a['ts'] ?? 0);
});
@@ -325,7 +197,15 @@ $previous_invoices = array_filter($invoices, function($inv) {
?>
-