diff --git a/modules/billing/admin_invoices.php b/modules/billing/admin_invoices.php
new file mode 100644
index 00000000..a06f3018
--- /dev/null
+++ b/modules/billing/admin_invoices.php
@@ -0,0 +1,152 @@
+
+
+
+
+
+ Admin — Invoices
+
+
+
+
+
+
+
+
Admin — All Invoices
+
+
+ ✓ Invoice # updated successfully.
+
+
+
+
+
No invoices found.
+
+
+
+
+ | Order ID |
+ User |
+ Home ID |
+ Home Name |
+ IP |
+ Price |
+ Duration |
+ Status |
+ Created |
+ Finish Date |
+ Actions |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ $ |
+ |
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/billing/api/capture_order.php b/modules/billing/api/capture_order.php
index 7cb57d86..919aab3a 100644
--- a/modules/billing/api/capture_order.php
+++ b/modules/billing/api/capture_order.php
@@ -37,10 +37,49 @@ curl_close($ch);
if ($http !== 201 && $http !== 200) { http_response_code($http); echo $res; exit; }
-$payload = json_decode($res, true);
-$status = $payload['status'] ?? 'UNKNOWN';
-$txnId = $payload['purchase_units'][0]['payments']['captures'][0]['id'] ?? null;
+// Parse the capture response
+$captureData = json_decode($res, true);
+$captureStatus = $captureData['status'] ?? '';
-echo json_encode(['status'=>$status, 'txn_id'=>$txnId]);
+// If capture was successful, immediately update the order status to 'paid'
+if ($captureStatus === 'COMPLETED') {
+ // Extract custom_id which contains the order_id
+ $customId = $captureData['purchase_units'][0]['payments']['captures'][0]['custom_id'] ?? null;
+ $txnId = $captureData['purchase_units'][0]['payments']['captures'][0]['id'] ?? null;
+
+ if ($customId && is_numeric($customId)) {
+ // Connect to DB and update order status
+ $db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name);
+ if ($db) {
+ $orderId = intval($customId);
+
+ // Calculate finish_date based on qty and invoice_duration
+ $qtyRes = mysqli_query($db, "SELECT qty, invoice_duration FROM ogp_billing_orders WHERE order_id = $orderId LIMIT 1");
+ $finish_date = null;
+ if ($qtyRes && $row = mysqli_fetch_assoc($qtyRes)) {
+ $qty = intval($row['qty'] ?? 1);
+ $duration = strtolower(trim($row['invoice_duration'] ?? 'month'));
+ $months = (strpos($duration, 'year') !== false) ? ($qty * 12) : $qty;
+ if ($months > 0) {
+ $dt = new DateTime('now');
+ $dt->modify('+' . $months . ' months');
+ $finish_date = $dt->format('Y-m-d H:i:s');
+ }
+ }
+
+ // Update order status to 'paid'
+ $sql = "UPDATE ogp_billing_orders SET status = 'paid', payment_txid = '" . mysqli_real_escape_string($db, $txnId) . "', paid_ts = NOW()";
+ if ($finish_date) {
+ $sql .= ", finish_date = '" . mysqli_real_escape_string($db, $finish_date) . "'";
+ }
+ $sql .= " WHERE order_id = $orderId AND status = 'in-cart' LIMIT 1";
+ mysqli_query($db, $sql);
+ mysqli_close($db);
+ }
+ }
+}
+
+// Return the full PayPal response for proper processing
+echo $res;
?>
diff --git a/modules/billing/api/create_order.php b/modules/billing/api/create_order.php
index 9f31f310..253f2d0a 100644
--- a/modules/billing/api/create_order.php
+++ b/modules/billing/api/create_order.php
@@ -47,6 +47,17 @@ if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_
$access = json_decode($tok, true)['access_token'] ?? null;
if (!$access) { http_response_code(500); echo json_encode(['error'=>'oauth_no_token']); exit; }
+// Update site base URL to exclude 'modules/billing'
+$siteBaseUrl = 'http://gameservers.world';
+
+// Ensure return_url and cancel_url are absolute URLs (relative to site root)
+if (strpos($return_url, 'http') !== 0) {
+ $return_url = $siteBaseUrl . '/' . ltrim($return_url, '/');
+}
+if (strpos($cancel_url, 'http') !== 0) {
+ $cancel_url = $siteBaseUrl . '/' . ltrim($cancel_url, '/');
+}
+
$purchaseUnit = [
'amount' => [ 'currency_code' => $currency, 'value' => $amount_value ],
'description' => $description,
@@ -64,6 +75,18 @@ $body = [
'application_context' => [ 'return_url'=>$return_url, 'cancel_url'=>$cancel_url, 'user_action'=>'PAY_NOW' ]
];
+// Log the payload for debugging
+$logDir = __DIR__ . '/../data';
+@mkdir($logDir, 0775, true);
+$logFile = $logDir . '/create_order_payload.log';
+$logEntry = date('Y-m-d H:i:s') . "\n" . json_encode($body, JSON_PRETTY_PRINT) . "\n\n";
+@file_put_contents($logFile, $logEntry, FILE_APPEND);
+
+// Log corrected URLs for debugging
+$logFile = $logDir . '/corrected_urls.log';
+$logEntry = date('Y-m-d H:i:s') . "\nReturn URL: $return_url\nCancel URL: $cancel_url\n\n";
+@file_put_contents($logFile, $logEntry, FILE_APPEND);
+
$ch = curl_init("$api/v2/checkout/orders");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
@@ -75,7 +98,18 @@ $res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
-if ($http !== 201) { http_response_code($http); echo $res; exit; }
+if ($http !== 201) {
+ // Log error for debugging
+ $logDir = __DIR__ . '/../data';
+ @mkdir($logDir, 0775, true);
+ $logFile = $logDir . '/create_order_errors.log';
+ $logEntry = date('Y-m-d H:i:s') . " HTTP $http: " . substr($res, 0, 500) . "\n";
+ @file_put_contents($logFile, $logEntry, FILE_APPEND);
+
+ http_response_code($http);
+ echo $res;
+ exit;
+}
echo $res;
?>
diff --git a/modules/billing/cart.php b/modules/billing/cart.php
index 537fda94..d639f02f 100644
--- a/modules/billing/cart.php
+++ b/modules/billing/cart.php
@@ -428,10 +428,12 @@ if (is_array($invoice) && count($invoice) === 1 && !empty($invoice[0]['order_id'
$description = 'Game server order (' . count($lineItems) . ' item' . (count($lineItems)===1?'': 's') . ')';
// URLs
-// URLs - since the billing "website" root is the files in modules/billing,
-// return.php lives alongside cart.php so use relative paths.
-$returnUrl = 'return.php?invoice=' . urlencode($invoiceId);
-$cancelUrl = 'return.php?invoice=' . urlencode($invoiceId) . '&cancel=1';
+// Define the site base URL
+$siteBaseUrl = 'http://gameservers.world/modules/billing';
+
+// Generate absolute URLs for return and cancel
+$returnUrl = $siteBaseUrl . '/return.php?invoice=' . urlencode($invoiceId);
+$cancelUrl = $siteBaseUrl . '/return.php?invoice=' . urlencode($invoiceId) . '&cancel=1';
// API base (relative) - point to billing module API endpoints
$apiBase = 'api';
@@ -439,6 +441,19 @@ $apiBase = 'api';
+
+
+
+ Debug Info:
+ Grand Total: $
+ Invoice Items:
+ Line Items:
+ Amount:
+ Invoice ID:
+ Custom ID:
+
+
+
@@ -446,14 +461,14 @@ $apiBase = 'api';
(function(){
const statusEl = document.getElementById('pp-status');
- // Values from PHP
- const amount = "= $amount ?>";
- const currency = "= $currency ?>";
- const invoice_id = "= $invoiceId ?>";
- const custom_id = "= $customId ?>";
- const description = "= htmlspecialchars($description, ENT_QUOTES) ?>";
- const return_url = "= $returnUrl ?>";
- const cancel_url = "= $cancelUrl ?>";
+ // Values from PHP - use json_encode for proper JavaScript escaping
+ const amount = ;
+ const currency = ;
+ const invoice_id = ;
+ const custom_id = ;
+ const description = ;
+ const return_url = ;
+ const cancel_url = ;
// Line items (serverID + per-item amount) for your records and webhook correlation
const line_invoices = ;
@@ -461,8 +476,17 @@ $apiBase = 'api';
// PayPal "items" for purchase_units (shows on PayPal + returns in webhook under purchase_units)
const items = ;
+ // Debug logging
+ console.log('PayPal cart debug:', {
+ amount, currency, invoice_id, custom_id, description,
+ line_invoices_count: line_invoices.length,
+ items_count: items.length,
+ return_url, cancel_url
+ });
+
function setStatus(msg){ if(statusEl) statusEl.textContent = msg; }
+
paypal.Buttons({
createOrder: function() {
setStatus('Creating order…');
@@ -477,11 +501,24 @@ $apiBase = 'api';
line_invoices // your raw cart detail, persisted in your DB if you choose
})
})
- .then(res => res.json())
+ .then(res => {
+ if (!res.ok) {
+ return res.text().then(errText => {
+ throw new Error('API error ' + res.status + ': ' + errText.substring(0, 200));
+ });
+ }
+ return res.json();
+ })
.then(data => {
- if (!data.id) { throw new Error(data.error || 'No order id'); }
+ if (!data.id) {
+ throw new Error(JSON.stringify(data).substring(0, 200) || 'No order id');
+ }
setStatus('Order created.');
return data.id;
+ })
+ .catch(err => {
+ setStatus('PayPal error: ' + err.message);
+ throw err;
});
},
diff --git a/modules/billing/css/header.css b/modules/billing/css/header.css
index 51e7208c..0e3bc377 100644
--- a/modules/billing/css/header.css
+++ b/modules/billing/css/header.css
@@ -202,6 +202,7 @@ input, textarea, select, button { color: #fff; background: #11141f; border: 1px
.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-status-expired{background:rgba(239,68,68,0.2);color:#ef4444}
.invoice-date{color:rgba(255,255,255,0.6);font-size:0.9rem}
/* Login placeholder for non-logged-in users */
diff --git a/modules/billing/my_account.php b/modules/billing/my_account.php
index 31863b3d..62a3fdfe 100644
--- a/modules/billing/my_account.php
+++ b/modules/billing/my_account.php
@@ -187,13 +187,26 @@ 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';
-});
+// Organize invoices by status
+$invoices_by_status = [];
+foreach ($invoices as $inv) {
+ $status = strtolower($inv['status'] ?? 'pending');
+ if (!isset($invoices_by_status[$status])) {
+ $invoices_by_status[$status] = [];
+ }
+ $invoices_by_status[$status][] = $inv;
+}
+
+// Define status display order and labels
+$status_config = [
+ 'pending' => ['label' => 'Pending Invoices', 'class' => 'pending'],
+ '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'],
+ 'expired' => ['label' => 'Expired Invoices', 'class' => 'expired'],
+ 'cancelled' => ['label' => 'Cancelled Invoices', 'class' => 'expired'],
+];
?>
@@ -333,45 +346,35 @@ $previous_invoices = array_filter($invoices, function($inv) {
-
-
-
-
Current Invoices Due
-
-
-
-
Invoice #
-
+
+
+ $status_info): ?>
+
+
-
-
- Pending
-
-
+
-
+
+
+
Invoices
+
No invoices found.
+
-
-
-
-
Previous Invoices
-
-
-
-
-
-
No previous invoices found.
-
-