diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b97ca1fd..3b8f0c90 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -12,6 +12,7 @@ - `_website/` — canonical website storefront and Checkout/Webhooks flow. - `modules/config_games/server_configs/` — authoritative game catalog XMLs (all supported games live here). - `modules/` — panel modules (legacy `billing/` exists; its **schema** is authoritative for multi-remote, but the **pages** are deprecated). +- `modules/billing/` — frontend website for selling gameservers to customers. Can interface with panel from same machine or external web host via MySQL tables. Uses `gameservers_website` session namespace (separate from panel sessions). - `includes/` — panel configuration and DB connectors. - `ogp_api.php` — internal API entry point for panel-side actions. - `api/` — Payment-related API code if present in this branch (previously under `paypal/` or `payments/`). @@ -26,6 +27,8 @@ ## 3) Scope & principles - **Website ↔ Panel on the same host.** Website uses the **panel DB for authentication** and the **panel’s internal APIs** for provisioning. **Sessions remain separate** (website session ≠ panel session). +- **Billing module flexibility.** The `modules/billing/` frontend can run on the **same machine as the panel** or on an **external web host**, interfacing primarily via MySQL table edits. All interaction with panel DB happens through direct MySQL queries using credentials in `modules/billing/includes/config.inc.php`. +- **Billing module flexibility.** The `modules/billing/` frontend can run on the **same machine as the panel** or on an **external web host**, interfacing primarily via MySQL table edits. All interaction with panel DB happens through direct MySQL queries using credentials in `modules/billing/includes/config.inc.php`. - **Catalog = XML.** Enable **every game** present under `modules/config_games/server_configs/`. The website reads those XMLs for ports, params, install/update metadata. New XMLs should become available without code changes. - **Regions/Nodes = panel DB.** Regions and nodes are configured in the panel and must be **queried live** from the panel DB. Never hardcode or mirror region lists on the website. - **Slotless model.** Pricing/UX must not enforce slot caps. If an engine requires a player count parameter, set a safe high default and surface engine limits transparently if they exist. diff --git a/modules/billing/css/header.css b/modules/billing/css/header.css index 757eb0d7..af64f4df 100644 --- a/modules/billing/css/header.css +++ b/modules/billing/css/header.css @@ -24,6 +24,8 @@ .gsw-header-nav{display:flex;gap:22px;align-items:center;} .gsw-nav-link{color:#fff;text-decoration:none;font-size:0.98rem;transition:opacity 0.2s;padding:6px 8px;border-radius:6px;} .gsw-nav-link:hover{opacity:0.9;text-decoration:underline;background:rgba(255,255,255,0.03);} +/* My Account link styling - larger font in middle of menu */ +.gsw-nav-link-myaccount{font-size:1.15rem;font-weight:600;padding:6px 12px;} .gsw-user-info{color:#fff;font-size:0.95rem;margin-right:8px;} diff --git a/modules/billing/includes/config.inc.php b/modules/billing/includes/config.inc.php new file mode 100644 index 00000000..4401518c --- /dev/null +++ b/modules/billing/includes/config.inc.php @@ -0,0 +1,35 @@ + diff --git a/modules/billing/includes/menu.php b/modules/billing/includes/menu.php index 819aa6b2..0ad4b635 100644 --- a/modules/billing/includes/menu.php +++ b/modules/billing/includes/menu.php @@ -90,6 +90,7 @@ if ($is_logged_in) { Home Game Servers + My Account My Servers Cart 0) echo ' ' . intval($cart_count) . ''; ?> + + Login Register diff --git a/modules/billing/my_account.php b/modules/billing/my_account.php new file mode 100644 index 00000000..0e2656e5 --- /dev/null +++ b/modules/billing/my_account.php @@ -0,0 +1,491 @@ + + + + + + My Account - GameServers.World + + + + 0) { + $query = "SELECT user_id, users_login, users_email, users_fname, users_lname FROM ogp_users WHERE user_id = $user_id LIMIT 1"; + $result = mysqli_query($db, $query); + if ($result && mysqli_num_rows($result) === 1) { + $user_info = mysqli_fetch_assoc($result); + } +} + +// Handle password change +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['change_password'])) { + $current_password = $_POST['current_password'] ?? ''; + $new_password = $_POST['new_password'] ?? ''; + $confirm_password = $_POST['confirm_password'] ?? ''; + + if (empty($current_password) || empty($new_password) || empty($confirm_password)) { + $error_message = 'All password fields are required.'; + } elseif ($new_password !== $confirm_password) { + $error_message = 'New passwords do not match.'; + } elseif (strlen($new_password) < 6) { + $error_message = 'New password must be at least 6 characters long.'; + } else { + // Verify current password (using MD5 as per panel legacy) + $current_hash = md5($current_password); + $verify_query = "SELECT user_id FROM ogp_users WHERE user_id = $user_id AND users_passwd = '$current_hash' LIMIT 1"; + $verify_result = mysqli_query($db, $verify_query); + + if ($verify_result && mysqli_num_rows($verify_result) === 1) { + // Update password + $new_hash = md5($new_password); + $update_query = "UPDATE ogp_users SET users_passwd = '$new_hash' WHERE user_id = $user_id LIMIT 1"; + if (mysqli_query($db, $update_query)) { + $success_message = 'Password changed successfully!'; + } else { + $error_message = 'Failed to update password. Please try again.'; + } + } else { + $error_message = 'Current password is incorrect.'; + } + } +} + +// Handle account info update +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_info'])) { + $fname = mysqli_real_escape_string($db, trim($_POST['fname'] ?? '')); + $lname = mysqli_real_escape_string($db, trim($_POST['lname'] ?? '')); + $email = mysqli_real_escape_string($db, trim($_POST['email'] ?? '')); + + if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) { + $error_message = 'Invalid email address.'; + } else { + $update_query = "UPDATE ogp_users SET users_fname = '$fname', users_lname = '$lname', users_email = '$email' WHERE user_id = $user_id LIMIT 1"; + if (mysqli_query($db, $update_query)) { + $success_message = 'Account information updated successfully!'; + // Refresh user info + $query = "SELECT user_id, users_login, users_email, users_fname, users_lname FROM ogp_users WHERE user_id = $user_id LIMIT 1"; + $result = mysqli_query($db, $query); + if ($result && mysqli_num_rows($result) === 1) { + $user_info = mysqli_fetch_assoc($result); + } + } else { + $error_message = 'Failed to update account information. Please try again.'; + } + } +} + +// Fetch user's game servers from billing_orders +$servers_query = "SELECT + o.order_id, + o.home_name, + o.status, + o.price, + o.invoice_duration, + o.created_at, + bs.service_name, + rs.remote_server_name + FROM ogp_billing_orders o + LEFT JOIN ogp_billing_services bs ON o.service_id = bs.service_id + LEFT JOIN ogp_remote_servers rs ON o.remote_server_id = rs.remote_server_id + WHERE o.user_id = $user_id + ORDER BY o.created_at DESC"; +$servers_result = mysqli_query($db, $servers_query); + +// Fetch invoices (from data directory JSON files) +$dataDir = (isset($SITE_DATA_DIR) && $SITE_DATA_DIR) ? $SITE_DATA_DIR : realpath(__DIR__ . '/') . DIRECTORY_SEPARATOR . 'data'; +$invoices = []; +if (is_dir($dataDir)) { + foreach (glob($dataDir . '/*.json') as $file) { + $j = json_decode(file_get_contents($file), true); + if (!$j || !is_array($j)) continue; + + // Try to match by user email or user_id in custom field + $match = false; + if ($user_info && !empty($user_info['users_email'])) { + if (!empty($j['payer']) && stripos($j['payer'], $user_info['users_email']) !== false) $match = true; + if (!$match && !empty($j['custom']) && stripos($j['custom'], $user_info['users_email']) !== false) $match = true; + } + + if ($match) { + $invoices[] = $j; + } + } +} + +// Sort invoices by date (newest first) +usort($invoices, function($a, $b) { + return strtotime($b['ts'] ?? 0) - strtotime($a['ts'] ?? 0); +}); + +// Separate current (pending) and previous (paid) invoices +$current_invoices = array_filter($invoices, function($inv) { + return strtolower($inv['status'] ?? '') === 'pending' || empty($inv['status']); +}); +$previous_invoices = array_filter($invoices, function($inv) { + return strtolower($inv['status'] ?? '') === 'paid' || strtolower($inv['status'] ?? '') === 'completed'; +}); + +?> + +
+ +
+ + + +
+ + + +
+

Account Information

+ + + + +
+ Edit Account Information +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
Unable to load account information.
+ +
+ + +
+

Change Password

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + +
+

My Game Servers

+ 0): ?> + +
+
+
+
+ Game: + +
+
+ Location: + +
+
+ Status: + +
+
+ Price: + $/ +
+
+ Created: + +
+
+
+ + +
+

You don't have any game servers yet.

+ Browse Game Servers +
+ +
+ + + +
+

Current Invoices Due

+ +
+
+
Invoice #
+
+
+
+ + Pending +
+
+ +
+ + + +
+

Previous Invoices

+ + +
+
+
Invoice #
+
+
+
+ + Paid +
+
+ + +
No previous invoices found.
+ +
+
+ + + + + +