website fix

This commit is contained in:
Frank Harris 2025-10-22 10:03:37 -04:00
parent e14794bc59
commit 309d08497b
58 changed files with 1690 additions and 363 deletions

68
_website/admin.php Normal file
View file

@ -0,0 +1,68 @@
<?php
// Admin landing page
require_once(__DIR__ . '/includes/admin_auth.php');
require_once(__DIR__ . '/includes/config.inc.php');
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php');
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Admin Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/header.css">
</head>
<body>
<div class="container-wide panel">
<h1>Admin Dashboard</h1>
<p>Welcome to the admin area. From here you can manage servers, payments, and site settings.</p>
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:12px;">
<a class="btn-primary" href="adminserverlist.php">Manage Servers & Services</a>
<a class="btn-primary" href="./invoices.php">Invoice History</a>
<a class="btn-primary" href="admin_config.php">Edit Site Config</a>
</div>
<hr>
<h3>Quick usage notes</h3>
<ul>
<li>The <strong>Manage Servers & Services</strong> page allows enabling/disabling nodes and editing service rows.</li>
<li>The <strong>Invoice History</strong> page reads JSON payment records from <code><?php echo h($SITE_DATA_DIR); ?></code>.</li>
<li>The <strong>Edit Site Config</strong> page edits <code>_website/includes/config.inc.php</code>. Edits create a timestamped backup before saving.</li>
</ul>
<h3>Sandbox account (testing)</h3>
<p>Use PayPal sandbox credentials when testing payments. Set your sandbox <code>client_id</code> and <code>client_secret</code> in the runtime config that the payment handlers use (for this site those are in the respective files under <code>_website/paypal/</code> and <code>_website/payments/</code> or in a central config if you moved credentials).</p>
<ul>
<li>Create a sandbox business account at <a href="https://developer.paypal.com">PayPal Developer</a> and obtain a sandbox client ID/secret.</li>
<li>Update the payment handler config and restart the webserver if required.</li>
<li>Run a checkout using the PayPal JS button on the checkout page after payment completes, the webhook will record a JSON file into <code><?php echo h($SITE_DATA_DIR); ?></code>.</li>
<li>If you need to simulate a webhook locally, drop a JSON file with the same schema into the <code>data/</code> folder (we added a sample: <code>SIMULATED-WEBHOOK-*.json</code>).</li>
</ul>
<h3>Payments: high-level program flow</h3>
<ol>
<li>User adds an item and proceeds to checkout (<code>_website/cart.php</code>).</li>
<li>The checkout page renders the PayPal JS SDK and calls server-side endpoints (create_order/capture_order).</li>
<li>After a successful capture, PayPal sends a webhook event to <code>_website/webhook.php</code> (or the equivalent handler under <code>_website/paypal/</code>).</li>
<li>The webhook verifies the signature, fetches any missing order details, and writes a JSON record to the <code>data/</code> directory (this powers <code>invoices.php</code> and <code>return.php</code>).</li>
<li>On successful payment we mark the order as PAID in the JSON and the site UI (invoices/returns) reads those JSONs to render receipts.</li>
<li>Admin pages can view invoices at <code>./invoices.php</code> and reconcile or trigger further provisioning via internal panel APIs.</li>
</ol>
<h3>Environment</h3>
<table class="cart-table">
<tr><th>Site Base URL</th><td><?php echo h($SITE_BASE_URL ?: '(empty — using relative paths)'); ?></td></tr>
<tr><th>Data directory</th><td><?php echo h($SITE_DATA_DIR); ?></td></tr>
<tr><th>PHP SAPI</th><td><?php echo h(PHP_SAPI); ?></td></tr>
<tr><th>Writable?</th><td><?php echo is_writable(__DIR__ . '/data') ? 'yes' : 'no'; ?></td></tr>
</table>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body>
</html>

103
_website/admin_config.php Normal file
View file

@ -0,0 +1,103 @@
<?php
// Admin config editor — lightweight editor for _website/includes/config.inc.php
require_once(__DIR__ . '/includes/admin_auth.php');
require_once(__DIR__ . '/includes/config.inc.php');
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php');
session_start();
if (empty($_SESSION['admin_csrf'])) $_SESSION['admin_csrf'] = bin2hex(random_bytes(16));
$csrf = $_SESSION['admin_csrf'];
$cfgPath = __DIR__ . '/includes/config.inc.php';
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
$status = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf'] ?? '';
if (!hash_equals($csrf, (string)$token)) {
$status = 'Invalid CSRF token.';
} else {
if (!is_writable($cfgPath)) {
$status = 'Config file not writable: ' . h($cfgPath);
} else {
// Backup
$bakDir = dirname($cfgPath) . '/backups';
@mkdir($bakDir, 0775, true);
$bakName = $bakDir . '/config.inc.php.' . date('Ymd-His') . '.' . bin2hex(random_bytes(4)) . '.bak';
if (!copy($cfgPath, $bakName)) {
$status = 'Failed to create backup. Aborting.';
} else {
$new = $_POST['config_text'] ?? '';
// Basic safety: ensure the file still starts with <?php
if (strpos(trim($new), '<?php') !== 0) {
$status = 'Config must start with <?php';
} else {
if (file_put_contents($cfgPath, $new) === false) {
$status = 'Failed to write config file.';
} else {
// Post-save syntax check: try to run php -l using a sensible PHP executable.
$phpExec = PHP_BINARY ?: (file_exists('C:\\xampp\\php\\php.exe') ? 'C:\\xampp\\php\\php.exe' : null);
$lintOk = true;
$lintOutput = '';
if ($phpExec) {
$cmd = escapeshellarg($phpExec) . ' -l ' . escapeshellarg($cfgPath);
// execute and capture output
$out = null; $rc = null;
@exec($cmd . ' 2>&1', $out, $rc);
$lintOutput = is_array($out) ? implode("\n", $out) : (string)$out;
if ($rc !== 0) {
$lintOk = false;
}
} else {
$lintOutput = 'PHP executable not found for linting; skipping post-save syntax check.';
}
if (!$lintOk) {
// rollback
@copy($bakName, $cfgPath);
$status = 'Syntax error detected in saved config. Changes rolled back. Lint output: ' . h($lintOutput);
} else {
$status = 'Config saved successfully. Backup: ' . basename($bakName) . (strlen($lintOutput) ? ' (lint: '.h($lintOutput).')' : '');
// reload values
require_once($cfgPath);
}
}
}
}
}
}
}
$currentText = '';
if (is_readable($cfgPath)) {
$currentText = file_get_contents($cfgPath);
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Admin Edit Config</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/header.css">
</head>
<body>
<div class="container-wide panel">
<h1>Edit Site Config</h1>
<?php if ($status): ?><div class="panel"><strong><?php echo h($status); ?></strong></div><?php endif; ?>
<form method="post" action="">
<input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
<div style="margin-bottom:8px;"><button type="submit">Save Config</button></div>
<textarea name="config_text" rows="24" style="width:100%;font-family:monospace;"><?php echo h($currentText); ?></textarea>
<div style="margin-top:8px;"><button type="submit">Save Config</button></div>
</form>
<p><small>Backups are stored in <code><?php echo h(dirname($cfgPath) . '/backups'); ?></code></small></p>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body>
</html>

View file

@ -0,0 +1,59 @@
<?php
// Admin payments viewer — lists persisted PayPal webhook JSON files
$session_name = session_name(); session_start();
require_once(__DIR__ . '/includes/config.inc.php');
require_once(__DIR__ . '/includes/admin_auth.php');
$dataDir = (isset($SITE_DATA_DIR) && $SITE_DATA_DIR) ? $SITE_DATA_DIR : realpath(__DIR__ . '/') . DIRECTORY_SEPARATOR . 'data';
$files = [];
if (is_dir($dataDir)) {
foreach (glob($dataDir . '/*.json') as $file) {
$files[] = $file;
}
}
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Admin Payments</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/header.css">
</head>
<body>
<?php include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?>
<div class="container-wide panel">
<h1>Payments (webhook)</h1>
<?php if (!$files): ?>
<p>No payment records found in <?php echo h($dataDir); ?></p>
<?php else: ?>
<table class="cart-table">
<thead>
<tr>
<th>Filename</th>
<th>Invoice</th>
<th>Amount</th>
<th>Payer</th>
<th>Date</th>
<th>View</th>
</tr>
</thead>
<tbody>
<?php foreach ($files as $f): $j = json_decode(file_get_contents($f), true) ?: []; ?>
<tr>
<td><?php echo h(basename($f)); ?></td>
<td><?php echo h($j['invoice'] ?? ($j['custom'] ?? '')); ?></td>
<td><?php echo h(($j['currency'] ?? '') . ' ' . number_format((float)($j['amount'] ?? 0),2)); ?></td>
<td><?php echo h($j['payer'] ?? ''); ?></td>
<td><?php echo h($j['ts'] ?? ''); ?></td>
<td><a href="return.php?invoice=<?php echo urlencode($j['invoice'] ?? ($j['custom'] ?? '')); ?>">View</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body>
</html>

View file

@ -9,19 +9,22 @@
<?php <?php
// gameservers.world admin — mysqli only, bulk + per-row update, image base URL + small button // gameservers.world admin — mysqli only, bulk + per-row update, image base URL + small button
/* === Configure your site base URL for image previews (MUST end with or without slash; we'll normalize) === */ /* === SITE_BASE_URL is loaded from includes/config.inc.php; leave empty to use relative paths === */
$SITE_BASE_URL = 'http://gameservers.world/';
// Include database configuration // Include database configuration
require_once(__DIR__ . '/includes/config.inc.php'); require_once(__DIR__ . '/includes/config.inc.php');
// Create database connection // Protect this page: require admin
require_once(__DIR__ . '/includes/admin_auth.php');
// Create database connection (admin_auth already validated DB but we need connection for UI ops)
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$db) { if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }
// Include menu // Include top bar and menu
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php'); include(__DIR__ . '/includes/menu.php');
/* show errors during setup */ /* show errors during setup */
@ -138,7 +141,8 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
?> ?>
<?php if ($flash): ?> <?php if ($flash): ?>
<div style="padding:8px;border:1px solid #ccc;margin:10px 0;"><?php foreach($flash as $m) echo "<div>".h($m)."</div>"; ?></div> <div class="panel" style="margin-bottom:12px"><?php foreach($flash as $m) echo "<div>".h($m)."</div>"; ?></div>
<div class="panel mb-12"><?php foreach($flash as $m) echo "<div>".h($m)."</div>"; ?></div>
<?php endif; ?> <?php endif; ?>
<h2>Enable/Disable Server Locations (Global)</h2> <h2>Enable/Disable Server Locations (Global)</h2>
@ -146,17 +150,18 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
<input type="hidden" name="update_remote_servers" value="1"> <input type="hidden" name="update_remote_servers" value="1">
<div style="display:flex;flex-wrap:wrap;gap:10px;"> <div style="display:flex;flex-wrap:wrap;gap:10px;">
<?php foreach ($remoteServers as $rs): ?> <?php foreach ($remoteServers as $rs): ?>
<label style="border:1px solid #ddd;border-radius:6px;padding:6px 10px;min-width:240px;"> <label class="loc-label min-w-240">
<input type="checkbox" name="rs[]" value="<?php echo (int)$rs['remote_server_id']; ?>" <?php echo ((int)$rs['enabled']===1?'checked':''); ?>> <input type="checkbox" name="rs[]" value="<?php echo (int)$rs['remote_server_id']; ?>" <?php echo ((int)$rs['enabled']===1?'checked':''); ?>>
<b><?php echo h($rs['remote_server_name']); ?></b> <b><?php echo h($rs['remote_server_name']); ?></b>
<small style="color:#666;">(ID: <?php echo (int)$rs['remote_server_id']; ?>)</small> <small class="muted">(ID: <?php echo (int)$rs['remote_server_id']; ?>)</small>
</label> </label>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div style="margin-top:10px;"><button type="submit">Update Enabled Servers</button></div> <div style="margin-top:10px;"><button type="submit">Update Enabled Servers</button></div>
<div class="mt-10"><button type="submit">Update Enabled Servers</button></div>
</form> </form>
<hr style="margin:20px 0;"> <hr>
<h2>Current Services</h2> <h2>Current Services</h2>
<?php if (!$services): ?> <?php if (!$services): ?>
@ -169,14 +174,14 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
<table class="center" style="text-align:center;width:100%;border-collapse:collapse;"> <table class="center" style="text-align:center;width:100%;border-collapse:collapse;">
<thead> <thead>
<tr> <tr>
<th style="border-bottom:1px solid #ddd;padding:6px;">Enabled</th> <th>Enabled</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Service Name <small style="color:#777;">(ID below)</small></th> <th>Service Name <small class="muted">(ID below)</small></th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Min Slots</th> <th>Min Slots</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Max Slots</th> <th>Max Slots</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Price (Monthly)</th> <th>Price (Monthly)</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Thumbnail URL</th> <th>Thumbnail URL</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Preview</th> <th>Preview</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Update Row</th> <th>Update Row</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -189,57 +194,61 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
$imgUrl = trim((string)$row['img_url']); $imgUrl = trim((string)$row['img_url']);
$displayUrl = ''; $displayUrl = '';
if ($imgUrl !== '') { if ($imgUrl !== '') {
$displayUrl = is_abs_url($imgUrl) ? $imgUrl : join_base($SITE_BASE_URL, $imgUrl); if (is_abs_url($imgUrl)) {
$displayUrl = $imgUrl;
} elseif ($SITE_BASE_URL !== '') {
$displayUrl = join_base($SITE_BASE_URL, $imgUrl);
} else {
// Use relative path (local folder)
$displayUrl = $imgUrl;
}
} }
?> ?>
<!-- MAIN ROW (no bottom border) --> <!-- MAIN ROW (no bottom border) -->
<tr> <tr>
<!-- Enabled first --> <!-- Enabled first -->
<td style="padding:6px;"> <td>
<input type="hidden" name="service[<?php echo $sid; ?>][enabled]" value="0"> <input type="hidden" name="service[<?php echo $sid; ?>][enabled]" value="0">
<input type="checkbox" name="service[<?php echo $sid; ?>][enabled]" value="1" <?php echo ((int)$row['enabled']===1?'checked':''); ?>> <input type="checkbox" name="service[<?php echo $sid; ?>][enabled]" value="1" <?php echo ((int)$row['enabled']===1?'checked':''); ?>>
</td> </td>
<!-- Service name (with tiny ID under it) --> <!-- Service name (with tiny ID under it) -->
<td style="padding:6px; text-align:left;"> <td>
<input type="text" name="service[<?php echo $sid; ?>][service_name]" value="<?php echo h($row['service_name']); ?>" style="min-width:260px;"> <input type="text" name="service[<?php echo $sid; ?>][service_name]" value="<?php echo h($row['service_name']); ?>" class="min-w-260">
<div style="color:#777; font-size:12px; margin-top:2px;">ID: <?php echo $sid; ?></div> <div class="small-muted">ID: <?php echo $sid; ?></div>
</td> </td>
<td style="padding:6px;"> <td>
<input type="number" name="service[<?php echo $sid; ?>][slot_min_qty]" value="<?php echo (int)$row['slot_min_qty']; ?>" min="1" step="1" style="width:90px;"> <input type="number" name="service[<?php echo $sid; ?>][slot_min_qty]" value="<?php echo (int)$row['slot_min_qty']; ?>" min="1" step="1" class="w-90">
</td> </td>
<td style="padding:6px;"> <td>
<input type="number" name="service[<?php echo $sid; ?>][slot_max_qty]" value="<?php echo (int)$row['slot_max_qty']; ?>" min="1" step="1" style="width:90px;"> <input type="number" name="service[<?php echo $sid; ?>][slot_max_qty]" value="<?php echo (int)$row['slot_max_qty']; ?>" min="1" step="1" class="w-90">
</td> </td>
<td style="padding:6px;"> <td>
<input type="text" name="service[<?php echo $sid; ?>][price_monthly]" value="<?php echo h($row['price_monthly']); ?>" size="8"> <input type="text" name="service[<?php echo $sid; ?>][price_monthly]" value="<?php echo h($row['price_monthly']); ?>" size="8">
</td> </td>
<!-- Thumbnail URL input --> <!-- Thumbnail URL input -->
<td style="padding:6px; vertical-align:top;"> <td>
<input type="text" name="service[<?php echo $sid; ?>][img_url]" value="<?php echo h($row['img_url']); ?>" style="min-width:240px;"> <input type="text" name="service[<?php echo $sid; ?>][img_url]" value="<?php echo h($row['img_url']); ?>" class="min-w-240">
</td> </td>
<!-- Preview (uses BASE + relative path) --> <!-- Preview (uses BASE + relative path) -->
<td style="padding:6px; vertical-align:top;"> <td>
<?php if ($displayUrl !== ''): ?> <?php if ($displayUrl !== ''): ?>
<img src="<?php echo h($displayUrl); ?>" alt="preview" loading="lazy" <img src="<?php echo h($displayUrl); ?>" alt="preview" loading="lazy" class="img-preview" onerror="this.style.display='none'">
style="max-height:48px; max-width:120px; border:1px solid #eee; display:block;"
onerror="this.style.display='none'">
<?php else: ?> <?php else: ?>
<span style="color:#aaa;">(no image)</span> <span class="muted">(no image)</span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<!-- Per-row Update (smaller) --> <!-- Per-row Update (smaller) -->
<td style="padding:6px;"> <td>
<button type="submit" name="update_single" value="<?php echo $sid; ?>" <button type="submit" name="update_single" value="<?php echo $sid; ?>" class="btn-small">Update Row</button>
style="padding:3px 8px; font-size:12px;">Update Row</button> </td>
</td>
</tr> </tr>
<!-- LOCATIONS ROW (single bottom divider) --> <!-- LOCATIONS ROW (single bottom divider) -->
@ -252,10 +261,10 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
$isChecked = isset($selSet[$rid]); $isChecked = isset($selSet[$rid]);
$isPrimary = ($primary === $rid); $isPrimary = ($primary === $rid);
?> ?>
<label style="border:1px solid #eee;border-radius:6px;padding:6px 8px; display:inline-flex; align-items:center;"> <label class="loc-label">
<input type="checkbox" class="locchk" data-sid="<?php echo $sid; ?>" <input type="checkbox" class="locchk" data-sid="<?php echo $sid; ?>"
name="service[<?php echo $sid; ?>][locations][]" value="<?php echo $rid; ?>" name="service[<?php echo $sid; ?>][locations][]" value="<?php echo $rid; ?>"
<?php echo $isChecked ? 'checked' : ''; ?> style="margin-right:6px;"> <?php echo $isChecked ? 'checked' : ''; ?> class="mr-6">
<?php echo h($rs['remote_server_name']); ?> (<?php echo $rid; ?>) <?php echo h($rs['remote_server_name']); ?> (<?php echo $rid; ?>)
<span style="margin-left:10px;"> <span style="margin-left:10px;">
<input type="radio" class="locprim" data-sid="<?php echo $sid; ?>" <input type="radio" class="locprim" data-sid="<?php echo $sid; ?>"
@ -264,7 +273,7 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
<small>Primary</small> <small>Primary</small>
</span> </span>
<?php if ((int)$rs['enabled'] === 0): ?> <?php if ((int)$rs['enabled'] === 0): ?>
<small style="color:#a00; margin-left:8px;">[Globally disabled]</small> <small class="text-danger ml-8">[Globally disabled]</small>
<?php endif; ?> <?php endif; ?>
</label> </label>
<?php endforeach; ?> <?php endforeach; ?>

View file

@ -233,23 +233,28 @@ try {
$error = $e->getMessage(); $error = $e->getMessage();
} }
?> ?>
<?php
// Include top and menu for website UI (session already started above)
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php');
?>
<!-- UI --> <!-- UI -->
<div style="max-width:760px; margin:20px auto; font-family:Arial, sans-serif;"> <div class="ai-container">
<h3>Site Assistant</h3> <h3>Site Assistant</h3>
<p>Type a question below. Press <b>Enter</b> to send, <b>Shift+Enter</b> for a new line.</p> <p>Type a question below. Press <b>Enter</b> to send, <b>Shift+Enter</b> for a new line.</p>
<?php if ($error): ?> <?php if ($error): ?>
<div style="margin:10px 0; padding:8px; border:1px solid #c00; border-radius:6px;"> <div class="ai-alert" style="border:1px solid #c00;">
<strong>Error:</strong> <?php echo h($error); ?> <strong>Error:</strong> <?php echo h($error); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if (!empty($_SESSION['thread_id'])): ?> <?php if (!empty($_SESSION['thread_id'])): ?>
<div style="margin:4px 0; font-size:12px;">Thread: <?php echo h($_SESSION['thread_id']); ?></div> <div class="ai-msg-meta">Thread: <?php echo h($_SESSION['thread_id']); ?></div>
<?php endif; ?> <?php endif; ?>
<form id="chat-form" method="post" style="margin:12px 0;"> <form id="chat-form" method="post" style="margin:12px 0;">
<textarea id="chat-input" name="user_input" rows="3" style="width:100%; padding:6px;" placeholder="Ask your question..."></textarea> <textarea id="chat-input" name="user_input" rows="3" class="ai-textarea" placeholder="Ask your question..."></textarea>
<div style="margin-top:8px; display:flex; gap:8px;"> <div style="margin-top:8px; display:flex; gap:8px;">
<button type="submit">Send</button> <button type="submit">Send</button>
<button type="submit" name="reset_thread" value="1">Reset Conversation</button> <button type="submit" name="reset_thread" value="1">Reset Conversation</button>

View file

@ -1,5 +1,5 @@
<?php <?php
// === CONFIG (Sandbox) === require_once(__DIR__ . '/../includes/config.inc.php');
$sandbox = true; // flip to false for Live $sandbox = true; // flip to false for Live
$client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c'; $client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c';
$client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0'; $client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0';
@ -11,7 +11,6 @@ if (!$order_id) { http_response_code(400); echo json_encode(['error'=>'missing o
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; $api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
// 1) OAuth2
$ch = curl_init("$api/v1/oauth2/token"); $ch = curl_init("$api/v1/oauth2/token");
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
@ -26,15 +25,11 @@ curl_close($ch);
if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_fail']); exit; } if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_fail']); exit; }
$access = json_decode($tok, true)['access_token'] ?? null; $access = json_decode($tok, true)['access_token'] ?? null;
// 2) Capture
$ch = curl_init("$api/v2/checkout/orders/$order_id/capture"); $ch = curl_init("$api/v2/checkout/orders/$order_id/capture");
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true, CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [ CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $access ],
'Content-Type: application/json',
'Authorization: Bearer ' . $access
],
]); ]);
$res = curl_exec($ch); $res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
@ -48,3 +43,4 @@ $txnId = $payload['purchase_units'][0]['payments']['captures'][0]['id'] ?? nul
echo json_encode(['status'=>$status, 'txn_id'=>$txnId]); echo json_encode(['status'=>$status, 'txn_id'=>$txnId]);
?>

View file

@ -1,5 +1,6 @@
<?php <?php
// === CONFIG (Sandbox) === require_once(__DIR__ . '/../includes/config.inc.php');
// create_order for PayPal — adapted to run from _website/api
$sandbox = true; // flip to false for Live $sandbox = true; // flip to false for Live
$client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c'; $client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c';
$client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0'; $client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0';
@ -8,25 +9,17 @@ header('Content-Type: application/json');
$in = json_decode(file_get_contents('php://input'), true) ?: []; $in = json_decode(file_get_contents('php://input'), true) ?: [];
// Incoming fields from your cart/client $amount_in = $in['amount'] ?? '0.00';
$amount_in = $in['amount'] ?? '0.00'; // overall intended amount (string)
$currency = $in['currency'] ?? 'USD'; $currency = $in['currency'] ?? 'USD';
$invoice_id = $in['invoice_id'] ?? null; // overall invoice id (string) $invoice_id = $in['invoice_id'] ?? null;
$custom_id = $in['custom_id'] ?? null; // your short reference (<=127 chars) $custom_id = $in['custom_id'] ?? null;
$description = $in['description'] ?? 'Order'; $description = $in['description'] ?? 'Order';
$return_url = $in['return_url'] ?? null; $return_url = $in['return_url'] ?? null;
$cancel_url = $in['cancel_url'] ?? null; $cancel_url = $in['cancel_url'] ?? null;
$items = (isset($in['items']) && is_array($in['items'])) ? $in['items'] : null;
$line_invoices= (isset($in['line_invoices']) && is_array($in['line_invoices'])) ? $in['line_invoices'] : null;
// Optional payloads:
$items = (isset($in['items']) && is_array($in['items'])) ? $in['items'] : null; // PayPal items
$line_invoices= (isset($in['line_invoices']) && is_array($in['line_invoices'])) ? $in['line_invoices'] : null; // your raw detail
// --- Server-side reconciliation for items ---
// If items are provided, ensure the order 'amount' equals the sum of item unit_amount * quantity.
// (Simplest policy: set the order amount to the exact sum of items.)
$amount_value = number_format((float)$amount_in, 2, '.', ''); $amount_value = number_format((float)$amount_in, 2, '.', '');
// Compute sum of items if present
if ($items) { if ($items) {
$sum = 0.00; $sum = 0.00;
foreach ($items as $it) { foreach ($items as $it) {
@ -34,13 +27,11 @@ if ($items) {
$val = isset($it['unit_amount']['value']) ? (float)$it['unit_amount']['value'] : 0.00; $val = isset($it['unit_amount']['value']) ? (float)$it['unit_amount']['value'] : 0.00;
$sum += $qty * $val; $sum += $qty * $val;
} }
// Use the item sum as the authoritative amount for PayPal (avoids mismatch errors)
$amount_value = number_format($sum, 2, '.', ''); $amount_value = number_format($sum, 2, '.', '');
} }
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; $api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
// 1) OAuth2
$ch = curl_init("$api/v1/oauth2/token"); $ch = curl_init("$api/v1/oauth2/token");
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
@ -56,42 +47,21 @@ if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_
$access = json_decode($tok, true)['access_token'] ?? null; $access = json_decode($tok, true)['access_token'] ?? null;
if (!$access) { http_response_code(500); echo json_encode(['error'=>'oauth_no_token']); exit; } if (!$access) { http_response_code(500); echo json_encode(['error'=>'oauth_no_token']); exit; }
// 2) Build purchase unit
$purchaseUnit = [ $purchaseUnit = [
'amount' => [ 'amount' => [ 'currency_code' => $currency, 'value' => $amount_value ],
'currency_code' => $currency,
'value' => $amount_value,
],
'description' => $description, 'description' => $description,
// Critical for webhook reconciliation:
'invoice_id' => $invoice_id, 'invoice_id' => $invoice_id,
'custom_id' => $custom_id 'custom_id' => $custom_id
]; ];
// If items provided, include them and add a breakdown with item_total to match the overall amount
if ($items) { if ($items) {
$purchaseUnit['items'] = $items; $purchaseUnit['items'] = $items;
$purchaseUnit['amount']['breakdown'] = [ $purchaseUnit['amount']['breakdown'] = [ 'item_total' => ['currency_code'=>$currency,'value'=>$amount_value] ];
'item_total' => [
'currency_code' => $currency,
'value' => $amount_value
]
];
} }
// (Optional) Persist your raw line_invoices server-side here if you wish.
// For example, write to a DB keyed by $invoice_id so you can join later.
// 3) Create order (intent = CAPTURE)
$body = [ $body = [
'intent' => 'CAPTURE', 'intent' => 'CAPTURE',
'purchase_units' => [ $purchaseUnit ], 'purchase_units' => [ $purchaseUnit ],
// Guides PayPal where to send the buyer if the flow becomes a full-page redirect 'application_context' => [ 'return_url'=>$return_url, 'cancel_url'=>$cancel_url, 'user_action'=>'PAY_NOW' ]
'application_context' => [
'return_url' => $return_url,
'cancel_url' => $cancel_url,
'user_action' => 'PAY_NOW'
]
]; ];
$ch = curl_init("$api/v2/checkout/orders"); $ch = curl_init("$api/v2/checkout/orders");
@ -99,10 +69,7 @@ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true, CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($body), CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [ CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $access ],
'Content-Type: application/json',
'Authorization: Bearer ' . $access
],
]); ]);
$res = curl_exec($ch); $res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
@ -111,3 +78,4 @@ curl_close($ch);
if ($http !== 201) { http_response_code($http); echo $res; exit; } if ($http !== 201) { http_response_code($http); echo $res; exit; }
echo $res; echo $res;
?>

View file

@ -11,6 +11,9 @@ ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); ini_set('display_startup_errors', 1);
error_reporting(E_ALL); error_reporting(E_ALL);
// Require login
require_once(__DIR__ . '/includes/login_required.php');
// Include database configuration // Include database configuration
require_once(__DIR__ . '/includes/config.inc.php'); require_once(__DIR__ . '/includes/config.inc.php');
@ -20,7 +23,41 @@ if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }
// Include menu // Admin quick-create handler: create a free "paid" record for an in-cart order
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['create_free_for'])) {
session_start();
if (!empty($_SESSION['website_user_role']) && strtolower($_SESSION['website_user_role']) === 'admin') {
$orderId = (int)$_POST['create_free_for'];
if ($orderId > 0) {
$stmt = $db->prepare("UPDATE ogp_billing_orders SET status = 'paid' WHERE order_id = ? LIMIT 1");
if ($stmt) { $stmt->bind_param('i', $orderId); $stmt->execute(); $stmt->close(); }
// write a simulated webhook file
require_once(__DIR__ . '/includes/config.inc.php');
$dataDir = (isset($SITE_DATA_DIR) && $SITE_DATA_DIR) ? $SITE_DATA_DIR : realpath(__DIR__ . '/') . DIRECTORY_SEPARATOR . 'data';
@mkdir($dataDir, 0775, true);
$rec = [
'event_type' => 'PAYMENT.CAPTURE.COMPLETED',
'status' => 'PAID',
'amount' => 0.00,
'currency' => 'USD',
'payer' => $_SESSION['website_user_email'] ?? ($_SESSION['website_username'] ?? ''),
'invoice' => 'FREE-' . $orderId . '-' . time(),
'custom' => 'admin_free_create_order_' . $orderId,
'resource_id' => 'FREE-' . bin2hex(random_bytes(6)),
'items' => [],
'ts' => date('c'),
];
$fname = $dataDir . DIRECTORY_SEPARATOR . $rec['invoice'] . '.json';
file_put_contents($fname, json_encode($rec, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
header('Location: return.php?invoice=' . urlencode($rec['invoice']));
exit;
}
}
}
// Include top bar and menu
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php'); include(__DIR__ . '/includes/menu.php');
$user_id=$_SESSION['user_id'] ?? 0; $user_id=$_SESSION['user_id'] ?? 0;
@ -71,51 +108,60 @@ if ($db){
?> ?>
<div style="width:100%; max-width:1000px; margin:auto; padding:1rem; background-color:#ffffff; border-radius:0.75rem; box-shadow:0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);"> <div class="site-panel">
<h2 style="font-size:1.5rem; font-weight:bold; color:#1f2937; margin-bottom:1.5rem; text-align:center;">Your Cart</h2> <h2 class="site-panel-title">Your Cart</h2>
<!-- <!--
This is our cart form just for display and deletion. There is a different form below that has the paypal button and fills in all the hidden fields This is our cart form just for display and deletion. There is a different form below that has the paypal button and fills in all the hidden fields
--> -->
<table style="border-collapse:separate; border-spacing:0; width:100%; color:#000000;"> <table class="cart-table">
<thead style="background-color:#f9fafb;"> <thead>
<tr> <tr>
<th style="padding:1rem 1.5rem; text-align:center; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;"></th> <th class="table-compact text-center"></th>
<th style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;">Server ID</th> <th>Server ID</th>
<th>Game Name</th>
<th style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;">Game Name</th> <th>Location</th>
<th style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;">Location</th> <th>Max Players</th>
<th style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;">Max Players</th> <th>Price per Player</th>
<th style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;">Price per Player</th> <th>Months</th>
<th style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;">Months</th> <th>Total</th>
<th style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb; font-weight:600; text-transform:uppercase; font-size:0.75rem; letter-spacing:0.05em;">Total</th> </tr>
</tr> </thead>
</thead> <tbody>
<tbody style="background-color:#ffffff;">
<?php <?php
$grandTotal = 0; // Initialize grand total variable $grandTotal = 0; // Initialize grand total variable
if (isset($carts) && $carts instanceof mysqli_result && $carts->num_rows > 0) { if (isset($carts) && $carts instanceof mysqli_result && $carts->num_rows > 0) {
while ($row = $carts->fetch_assoc()) { while ($row = $carts->fetch_assoc()) {
?> ?>
<tr data-cart-id="<?php echo htmlspecialchars($row['order_id']); ?>" style="color:#000000;"> <tr data-cart-id="<?php echo htmlspecialchars($row['order_id']); ?>">
<td style="padding:1rem 1.5rem; text-align:center; border-bottom:1px solid #e5e7eb;"> <td>
<form method="post" action="" style="margin:0; display:inline;"> <form method="post" action="" class="inline-form">
<button type="submit" name="delete_single" value="<?php echo htmlspecialchars($row['order_id']); ?>" style="background-color:#ef4444; color:#fff; border:none; border-radius:0.25rem; width:2rem; height:2rem; font-weight:bold; cursor:pointer; display:flex; align-items:center; justify-content:center;"> <button type="submit" name="delete_single" value="<?php echo htmlspecialchars($row['order_id']); ?>" class="btn-square text-danger">

</button> </button>
</form> </form>
</td> </td>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;"><?php echo htmlspecialchars($row['home_id']); ?></td> <td><?php echo htmlspecialchars($row['home_id']); ?></td>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;"><?php echo htmlspecialchars($row['home_name']); ?></td> <td><?php echo htmlspecialchars($row['home_name']); ?></td>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;"><?php echo htmlspecialchars($row['ip']); ?></td> <td><?php echo htmlspecialchars($row['ip']); ?></td>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;"><?php echo htmlspecialchars($row['max_players']); ?></td> <td><?php echo htmlspecialchars($row['max_players']); ?></td>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;">$<?php echo number_format($row['price'], 2); ?></td> <td>$<?php echo number_format($row['price'], 2); ?></td>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;"><?php echo htmlspecialchars($row['qty']); ?></td> <td><?php echo htmlspecialchars($row['qty']); ?></td>
<?php $rowtotal = $row['price'] * $row['qty'] * $row['max_players'];?> <?php $rowtotal = $row['price'] * $row['qty'] * $row['max_players'];?>
<?php if ((float)$row['price'] == 0.0 && isset($_SESSION['website_user_role']) && strtolower($_SESSION['website_user_role']) === 'admin'): ?>
<td>
<form method="post" action="" class="inline-form">
<input type="hidden" name="create_free_for" value="<?php echo (int)$row['order_id']; ?>">
<button type="submit" class="btn-primary">Create (Free)</button>
</form>
</td>
<?php else: ?>
<td>&nbsp;</td>
<?php endif; ?>
<?php $grandTotal += $rowtotal; // Add to grand total ?> <?php $grandTotal += $rowtotal; // Add to grand total ?>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;">$<?php echo number_format($rowtotal, 2); ?></td> <td>$<?php echo number_format($rowtotal, 2); ?></td>
</tr> </tr>
@ -124,20 +170,20 @@ if ($db){
// Add total row // Add total row
?> ?>
<tr style="background-color:#f9fafb; font-weight:bold;"> <tr class="cart-total-row">
<td colspan="7" style="padding:1rem 1.5rem; text-align:right; border-top:2px solid #374151; font-weight:600; color:#374151;"> <td colspan="7" class="cart-total-label">
Cart Total: Cart Total:
</td> </td>
<td style="padding:1rem 1.5rem; text-align:left; border-top:2px solid #374151; font-weight:600; color:#374151; font-size:1.1rem;"> <td class="cart-total-value">
$<?php echo number_format($grandTotal, 2); ?> $<?php echo number_format($grandTotal, 2); ?>
</td> </td>
</tr> </tr>
<?php <?php
} else { } else {
// Display a message if no cart items are found // Display a message if no cart items are found
?> ?>
<tr> <tr>
<td colspan="7" style="text-align:center; padding:1rem; color:#6b7280;">No items in your cart.</td> <td colspan="7" class="text-center muted">No items in your cart.</td>
</tr> </tr>
<?php <?php
} }
@ -186,17 +232,17 @@ $description = 'Game server order (' . count($lineItems) . ' item' . (count($lin
// URLs // URLs
$siteBase = 'https://panel.iaregamer.com'; $siteBase = 'https://panel.iaregamer.com';
$returnUrl = $siteBase . '/paypal/return.php?invoice=' . urlencode($invoiceId); $returnUrl = $siteBase . '/_website/return.php?invoice=' . urlencode($invoiceId);
$cancelUrl = $siteBase . '/paypal/return.php?invoice=' . urlencode($invoiceId) . '&cancel=1'; $cancelUrl = $siteBase . '/_website/return.php?invoice=' . urlencode($invoiceId) . '&cancel=1';
// API base (relative) // API base (relative)
$apiBase = '/paypal/api'; $apiBase = '/_website/api';
?> ?>
<!-- PayPal JS SDK (Sandbox). Use LIVE client-id when going live. --> <!-- PayPal JS SDK (Sandbox). Use LIVE client-id when going live. -->
<script src="https://www.paypal.com/sdk/js?client-id=AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c&currency=USD&intent=capture"></script> <script src="https://www.paypal.com/sdk/js?client-id=AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c&currency=USD&intent=capture"></script>
<div id="paypal-button-container"></div> <div id="paypal-button-container"></div>
<div id="pp-status" style="margin-top:12px;font:14px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;"></div> <div id="pp-status" class="mt-12" style="font:14px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;"></div>
<script> <script>
(function(){ (function(){
@ -278,5 +324,6 @@ $apiBase = '/paypal/api';
// Close database connection // Close database connection
mysqli_close($db); mysqli_close($db);
?> ?>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body> </body>
</html> </html>

115
_website/css/header.css Normal file
View file

@ -0,0 +1,115 @@
.gsw-top{display:flex;align-items:center;gap:12px;padding:12px 24px;background:#fff;border-bottom:1px solid rgba(0,0,0,0.05);}
.gsw-top img{height:40px;width:auto;display:block}
.gsw-top .gsw-site-name{font-weight:700;font-size:1.1rem;color:#333}
@media(max-width:480px){.gsw-top{padding:10px}.gsw-top img{height:32px}.gsw-top .gsw-site-name{font-size:1rem}}
.gsw-header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;background:rgba(102, 126, 234, 0.95);backdrop-filter:blur(10px);margin-bottom:20px;box-shadow:0 2px 4px rgba(0,0,0,0.1);}
.gsw-header-left{font-weight:700;font-size:1.2rem;color:#fff;}
.gsw-header-left a{color:#fff;text-decoration:none;}
.gsw-header-nav{display:flex;gap:20px;align-items:center;}
.gsw-nav-link{color:#fff;text-decoration:none;font-size:0.95rem;transition:opacity 0.2s;}
.gsw-nav-link:hover{opacity:0.8;text-decoration:underline;}
.gsw-header-right{display:flex;gap:12px;align-items:center;}
.gsw-user-info{color:#fff;font-size:0.95rem;}
.gsw-header-btn{padding:8px 16px;background:#fff;color:#667eea;border-radius:6px;text-decoration:none;font-weight:600;transition:transform 0.2s;}
.gsw-header-btn:hover{transform:translateY(-2px);}
@media(max-width:768px){
.gsw-header{flex-direction:column;gap:12px;}
.gsw-header-nav{flex-wrap:wrap;justify-content:center;}
}
/* Banner styling (index only) */
.gsw-banner{width:100%;text-align:center;margin-bottom:18px}
.gsw-banner img{max-width:100%;height:auto;display:inline-block}
/* Footer styles */
footer.gsw-footer{background:#000;color:#fff;padding:18px 12px;text-align:center;margin-top:28px}
footer.gsw-footer a{color:#4aa3ff;text-decoration:none}
footer.gsw-footer a:hover{text-decoration:underline}
/* Page color scheme: prefer dark text on light backgrounds by default */
/* Dark site theme: dark background with light text */
body { color: #fff; background: #0b1020; }
/* Make links readable on dark background */
a { color: #7fb3ff; }
/* Form inputs: light text on darker inputs by default */
input, textarea, select, button { color: #fff; background: #11141f; border: 1px solid rgba(255,255,255,0.06); }
.cart-badge{display:inline-block;background:#ff3b30;color:#fff;font-size:0.8rem;padding:2px 6px;border-radius:12px;margin-left:6px;vertical-align:middle}
.site-panel{width:100%; max-width:1000px; margin:auto; padding:1rem; background:rgba(0,0,0,0.25); border-radius:0.75rem;}
.site-panel-title{font-size:1.5rem; font-weight:bold; color:#fff; margin-bottom:1.5rem; text-align:center}
.cart-table{border-collapse:separate; border-spacing:0; width:100%; color:#fff}
.cart-table thead{background:rgba(255,255,255,0.03)}
.cart-table th, .cart-table td{padding:1rem 1.5rem; text-align:left; border-bottom:1px solid rgba(255,255,255,0.03)}
.cart-total-row{background:transparent; font-weight:bold}
.cart-total-label{padding:1rem 1.5rem; text-align:right; border-top:2px solid rgba(255,255,255,0.06); font-weight:600; color:#fff}
.cart-total-value{padding:1rem 1.5rem; text-align:left; border-top:2px solid rgba(255,255,255,0.06); font-weight:600; color:#fff; font-size:1.1rem}
/* Utility classes */
.container-wide{width:100%; max-width:1000px; margin:28px auto;}
.panel{background:rgba(0,0,0,0.25); padding:16px; border-radius:8px}
.muted{color:rgba(255,255,255,0.6)}
.center{text-align:center}
.pad-40{padding:40px}
.btn-danger{background:#ef4444;color:#fff;border:none;padding:6px 10px;border-radius:6px}
.btn-primary{background:#06b6d4;color:#fff;border:none;padding:6px 10px;border-radius:6px}
.float-left{float:left}
.clearfix::after{content:"";display:table;clear:both}
.table-compact th,.table-compact td{padding:0.5rem}
/* Small spacing utilities used by a few pages */
.mb-18{margin-bottom:18px}
.mt-6{margin-top:6px}
.mt-12{margin-top:12px}
/* Padding helper used where a wider card/panel is desired */
.p-30-20{padding:30px 20px}
/* Decorative container used in a few places */
.decorative-bottom{border:4px solid transparent;border-bottom:25px solid transparent}
/* Inline form helper (used for small inline forms inside table cells) */
.inline-form{margin:0;display:inline}
/* Small square button (used for delete icons) */
.btn-square{width:2rem;height:2rem;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;border-radius:0.25rem;border:none}
.text-right{text-align:right}
.text-danger{color:#ef4444}
.text-center{text-align:center}
/* small helpers for admin server list inputs */
.min-w-260{min-width:260px}
.min-w-240{min-width:240px}
.w-90{width:90px}
.img-preview{max-height:48px; max-width:120px; border:1px solid #eee; display:block}
.loc-label{border:1px solid #eee;border-radius:6px;padding:6px 8px; display:inline-flex; align-items:center}
.small-muted{color:#777;font-size:12px;margin-top:2px}
/* PayPal status */
.pp-status{margin-top:12px;font:14px system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif}
/* AI UI helpers */
.ai-container{max-width:760px; margin:20px auto; font-family:Arial, sans-serif}
.ai-panel{margin:10px 0; padding:8px; border-radius:6px}
.ai-alert{margin:10px 0; padding:8px; border-radius:6px; border:1px solid #c00}
.ai-textarea{width:100%; padding:6px}
.ai-message{margin-top:16px; padding:10px; border:1px solid #ccc; border-radius:8px}
.ai-msg-title{font-weight:bold}
.ai-msg-meta{margin-top:6px; font-size:12px}
.flex-gap-wrap{display:flex;flex-wrap:wrap;gap:10px}
.table-center{text-align:center;width:100%;border-collapse:collapse}
.tb-row-bottom{border-bottom:1px solid #f0f0f0;padding:8px 6px;text-align:left}
.locs-box{display:flex;flex-wrap:wrap;gap:8px}
.mb-12{margin-bottom:12px}
.mt-10{margin-top:10px}
.mt-14{margin-top:14px}
.mt-20{margin-top:20px}
.mt-8{margin-top:8px}
.btn-small{padding:3px 8px;font-size:12px}
.mr-6{margin-right:6px}
.ml-8{margin-left:8px}
.flex-row-gap{display:flex;gap:8px;align-items:center}

View file

@ -0,0 +1,10 @@
{
"event_type": "PAYMENT.CAPTURE.COMPLETED",
"status": "PAID",
"amount": "9.99",
"currency": "USD",
"invoice": "INV-20251022-101500-TEST",
"resource_id": "SIMULATED12345",
"ts": "2025-10-22T10:15:00-04:00",
"note": "Simulated webhook write for testing"
}

View file

@ -0,0 +1,2 @@
[2025-10-22T15:15:01+02:00] HIT ip=::1 bytes=281
[2025-10-22T15:15:02+02:00] VERIFY_FAIL http=200 status=FAILURE

View file

@ -77,7 +77,7 @@ $docs = [
<div class="embed-snippet"> <div class="embed-snippet">
<strong>Generic embed snippet (replace <code>doc=</code> value):</strong> <strong>Generic embed snippet (replace <code>doc=</code> value):</strong>
<pre><code>&lt;iframe src="/serve.php?doc=sales" width="100%" height="720" style="border:1px solid #ddd;border-radius:12px"&gt;&lt;/iframe&gt;</code></pre> <pre><code>&lt;iframe src="/serve.php?doc=sales" width="100%" height="720" class="doc-iframe"&gt;&lt;/iframe&gt;</code></pre>
</div> </div>
<p class="footer">Tip: You can place <code>serve.php</code> and <code>docs.php</code> anywhere; just keep the whitelist in <code>serve.php</code> updated to match your files.</p> <p class="footer">Tip: You can place <code>serve.php</code> and <code>docs.php</code> anywhere; just keep the whitelist in <code>serve.php</code> updated to match your files.</p>

View file

@ -59,7 +59,7 @@ function filterList(q) {
<header class="wrap"> <header class="wrap">
<h1>Game Server Guides</h1> <h1>Game Server Guides</h1>
<input class="search" placeholder="Search games…" oninput="filterList(this.value)"> <input class="search" placeholder="Search games…" oninput="filterList(this.value)">
<p style="color:#9ca3af;margin:8px 2px 0">Click a game to open its guide in a new tab; each page has a “Print / Save PDF” button.</p> <p class="muted">Click a game to open its guide in a new tab; each page has a “Print / Save PDF” button.</p>
</header> </header>
<div class="wrap"> <div class="wrap">
<div class="grid"> <div class="grid">

BIN
_website/images/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
_website/images/dark.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

BIN
_website/images/logo-sm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -0,0 +1,60 @@
<?php
// Admin authorization include — include early (before output) on admin pages
if (session_status() === PHP_SESSION_NONE) {
session_name("gameservers_website");
session_start();
}
// If not logged in, redirect to login
if (empty($_SESSION['website_user_id'])) {
// Build absolute login URL to avoid browser-relative resolution issues
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$siteRoot = '/';
$pos = strpos($script, '/_website');
if ($pos !== false) {
$siteRoot = substr($script, 0, $pos + strlen('/_website'));
} else {
$siteRoot = rtrim(dirname($script), '/\\');
}
$loginUrl = $siteRoot . '/login.php';
$returnTo = $siteRoot . '/' . basename($_SERVER['PHP_SELF']);
header('Location: ' . $loginUrl . '?return_to=' . urlencode($returnTo));
exit();
}
// Require DB config and check role live from panel DB
require_once(__DIR__ . '/config.inc.php');
// Use a local connection variable so we don't clash with pages that also use $db
$auth_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$auth_db) {
// If DB unavailable, deny access gracefully
// Redirect to absolute login URL
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
header('Location: ' . $siteRoot . '/login.php');
exit();
}
$uid = intval($_SESSION['website_user_id']);
$role = '';
$res = mysqli_query($auth_db, "SELECT users_role FROM ogp_users WHERE user_id = $uid LIMIT 1");
if ($res && mysqli_num_rows($res) === 1) {
$row = mysqli_fetch_assoc($res);
$role = (string)($row['users_role'] ?? '');
}
mysqli_close($auth_db);
if (strtolower($role) !== 'admin') {
// Not an admin — redirect to login or home
// Redirect to absolute login URL
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
header('Location: ' . $siteRoot . '/login.php');
exit();
}
// If we reach here, user is an admin
?>

View file

@ -0,0 +1,22 @@
<?php
// Helper to read cart items stored in session and return count
// Non-invasive: reads $_SESSION['cart'] if present and returns total quantity or items count
if (session_status() === PHP_SESSION_NONE) {
@session_start();
}
function get_cart_count() {
if (!isset($_SESSION['cart']) || !is_array($_SESSION['cart'])) {
return 0;
}
$count = 0;
foreach ($_SESSION['cart'] as $item) {
if (is_array($item) && isset($item['quantity'])) {
$count += (int) $item['quantity'];
} else {
$count += 1;
}
}
return $count;
}

View file

@ -13,4 +13,20 @@ $db_pass="Pkloyn7yvpht!";
$db_name="panel"; $db_name="panel";
$table_prefix="ogp_"; $table_prefix="ogp_";
$db_type="mysql"; $db_type="mysql";
// Optional: base URL used by admin pages to build absolute image previews.
// Leave empty to prefer relative paths (local folder).
// To enable production base URL, uncomment and set it to your site, e.g.:
// $SITE_BASE_URL = 'https://gameservers.world/';
$SITE_BASE_URL = '';
// Normalize: ensure either empty or ends without trailing slash (we use join_base to handle joining)
$SITE_BASE_URL = trim((string)$SITE_BASE_URL);
// Site-wide background image (relative to site root). Change to your preferred background.
$SITE_BACKGROUND = 'images/dark.jpg';
// Normalize
$SITE_BACKGROUND = trim((string)$SITE_BACKGROUND);
// Data directory for persisted payment webhook JSON files (relative to repo root)
$SITE_DATA_DIR = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data';
?> ?>

View file

@ -0,0 +1,8 @@
<?php
// Simple footer include
?>
<footer class="gsw-footer">
<div class="container-wide">
<a href="privacy.php">Privacy</a> | <a href="tos.php">TOS</a> | <a href="https://worlddomination.dev" target="_blank" rel="noopener">Worlddomination.dev</a>
</div>
</footer>

View file

@ -0,0 +1,19 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_name("gameservers_website");
session_start();
}
if (empty($_SESSION['website_user_id'])) {
// Build return_to pointing to current script + query and force absolute login URL
// Use raw REQUEST_URI (already absolute) and urlencode once when passing to login
$requestUri = $_SERVER['REQUEST_URI'] ?? '/index.php';
// Determine site root (prefer up to /_website)
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
$loginUrl = $siteRoot . '/login.php';
header('Location: ' . $loginUrl . '?return_to=' . urlencode($requestUri));
exit();
}
?>

View file

@ -13,43 +13,84 @@ if (session_status() === PHP_SESSION_NONE) {
// Check login status // Check login status
$is_logged_in = isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id']); $is_logged_in = isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id']);
$username = $is_logged_in ? htmlspecialchars($_SESSION['website_username']) : ''; $username = $is_logged_in ? htmlspecialchars($_SESSION['website_username']) : '';
?>
<style> // Determine if the logged-in user is an admin by checking the panel DB
.gsw-header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;background:rgba(102, 126, 234, 0.95);backdrop-filter:blur(10px);margin-bottom:20px;box-shadow:0 2px 4px rgba(0,0,0,0.1);} $is_admin = false;
.gsw-header-left{font-weight:700;font-size:1.2rem;color:#fff;} if ($is_logged_in) {
.gsw-header-left a{color:#fff;text-decoration:none;} // load DB credentials
.gsw-header-nav{display:flex;gap:20px;align-items:center;} require_once(__DIR__ . '/config.inc.php');
.gsw-nav-link{color:#fff;text-decoration:none;font-size:0.95rem;transition:opacity 0.2s;} // Prefer reusing an existing $db if present, otherwise open a local connection
.gsw-nav-link:hover{opacity:0.8;text-decoration:underline;} $menu_db = null;
.gsw-header-right{display:flex;gap:12px;align-items:center;} $menu_db_opened = false;
.gsw-user-info{color:#fff;font-size:0.95rem;} if (isset($db) && $db instanceof mysqli) {
.gsw-header-btn{padding:8px 16px;background:#fff;color:#667eea;border-radius:6px;text-decoration:none;font-weight:600;transition:transform 0.2s;} $menu_db = $db;
.gsw-header-btn:hover{transform:translateY(-2px);} } else {
@media(max-width:768px){ $menu_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name);
.gsw-header{flex-direction:column;gap:12px;} $menu_db_opened = true;
.gsw-header-nav{flex-wrap:wrap;justify-content:center;}
} }
</style>
if ($menu_db) {
$uid = intval($_SESSION['website_user_id']);
$res = mysqli_query($menu_db, "SELECT users_role FROM ogp_users WHERE user_id = $uid LIMIT 1");
if ($res && mysqli_num_rows($res) === 1) {
$row = mysqli_fetch_assoc($res);
if (strtolower((string)($row['users_role'] ?? '')) === 'admin') $is_admin = true;
}
if ($menu_db_opened) {
mysqli_close($menu_db);
}
}
}
?>
<link rel="stylesheet" href="css/header.css">
<div class="gsw-header"> <div class="gsw-header">
<div class="gsw-header-left"> <div class="gsw-header-left">
<a href="/">GameServers.World</a> <a href="index.php">GameServers.World</a>
</div> </div>
<nav class="gsw-header-nav"> <nav class="gsw-header-nav">
<a href="/" class="gsw-nav-link">Home</a> <a href="index.php" class="gsw-nav-link">Home</a>
<a href="/serverlist.php" class="gsw-nav-link">Game Servers</a> <a href="serverlist.php" class="gsw-nav-link">Game Servers</a>
<a href="/cart.php" class="gsw-nav-link">Cart</a> <li>
<?php if ($is_logged_in): ?> <a href="cart.php">Cart
<a href="/adminserverlist.php" class="gsw-nav-link">Admin</a> <?php
// show cart badge if helper available
$cart_count = 0;
if (file_exists(__DIR__ . '/cart_helper.php')) {
include_once __DIR__ . '/cart_helper.php';
if (function_exists('get_cart_count')) {
$cart_count = (int) get_cart_count();
}
}
if ($cart_count > 0) {
echo ' <span class="cart-badge">' . intval($cart_count) . '</span>';
}
?>
</a>
</li>
<?php if (basename($_SERVER['PHP_SELF']) === 'login.php'): ?>
<a href="register.php" class="gsw-nav-link">Register</a>
<?php endif; ?>
<?php if ($is_logged_in && $is_admin): ?>
<a href="admin.php" class="gsw-nav-link">Admin</a>
<?php endif; ?> <?php endif; ?>
<a href="http://panel.iaregamer.com" class="gsw-nav-link" target="_blank">Panel Login</a> <a href="http://panel.iaregamer.com" class="gsw-nav-link" target="_blank">Panel Login</a>
</nav> </nav>
<div class="gsw-header-right"> <div class="gsw-header-right">
<?php if ($is_logged_in): ?> <?php if ($is_logged_in): ?>
<span class="gsw-user-info">Welcome, <?php echo $username; ?>!</span> <span class="gsw-user-info">Welcome, <?php echo $username; ?>!</span>
<a href="/logout.php" class="gsw-header-btn">Logout</a> <?php
// Build a safe absolute return_to under this site so logout redirects stay in _website
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
$current = $_SERVER['REQUEST_URI'] ?? $siteRoot . '/index.php';
// Ensure current is absolute and under site root; urlencode only when embedding in URL
$return_to_param = $current;
?>
<a href="logout.php?return_to=<?php echo urlencode($return_to_param); ?>" class="gsw-header-btn">Logout</a>
<?php else: ?> <?php else: ?>
<a href="/login.php" class="gsw-header-btn">Login</a> <a href="login.php" class="gsw-header-btn">Login</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>

18
_website/includes/top.php Normal file
View file

@ -0,0 +1,18 @@
<?php
// Top include for all _website pages: logo + site name
?>
<link rel="stylesheet" href="css/header.css">
<?php
// Optionally set a background image from config
if (isset($SITE_BACKGROUND) && $SITE_BACKGROUND) {
$bg = htmlspecialchars($SITE_BACKGROUND, ENT_QUOTES, 'UTF-8');
echo "<style>body{background-image:url('". $bg ."');background-size:cover;background-position:center fixed;}</style>\n";
}
?>
<div class="gsw-top">
<div class="gsw-top-left">
<img src="images/logo-sm.png" alt="Gameservers World logo">
</div>
<div class="gsw-site-name">Gameservers World</div>
</div>

View file

@ -35,8 +35,14 @@
</head> </head>
<body> <body>
<?php include(__DIR__ . '/includes/top.php'); ?>
<?php include(__DIR__ . '/includes/menu.php'); ?> <?php include(__DIR__ . '/includes/menu.php'); ?>
<!-- Page banner -->
<div class="center mb-18">
<img src="images/banner.png" alt="Banner" class="gsw-banner">
</div>
<div class="gsw-outer-full"> <div class="gsw-outer-full">
<div class="gsw-page-center"> <div class="gsw-page-center">
<section class="gsw-wrap" aria-label="GameServers.World"> <section class="gsw-wrap" aria-label="GameServers.World">
@ -44,7 +50,7 @@
<h1>Virtual Private Gameservers</h1> <h1>Virtual Private Gameservers</h1>
<p>Just like running on your own dedicated box <strong>full configurability</strong> with <strong>help when you need it</strong>.</p> <p>Just like running on your own dedicated box <strong>full configurability</strong> with <strong>help when you need it</strong>.</p>
<span class="gsw-badge" aria-label="Never oversold">Never Oversold Capacity</span> <span class="gsw-badge" aria-label="Never oversold">Never Oversold Capacity</span>
<p style="margin-top:6px;">We also specialize in classics <strong>50+ older/community-favorite games</strong> hosted right.</p> <p class="muted mt-6">We also specialize in classics <strong>50+ older/community-favorite games</strong> hosted right.</p>
</header> </header>
<div class="gsw-callout" role="note"> <div class="gsw-callout" role="note">
@ -76,9 +82,9 @@
</article> </article>
</section> </section>
<nav class="gsw-cta" aria-label="Primary actions"> <nav class="gsw-cta" aria-label="Primary actions">
<a class="gsw-btn" href="/server-list/">Browse Game Servers</a> <a class="gsw-btn" href="serverlist.php">Browse Game Servers</a>
<a class="gsw-btn" href="/contact/">Talk to Support</a> <a class="gsw-btn" href="contact/">Talk to Support</a>
</nav> </nav>
<p class="gsw-fine">Looking for a specific title or region? Tell us what you need we add games and locations regularly.</p> <p class="gsw-fine">Looking for a specific title or region? Tell us what you need we add games and locations regularly.</p>
@ -87,4 +93,5 @@
</div> </div>
</body> </body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</html> </html>

66
_website/invoices.php Normal file
View file

@ -0,0 +1,66 @@
<?php
// User invoice history (reads payments/data/*.json)
$session_name = session_name(); session_start();
require_once(__DIR__ . '/includes/config.inc.php');
// Intentionally do not require login here; invoices should be viewable (or filtered) without forcing a login.
// try to get logged-in user's email for matching
$user_email = $_SESSION['website_user_email'] ?? '';
$dataDir = (isset($SITE_DATA_DIR) && $SITE_DATA_DIR) ? $SITE_DATA_DIR : realpath(__DIR__ . '/') . DIRECTORY_SEPARATOR . 'data';
$records = [];
if (is_dir($dataDir)) {
foreach (glob($dataDir . '/*.json') as $file) {
$j = json_decode(file_get_contents($file), true);
if (!$j || !is_array($j)) continue;
// Best-effort match: payer email or custom contains user identifier
$match = false;
if ($user_email && !empty($j['payer']) && stripos($j['payer'], $user_email) !== false) $match = true;
if (!$match && !empty($j['custom']) && stripos($j['custom'], $user_email) !== false) $match = true;
if (!$match && empty($user_email)) $match = true; // fallback: show everything when no email in session
if ($match) {
$records[] = $j;
}
}
}
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Your Invoices</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/header.css">
</head>
<body>
<?php include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?>
<div class="container-wide panel">
<h1>Your Invoices</h1>
<?php if (!$records): ?>
<p>No invoices found for your account.</p>
<?php else: ?>
<table class="cart-table">
<thead>
<tr>
<th>Invoice</th><th>Amount</th><th>Payer</th><th>Date</th><th>Details</th>
</tr>
</thead>
<tbody>
<?php foreach ($records as $r): ?>
<tr>
<td><?php echo h($r['invoice'] ?? ($r['custom'] ?? 'NO-ID')); ?></td>
<td><?php echo h(($r['currency'] ?? '') . ' ' . number_format((float)($r['amount'] ?? 0),2)); ?></td>
<td><?php echo h($r['payer'] ?? ''); ?></td>
<td><?php echo h($r['ts'] ?? ''); ?></td>
<td><a href="return.php?invoice=<?php echo urlencode($r['invoice'] ?? ($r['custom'] ?? '')); ?>">View</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body>
</html>

12
_website/logfile.txt Normal file
View file

@ -0,0 +1,12 @@
Website login successful: iaregamer
Website logout: iaregamer
Website login successful: iaregamer
Website logout: iaregamer
Website login failed - user not found: iaregamer
Website login failed - user not found: iaregamer
Website login failed - user not found: iaregamer
Website login successful: iaregamer
Website logout: iaregamer
Website login successful: iaregamer
Website logout: iaregamer
Website login successful: iaregamer

View file

@ -3,9 +3,40 @@
session_name("gameservers_website"); session_name("gameservers_website");
session_start(); session_start();
// We'll compute a site root below (up to /_website) and define a strict sanitizer after config is loaded
// Include database configuration // Include database configuration
require_once(__DIR__ . '/includes/config.inc.php'); require_once(__DIR__ . '/includes/config.inc.php');
// Determine site root up to /_website so we can enforce absolute redirects within this site
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$SITE_ROOT_PATH = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
// Strict sanitizer that returns an absolute path under $SITE_ROOT_PATH or empty string on invalid
$sanitize_return_path = function($p) use ($SITE_ROOT_PATH) {
$p = trim((string)$p);
if ($p === '') return '';
// disallow absolute URLs or protocol-relative paths
if (preg_match('#^(https?:)?//#i', $p)) return '';
if (strpos($p, "\n") !== false || strpos($p, "\r") !== false) return '';
// Reject path traversal
if (strpos($p, '..') !== false) return '';
// Normalize: if it starts with '/', treat as absolute path and ensure it's under SITE_ROOT_PATH
if (substr($p,0,1) === '/') {
// simple character whitelist
if (!preg_match('#^/[A-Za-z0-9_./?&=%:\-]+$#', $p)) return '';
// Disallow entry to 'dashboard' (panel area) explicitly
if (stripos($p, '/dashboard') !== false) return '';
return $p;
}
// Relative path: restrict characters and build absolute under site root
if (!preg_match('#^[A-Za-z0-9_./?&=%:\-]+$#', $p)) return '';
// Disallow references to panel dashboard
if (stripos($p, 'dashboard') !== false) return '';
return $SITE_ROOT_PATH . '/' . ltrim($p, '/');
};
// Create database connection // Create database connection
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name); $db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$db) { if (!$db) {
@ -19,8 +50,14 @@ function logger($logtext){
// Check if user is already logged in // Check if user is already logged in
if (isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id'])) { if (isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id'])) {
// Determine return path and sanitize it strictly to stay under _website
// Request values may be url-encoded (from previous urlencode calls); decode first
$return_to_raw = isset($_REQUEST['return_to']) ? urldecode($_REQUEST['return_to']) : '';
$sanitized_return = $sanitize_return_path($return_to_raw);
if ($sanitized_return === '') $sanitized_return = $SITE_ROOT_PATH . '/index.php';
// Already logged in, redirect to appropriate page // Already logged in, redirect to appropriate page
header('Location: /'); header('Location: ' . $sanitized_return);
exit(); exit();
} }
@ -39,15 +76,50 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
// Sanitize username to prevent SQL injection // Sanitize username to prevent SQL injection
$username = mysqli_real_escape_string($db, $username); $username = mysqli_real_escape_string($db, $username);
// Query the panel database for the user // Detect if shadow column for modern hash exists, and build a safe SELECT
$query = "SELECT user_id, users_login, users_passwd, users_role, users_email FROM ogp_users WHERE users_login = '$username'"; $has_shadow = false;
$result = mysqli_query($db, $query); $res_cols = mysqli_query($db, "SHOW COLUMNS FROM ogp_users LIKE 'users_pass_hash'");
if ($res_cols && mysqli_num_rows($res_cols) > 0) {
if ($result && mysqli_num_rows($result) === 1) { $has_shadow = true;
$user = mysqli_fetch_assoc($result); }
$select_fields = 'user_id, users_login, users_passwd, users_role, users_email';
if ($has_shadow) $select_fields .= ', users_pass_hash';
// Query the panel database for the user
$query = "SELECT $select_fields FROM ogp_users WHERE users_login = '$username'";
$result = mysqli_query($db, $query);
if ($result && mysqli_num_rows($result) === 1) {
$user = mysqli_fetch_assoc($result);
// Verify password (panel uses MD5) // Prefer modern password hash if present (shadow column), otherwise fall back to MD5 and migrate
if (md5($password) === $user['users_passwd']) { $verified = false;
if (!empty($user['users_pass_hash'])) {
// verify against modern hash
if (password_verify($password, $user['users_pass_hash'])) {
$verified = true;
}
} else {
// legacy MD5
if (md5($password) === $user['users_passwd']) {
$verified = true;
// attempt to migrate: store modern hash if column exists
$res = mysqli_query($db, "SHOW COLUMNS FROM ogp_users LIKE 'users_pass_hash'");
if ($res && mysqli_num_rows($res) > 0) {
$newhash = password_hash($password, PASSWORD_DEFAULT);
$safe_user_id = (int)$user['user_id'];
$stmt_m = $db->prepare("UPDATE ogp_users SET users_pass_hash = ? WHERE user_id = ?");
if ($stmt_m) {
$stmt_m->bind_param('si', $newhash, $safe_user_id);
$stmt_m->execute();
$stmt_m->close();
}
}
}
}
if ($verified) {
// Login successful - create website session // Login successful - create website session
$_SESSION['website_user_id'] = $user['user_id']; $_SESSION['website_user_id'] = $user['user_id'];
$_SESSION['website_username'] = $user['users_login']; $_SESSION['website_username'] = $user['users_login'];
@ -60,8 +132,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
// Log the login // Log the login
logger("Website login successful: " . $user['users_login']); logger("Website login successful: " . $user['users_login']);
// Redirect after 2 seconds // Redirect after 2 seconds to the requested return path or index.php, using strict sanitizer
header('Refresh: 2; URL=/'); // POST may contain a raw (not URL-encoded) return_to from the hidden form; decode defensively
$post_return = isset($_POST['return_to']) ? urldecode($_POST['return_to']) : '';
$return_candidate = $post_return !== '' ? $post_return : ($return_to_raw ?? '');
$sanitized_after = $sanitize_return_path($return_candidate ?? '');
if ($sanitized_after === '') $sanitized_after = $SITE_ROOT_PATH . '/index.php';
// Use immediate server-side redirect to avoid client-side relative resolution or delays
header('Location: ' . $sanitized_after);
exit();
} else { } else {
$error_message = 'Invalid username or password.'; $error_message = 'Invalid username or password.';
logger("Website login failed - wrong password: $username"); logger("Website login failed - wrong password: $username");
@ -93,10 +172,17 @@ mysqli_close($db);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh; min-height: 100vh;
display: flex; display: block;
align-items: center; padding: 0; /* we'll handle padding in content wrapper */
justify-content: center; }
padding: 20px;
/* content area below the top/menu; aligns the login box to the right */
.content{
display:flex;
align-items:center;
justify-content:flex-end;
min-height: calc(100vh - 140px); /* leave room for header/menu */
padding:20px;
} }
.login-container { .login-container {
@ -215,7 +301,9 @@ mysqli_close($db);
</style> </style>
</head> </head>
<body> <body>
<?php include(__DIR__ . '/includes/top.php'); ?>
<?php include(__DIR__ . '/includes/menu.php'); ?> <?php include(__DIR__ . '/includes/menu.php'); ?>
<div class="content">
<div class="login-container"> <div class="login-container">
<div class="login-header"> <div class="login-header">
<h1>Welcome Back</h1> <h1>Welcome Back</h1>
@ -230,7 +318,13 @@ mysqli_close($db);
<div class="alert alert-success"><?php echo htmlspecialchars($success_message); ?></div> <div class="alert alert-success"><?php echo htmlspecialchars($success_message); ?></div>
<?php endif; ?> <?php endif; ?>
<?php
// Capture a return_to GET parameter so we can send users back after login
$return_to_raw = $_GET['return_to'] ?? '';
// ensure we don't break if not set; the sanitizer is defined above
?>
<form method="POST" action="login.php"> <form method="POST" action="login.php">
<input type="hidden" name="return_to" value="<?php echo htmlspecialchars($return_to_raw); ?>">
<div class="form-group"> <div class="form-group">
<label for="ulogin">Username</label> <label for="ulogin">Username</label>
<input type="text" id="ulogin" name="ulogin" required autofocus> <input type="text" id="ulogin" name="ulogin" required autofocus>
@ -243,13 +337,18 @@ mysqli_close($db);
<button type="submit" name="login" class="btn-login">Sign In</button> <button type="submit" name="login" class="btn-login">Sign In</button>
</form> </form>
<div class="center mt-12">
<a href="register.php">Register</a>
</div>
<div class="divider">or</div> <div class="divider">or</div>
<div class="footer-links"> <div class="footer-links">
<a href="/">Back to Home</a> | <a href="index.php">Back to Home</a> |
<a href="../index.php">Panel Login</a> <a href="../index.php">Panel Login</a>
</div> </div>
</div> </div>
</div>
</body> </body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</html> </html>

View file

@ -23,8 +23,35 @@ if (isset($_COOKIE[session_name()])) {
// Destroy the session // Destroy the session
session_destroy(); session_destroy();
// Optional safe return_to handling
$return_raw = $_GET['return_to'] ?? '';
// Determine site root (prefer up to /_website)
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
// Redirect to home page // sanitize: disallow absolute URLs (with protocol), CR/LF; allow safe path characters.
header('Location: /'); $sanitize_return = function($p) use ($siteRoot) {
$p = trim((string)$p);
if ($p === '') return '';
// disallow absolute URLs or protocol-relative paths
if (preg_match('#^(https?:)?//#i', $p)) return '';
if (strpos($p, "\n") !== false || strpos($p, "\r") !== false) return '';
// allow only safe characters (slash, query, percent-encodings, alnum and a few safe symbols)
if (!preg_match('#^[A-Za-z0-9_./?&=%:\-]+$#', $p)) return '';
// If it already starts with '/', treat it as an absolute path and return as-is
if (strpos($p, '/') === 0) {
return $p;
}
// Otherwise, build an absolute path under the site root
return $siteRoot . '/' . ltrim($p, '/');
};
$sanitized = $sanitize_return($return_raw);
if ($sanitized !== '') {
header('Location: ' . $sanitized);
} else {
header('Location: ' . $siteRoot . '/index.php');
}
exit(); exit();
?> ?>

View file

@ -20,6 +20,9 @@ There are other methods that might be better to get the info. But all we need i
This method means we can use one code block in every game page and fill in the data dynamically. This method means we can use one code block in every game page and fill in the data dynamically.
*/ */
// Require login for ordering
require_once(__DIR__ . '/includes/login_required.php');
// Include database configuration // Include database configuration
require_once(__DIR__ . '/includes/config.inc.php'); require_once(__DIR__ . '/includes/config.inc.php');
@ -29,7 +32,8 @@ if (!$db) {
die("Connection failed: " . mysqli_connect_error()); die("Connection failed: " . mysqli_connect_error());
} }
// Include menu // Include top bar and menu
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php'); include(__DIR__ . '/includes/menu.php');
@ -65,10 +69,10 @@ THIS IS WHAT WE DISPLAY ON THE SHOP PAGE AT THE TOP
} }
foreach ($services as $key => $row) { foreach ($services as $key => $row) {
$service_id[$key] = $row['service_id']; $service_ids[$key] = $row['service_id'];
$home_cfg_id[$key] = $row['home_cfg_id']; $home_cfg_id[$key] = $row['home_cfg_id'];
$mod_cfg_id[$key] = $row['mod_cfg_id']; $mod_cfg_id[$key] = $row['mod_cfg_id'];
$service_name[$key] = $row['service_name']; $service_name[$key] = $row['service_name'];
$remote_server_id[$key] = $row['remote_server_id']; $remote_server_id[$key] = $row['remote_server_id'];
$slot_max_qty[$key] = $row['slot_max_qty']; $slot_max_qty[$key] = $row['slot_max_qty'];
$slot_min_qty[$key] = $row['slot_min_qty']; $slot_min_qty[$key] = $row['slot_min_qty'];
@ -80,23 +84,18 @@ THIS IS WHAT WE DISPLAY ON THE SHOP PAGE AT THE TOP
$ftp[$key] = $row['ftp']; $ftp[$key] = $row['ftp'];
$install_method[$key] = $row['install_method']; $install_method[$key] = $row['install_method'];
$manual_url[$key] = $row['manual_url']; $manual_url[$key] = $row['manual_url'];
$access_rights[$key] = $row['access_rights']; $access_rights_list[$key] = $row['access_rights'];
} }
?> ?>
<div style="border-left:10px solid transparent;"> <div class="clearfix">
<?php <?php
foreach($services as $row) foreach($services as $row)
{ {
if(!isset($_REQUEST['service_id'])) if(!isset($_REQUEST['service_id']))
{ {
?> ?>
<div style=" <div class="float-left p-30-20">
float:left;
padding-top: 30px;
padding-right: 20px;
padding-bottom: 30px;
padding-left: 20px;">
@ -135,7 +134,7 @@ if ($row['price_monthly'] == 0.0) {
//THIS IS THE SERVER WE WANT TO ORDER //THIS IS THE SERVER WE WANT TO ORDER
{ {
?> ?>
<div style="float:left; border: 4px solid transparent;border-bottom: 25px solid transparent;"> <div class="float-left decorative-bottom">
<img src="<?php echo $row['img_url'];?>" width=230 height=112 border=0 "> <img src="<?php echo $row['img_url'];?>" width=230 height=112 border=0 ">
<center><b> <?php echo $row['service_name'];?></b></center> <center><b> <?php echo $row['service_name'];?></b></center>
@ -169,7 +168,7 @@ if ($row['price_monthly'] == 0.0) {
echo "<p style='color:gray;width:280px;' >$row[description]<p>"; echo "<p style='color:gray;width:280px;' >$row[description]<p>";
?> ?>
</div> </div>
<table style="float:left;"> <table class="float-left">
<form method="post" action="panel/_add_to_cart.php"> <form method="post" action="panel/_add_to_cart.php">
<input type="hidden" name="service_id" size="15" value="<?php if(isset($_POST['service_id'])) echo $_POST['service_id'];?>"> <input type="hidden" name="service_id" size="15" value="<?php if(isset($_POST['service_id'])) echo $_POST['service_id'];?>">
<input type="hidden" name="remote_control_password" size="15" value="ChangeMe"> <input type="hidden" name="remote_control_password" size="15" value="ChangeMe">
@ -295,7 +294,7 @@ if ($row['price_monthly'] == 0.0) {
</tr> </tr>
<tr> <tr>
<td align="left" colspan="2"> <td align="left" colspan="2">
<form action ="https://gameservers.world/server-list/" method="POST"> <form action ="serverlist.php" method="POST">
<button >Back to List</button> <button >Back to List</button>
</form> </form>
</td> </td>
@ -311,4 +310,5 @@ if ($row['price_monthly'] == 0.0) {
mysqli_close($db); mysqli_close($db);
?> ?>
</body> </body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</html> </html>

View file

@ -0,0 +1 @@
Compatibility wrappers for payments API endpoints. Canonical implementations are under /_website/api/.

View file

@ -0,0 +1,4 @@
<?php
// payments compatibility config — centralized in includes/config.inc.php
require_once(__DIR__ . '/../includes/config.inc.php');
?>

View file

@ -0,0 +1,4 @@
<?php
// Compatibility wrapper: redirect legacy /payments/pay.php to new create_order API
header('Location: /_website/api/create_order.php');
exit;

View file

@ -0,0 +1,4 @@
<?php
// Compatibility wrapper for /payments/return.php
header('Location: /_website/return.php' . (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : ''));
exit;

View file

@ -0,0 +1,159 @@
<?php
// Full payments webhook implementation (migrated from top-level payments/webhook.php)
require_once(__DIR__ . '/../includes/config.inc.php');
$config = [
'sandbox' => true,
'client_id' => '',
'client_secret' => '',
'webhook_id' => '',
'data_dir' => realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data',
'log_file' => realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'webhook.log',
];
if (defined('SITE_DATA_DIR') && SITE_DATA_DIR) {
$config['data_dir'] = rtrim(SITE_DATA_DIR, "\\/") . DIRECTORY_SEPARATOR;
}
@mkdir($config['data_dir'], 0775, true);
function log_line($m){global $config; @file_put_contents($config['log_file'],'['.date('c')."] $m\n",FILE_APPEND);}
function api_base(){global $config; return $config['sandbox'] ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';}
http_response_code(200);
$raw = file_get_contents('php://input');
$headers = array_change_key_case(getallheaders() ?: [], CASE_UPPER);
log_line("HIT ip=".($_SERVER['REMOTE_ADDR']??'') ." bytes=".strlen($raw));
if (!$raw) { log_line("NO_BODY"); exit; }
// 1) OAuth2
$ch = curl_init(api_base().'/v1/oauth2/token');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_POST=>true,
CURLOPT_POSTFIELDS=>'grant_type=client_credentials',
CURLOPT_HTTPHEADER=>['Accept: application/json'],
CURLOPT_USERPWD=>$config['client_id'].':'.$config['client_secret'],
]);
$tokenResp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http!==200){ log_line("OAUTH_FAIL http=$http resp=$tokenResp"); exit; }
$access_token = json_decode($tokenResp, true)['access_token'] ?? null;
if (!$access_token){ log_line("OAUTH_NO_TOKEN"); exit; }
// 2) Verify webhook signature
$verifyPayload = [
'transmission_id' => $headers['PAYPAL-TRANSMISSION-ID'] ?? '',
'transmission_time' => $headers['PAYPAL-TRANSMISSION-TIME'] ?? '',
'cert_url' => $headers['PAYPAL-CERT-URL'] ?? '',
'auth_algo' => $headers['PAYPAL-AUTH-ALGO'] ?? '',
'transmission_sig' => $headers['PAYPAL-TRANSMISSION-SIG'] ?? '',
'webhook_id' => $config['webhook_id'],
'webhook_event' => json_decode($raw, true),
];
$ch = curl_init(api_base().'/v1/notifications/verify-webhook-signature');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_POST=>true,
CURLOPT_POSTFIELDS=>json_encode($verifyPayload),
CURLOPT_HTTPHEADER=>[
'Content-Type: application/json',
'Authorization: Bearer '.$access_token
],
]);
$verifyResp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$verifyJson = json_decode($verifyResp, true);
if ($http!==200 || ($verifyJson['verification_status'] ?? '') !== 'SUCCESS'){
log_line("VERIFY_FAIL http=$http status=".($verifyJson['verification_status']??'NONE'));
exit;
}
log_line("VERIFY_OK");
// 3) Parse and persist (now with items)
$evt = json_decode($raw, true);
$type = $evt['event_type'] ?? '';
$res = $evt['resource'] ?? [];
// Extract common fields
$invoice = $res['invoice_id'] ?? ($res['invoice_number'] ?? null);
$custom = $res['custom_id'] ?? ($res['custom'] ?? null);
// Amounts/payer
$amount = $res['amount']['value'] ?? ($res['amount']['total'] ?? null);
$currency = $res['amount']['currency_code'] ?? ($res['amount']['currency'] ?? null);
$payer = $res['payer']['email_address'] ?? ($res['payer']['payer_info']['email'] ?? null);
// Try to capture line items if present directly in this event:
$items = [];
if (isset($res['purchase_units'][0]['items']) && is_array($res['purchase_units'][0]['items'])) {
$items = $res['purchase_units'][0]['items'];
}
// If capture event, try to fetch the parent ORDER to get items
if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
$orderId =
$res['supplementary_data']['related_ids']['order_id'] // preferred
?? null;
if (!$orderId && isset($res['links']) && is_array($res['links'])) {
// Fallback: look for a link to the parent order
foreach ($res['links'] as $lnk) {
if (!empty($lnk['href']) && !empty($lnk['rel']) && stripos($lnk['href'], '/v2/checkout/orders/') !== false) {
$orderId = basename(parse_url($lnk['href'], PHP_URL_PATH));
break;
}
}
}
if ($orderId) {
$ch = curl_init(api_base()."/v2/checkout/orders/".urlencode($orderId));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer '.$access_token,
'Content-Type: application/json'
],
]);
$orderJson = curl_exec($ch);
$httpOrder = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpOrder === 200) {
$order = json_decode($orderJson, true);
if (isset($order['purchase_units'][0]['items']) && is_array($order['purchase_units'][0]['items'])) {
$items = $order['purchase_units'][0]['items'];
}
// If the order has invoice/custom (sometimes more reliable), prefer those:
if (!$invoice) { $invoice = $order['purchase_units'][0]['invoice_id'] ?? $invoice; }
if (!$custom) { $custom = $order['purchase_units'][0]['custom_id'] ?? $custom; }
} else {
log_line("ORDER_FETCH_FAIL id=$orderId http=$httpOrder");
}
}
}
$status = 'IGNORED';
// We persist on payment completed events
if (in_array($type, ['PAYMENT.CAPTURE.COMPLETED','PAYMENT.SALE.COMPLETED'], true)) {
$record = [
'event_type' => $type,
'status' => 'PAID',
'amount' => $amount,
'currency' => $currency,
'payer' => $payer,
'invoice' => $invoice,
'custom' => $custom,
'resource_id' => $res['id'] ?? null,
'items' => $items, // Persist line items for your return.php/UI
'ts' => date('c'),
];
$name = $invoice ?: 'NO-INVOICE';
@file_put_contents($config['data_dir']."/$name.json", json_encode($record, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
$status = 'WROTE_FILE';
}
log_line("EVENT $type invoice=".($invoice ?: 'none')." items_count=".count($items)." status=$status");

View file

@ -0,0 +1 @@
This folder contains compatibility wrappers for PayPal API endpoints. The canonical implementations live in /_website/api/.

View file

@ -0,0 +1,7 @@
<?php
// Local _website copy of paypal/config.php - configuration is centralized in includes/config.inc.php
// This file is intentionally lightweight and will include the site config.
require_once(__DIR__ . '/../includes/config.inc.php');
// If you need PayPal-specific overrides, add them here.
?>

103
_website/paypal/pay.php Normal file
View file

@ -0,0 +1,103 @@
<?php
// ==== YOUR CART DATA (server authoritative) ====
// TODO: set these from your cart/session/DB:
$amount = number_format(19.99, 2, '.', '');
$currency = 'USD';
$invoiceId = 'INV-' . date('Ymd-His') . '-' . bin2hex(random_bytes(3));
$customId = 'user_1234_order_5678';
$description = 'Game server monthly plan';
// Site base (adjust if different)
$siteBase = 'https://panel.iaregamer.com';
// Where your API endpoints live:
$returnUrl = $siteBase . '/_website/return.php?invoice=' . urlencode($invoiceId);
$cancelUrl = $siteBase . '/_website/return.php?invoice=' . urlencode($invoiceId) . '&cancel=1';
// Where your API endpoints live:
$apiBase = '/_website/api';
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Checkout</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- PayPal JS SDK (Sandbox). Use LIVE client-id when you go live. -->
<script src="https://www.paypal.com/sdk/js?client-id=AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c&currency=USD&intent=capture"></script>
<style>body{font-family:system-ui,Arial,sans-serif;max-width:700px;margin:40px auto;padding:0 16px}</style>
</head>
<body>
<h1>Complete your purchase</h1>
<p><strong>Amount:</strong> <?= htmlspecialchars($currency) ?> <?= htmlspecialchars($amount) ?></p>
<p><strong>Invoice:</strong> <?= htmlspecialchars($invoiceId) ?></p>
<div id="paypal-button-container"></div>
<div id="status" style="margin-top:16px"></div>
<script>
const statusEl = document.getElementById('status');
const amount = "<?= $amount ?>";
const currency = "<?= $currency ?>";
const invoice_id = "<?= $invoiceId ?>";
const custom_id = "<?= htmlspecialchars($customId, ENT_QUOTES) ?>";
const description = "<?= htmlspecialchars($description, ENT_QUOTES) ?>";
const return_url = "<?= $returnUrl ?>";
const cancel_url = "<?= $cancelUrl ?>";
function setStatus(msg){ statusEl.textContent = msg; }
paypal.Buttons({
// Show a single, small PayPal button
style: {
layout: 'vertical', // or 'horizontal'
color: 'gold', // gold | blue | silver | black | white
shape: 'pill', // pill | rect
label: 'paypal', // paypal | pay | checkout | buynow
height: 35, // 25
55 (smaller button = lower height)
tagline: false
},
fundingSource: paypal.FUNDING.PAYPAL, // only the PayPal button
createOrder: function() {
// (unchanged) 5 your fetch to create_order.php
return fetch("<?= $apiBase ?>/create_order.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({
amount, currency, invoice_id, custom_id, description,
return_url, cancel_url,
items, line_invoices
})
})
.then(r => r.json())
.then(d => {
if (!d.id) throw new Error(d.error || 'No order id');
return d.id;
});
},
onApprove: function(data) {
// (unchanged) 5 capture then redirect
return fetch("<?= $apiBase ?>/capture_order.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ order_id: data.orderID })
})
.then(r => r.json())
.then(c => {
if (c.status === 'COMPLETED') {
window.location.href = return_url;
} else {
document.getElementById('pp-status').textContent = 'Capture status: ' + c.status;
}
});
},
onCancel: function(){ window.location.href = cancel_url; },
onError: function(err){ document.getElementById('pp-status').textContent = 'PayPal error: ' + err; }
}).render('#paypal-button-container');
</script>
</body>
</html>

View file

@ -0,0 +1,4 @@
<?php
// Compatibility wrapper for old /paypal/return.php — route to unified return page
header('Location: /_website/return.php' . (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : ''));
exit;

161
_website/paypal/webhook.php Normal file
View file

@ -0,0 +1,161 @@
<?php
// Full webhook implementation (migrated from top-level paypal/webhook.php)
// Uses central site config where possible; fall back to local defaults.
require_once(__DIR__ . '/../includes/config.inc.php');
$config = [
'sandbox' => true,
'client_id' => '',
'client_secret' => '',
'webhook_id' => '',
'data_dir' => realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data',
'log_file' => realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'webhook.log',
];
// Allow includes/config.inc.php to override SITE_DATA_DIR if set
if (defined('SITE_DATA_DIR') && SITE_DATA_DIR) {
$config['data_dir'] = rtrim(SITE_DATA_DIR, "\\/") . DIRECTORY_SEPARATOR;
}
@mkdir($config['data_dir'], 0775, true);
function log_line($m){global $config; @file_put_contents($config['log_file'],'['.date('c')."] $m\n",FILE_APPEND);}
function api_base(){global $config; return $config['sandbox'] ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';}
http_response_code(200);
$raw = file_get_contents('php://input');
$headers = array_change_key_case(getallheaders() ?: [], CASE_UPPER);
log_line("HIT ip=".($_SERVER['REMOTE_ADDR']??'') ." bytes=".strlen($raw));
if (!$raw) { log_line("NO_BODY"); exit; }
// 1) OAuth2
$ch = curl_init(api_base().'/v1/oauth2/token');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_POST=>true,
CURLOPT_POSTFIELDS=>'grant_type=client_credentials',
CURLOPT_HTTPHEADER=>['Accept: application/json'],
CURLOPT_USERPWD=>$config['client_id'].':'.$config['client_secret'],
]);
$tokenResp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http!==200){ log_line("OAUTH_FAIL http=$http resp=$tokenResp"); exit; }
$access_token = json_decode($tokenResp, true)['access_token'] ?? null;
if (!$access_token){ log_line("OAUTH_NO_TOKEN"); exit; }
// 2) Verify webhook signature
$verifyPayload = [
'transmission_id' => $headers['PAYPAL-TRANSMISSION-ID'] ?? '',
'transmission_time' => $headers['PAYPAL-TRANSMISSION-TIME'] ?? '',
'cert_url' => $headers['PAYPAL-CERT-URL'] ?? '',
'auth_algo' => $headers['PAYPAL-AUTH-ALGO'] ?? '',
'transmission_sig' => $headers['PAYPAL-TRANSMISSION-SIG'] ?? '',
'webhook_id' => $config['webhook_id'],
'webhook_event' => json_decode($raw, true),
];
$ch = curl_init(api_base().'/v1/notifications/verify-webhook-signature');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_POST=>true,
CURLOPT_POSTFIELDS=>json_encode($verifyPayload),
CURLOPT_HTTPHEADER=>[
'Content-Type: application/json',
'Authorization: Bearer '.$access_token
],
]);
$verifyResp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$verifyJson = json_decode($verifyResp, true);
if ($http!==200 || ($verifyJson['verification_status'] ?? '') !== 'SUCCESS'){
log_line("VERIFY_FAIL http=$http status=".($verifyJson['verification_status']??'NONE'));
exit;
}
log_line("VERIFY_OK");
// 3) Parse and persist (now with items)
$evt = json_decode($raw, true);
$type = $evt['event_type'] ?? '';
$res = $evt['resource'] ?? [];
// Extract common fields
$invoice = $res['invoice_id'] ?? ($res['invoice_number'] ?? null);
$custom = $res['custom_id'] ?? ($res['custom'] ?? null);
// Amounts/payer
$amount = $res['amount']['value'] ?? ($res['amount']['total'] ?? null);
$currency = $res['amount']['currency_code'] ?? ($res['amount']['currency'] ?? null);
$payer = $res['payer']['email_address'] ?? ($res['payer']['payer_info']['email'] ?? null);
// Try to capture line items if present directly in this event:
$items = [];
if (isset($res['purchase_units'][0]['items']) && is_array($res['purchase_units'][0]['items'])) {
$items = $res['purchase_units'][0]['items'];
}
// If capture event, try to fetch the parent ORDER to get items
if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
$orderId =
$res['supplementary_data']['related_ids']['order_id'] // preferred
?? null;
if (!$orderId && isset($res['links']) && is_array($res['links'])) {
// Fallback: look for a link to the parent order
foreach ($res['links'] as $lnk) {
if (!empty($lnk['href']) && !empty($lnk['rel']) && stripos($lnk['href'], '/v2/checkout/orders/') !== false) {
$orderId = basename(parse_url($lnk['href'], PHP_URL_PATH));
break;
}
}
}
if ($orderId) {
$ch = curl_init(api_base()."/v2/checkout/orders/".urlencode($orderId));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer '.$access_token,
'Content-Type: application/json'
],
]);
$orderJson = curl_exec($ch);
$httpOrder = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpOrder === 200) {
$order = json_decode($orderJson, true);
if (isset($order['purchase_units'][0]['items']) && is_array($order['purchase_units'][0]['items'])) {
$items = $order['purchase_units'][0]['items'];
}
// If the order has invoice/custom (sometimes more reliable), prefer those:
if (!$invoice) { $invoice = $order['purchase_units'][0]['invoice_id'] ?? $invoice; }
if (!$custom) { $custom = $order['purchase_units'][0]['custom_id'] ?? $custom; }
} else {
log_line("ORDER_FETCH_FAIL id=$orderId http=$httpOrder");
}
}
}
$status = 'IGNORED';
// We persist on payment completed events
if (in_array($type, ['PAYMENT.CAPTURE.COMPLETED','PAYMENT.SALE.COMPLETED'], true)) {
$record = [
'event_type' => $type,
'status' => 'PAID',
'amount' => $amount,
'currency' => $currency,
'payer' => $payer,
'invoice' => $invoice,
'custom' => $custom,
'resource_id' => $res['id'] ?? null,
'items' => $items, // Persist line items for your return.php/UI
'ts' => date('c'),
];
$name = $invoice ?: 'NO-INVOICE';
@file_put_contents($config['data_dir']."/$name.json", json_encode($record, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
$status = 'WROTE_FILE';
}
log_line("EVENT $type invoice=".($invoice ?: 'none')." items_count=".count($items)." status=$status");

6
_website/privacy.php Normal file
View file

@ -0,0 +1,6 @@
<?php include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?>
<div class="container-wide pad-40">
<h1>Privacy</h1>
<p>This is the privacy page placeholder.</p>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>

65
_website/register.php Normal file
View file

@ -0,0 +1,65 @@
<?php
session_name("gameservers_website");
session_start();
require_once(__DIR__ . '/includes/config.inc.php');
// Simple registration form (creates a user in ogp_users with MD5 password)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['username']) && !empty($_POST['password'])) {
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if ($db) {
$username = trim($_POST['username']);
$password = $_POST['password'];
$email = trim($_POST['email']);
// basic validation
if ($username === '' || $password === '' || $email === '') {
$error = 'All fields are required.';
} else {
// Store legacy MD5 for panel compatibility, and also store a modern hash
$md5pw = md5($password);
$modern = password_hash($password, PASSWORD_DEFAULT);
// Try to insert with shadow column if it exists
$has_shadow = false;
$res = $db->query("SHOW COLUMNS FROM ogp_users LIKE 'users_pass_hash'");
if ($res && $res->num_rows > 0) {
$has_shadow = true;
}
if ($has_shadow) {
$stmt = $db->prepare("INSERT INTO ogp_users (users_login, users_passwd, users_pass_hash, users_email, users_role) VALUES (?, ?, ?, ?, 'user')");
$stmt->bind_param('ssss', $username, $md5pw, $modern, $email);
} else {
$stmt = $db->prepare("INSERT INTO ogp_users (users_login, users_passwd, users_email, users_role) VALUES (?, ?, ?, 'user')");
$stmt->bind_param('sss', $username, $md5pw, $email);
}
if ($stmt->execute()) {
// Redirect to absolute login URL
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$pos = strpos($script, '/_website');
$siteRoot = $pos !== false ? substr($script, 0, $pos + strlen('/_website')) : rtrim(dirname($script), '/\\');
header('Location: ' . $siteRoot . '/login.php?registered=1');
exit;
} else {
$error = 'Could not create user. Maybe the name is taken.';
}
}
}
}
?>
<!doctype html>
<html>
<head><meta charset="utf-8"><title>Register - GameServers.World</title></head>
<body>
<?php include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?>
<h2>Register</h2>
<?php if (!empty($error)) echo '<div class="muted text-danger">'.htmlspecialchars($error).'</div>'; ?>
<form method="post" action="register.php">
<label>Username<br><input type="text" name="username" required></label><br>
<label>Email<br><input type="email" name="email"></label><br>
<label>Password<br><input type="password" name="password" required></label><br>
<button type="submit">Register</button>
</form>
</body>
</html>

View file

@ -1,7 +1,7 @@
<?php <?php
// Reads data/<invoice>.json written by webhook.php and shows a receipt with items require_once(__DIR__ . '/includes/config.inc.php');
$dataDir = __DIR__ . '/data'; $dataDir = (isset($SITE_DATA_DIR) && $SITE_DATA_DIR) ? $SITE_DATA_DIR : realpath(__DIR__ . '/') . DIRECTORY_SEPARATOR . 'data';
$invoice = $_GET['invoice'] ?? ''; $invoice = $_GET['invoice'] ?? '';
$cancel = isset($_GET['cancel']); $cancel = isset($_GET['cancel']);
@ -9,8 +9,8 @@ $status = 'PENDING';
$details = null; $details = null;
$items = []; $items = [];
if ($invoice && is_file("$dataDir/$invoice.json")) { if ($invoice && is_file($dataDir . DIRECTORY_SEPARATOR . $invoice . '.json')) {
$details = json_decode(file_get_contents("$dataDir/$invoice.json"), true); $details = json_decode(file_get_contents($dataDir . DIRECTORY_SEPARATOR . $invoice . '.json'), true);
if (!empty($details['status'])) { if (!empty($details['status'])) {
$status = $details['status']; $status = $details['status'];
} }
@ -19,7 +19,6 @@ if ($invoice && is_file("$dataDir/$invoice.json")) {
} }
} }
// Helpers
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function money_fmt($value, $currency) { function money_fmt($value, $currency) {
if ($value === null || $value === '') return ''; if ($value === null || $value === '') return '';
@ -32,16 +31,11 @@ function money_fmt($value, $currency) {
<meta charset="utf-8"> <meta charset="utf-8">
<title>Payment Status</title> <title>Payment Status</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<style> <link rel="stylesheet" href="css/header.css">
body{font-family:system-ui,Arial,sans-serif;max-width:900px;margin:40px auto;padding:0 16px;line-height:1.45}
.muted{color:#555}
table{width:100%;border-collapse:collapse;margin-top:12px}
th,td{border:1px solid #ddd;padding:8px;text-align:left}
th{background:#f6f6f6}
code{background:#f4f4f4;padding:2px 4px;border-radius:4px}
</style>
</head> </head>
<body> <body>
<?php include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?>
<div class="site-panel">
<?php if ($cancel): ?> <?php if ($cancel): ?>
<h1>Payment canceled</h1> <h1>Payment canceled</h1>
<p>Invoice: <?= h($invoice) ?></p> <p>Invoice: <?= h($invoice) ?></p>
@ -67,7 +61,7 @@ function money_fmt($value, $currency) {
<h3>Items</h3> <h3>Items</h3>
<?php if ($items): ?> <?php if ($items): ?>
<table> <table class="cart-table">
<thead> <thead>
<tr> <tr>
<th>Server ID</th> <th>Server ID</th>
@ -83,7 +77,7 @@ function money_fmt($value, $currency) {
$grand = 0.00; $grand = 0.00;
foreach ($items as $it) { foreach ($items as $it) {
$name = $it['name'] ?? ''; $name = $it['name'] ?? '';
$sku = $it['sku'] ?? ''; // we sent serverID here $sku = $it['sku'] ?? '';
$qty = isset($it['quantity']) ? (int)$it['quantity'] : 1; $qty = isset($it['quantity']) ? (int)$it['quantity'] : 1;
$unit = isset($it['unit_amount']['value']) ? (float)$it['unit_amount']['value'] : 0.00; $unit = isset($it['unit_amount']['value']) ? (float)$it['unit_amount']['value'] : 0.00;
$line = $qty * $unit; $line = $qty * $unit;
@ -98,7 +92,7 @@ function money_fmt($value, $currency) {
} }
?> ?>
<tr> <tr>
<td colspan="4" style="text-align:right;"><strong>Total</strong></td> <td colspan="4" class="text-right"><strong>Total</strong></td>
<td><strong><?= money_fmt($grand, $currency) ?></strong></td> <td><strong><?= money_fmt($grand, $currency) ?></strong></td>
</tr> </tr>
</tbody> </tbody>
@ -114,6 +108,7 @@ function money_fmt($value, $currency) {
<p class="muted">Were waiting for PayPal to confirm your payment. This page will show the receipt once we receive the webhook. Try refreshing in a few seconds.</p> <p class="muted">Were waiting for PayPal to confirm your payment. This page will show the receipt once we receive the webhook. Try refreshing in a few seconds.</p>
<?php endif; ?> <?php endif; ?>
<?php endif; ?> <?php endif; ?>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body> </body>
</html> </html>

View file

@ -42,15 +42,16 @@ if (!$services) {
return; return;
} }
// Include menu // Include top bar and menu
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php'); include(__DIR__ . '/includes/menu.php');
?> ?>
<div style="border-left:10px solid transparent;"> <div>
<?php foreach ($services as $row): ?> <?php foreach ($services as $row): ?>
<?php if (!isset($_REQUEST['service_id'])): ?> <?php if (!isset($_REQUEST['service_id'])): ?>
<!-- Service listing (all) --> <!-- Service listing (all) -->
<div style="float:left; padding:30px 20px;"> <div class="float-left p-30-20">
<img src="../<?php echo $row['img_url']; ?>" width="460" height="225"><br> <img src="../<?php echo $row['img_url']; ?>" width="460" height="225"><br>
<strong><?php echo $row['service_name']; ?></strong><br> <strong><?php echo $row['service_name']; ?></strong><br>
<?php <?php
@ -58,14 +59,11 @@ include(__DIR__ . '/includes/menu.php');
?> ?>
<br> <br>
<form action="https://gameservers.world/order-server" method="POST"> <a href="order.php?service_id=<?php echo urlencode($row['service_id']); ?>" class="gsw-btn">Order Server</a>
<input type="hidden" name="service_id" value="<?php echo $row['service_id']; ?>">
<input type="submit" name="order" value="Order Server" >
</form>
</div> </div>
<?php else: ?> <?php else: ?>
<!-- Single service detail view --> <!-- Single service detail view -->
<div style="float:left; border: 4px solid transparent; border-bottom: 25px solid transparent;"> <div class="float-left decorative-bottom">
<img src="<?php echo $row['img_url']; ?>" width="230" height="112"><br> <img src="<?php echo $row['img_url']; ?>" width="230" height="112"><br>
<center><b><?php echo $row['service_name']; ?></b></center> <center><b><?php echo $row['service_name']; ?></b></center>
@ -97,7 +95,7 @@ include(__DIR__ . '/includes/menu.php');
<input type="hidden" name="service_id" value="<?php echo $row['service_id']; ?>"> <input type="hidden" name="service_id" value="<?php echo $row['service_id']; ?>">
<input type="hidden" name="remote_control_password" value="ChangeMe"> <input type="hidden" name="remote_control_password" value="ChangeMe">
<input type="hidden" name="ftp_password" value="ChangeMe"> <input type="hidden" name="ftp_password" value="ChangeMe">
<table style="float:left;"> <table class="float-left">
<tr> <tr>
<td align="right"><b>Game Server Name</b></td> <td align="right"><b>Game Server Name</b></td>
<td><input type="text" name="home_name" size="40" value="<?php echo $row['service_name']; ?>"></td> <td><input type="text" name="home_name" size="40" value="<?php echo $row['service_name']; ?>"></td>
@ -117,4 +115,5 @@ include(__DIR__ . '/includes/menu.php');
mysqli_close($db); mysqli_close($db);
?> ?>
</body> </body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</html> </html>

View file

@ -0,0 +1,30 @@
<?php
require_once(__DIR__ . '/../includes/config.inc.php');
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$db) {
echo "DB connect failed: " . mysqli_connect_error() . PHP_EOL;
exit(1);
}
$user = $argv[1] ?? 'iaregamer';
$user_safe = mysqli_real_escape_string($db, $user);
$has_shadow = false;
$res_cols = mysqli_query($db, "SHOW COLUMNS FROM ogp_users LIKE 'users_pass_hash'");
if ($res_cols && mysqli_num_rows($res_cols) > 0) $has_shadow = true;
$select_fields = 'user_id, users_login, users_passwd';
if ($has_shadow) $select_fields .= ", users_pass_hash";
$q = "SELECT $select_fields FROM ogp_users WHERE users_login = '$user_safe' LIMIT 1";
$res = mysqli_query($db, $q);
if (!$res) {
echo "Query error: " . mysqli_error($db) . PHP_EOL;
exit(1);
}
if (mysqli_num_rows($res) === 0) {
echo "No user found for '$user'\n";
} else {
$row = mysqli_fetch_assoc($res);
echo "Found user: id={$row['user_id']}, login={$row['users_login']}\n";
echo "passwd(db)=" . ($row['users_passwd'] ?? '') . "\n";
echo "pass_hash(db)=" . ($row['users_pass_hash'] ?? '') . "\n";
}
mysqli_close($db);
?>

View file

@ -0,0 +1,16 @@
<?php
$target = 'http://localhost/GSP/_website/invoices.php';
$ch = curl_init($target);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_HEADER=>true, CURLOPT_FOLLOWLOCATION=>false]);
$res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($res, 0, $header_size);
$body = substr($res, $header_size);
curl_close($ch);
echo "Request: $target\n";
echo "HTTP: $http\n";
echo "Headers:\n$headers\n";
echo "Body snippet:\n" . substr($body,0,400) . "\n";
?>

View file

@ -0,0 +1,21 @@
<?php
// Simple header fetcher for logout.php
$url = 'http://localhost/GSP/_website/logout.php';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
$res = curl_exec($ch);
if ($res === false) {
echo "Curl error: " . curl_error($ch) . PHP_EOL;
exit(1);
}
$info = curl_getinfo($ch);
echo "Request: $url\n";
echo "HTTP: " . ($info['http_code'] ?? '0') . "\n";
echo "Headers:\n";
$header_text = substr($res, 0, $info['header_size'] ?? 0);
echo $header_text . PHP_EOL;
curl_close($ch);
?>

View file

@ -0,0 +1,40 @@
<?php
// Fetch admin.php and then fetch the invoices.php link to observe redirects and Location headers
$adminUrl = 'http://localhost/GSP/_website/admin.php';
$ch = curl_init($adminUrl);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_HEADER=>true, CURLOPT_FOLLOWLOCATION=>false]);
$res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($res, 0, $header_size);
$body = substr($res, $header_size);
curl_close($ch);
echo "admin.php HTTP: $http\n";
echo "Headers:\n$headers\n";
// Find invoices link
if (preg_match('#href="([^"]*invoices\.php)"#i', $body, $m)) {
$link = $m[1];
echo "Found invoices link: $link\n";
// Resolve relative link
$linkUrl = (strpos($link, 'http')===0) ? $link : 'http://localhost/GSP/_website/' . ltrim($link, './');
echo "Resolved invoices URL: $linkUrl\n";
// Fetch invoices.php and show headers
$ch = curl_init($linkUrl);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_HEADER=>true, CURLOPT_FOLLOWLOCATION=>false]);
$res2 = curl_exec($ch);
$h2 = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$http2 = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headers2 = substr($res2, 0, $h2);
$body2 = substr($res2, $h2);
curl_close($ch);
echo "invoices.php HTTP: $http2\n";
echo "invoices headers:\n$headers2\n";
echo "invoices body snippet:\n" . substr($body2,0,400) . "\n";
} else {
echo "No invoices link found in admin.php body.\n";
}
?>

View file

@ -0,0 +1,39 @@
<?php
// Simple script to POST an existing JSON file to the local webhook endpoint
$webhookUrl = 'http://localhost/GSP/_website/webhook.php';
$sample = __DIR__ . '/../data/SIMULATED-WEBHOOK-20251022-101500.json';
if (!file_exists($sample)) {
echo "Sample file not found: $sample\n";
exit(1);
}
$raw = file_get_contents($sample);
$ch = curl_init($webhookUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $raw,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'PayPal-Transmission-Id: SIM-TEST',
'PayPal-Transmission-Time: ' . gmdate('c'),
'PayPal-Cert-Url: https://example.com/cert.pem',
'PayPal-Auth-Algo: SHA256withRSA',
'PayPal-Transmission-Sig: FAKE',
],
]);
$res = curl_exec($ch);
$err = curl_error($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP: $http\n";
if ($err) echo "CURL_ERROR: $err\n";
echo "RESPONSE:\n" . $res . "\n";
// show if a new file was written
$dataDir = realpath(__DIR__ . '/../data');
$files = glob($dataDir . '/*.json');
echo "Files in data/ after run: \n";
foreach ($files as $f) echo basename($f) . "\n";
?>

6
_website/tos.php Normal file
View file

@ -0,0 +1,6 @@
<?php include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?>
<div class="container-wide pad-40">
<h1>Terms of Service</h1>
<p>This is the terms of service placeholder.</p>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>

View file

@ -1,15 +1,23 @@
<?php <?php
// ========== CONFIG ========== require_once(__DIR__ . '/includes/config.inc.php');
$config = [ $config = [
'sandbox' => true, // flip to false for Live 'sandbox' => true,
'client_id' => 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c', 'client_id' => 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c',
'client_secret' => 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0', 'client_secret' => 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0',
'webhook_id' => '6N620673281740730', 'webhook_id' => '6N620673281740730',
'data_dir' => __DIR__ . '/data', 'data_dir' => rtrim(
'log_file' => __DIR__ . '/webhook.log', (defined('SITE_DATA_DIR') ? SITE_DATA_DIR : '') ?: ($SITE_DATA_DIR ?? ''),
DIRECTORY_SEPARATOR
),
'log_file' => __DIR__ . '/data/webhook.log',
]; ];
function log_line($m){global $config; @file_put_contents($config['log_file'],'['.date('c')."] $m\n",FILE_APPEND);} if (!$config['data_dir']) {
$config['data_dir'] = realpath(__DIR__ . '/') . DIRECTORY_SEPARATOR . 'data';
}
function log_line($m){global $config; @file_put_contents($config['log_file'],'['.date('c')."] $m\n",FILE_APPEND);}
function api_base(){global $config; return $config['sandbox'] ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';} function api_base(){global $config; return $config['sandbox'] ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';}
http_response_code(200); http_response_code(200);
@ -66,35 +74,26 @@ if ($http!==200 || ($verifyJson['verification_status'] ?? '') !== 'SUCCESS'){
} }
log_line("VERIFY_OK"); log_line("VERIFY_OK");
// 3) Parse and persist (now with items) // 3) Parse and persist
$evt = json_decode($raw, true); $evt = json_decode($raw, true);
$type = $evt['event_type'] ?? ''; $type = $evt['event_type'] ?? '';
$res = $evt['resource'] ?? []; $res = $evt['resource'] ?? [];
// Extract common fields
$invoice = $res['invoice_id'] ?? ($res['invoice_number'] ?? null); $invoice = $res['invoice_id'] ?? ($res['invoice_number'] ?? null);
$custom = $res['custom_id'] ?? ($res['custom'] ?? null); $custom = $res['custom_id'] ?? ($res['custom'] ?? null);
// Amounts/payer
$amount = $res['amount']['value'] ?? ($res['amount']['total'] ?? null); $amount = $res['amount']['value'] ?? ($res['amount']['total'] ?? null);
$currency = $res['amount']['currency_code'] ?? ($res['amount']['currency'] ?? null); $currency = $res['amount']['currency_code'] ?? ($res['amount']['currency'] ?? null);
$payer = $res['payer']['email_address'] ?? ($res['payer']['payer_info']['email'] ?? null); $payer = $res['payer']['email_address'] ?? ($res['payer']['payer_info']['email'] ?? null);
// Try to capture line items if present directly in this event:
// (Some events—like ORDER.*—include purchase_units; CAPTURE events often don't.)
$items = []; $items = [];
if (isset($res['purchase_units'][0]['items']) && is_array($res['purchase_units'][0]['items'])) { if (isset($res['purchase_units'][0]['items']) && is_array($res['purchase_units'][0]['items'])) {
$items = $res['purchase_units'][0]['items']; $items = $res['purchase_units'][0]['items'];
} }
// If capture event, try to fetch the parent ORDER to get items
if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') { if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
$orderId = $orderId = $res['supplementary_data']['related_ids']['order_id'] ?? null;
$res['supplementary_data']['related_ids']['order_id'] // preferred
?? null;
if (!$orderId && isset($res['links']) && is_array($res['links'])) { if (!$orderId && isset($res['links']) && is_array($res['links'])) {
// Fallback: look for a link to the parent order
foreach ($res['links'] as $lnk) { foreach ($res['links'] as $lnk) {
if (!empty($lnk['href']) && !empty($lnk['rel']) && stripos($lnk['href'], '/v2/checkout/orders/') !== false) { if (!empty($lnk['href']) && !empty($lnk['rel']) && stripos($lnk['href'], '/v2/checkout/orders/') !== false) {
$orderId = basename(parse_url($lnk['href'], PHP_URL_PATH)); $orderId = basename(parse_url($lnk['href'], PHP_URL_PATH));
@ -102,15 +101,11 @@ if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
} }
} }
} }
if ($orderId) { if ($orderId) {
$ch = curl_init(api_base()."/v2/checkout/orders/".urlencode($orderId)); $ch = curl_init(api_base()."/v2/checkout/orders/".urlencode($orderId));
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [ CURLOPT_HTTPHEADER => [ 'Authorization: Bearer '.$access_token, 'Content-Type: application/json' ],
'Authorization: Bearer '.$access_token,
'Content-Type: application/json'
],
]); ]);
$orderJson = curl_exec($ch); $orderJson = curl_exec($ch);
$httpOrder = curl_getinfo($ch, CURLINFO_HTTP_CODE); $httpOrder = curl_getinfo($ch, CURLINFO_HTTP_CODE);
@ -120,7 +115,6 @@ if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
if (isset($order['purchase_units'][0]['items']) && is_array($order['purchase_units'][0]['items'])) { if (isset($order['purchase_units'][0]['items']) && is_array($order['purchase_units'][0]['items'])) {
$items = $order['purchase_units'][0]['items']; $items = $order['purchase_units'][0]['items'];
} }
// If the order has invoice/custom (sometimes more reliable), prefer those:
if (!$invoice) { $invoice = $order['purchase_units'][0]['invoice_id'] ?? $invoice; } if (!$invoice) { $invoice = $order['purchase_units'][0]['invoice_id'] ?? $invoice; }
if (!$custom) { $custom = $order['purchase_units'][0]['custom_id'] ?? $custom; } if (!$custom) { $custom = $order['purchase_units'][0]['custom_id'] ?? $custom; }
} else { } else {
@ -130,8 +124,6 @@ if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
} }
$status = 'IGNORED'; $status = 'IGNORED';
// We persist on payment completed events
if (in_array($type, ['PAYMENT.CAPTURE.COMPLETED','PAYMENT.SALE.COMPLETED'], true)) { if (in_array($type, ['PAYMENT.CAPTURE.COMPLETED','PAYMENT.SALE.COMPLETED'], true)) {
$record = [ $record = [
'event_type' => $type, 'event_type' => $type,
@ -142,13 +134,14 @@ if (in_array($type, ['PAYMENT.CAPTURE.COMPLETED','PAYMENT.SALE.COMPLETED'], true
'invoice' => $invoice, 'invoice' => $invoice,
'custom' => $custom, 'custom' => $custom,
'resource_id' => $res['id'] ?? null, 'resource_id' => $res['id'] ?? null,
'items' => $items, // <— Persist line items for your return.php/UI 'items' => $items,
'ts' => date('c'), 'ts' => date('c'),
]; ];
$name = $invoice ?: 'NO-INVOICE'; $name = $invoice ?: 'NO-INVOICE-'.bin2hex(random_bytes(4));
@file_put_contents($config['data_dir']."/$name.json", json_encode($record, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); @file_put_contents($config['data_dir']."/".$name.".json", json_encode($record, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
$status = 'WROTE_FILE'; $status = 'WROTE_FILE';
} }
log_line("EVENT $type invoice=".($invoice ?: 'none')." items_count=".count($items)." status=$status"); log_line("EVENT $type invoice=".($invoice ?: 'none')." items_count=".count($items)." status=$status");
?>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,6 +0,0 @@
Webhook https://panel.iaregamer.com/paypal/webhook.php
Webhook ID 6N620673281740730
App Gameservers World
Client ID AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c
Secret Key EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0

View file

@ -1,102 +0,0 @@
-<?php
// ==== YOUR CART DATA (server authoritative) ====
// TODO: set these from your cart/session/DB:
$amount = number_format(19.99, 2, '.', '');
$currency = 'USD';
$invoiceId = 'INV-' . date('Ymd-His') . '-' . bin2hex(random_bytes(3));
$customId = 'user_1234_order_5678';
$description = 'Game server monthly plan';
// Site base (adjust if different)
$siteBase = 'https://panel.iaregamer.com';
$returnUrl = $siteBase . '/paypal/return.php?invoice=' . urlencode($invoiceId);
$cancelUrl = $siteBase . '/paypal/return.php?invoice=' . urlencode($invoiceId) . '&cancel=1';
// Where your API endpoints live:
$apiBase = '/paypal/api';
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Checkout</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- PayPal JS SDK (Sandbox). Use LIVE client-id when you go live. -->
<script src="https://www.paypal.com/sdk/js?client-id=AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c&currency=USD&intent=capture"></script>
<style>body{font-family:system-ui,Arial,sans-serif;max-width:700px;margin:40px auto;padding:0 16px}</style>
</head>
<body>
<h1>Complete your purchase</h1>
<p><strong>Amount:</strong> <?= htmlspecialchars($currency) ?> <?= htmlspecialchars($amount) ?></p>
<p><strong>Invoice:</strong> <?= htmlspecialchars($invoiceId) ?></p>
<div id="paypal-button-container"></div>
<div id="status" style="margin-top:16px"></div>
<script>
const statusEl = document.getElementById('status');
const amount = "<?= $amount ?>";
const currency = "<?= $currency ?>";
const invoice_id = "<?= $invoiceId ?>";
const custom_id = "<?= htmlspecialchars($customId, ENT_QUOTES) ?>";
const description = "<?= htmlspecialchars($description, ENT_QUOTES) ?>";
const return_url = "<?= $returnUrl ?>";
const cancel_url = "<?= $cancelUrl ?>";
function setStatus(msg){ statusEl.textContent = msg; }
paypal.Buttons({
// Show a single, small PayPal button
style: {
layout: 'vertical', // or 'horizontal'
color: 'gold', // gold | blue | silver | black | white
shape: 'pill', // pill | rect
label: 'paypal', // paypal | pay | checkout | buynow
height: 35, // 2555 (smaller button = lower height)
tagline: false
},
fundingSource: paypal.FUNDING.PAYPAL, // only the PayPal button
createOrder: function() {
// (unchanged) — your fetch to create_order.php
return fetch("<?= $apiBase ?>/create_order.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({
amount, currency, invoice_id, custom_id, description,
return_url, cancel_url,
items, line_invoices
})
})
.then(r => r.json())
.then(d => {
if (!d.id) throw new Error(d.error || 'No order id');
return d.id;
});
},
onApprove: function(data) {
// (unchanged) — capture then redirect
return fetch("<?= $apiBase ?>/capture_order.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ order_id: data.orderID })
})
.then(r => r.json())
.then(c => {
if (c.status === 'COMPLETED') {
window.location.href = return_url;
} else {
document.getElementById('pp-status').textContent = 'Capture status: ' + c.status;
}
});
},
onCancel: function(){ window.location.href = cancel_url; },
onError: function(err){ document.getElementById('pp-status').textContent = 'PayPal error: ' + err; }
}).render('#paypal-button-container');
</script>
</body>
</html>