diff --git a/_website/admin.php b/_website/admin.php new file mode 100644 index 00000000..738dc177 --- /dev/null +++ b/_website/admin.php @@ -0,0 +1,68 @@ + + + + + + Admin — Dashboard + + + + +
+

Admin Dashboard

+

Welcome to the admin area. From here you can manage servers, payments, and site settings.

+ +
+ Manage Servers & Services + Invoice History + Edit Site Config +
+ +
+

Quick usage notes

+ + +

Sandbox account (testing)

+

Use PayPal sandbox credentials when testing payments. Set your sandbox client_id and client_secret in the runtime config that the payment handlers use (for this site those are in the respective files under _website/paypal/ and _website/payments/ or in a central config if you moved credentials).

+ + +

Payments: high-level program flow

+
    +
  1. User adds an item and proceeds to checkout (_website/cart.php).
  2. +
  3. The checkout page renders the PayPal JS SDK and calls server-side endpoints (create_order/capture_order).
  4. +
  5. After a successful capture, PayPal sends a webhook event to _website/webhook.php (or the equivalent handler under _website/paypal/).
  6. +
  7. The webhook verifies the signature, fetches any missing order details, and writes a JSON record to the data/ directory (this powers invoices.php and return.php).
  8. +
  9. On successful payment we mark the order as PAID in the JSON and the site UI (invoices/returns) reads those JSONs to render receipts.
  10. +
  11. Admin pages can view invoices at ./invoices.php and reconcile or trigger further provisioning via internal panel APIs.
  12. +
+ +

Environment

+ + + + + +
Site Base URL
Data directory
PHP SAPI
Writable?
+ +
+ + + diff --git a/_website/admin_config.php b/_website/admin_config.php new file mode 100644 index 00000000..dd30a966 --- /dev/null +++ b/_website/admin_config.php @@ -0,0 +1,103 @@ +&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); +} + +?> + + + + + Admin — Edit Config + + + + +
+

Edit Site Config

+
+ +
+ +
+ +
+
+ +

Backups are stored in

+
+ + + diff --git a/_website/admin_payments.php b/_website/admin_payments.php new file mode 100644 index 00000000..7fcfa1c7 --- /dev/null +++ b/_website/admin_payments.php @@ -0,0 +1,59 @@ + + + + + + Admin — Payments + + + + + +
+

Payments (webhook)

+ +

No payment records found in

+ + + + + + + + + + + + + + + + + + + + + + + + +
FilenameInvoiceAmountPayerDateView
View
+ +
+ + + diff --git a/_website/adminserverlist.php b/_website/adminserverlist.php index 1126a8a9..290b6468 100644 --- a/_website/adminserverlist.php +++ b/_website/adminserverlist.php @@ -9,19 +9,22 @@ -
".h($m)."
"; ?> +
".h($m)."
"; ?> +
".h($m)."
"; ?>

Enable/Disable Server Locations (Global)

@@ -146,17 +150,18 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat
-
+
-
+

Current Services

@@ -169,14 +174,14 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat - - - - - - - - + + + + + + + + @@ -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; + } } ?> - - - - - - - - + @@ -252,10 +261,10 @@ $services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locat $isChecked = isset($selSet[$rid]); $isPrimary = ($primary === $rid); ?> -
EnabledService Name (ID below)Min SlotsMax SlotsPrice (Monthly)Thumbnail URLPreviewUpdate RowEnabledService Name (ID below)Min SlotsMax SlotsPrice (Monthly)Thumbnail URLPreviewUpdate Row
+ > - -
ID:
+
+ +
ID:
- + + - + + + - + + + - preview + preview - (no image) + (no image) - - + +
- - - - - - - - - - - - - - +
Server IDGame NameLocationMax PlayersPrice per PlayerMonthsTotal
+ + + + + + + + + + + + + num_rows > 0) { while ($row = $carts->fetch_assoc()) { ?> - - - - - - - - - + + + + + + + + + + + + + + - + @@ -124,20 +170,20 @@ if ($db){ // Add total row ?> - - + - + - +
-
+
+ + + +

Complete your purchase

+

Amount:

+

Invoice:

+
+
+ + + + + diff --git a/_website/paypal/return.php b/_website/paypal/return.php new file mode 100644 index 00000000..4243a345 --- /dev/null +++ b/_website/paypal/return.php @@ -0,0 +1,4 @@ + 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"); diff --git a/_website/privacy.php b/_website/privacy.php new file mode 100644 index 00000000..0fbc4b5e --- /dev/null +++ b/_website/privacy.php @@ -0,0 +1,6 @@ + +
+

Privacy

+

This is the privacy page placeholder.

+
+ diff --git a/_website/register.php b/_website/register.php new file mode 100644 index 00000000..ed2c450d --- /dev/null +++ b/_website/register.php @@ -0,0 +1,65 @@ +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.'; + } + } + } +} +?> + + +Register - GameServers.World + + +

Register

+'.htmlspecialchars($error).''; ?> + +
+
+
+ + + + diff --git a/paypal/return.php b/_website/return.php similarity index 81% rename from paypal/return.php rename to _website/return.php index 4c0b1b9e..cdb3f314 100644 --- a/paypal/return.php +++ b/_website/return.php @@ -1,7 +1,7 @@ .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) { Payment Status - + + +

Payment canceled

Invoice:

@@ -67,7 +61,7 @@ function money_fmt($value, $currency) {

Items

-
Server IDGame NameLocationMax PlayersPrice per PlayerMonthsTotal
- - - - $
+
+ +
+
$ +
+ + +
+
  $$
+
Cart Total: - $ - + $ +
No items in your cart.No items in your cart.
+
@@ -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) { } ?> - + @@ -114,6 +108,7 @@ function money_fmt($value, $currency) {

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.

+ + - diff --git a/_website/serverlist.php b/_website/serverlist.php index d18d2dec..21a03f31 100644 --- a/_website/serverlist.php +++ b/_website/serverlist.php @@ -42,15 +42,16 @@ if (!$services) { return; } -// Include menu +// Include top bar and menu +include(__DIR__ . '/includes/top.php'); include(__DIR__ . '/includes/menu.php'); ?> -
+
-
+



-
- - - + Order Server
-
+

@@ -97,7 +95,7 @@ include(__DIR__ . '/includes/menu.php'); -
Server ID
TotalTotal
+
@@ -117,4 +115,5 @@ include(__DIR__ . '/includes/menu.php'); mysqli_close($db); ?> + diff --git a/_website/tools/check_db_user.php b/_website/tools/check_db_user.php new file mode 100644 index 00000000..1d829ece --- /dev/null +++ b/_website/tools/check_db_user.php @@ -0,0 +1,30 @@ + 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); +?> \ No newline at end of file diff --git a/_website/tools/check_invoices_redirect.php b/_website/tools/check_invoices_redirect.php new file mode 100644 index 00000000..69ccd956 --- /dev/null +++ b/_website/tools/check_invoices_redirect.php @@ -0,0 +1,16 @@ +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"; +?> diff --git a/_website/tools/check_logout_redirect.php b/_website/tools/check_logout_redirect.php new file mode 100644 index 00000000..fe7cab7e --- /dev/null +++ b/_website/tools/check_logout_redirect.php @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/_website/tools/debug_invoices_redirect.php b/_website/tools/debug_invoices_redirect.php new file mode 100644 index 00000000..e3681f5f --- /dev/null +++ b/_website/tools/debug_invoices_redirect.php @@ -0,0 +1,40 @@ +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"; +} + +?> diff --git a/_website/tools/simulate_webhook.php b/_website/tools/simulate_webhook.php new file mode 100644 index 00000000..1c7ea66c --- /dev/null +++ b/_website/tools/simulate_webhook.php @@ -0,0 +1,39 @@ + 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"; + +?> diff --git a/_website/tos.php b/_website/tos.php new file mode 100644 index 00000000..653d9cfe --- /dev/null +++ b/_website/tos.php @@ -0,0 +1,6 @@ + +
+

Terms of Service

+

This is the terms of service placeholder.

+
+ diff --git a/paypal/webhook.php b/_website/webhook.php similarity index 80% rename from paypal/webhook.php rename to _website/webhook.php index f24bafc8..f3464011 100644 --- a/paypal/webhook.php +++ b/_website/webhook.php @@ -1,15 +1,23 @@ 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"); +?> diff --git a/images/xpgamehost.png b/images/xpgamehost.png deleted file mode 100644 index 4487e926..00000000 Binary files a/images/xpgamehost.png and /dev/null differ diff --git a/images/xplogo.png b/images/xplogo.png deleted file mode 100644 index ebd98b0d..00000000 Binary files a/images/xplogo.png and /dev/null differ diff --git a/paypal/config.php b/paypal/config.php deleted file mode 100644 index 5d20cea2..00000000 --- a/paypal/config.php +++ /dev/null @@ -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 - diff --git a/paypal/pay.php b/paypal/pay.php deleted file mode 100644 index 36c98705..00000000 --- a/paypal/pay.php +++ /dev/null @@ -1,102 +0,0 @@ -- - - - - - Checkout - - - - - - -

Complete your purchase

-

Amount:

-

Invoice:

-
-
- - - - - -
Game Server Name