website fix
This commit is contained in:
parent
e14794bc59
commit
309d08497b
58 changed files with 1690 additions and 363 deletions
68
_website/admin.php
Normal file
68
_website/admin.php
Normal 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
103
_website/admin_config.php
Normal 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>
|
||||
59
_website/admin_payments.php
Normal file
59
_website/admin_payments.php
Normal 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>
|
||||
|
|
@ -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; ?>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
?>
|
||||
|
|
@ -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;
|
||||
|
||||
?>
|
||||
|
|
@ -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> </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¤cy=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
115
_website/css/header.css
Normal 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}
|
||||
|
||||
10
_website/data/SIMULATED-WEBHOOK-20251022-101500.json
Normal file
10
_website/data/SIMULATED-WEBHOOK-20251022-101500.json
Normal 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"
|
||||
}
|
||||
2
_website/data/webhook.log
Normal file
2
_website/data/webhook.log
Normal 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
|
||||
|
|
@ -77,7 +77,7 @@ $docs = [
|
|||
|
||||
<div class="embed-snippet">
|
||||
<strong>Generic embed snippet (replace <code>doc=</code> value):</strong>
|
||||
<pre><code><iframe src="/serve.php?doc=sales" width="100%" height="720" style="border:1px solid #ddd;border-radius:12px"></iframe></code></pre>
|
||||
<pre><code><iframe src="/serve.php?doc=sales" width="100%" height="720" class="doc-iframe"></iframe></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>
|
||||
|
|
|
|||
|
|
@ -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
BIN
_website/images/banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 278 KiB |
BIN
_website/images/bf3_the_russian.jpg
Normal file
BIN
_website/images/bf3_the_russian.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
BIN
_website/images/dark.jpg
Normal file
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
BIN
_website/images/logo-sm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
60
_website/includes/admin_auth.php
Normal file
60
_website/includes/admin_auth.php
Normal 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
|
||||
?>
|
||||
22
_website/includes/cart_helper.php
Normal file
22
_website/includes/cart_helper.php
Normal 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;
|
||||
}
|
||||
|
|
@ -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';
|
||||
?>
|
||||
|
|
|
|||
8
_website/includes/footer.php
Normal file
8
_website/includes/footer.php
Normal 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>
|
||||
19
_website/includes/login_required.php
Normal file
19
_website/includes/login_required.php
Normal 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();
|
||||
}
|
||||
?>
|
||||
|
|
@ -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
18
_website/includes/top.php
Normal 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>
|
||||
|
|
@ -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
66
_website/invoices.php
Normal 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
12
_website/logfile.txt
Normal 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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
1
_website/payments/api/README.md
Normal file
1
_website/payments/api/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Compatibility wrappers for payments API endpoints. Canonical implementations are under /_website/api/.
|
||||
4
_website/payments/config.php
Normal file
4
_website/payments/config.php
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
// payments compatibility config — centralized in includes/config.inc.php
|
||||
require_once(__DIR__ . '/../includes/config.inc.php');
|
||||
?>
|
||||
4
_website/payments/pay.php
Normal file
4
_website/payments/pay.php
Normal 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;
|
||||
4
_website/payments/return.php
Normal file
4
_website/payments/return.php
Normal 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;
|
||||
159
_website/payments/webhook.php
Normal file
159
_website/payments/webhook.php
Normal 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");
|
||||
1
_website/paypal/api/README.md
Normal file
1
_website/paypal/api/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
This folder contains compatibility wrappers for PayPal API endpoints. The canonical implementations live in /_website/api/.
|
||||
7
_website/paypal/config.php
Normal file
7
_website/paypal/config.php
Normal 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
103
_website/paypal/pay.php
Normal 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¤cy=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>
|
||||
4
_website/paypal/return.php
Normal file
4
_website/paypal/return.php
Normal 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
161
_website/paypal/webhook.php
Normal 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
6
_website/privacy.php
Normal 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
65
_website/register.php
Normal 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>
|
||||
|
|
@ -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">We’re 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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
30
_website/tools/check_db_user.php
Normal file
30
_website/tools/check_db_user.php
Normal 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);
|
||||
?>
|
||||
16
_website/tools/check_invoices_redirect.php
Normal file
16
_website/tools/check_invoices_redirect.php
Normal 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";
|
||||
?>
|
||||
21
_website/tools/check_logout_redirect.php
Normal file
21
_website/tools/check_logout_redirect.php
Normal 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);
|
||||
?>
|
||||
40
_website/tools/debug_invoices_redirect.php
Normal file
40
_website/tools/debug_invoices_redirect.php
Normal 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";
|
||||
}
|
||||
|
||||
?>
|
||||
39
_website/tools/simulate_webhook.php
Normal file
39
_website/tools/simulate_webhook.php
Normal 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
6
_website/tos.php
Normal 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'); ?>
|
||||
|
|
@ -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 |
|
|
@ -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
|
||||
|
||||
102
paypal/pay.php
102
paypal/pay.php
|
|
@ -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¤cy=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) — 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>
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue