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
// 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 = 'http://gameservers.world/';
/* === SITE_BASE_URL is loaded from includes/config.inc.php; leave empty to use relative paths === */
// Include database configuration
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);
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');
/* show errors during setup */
@ -138,7 +141,8 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
?>
<?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; ?>
<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">
<div style="display:flex;flex-wrap:wrap;gap:10px;">
<?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':''); ?>>
<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>
<?php endforeach; ?>
</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>
<hr style="margin:20px 0;">
<hr>
<h2>Current Services</h2>
<?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;">
<thead>
<tr>
<th style="border-bottom:1px solid #ddd;padding:6px;">Enabled</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Service Name <small style="color:#777;">(ID below)</small></th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Min Slots</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Max Slots</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Price (Monthly)</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Thumbnail URL</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Preview</th>
<th style="border-bottom:1px solid #ddd;padding:6px;">Update Row</th>
<th>Enabled</th>
<th>Service Name <small class="muted">(ID below)</small></th>
<th>Min Slots</th>
<th>Max Slots</th>
<th>Price (Monthly)</th>
<th>Thumbnail URL</th>
<th>Preview</th>
<th>Update Row</th>
</tr>
</thead>
<tbody>
@ -189,57 +194,61 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
$imgUrl = trim((string)$row['img_url']);
$displayUrl = '';
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) -->
<tr>
<!-- Enabled first -->
<td style="padding:6px;">
<td>
<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':''); ?>>
</td>
<!-- Service name (with tiny ID under it) -->
<td style="padding:6px; text-align:left;">
<input type="text" name="service[<?php echo $sid; ?>][service_name]" value="<?php echo h($row['service_name']); ?>" style="min-width:260px;">
<div style="color:#777; font-size:12px; margin-top:2px;">ID: <?php echo $sid; ?></div>
<td>
<input type="text" name="service[<?php echo $sid; ?>][service_name]" value="<?php echo h($row['service_name']); ?>" class="min-w-260">
<div class="small-muted">ID: <?php echo $sid; ?></div>
</td>
<td style="padding:6px;">
<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;">
<td>
<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 style="padding:6px;">
<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;">
<td>
<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 style="padding:6px;">
<td>
<input type="text" name="service[<?php echo $sid; ?>][price_monthly]" value="<?php echo h($row['price_monthly']); ?>" size="8">
</td>
<!-- Thumbnail URL input -->
<td style="padding:6px; vertical-align:top;">
<input type="text" name="service[<?php echo $sid; ?>][img_url]" value="<?php echo h($row['img_url']); ?>" style="min-width:240px;">
<td>
<input type="text" name="service[<?php echo $sid; ?>][img_url]" value="<?php echo h($row['img_url']); ?>" class="min-w-240">
</td>
<!-- Preview (uses BASE + relative path) -->
<td style="padding:6px; vertical-align:top;">
<td>
<?php if ($displayUrl !== ''): ?>
<img src="<?php echo h($displayUrl); ?>" alt="preview" loading="lazy"
style="max-height:48px; max-width:120px; border:1px solid #eee; display:block;"
onerror="this.style.display='none'">
<img src="<?php echo h($displayUrl); ?>" alt="preview" loading="lazy" class="img-preview" onerror="this.style.display='none'">
<?php else: ?>
<span style="color:#aaa;">(no image)</span>
<span class="muted">(no image)</span>
<?php endif; ?>
</td>
<!-- Per-row Update (smaller) -->
<td style="padding:6px;">
<button type="submit" name="update_single" value="<?php echo $sid; ?>"
style="padding:3px 8px; font-size:12px;">Update Row</button>
</td>
<td>
<button type="submit" name="update_single" value="<?php echo $sid; ?>" class="btn-small">Update Row</button>
</td>
</tr>
<!-- LOCATIONS ROW (single bottom divider) -->
@ -252,10 +261,10 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
$isChecked = isset($selSet[$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; ?>"
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; ?>)
<span style="margin-left:10px;">
<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>
</span>
<?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; ?>
</label>
<?php endforeach; ?>

View file

@ -233,23 +233,28 @@ try {
$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 -->
<div style="max-width:760px; margin:20px auto; font-family:Arial, sans-serif;">
<div class="ai-container">
<h3>Site Assistant</h3>
<p>Type a question below. Press <b>Enter</b> to send, <b>Shift+Enter</b> for a new line.</p>
<?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); ?>
</div>
<?php endif; ?>
<?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; ?>
<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;">
<button type="submit">Send</button>
<button type="submit" name="reset_thread" value="1">Reset Conversation</button>

View file

@ -1,5 +1,5 @@
<?php
// === CONFIG (Sandbox) ===
require_once(__DIR__ . '/../includes/config.inc.php');
$sandbox = true; // flip to false for Live
$client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c';
$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';
// 1) OAuth2
$ch = curl_init("$api/v1/oauth2/token");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
@ -26,15 +25,11 @@ curl_close($ch);
if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_fail']); exit; }
$access = json_decode($tok, true)['access_token'] ?? null;
// 2) Capture
$ch = curl_init("$api/v2/checkout/orders/$order_id/capture");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $access
],
CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $access ],
]);
$res = curl_exec($ch);
$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]);
?>

View file

@ -1,5 +1,6 @@
<?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
$client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c';
$client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0';
@ -8,25 +9,17 @@ header('Content-Type: application/json');
$in = json_decode(file_get_contents('php://input'), true) ?: [];
// Incoming fields from your cart/client
$amount_in = $in['amount'] ?? '0.00'; // overall intended amount (string)
$amount_in = $in['amount'] ?? '0.00';
$currency = $in['currency'] ?? 'USD';
$invoice_id = $in['invoice_id'] ?? null; // overall invoice id (string)
$custom_id = $in['custom_id'] ?? null; // your short reference (<=127 chars)
$invoice_id = $in['invoice_id'] ?? null;
$custom_id = $in['custom_id'] ?? null;
$description = $in['description'] ?? 'Order';
$return_url = $in['return_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, '.', '');
// Compute sum of items if present
if ($items) {
$sum = 0.00;
foreach ($items as $it) {
@ -34,13 +27,11 @@ if ($items) {
$val = isset($it['unit_amount']['value']) ? (float)$it['unit_amount']['value'] : 0.00;
$sum += $qty * $val;
}
// Use the item sum as the authoritative amount for PayPal (avoids mismatch errors)
$amount_value = number_format($sum, 2, '.', '');
}
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
// 1) OAuth2
$ch = curl_init("$api/v1/oauth2/token");
curl_setopt_array($ch, [
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;
if (!$access) { http_response_code(500); echo json_encode(['error'=>'oauth_no_token']); exit; }
// 2) Build purchase unit
$purchaseUnit = [
'amount' => [
'currency_code' => $currency,
'value' => $amount_value,
],
'amount' => [ 'currency_code' => $currency, 'value' => $amount_value ],
'description' => $description,
// Critical for webhook reconciliation:
'invoice_id' => $invoice_id,
'custom_id' => $custom_id
];
// If items provided, include them and add a breakdown with item_total to match the overall amount
if ($items) {
$purchaseUnit['items'] = $items;
$purchaseUnit['amount']['breakdown'] = [
'item_total' => [
'currency_code' => $currency,
'value' => $amount_value
]
];
$purchaseUnit['amount']['breakdown'] = [ '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 = [
'intent' => 'CAPTURE',
'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");
@ -99,10 +69,7 @@ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $access
],
CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $access ],
]);
$res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
@ -111,3 +78,4 @@ curl_close($ch);
if ($http !== 201) { http_response_code($http); echo $res; exit; }
echo $res;
?>

View file

@ -11,6 +11,9 @@ ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Require login
require_once(__DIR__ . '/includes/login_required.php');
// Include database configuration
require_once(__DIR__ . '/includes/config.inc.php');
@ -20,7 +23,41 @@ if (!$db) {
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');
$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);">
<h2 style="font-size:1.5rem; font-weight:bold; color:#1f2937; margin-bottom:1.5rem; text-align:center;">Your Cart</h2>
<div class="site-panel">
<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
-->
<table style="border-collapse:separate; border-spacing:0; width:100%; color:#000000;">
<thead style="background-color:#f9fafb;">
<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 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 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 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 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 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 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 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>
</thead>
<tbody style="background-color:#ffffff;">
<table class="cart-table">
<thead>
<tr>
<th class="table-compact text-center"></th>
<th>Server ID</th>
<th>Game Name</th>
<th>Location</th>
<th>Max Players</th>
<th>Price per Player</th>
<th>Months</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<?php
$grandTotal = 0; // Initialize grand total variable
if (isset($carts) && $carts instanceof mysqli_result && $carts->num_rows > 0) {
while ($row = $carts->fetch_assoc()) {
?>
<tr data-cart-id="<?php echo htmlspecialchars($row['order_id']); ?>" style="color:#000000;">
<td style="padding:1rem 1.5rem; text-align:center; border-bottom:1px solid #e5e7eb;">
<form method="post" action="" style="margin:0; display:inline;">
<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>
</form>
</td>
<td style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;"><?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 style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;"><?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 style="padding:1rem 1.5rem; text-align:left; border-bottom:1px solid #e5e7eb;">$<?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>
<?php $rowtotal = $row['price'] * $row['qty'] * $row['max_players'];?>
<tr data-cart-id="<?php echo htmlspecialchars($row['order_id']); ?>">
<td>
<form method="post" action="" class="inline-form">
<button type="submit" name="delete_single" value="<?php echo htmlspecialchars($row['order_id']); ?>" class="btn-square text-danger">

</button>
</form>
</td>
<td><?php echo htmlspecialchars($row['home_id']); ?></td>
<td><?php echo htmlspecialchars($row['home_name']); ?></td>
<td><?php echo htmlspecialchars($row['ip']); ?></td>
<td><?php echo htmlspecialchars($row['max_players']); ?></td>
<td>$<?php echo number_format($row['price'], 2); ?></td>
<td><?php echo htmlspecialchars($row['qty']); ?></td>
<?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 ?>
<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>
@ -124,20 +170,20 @@ if ($db){
// Add total row
?>
<tr style="background-color:#f9fafb; font-weight:bold;">
<td colspan="7" style="padding:1rem 1.5rem; text-align:right; border-top:2px solid #374151; font-weight:600; color:#374151;">
<tr class="cart-total-row">
<td colspan="7" class="cart-total-label">
Cart Total:
</td>
<td style="padding:1rem 1.5rem; text-align:left; border-top:2px solid #374151; font-weight:600; color:#374151; font-size:1.1rem;">
$<?php echo number_format($grandTotal, 2); ?>
</td>
<td class="cart-total-value">
$<?php echo number_format($grandTotal, 2); ?>
</td>
</tr>
<?php
} else {
// Display a message if no cart items are found
?>
<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>
<?php
}
@ -186,17 +232,17 @@ $description = 'Game server order (' . count($lineItems) . ' item' . (count($lin
// URLs
$siteBase = 'https://panel.iaregamer.com';
$returnUrl = $siteBase . '/paypal/return.php?invoice=' . urlencode($invoiceId);
$cancelUrl = $siteBase . '/paypal/return.php?invoice=' . urlencode($invoiceId) . '&cancel=1';
$returnUrl = $siteBase . '/_website/return.php?invoice=' . urlencode($invoiceId);
$cancelUrl = $siteBase . '/_website/return.php?invoice=' . urlencode($invoiceId) . '&cancel=1';
// API base (relative)
$apiBase = '/paypal/api';
$apiBase = '/_website/api';
?>
<!-- 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>
<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>
(function(){
@ -278,5 +324,6 @@ $apiBase = '/paypal/api';
// Close database connection
mysqli_close($db);
?>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body>
</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">
<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>
<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">
<h1>Game Server Guides</h1>
<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>
<div class="wrap">
<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";
$table_prefix="ogp_";
$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
$is_logged_in = isset($_SESSION['website_user_id']) && !empty($_SESSION['website_user_id']);
$username = $is_logged_in ? htmlspecialchars($_SESSION['website_username']) : '';
?>
<style>
.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;}
// Determine if the logged-in user is an admin by checking the panel DB
$is_admin = false;
if ($is_logged_in) {
// load DB credentials
require_once(__DIR__ . '/config.inc.php');
// Prefer reusing an existing $db if present, otherwise open a local connection
$menu_db = null;
$menu_db_opened = false;
if (isset($db) && $db instanceof mysqli) {
$menu_db = $db;
} else {
$menu_db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name);
$menu_db_opened = true;
}
</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-left">
<a href="/">GameServers.World</a>
<a href="index.php">GameServers.World</a>
</div>
<nav class="gsw-header-nav">
<a href="/" class="gsw-nav-link">Home</a>
<a href="/serverlist.php" class="gsw-nav-link">Game Servers</a>
<a href="/cart.php" class="gsw-nav-link">Cart</a>
<?php if ($is_logged_in): ?>
<a href="/adminserverlist.php" class="gsw-nav-link">Admin</a>
<a href="index.php" class="gsw-nav-link">Home</a>
<a href="serverlist.php" class="gsw-nav-link">Game Servers</a>
<li>
<a href="cart.php">Cart
<?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; ?>
<a href="http://panel.iaregamer.com" class="gsw-nav-link" target="_blank">Panel Login</a>
</nav>
<div class="gsw-header-right">
<?php if ($is_logged_in): ?>
<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: ?>
<a href="/login.php" class="gsw-header-btn">Login</a>
<a href="login.php" class="gsw-header-btn">Login</a>
<?php endif; ?>
</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>
<body>
<?php include(__DIR__ . '/includes/top.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-page-center">
<section class="gsw-wrap" aria-label="GameServers.World">
@ -44,7 +50,7 @@
<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>
<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>
<div class="gsw-callout" role="note">
@ -76,9 +82,9 @@
</article>
</section>
<nav class="gsw-cta" aria-label="Primary actions">
<a class="gsw-btn" href="/server-list/">Browse Game Servers</a>
<a class="gsw-btn" href="/contact/">Talk to Support</a>
<nav class="gsw-cta" aria-label="Primary actions">
<a class="gsw-btn" href="serverlist.php">Browse Game Servers</a>
<a class="gsw-btn" href="contact/">Talk to Support</a>
</nav>
<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>
</body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</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_start();
// We'll compute a site root below (up to /_website) and define a strict sanitizer after config is loaded
// Include database configuration
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
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$db) {
@ -19,8 +50,14 @@ function logger($logtext){
// Check if user is already logged in
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
header('Location: /');
header('Location: ' . $sanitized_return);
exit();
}
@ -39,15 +76,50 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
// Sanitize username to prevent SQL injection
$username = mysqli_real_escape_string($db, $username);
// Query the panel database for the user
$query = "SELECT user_id, users_login, users_passwd, users_role, users_email FROM ogp_users WHERE users_login = '$username'";
$result = mysqli_query($db, $query);
if ($result && mysqli_num_rows($result) === 1) {
$user = mysqli_fetch_assoc($result);
// Detect if shadow column for modern hash exists, and build a safe SELECT
$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, 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)
if (md5($password) === $user['users_passwd']) {
// Prefer modern password hash if present (shadow column), otherwise fall back to MD5 and migrate
$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
$_SESSION['website_user_id'] = $user['user_id'];
$_SESSION['website_username'] = $user['users_login'];
@ -60,8 +132,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
// Log the login
logger("Website login successful: " . $user['users_login']);
// Redirect after 2 seconds
header('Refresh: 2; URL=/');
// Redirect after 2 seconds to the requested return path or index.php, using strict sanitizer
// 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 {
$error_message = 'Invalid username or password.';
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;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
display: block;
padding: 0; /* we'll handle padding in content wrapper */
}
/* 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 {
@ -215,7 +301,9 @@ mysqli_close($db);
</style>
</head>
<body>
<?php include(__DIR__ . '/includes/top.php'); ?>
<?php include(__DIR__ . '/includes/menu.php'); ?>
<div class="content">
<div class="login-container">
<div class="login-header">
<h1>Welcome Back</h1>
@ -230,7 +318,13 @@ mysqli_close($db);
<div class="alert alert-success"><?php echo htmlspecialchars($success_message); ?></div>
<?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">
<input type="hidden" name="return_to" value="<?php echo htmlspecialchars($return_to_raw); ?>">
<div class="form-group">
<label for="ulogin">Username</label>
<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>
</form>
<div class="center mt-12">
<a href="register.php">Register</a>
</div>
<div class="divider">or</div>
<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>
</div>
</div>
</div>
</body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</html>

View file

@ -23,8 +23,35 @@ if (isset($_COOKIE[session_name()])) {
// Destroy the session
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
header('Location: /');
// sanitize: disallow absolute URLs (with protocol), CR/LF; allow safe path characters.
$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();
?>

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.
*/
// Require login for ordering
require_once(__DIR__ . '/includes/login_required.php');
// Include database configuration
require_once(__DIR__ . '/includes/config.inc.php');
@ -29,7 +32,8 @@ if (!$db) {
die("Connection failed: " . mysqli_connect_error());
}
// Include menu
// Include top bar and menu
include(__DIR__ . '/includes/top.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) {
$service_id[$key] = $row['service_id'];
$service_ids[$key] = $row['service_id'];
$home_cfg_id[$key] = $row['home_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'];
$slot_max_qty[$key] = $row['slot_max_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'];
$install_method[$key] = $row['install_method'];
$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
foreach($services as $row)
{
if(!isset($_REQUEST['service_id']))
{
?>
<div style="
float:left;
padding-top: 30px;
padding-right: 20px;
padding-bottom: 30px;
padding-left: 20px;">
<div class="float-left p-30-20">
@ -135,7 +134,7 @@ if ($row['price_monthly'] == 0.0) {
//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 ">
<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>";
?>
</div>
<table style="float:left;">
<table class="float-left">
<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="remote_control_password" size="15" value="ChangeMe">
@ -295,7 +294,7 @@ if ($row['price_monthly'] == 0.0) {
</tr>
<tr>
<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>
</form>
</td>
@ -311,4 +310,5 @@ if ($row['price_monthly'] == 0.0) {
mysqli_close($db);
?>
</body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</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
// 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'] ?? '';
$cancel = isset($_GET['cancel']);
@ -9,8 +9,8 @@ $status = 'PENDING';
$details = null;
$items = [];
if ($invoice && is_file("$dataDir/$invoice.json")) {
$details = json_decode(file_get_contents("$dataDir/$invoice.json"), true);
if ($invoice && is_file($dataDir . DIRECTORY_SEPARATOR . $invoice . '.json')) {
$details = json_decode(file_get_contents($dataDir . DIRECTORY_SEPARATOR . $invoice . '.json'), true);
if (!empty($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 money_fmt($value, $currency) {
if ($value === null || $value === '') return '';
@ -32,16 +31,11 @@ function money_fmt($value, $currency) {
<meta charset="utf-8">
<title>Payment Status</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
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>
<link rel="stylesheet" href="css/header.css">
</head>
<body>
<?php include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?>
<div class="site-panel">
<?php if ($cancel): ?>
<h1>Payment canceled</h1>
<p>Invoice: <?= h($invoice) ?></p>
@ -67,7 +61,7 @@ function money_fmt($value, $currency) {
<h3>Items</h3>
<?php if ($items): ?>
<table>
<table class="cart-table">
<thead>
<tr>
<th>Server ID</th>
@ -83,7 +77,7 @@ function money_fmt($value, $currency) {
$grand = 0.00;
foreach ($items as $it) {
$name = $it['name'] ?? '';
$sku = $it['sku'] ?? ''; // we sent serverID here
$sku = $it['sku'] ?? '';
$qty = isset($it['quantity']) ? (int)$it['quantity'] : 1;
$unit = isset($it['unit_amount']['value']) ? (float)$it['unit_amount']['value'] : 0.00;
$line = $qty * $unit;
@ -98,7 +92,7 @@ function money_fmt($value, $currency) {
}
?>
<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>
</tr>
</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>
<?php endif; ?>
<?php endif; ?>
</div>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</body>
</html>

View file

@ -42,15 +42,16 @@ if (!$services) {
return;
}
// Include menu
// Include top bar and menu
include(__DIR__ . '/includes/top.php');
include(__DIR__ . '/includes/menu.php');
?>
<div style="border-left:10px solid transparent;">
<div>
<?php foreach ($services as $row): ?>
<?php if (!isset($_REQUEST['service_id'])): ?>
<!-- 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>
<strong><?php echo $row['service_name']; ?></strong><br>
<?php
@ -58,14 +59,11 @@ include(__DIR__ . '/includes/menu.php');
?>
<br>
<form action="https://gameservers.world/order-server" method="POST">
<input type="hidden" name="service_id" value="<?php echo $row['service_id']; ?>">
<input type="submit" name="order" value="Order Server" >
</form>
<a href="order.php?service_id=<?php echo urlencode($row['service_id']); ?>" class="gsw-btn">Order Server</a>
</div>
<?php else: ?>
<!-- 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>
<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="remote_control_password" value="ChangeMe">
<input type="hidden" name="ftp_password" value="ChangeMe">
<table style="float:left;">
<table class="float-left">
<tr>
<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>
@ -117,4 +115,5 @@ include(__DIR__ . '/includes/menu.php');
mysqli_close($db);
?>
</body>
<?php include(__DIR__ . '/includes/footer.php'); ?>
</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
// ========== CONFIG ==========
require_once(__DIR__ . '/includes/config.inc.php');
$config = [
'sandbox' => true, // flip to false for Live
'sandbox' => true,
'client_id' => 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c',
'client_secret' => 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0',
'webhook_id' => '6N620673281740730',
'data_dir' => __DIR__ . '/data',
'log_file' => __DIR__ . '/webhook.log',
'data_dir' => rtrim(
(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';}
http_response_code(200);
@ -66,35 +74,26 @@ if ($http!==200 || ($verifyJson['verification_status'] ?? '') !== 'SUCCESS'){
}
log_line("VERIFY_OK");
// 3) Parse and persist (now with items)
// 3) Parse and persist
$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:
// (Some events—like ORDER.*—include purchase_units; CAPTURE events often don't.)
$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;
$orderId = $res['supplementary_data']['related_ids']['order_id'] ?? 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));
@ -102,15 +101,11 @@ if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
}
}
}
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'
],
CURLOPT_HTTPHEADER => [ 'Authorization: Bearer '.$access_token, 'Content-Type: application/json' ],
]);
$orderJson = curl_exec($ch);
$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'])) {
$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 {
@ -130,8 +124,6 @@ if (!$items && $type === 'PAYMENT.CAPTURE.COMPLETED') {
}
$status = 'IGNORED';
// We persist on payment completed events
if (in_array($type, ['PAYMENT.CAPTURE.COMPLETED','PAYMENT.SALE.COMPLETED'], true)) {
$record = [
'event_type' => $type,
@ -142,13 +134,14 @@ if (in_array($type, ['PAYMENT.CAPTURE.COMPLETED','PAYMENT.SALE.COMPLETED'], true
'invoice' => $invoice,
'custom' => $custom,
'resource_id' => $res['id'] ?? null,
'items' => $items, // <— Persist line items for your return.php/UI
'items' => $items,
'ts' => date('c'),
];
$name = $invoice ?: 'NO-INVOICE';
@file_put_contents($config['data_dir']."/$name.json", json_encode($record, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
$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));
$status = 'WROTE_FILE';
}
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>