feat: add PayPal sandbox/live credentials, webhook endpoint, and admin diagnostics

- config.inc.php: new sandbox/live credential structure with paypal_mode, separate
  sandbox/live client_id, client_secret, webhook_id, and webhook_path
- config.example.php: updated to match new structure
- config_loader.php: adds defaults and backward compat mapping from old
  $paypal_sandbox/$paypal_client_id variables; adds gsp_paypal_* helper functions
- PayPalGateway.php: fromConfig() uses gsp_paypal_* helpers with fallback
- cart.php: uses gsp_paypal_get_client_id()/gsp_paypal_is_sandbox() helpers
- webhook.php: updated to use gsp_paypal_* helpers for credentials/API base
- paypal/webhook.php: new full-featured webhook receiver with signature
  verification, idempotency log, event processing, provisioning trigger
- admin_config.php: expanded to separate sandbox/live fields, computed webhook URL,
  diagnostics panel showing credential status and recent webhook events
- module.php: bumped to v3.3/db_version 3, adds billing_paypal_webhook_events table

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/f974e469-8562-41df-ba37-bc340f5a154c

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-06 16:14:47 +00:00 committed by GitHub
parent 0f4c4b3634
commit 41a812fdd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1351 additions and 109 deletions

View file

@ -22,9 +22,8 @@ $table_prefix = "gsp_"; // Table prefix used in the panel database
$db_type = "mysql";
# --- Site base URL ---
# Leave empty to use relative paths (works for any install path).
# Set to your full base URL (without trailing slash) if you need absolute URLs:
# e.g. "https://gameservers.world" or "http://173.208.136.11/testing/modules/billing"
# Full base URL WITHOUT trailing slash. Leave empty to use relative paths.
# Example: "https://gameservers.world" or "https://your-domain.com"
$SITE_BASE_URL = '';
# --- Background image ---
@ -37,10 +36,23 @@ $SITE_BACKGROUND = 'images/dark.jpg';
$SITE_DATA_DIR = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data';
# --- PayPal settings ---
$paypal_sandbox = true; // Set to false for live payments
$paypal_client_id = ''; // Your PayPal Client ID
$paypal_client_secret = ''; // Your PayPal Client Secret
$paypal_webhook_id = ''; // Your PayPal Webhook ID (for webhook signature verification)
# Mode: 'sandbox' for testing, 'live' for real payments.
$paypal_mode = 'sandbox';
# Sandbox credentials (PayPal Developer Dashboard → sandbox app)
$paypal_sandbox_client_id = ''; // e.g. AfvY_...
$paypal_sandbox_client_secret = ''; // Keep server-side only
$paypal_sandbox_webhook_id = ''; // Set after registering webhook in PayPal
# Live credentials (leave blank until ready for production)
$paypal_live_client_id = '';
$paypal_live_client_secret = '';
$paypal_live_webhook_id = '';
# Webhook path (relative to billing site root, must start with /)
# Full public URL = $SITE_BASE_URL + $paypal_webhook_path
# Example full URL: https://gameservers.world/paypal/webhook.php
$paypal_webhook_path = '/paypal/webhook.php';
# --- Admin config backup retention ---
# Number of config backups to keep (110). Oldest backups beyond this limit are deleted.

View file

@ -3,7 +3,7 @@
# Website Database Configuration
# This file contains the database connection
# settings for the _website standalone site.
#
#
# These settings should match the panel's
# database configuration in includes/config.inc.php
###############################################
@ -14,14 +14,15 @@ $db_pass="Pkloyn7yvpht!";
$db_name="panel";
$table_prefix="gsp_";
$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 = '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);
// Normalize: ensure either empty or ends without trailing slash
$SITE_BASE_URL = rtrim(trim((string)$SITE_BASE_URL), '/');
// Site-wide background image (relative to site root). Change to your preferred background.
$SITE_BACKGROUND = 'images/dark.jpg';
@ -31,11 +32,28 @@ $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';
// PayPal configuration — set credentials here, never in API files
$paypal_sandbox = true; // Set to false for live payments
$paypal_client_id = ''; // Your PayPal Client ID
$paypal_client_secret = ''; // Your PayPal Client Secret
$paypal_webhook_id = ''; // Your PayPal Webhook ID (for webhook signature verification)
// ---------------------------------------------------------------------------
// PayPal configuration
// Set credentials here — never in API files or public pages.
//
// Mode: 'sandbox' for testing, 'live' for real payments.
// ---------------------------------------------------------------------------
$paypal_mode = 'sandbox'; // 'sandbox' or 'live'
// Sandbox credentials (use for testing — safe to share with the dev team)
$paypal_sandbox_client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c';
$paypal_sandbox_client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0';
$paypal_sandbox_webhook_id = ''; // Set after registering the webhook in PayPal sandbox dashboard
// Live credentials (leave blank until ready for production)
$paypal_live_client_id = '';
$paypal_live_client_secret = '';
$paypal_live_webhook_id = '';
// Webhook path (relative to billing site root, must start with /)
// Full public URL = $SITE_BASE_URL + $paypal_webhook_path
// e.g. https://gameservers.world/paypal/webhook.php
$paypal_webhook_path = '/paypal/webhook.php';
// Admin config backup retention: how many backups to keep (110). Default 5.
$SITE_CONFIG_BACKUP_RETENTION = 5;

View file

@ -207,21 +207,71 @@ unset($_billing_panel_config, $panelDbVars, $diskVars, $needsSync, $k, $v);
// Step 4: Apply safe defaults for billing-specific variables that may be absent
// in older config files (never overwrite values already set by the config).
// ---------------------------------------------------------------------------
// --- PayPal mode (new-style) ---
// Backward compat: if $paypal_sandbox was set (old config) and $paypal_mode is absent,
// derive $paypal_mode from $paypal_sandbox.
if (!isset($paypal_mode)) {
if (isset($paypal_sandbox)) {
$paypal_mode = $paypal_sandbox ? 'sandbox' : 'live';
} else {
$paypal_mode = 'sandbox';
}
}
$paypal_mode = (strtolower((string)$paypal_mode) === 'live') ? 'live' : 'sandbox';
// --- Sandbox credentials ---
if (!isset($paypal_sandbox_client_id)) {
// Backward compat: if old $paypal_client_id was set while in sandbox mode, use it
$paypal_sandbox_client_id = (isset($paypal_client_id) && $paypal_mode === 'sandbox') ? $paypal_client_id : '';
}
if (!isset($paypal_sandbox_client_secret)) {
$paypal_sandbox_client_secret = (isset($paypal_client_secret) && $paypal_mode === 'sandbox') ? $paypal_client_secret : '';
}
if (!isset($paypal_sandbox_webhook_id)) {
$paypal_sandbox_webhook_id = (isset($paypal_webhook_id) && $paypal_mode === 'sandbox') ? $paypal_webhook_id : '';
}
// --- Live credentials ---
if (!isset($paypal_live_client_id)) {
$paypal_live_client_id = (isset($paypal_client_id) && $paypal_mode === 'live') ? $paypal_client_id : '';
}
if (!isset($paypal_live_client_secret)) {
$paypal_live_client_secret = (isset($paypal_client_secret) && $paypal_mode === 'live') ? $paypal_client_secret : '';
}
if (!isset($paypal_live_webhook_id)) {
$paypal_live_webhook_id = (isset($paypal_webhook_id) && $paypal_mode === 'live') ? $paypal_webhook_id : '';
}
// --- Legacy compatibility shims (read-only derived values) ---
// Keep old variable names populated so any code that still reads $paypal_sandbox,
// $paypal_client_id, $paypal_client_secret, $paypal_webhook_id keeps working.
if (!isset($paypal_sandbox)) {
$paypal_sandbox = true;
$paypal_sandbox = ($paypal_mode !== 'live');
}
if (!isset($paypal_client_id)) {
$paypal_client_id = '';
$paypal_client_id = ($paypal_mode === 'live') ? $paypal_live_client_id : $paypal_sandbox_client_id;
}
if (!isset($paypal_client_secret)) {
$paypal_client_secret = '';
$paypal_client_secret = ($paypal_mode === 'live') ? $paypal_live_client_secret : $paypal_sandbox_client_secret;
}
if (!isset($paypal_webhook_id)) {
$paypal_webhook_id = '';
$paypal_webhook_id = ($paypal_mode === 'live') ? $paypal_live_webhook_id : $paypal_sandbox_webhook_id;
}
// --- Webhook path ---
if (!isset($paypal_webhook_path) || (string)$paypal_webhook_path === '') {
$paypal_webhook_path = '/paypal/webhook.php';
}
// Ensure webhook path starts with /
$paypal_webhook_path = '/' . ltrim((string)$paypal_webhook_path, '/');
// --- Site settings ---
if (!isset($SITE_BASE_URL)) {
$SITE_BASE_URL = '';
}
$SITE_BASE_URL = rtrim(trim((string)$SITE_BASE_URL), '/');
if (!isset($SITE_BACKGROUND)) {
$SITE_BACKGROUND = 'images/dark.jpg';
}
@ -234,3 +284,77 @@ if (!isset($SITE_CONFIG_BACKUP_RETENTION) || !is_int($SITE_CONFIG_BACKUP_RETENTI
$SITE_CONFIG_BACKUP_RETENTION = max(1, min(10, (int)$SITE_CONFIG_BACKUP_RETENTION));
define('BILLING_CONFIG_LOADED', true);
// ---------------------------------------------------------------------------
// PayPal helper functions — use these everywhere instead of reading globals.
// ---------------------------------------------------------------------------
if (!function_exists('gsp_paypal_get_mode')) {
function gsp_paypal_get_mode(): string
{
return $GLOBALS['paypal_mode'] === 'live' ? 'live' : 'sandbox';
}
}
if (!function_exists('gsp_paypal_is_sandbox')) {
function gsp_paypal_is_sandbox(): bool
{
return gsp_paypal_get_mode() !== 'live';
}
}
if (!function_exists('gsp_paypal_get_client_id')) {
function gsp_paypal_get_client_id(): string
{
if (gsp_paypal_is_sandbox()) {
return (string)($GLOBALS['paypal_sandbox_client_id'] ?? '');
}
return (string)($GLOBALS['paypal_live_client_id'] ?? '');
}
}
if (!function_exists('gsp_paypal_get_client_secret')) {
function gsp_paypal_get_client_secret(): string
{
if (gsp_paypal_is_sandbox()) {
return (string)($GLOBALS['paypal_sandbox_client_secret'] ?? '');
}
return (string)($GLOBALS['paypal_live_client_secret'] ?? '');
}
}
if (!function_exists('gsp_paypal_get_webhook_id')) {
function gsp_paypal_get_webhook_id(): string
{
if (gsp_paypal_is_sandbox()) {
return (string)($GLOBALS['paypal_sandbox_webhook_id'] ?? '');
}
return (string)($GLOBALS['paypal_live_webhook_id'] ?? '');
}
}
if (!function_exists('gsp_paypal_get_api_base')) {
function gsp_paypal_get_api_base(): string
{
return gsp_paypal_is_sandbox()
? 'https://api-m.sandbox.paypal.com'
: 'https://api-m.paypal.com';
}
}
if (!function_exists('gsp_paypal_get_webhook_path')) {
function gsp_paypal_get_webhook_path(): string
{
$path = (string)($GLOBALS['paypal_webhook_path'] ?? '/paypal/webhook.php');
return '/' . ltrim($path, '/');
}
}
if (!function_exists('gsp_paypal_get_full_webhook_url')) {
function gsp_paypal_get_full_webhook_url(): string
{
$base = rtrim((string)($GLOBALS['SITE_BASE_URL'] ?? ''), '/');
$path = gsp_paypal_get_webhook_path();
return $base . $path;
}
}