diff --git a/modules/billing/includes/payment_processor.php b/modules/billing/includes/payment_processor.php index 4856994e..7ed1d6c7 100644 --- a/modules/billing/includes/payment_processor.php +++ b/modules/billing/includes/payment_processor.php @@ -1,191 +1,210 @@ ../../includes/database_mysqli.php - // When in repo: modules/billing/includes/payment_processor.php -> ../../../includes/database_mysqli.php - $db_mysqli_path = file_exists(__DIR__ . '/../../includes/database_mysqli.php') - ? __DIR__ . '/../../includes/database_mysqli.php' // Deployed at website root - : __DIR__ . '/../../../includes/database_mysqli.php'; // In repo structure - require_once($db_mysqli_path); - $db = createDatabaseConnection($db_host, $db_user, $db_pass, $db_name, $db_port); - if (!$db) { - if (function_exists('site_log_error')) site_log_error('process_payment_db_fail', ['invoice'=>$invoice]); - else error_log('[payment_success] DB connection failed for invoice=' . $invoice); - return false; +// Include the module-local config (must be present in the module includes folder). +// Use ONLY `config.inc.php` — do NOT fall back to other filenames. +$moduleConfig = __DIR__ . '/config.inc.php'; +if (is_file($moduleConfig)) { + require_once $moduleConfig; +} else { + error_log('[payment_processor] Module config not found: expected ' . $moduleConfig); +} + +// Normalize table prefix variable: many files use $table_prefix (lowercase) +if (!isset($TABLE_PREFIX) && isset($table_prefix)) { + $TABLE_PREFIX = $table_prefix; +} + +/** + * Create and return a mysqli connection using the site's config values. + * Returns mysqli or false on failure. + */ +function payment_db_connect() { + global $db_host, $db_user, $db_pass, $db_name, $db_port; + + if (empty($db_host) || empty($db_user) || empty($db_name)) { + error_log('[payment_processor] DB globals not available from includes/config.inc.php'); + return false; + } + + $port = intval($db_port) ?: 3306; + $db = @mysqli_connect($db_host, $db_user, $db_pass, $db_name, $port); + if (!$db) { + error_log('[payment_processor] mysqli_connect failed: ' . mysqli_connect_error()); + return false; + } + mysqli_set_charset($db, 'utf8mb4'); + return $db; +} + +/** + * Process a payment record. Self-contained: uses module-local DB config. + * @param array $record { invoice, custom, resource_id, amount } + * @return bool true if at least one invoice processed + */ +function process_payment_record(array $record) { + global $TABLE_PREFIX; + + // Normalize inputs + $invoice_ref = $record['invoice'] ?? null; // e.g. PayPal invoice id or custom label + $custom = $record['custom'] ?? null; // our custom_id (often invoice_id) + $txid = $record['resource_id'] ?? ($record['txid'] ?? null); + $amount = isset($record['amount']) ? floatval($record['amount']) : null; + + $db = payment_db_connect(); + if (!$db) return false; + + $now = date('Y-m-d H:i:s'); + $esc_txid = mysqli_real_escape_string($db, (string)$txid); + + $invoices_to_process = []; + + // 1) If custom is a single numeric invoice id, try to fetch that exact invoice + if ($custom && ctype_digit((string)$custom)) { + $invoice_id = intval($custom); + $sql = "SELECT * FROM `" . $TABLE_PREFIX . "billing_invoices` WHERE invoice_id = ? AND status = 'due' LIMIT 1"; + if ($stmt = mysqli_prepare($db, $sql)) { + mysqli_stmt_bind_param($stmt, 'i', $invoice_id); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + if ($res && $row = mysqli_fetch_assoc($res)) $invoices_to_process[] = $row; + mysqli_stmt_close($stmt); } - - $now = date('Y-m-d H:i:s'); - $esc_txid = mysqli_real_escape_string($db, (string)$txid); - - // Find invoices to mark as paid - $invoices_to_process = []; - - // Try to match by custom_id (which should be invoice_id for single-item carts) - if ($custom && ctype_digit((string)$custom)) { - $invoice_id = intval($custom); - $stmt = $db->prepare("SELECT * FROM " . $table_prefix . "billing_invoices WHERE invoice_id = ? AND status = 'due' LIMIT 1"); - if ($stmt) { - $stmt->bind_param('i', $invoice_id); - $stmt->execute(); - $result = $stmt->get_result(); - if ($result && $row = $result->fetch_assoc()) { - $invoices_to_process[] = $row; - } - $stmt->close(); + } + + // 2) If we didn't find by custom and invoice_ref is provided, match by description containing invoice_ref + if (empty($invoices_to_process) && $invoice_ref) { + $like = '%' . mysqli_real_escape_string($db, $invoice_ref) . '%'; + $sql = "SELECT * FROM `" . $TABLE_PREFIX . "billing_invoices` WHERE status = 'due' AND description LIKE ?"; + if ($stmt = mysqli_prepare($db, $sql)) { + mysqli_stmt_bind_param($stmt, 's', $like); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + if ($res) { + while ($row = mysqli_fetch_assoc($res)) $invoices_to_process[] = $row; } + mysqli_stmt_close($stmt); } - - // If no match by custom_id, try matching all unpaid invoices for this payment amount - // (This handles multi-item carts where custom_id isn't a single invoice_id) - if (empty($invoices_to_process) && $invoice) { - // Match by invoice reference from PayPal - $esc_invoice = mysqli_real_escape_string($db, $invoice); - $query = "SELECT * FROM " . $table_prefix . "billing_invoices WHERE status = 'due' AND description LIKE '%$esc_invoice%'"; - $result = mysqli_query($db, $query); - if ($result) { - while ($row = mysqli_fetch_assoc($result)) { - $invoices_to_process[] = $row; - } + } + + // 3) As a fallback, match unpaid invoices by amount (exact match). Useful for simple carts. + if (empty($invoices_to_process) && $amount !== null) { + $sql = "SELECT * FROM `" . $TABLE_PREFIX . "billing_invoices` WHERE status = 'due' AND amount = ?"; + if ($stmt = mysqli_prepare($db, $sql)) { + mysqli_stmt_bind_param($stmt, 'd', $amount); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + if ($res) { + while ($row = mysqli_fetch_assoc($res)) $invoices_to_process[] = $row; } + mysqli_stmt_close($stmt); } - - // Process each invoice - $processed_count = 0; - foreach ($invoices_to_process as $inv) { - $invoice_id = intval($inv['invoice_id']); - $existing_order_id = intval($inv['order_id'] ?? 0); - $user_id = intval($inv['user_id']); - $service_id = intval($inv['service_id']); - $home_name = mysqli_real_escape_string($db, $inv['home_name']); - $ip = intval($inv['ip']); - $max_players = intval($inv['max_players']); - $qty = intval($inv['qty']); - $duration = mysqli_real_escape_string($db, $inv['invoice_duration']); - $invoice_amount = floatval($inv['amount']); - $rcon_pw = mysqli_real_escape_string($db, $inv['remote_control_password'] ?? ''); - $ftp_pw = mysqli_real_escape_string($db, $inv['ftp_password'] ?? ''); - - // Mark invoice as paid - $upd_inv = $db->prepare("UPDATE " . $table_prefix . "billing_invoices SET status = 'paid', paid_date = ?, payment_txid = ?, payment_method = 'paypal' WHERE invoice_id = ? LIMIT 1"); - if ($upd_inv) { - $upd_inv->bind_param('ssi', $now, $esc_txid, $invoice_id); - $upd_inv->execute(); - $upd_inv->close(); - } - - // Check if this is a renewal (existing order_id > 0) or new order (order_id = 0) - if ($existing_order_id > 0) { - // RENEWAL: Extend the existing order's end_date - // Calculate months to add - $months = 0; - $q = intval($qty); - $invdur = strtolower(trim($duration)); - if (strpos($invdur, 'year') !== false) { - $months = $q * 12; - } else { - $months = $q; - } - - // Get current end_date and extend it - $getEndDate = "SELECT end_date FROM " . $table_prefix . "billing_orders WHERE order_id = $existing_order_id LIMIT 1"; - $endDateResult = mysqli_query($db, $getEndDate); - if ($endDateResult && mysqli_num_rows($endDateResult) === 1) { - $endRow = mysqli_fetch_assoc($endDateResult); - $current_end = $endRow['end_date'] ?? date('Y-m-d H:i:s'); - - // Extend from current end_date or now (whichever is later) + } + + $processed_count = 0; + + foreach ($invoices_to_process as $inv) { + $invoice_id = intval($inv['invoice_id']); + $order_id = intval($inv['order_id'] ?? 0); + $user_id = intval($inv['user_id']); + $service_id = intval($inv['service_id'] ?? 0); + $home_name = mysqli_real_escape_string($db, $inv['home_name'] ?? ''); + $ip = intval($inv['ip'] ?? 0); + $max_players = intval($inv['max_players'] ?? 0); + $qty = intval($inv['qty'] ?? 1); + $duration = $inv['invoice_duration'] ?? 'month'; + $invoice_amount = floatval($inv['amount'] ?? 0); + $rcon_pw = mysqli_real_escape_string($db, $inv['remote_control_password'] ?? ''); + $ftp_pw = mysqli_real_escape_string($db, $inv['ftp_password'] ?? ''); + + // Mark invoice as paid + $upd = "UPDATE `" . $TABLE_PREFIX . "billing_invoices` SET status='paid', paid_date=?, payment_txid=?, payment_method='paypal' WHERE invoice_id = ? LIMIT 1"; + if ($stmt = mysqli_prepare($db, $upd)) { + mysqli_stmt_bind_param($stmt, 'ssi', $now, $esc_txid, $invoice_id); + mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + } + + // If this invoice already has an order -> treat as renewal + if ($order_id > 0) { + // compute months + $months = (stripos($duration, 'year') !== false) ? ($qty * 12) : $qty; + // fetch current end_date + $get = "SELECT end_date FROM `" . $TABLE_PREFIX . "billing_orders` WHERE order_id = ? LIMIT 1"; + if ($stmt = mysqli_prepare($db, $get)) { + mysqli_stmt_bind_param($stmt, 'i', $order_id); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + if ($res && $row = mysqli_fetch_assoc($res)) { + $current_end = $row['end_date'] ?? date('Y-m-d H:i:s'); $extend_from = (strtotime($current_end) > time()) ? $current_end : date('Y-m-d H:i:s'); $dt = new DateTime($extend_from); - if ($months > 0) { - $dt->modify('+' . intval($months) . ' months'); - } - $new_end_date = $dt->format('Y-m-d H:i:s'); - - // Update order with new end_date and payment info - $updateOrder = "UPDATE " . $table_prefix . "billing_orders - SET end_date = '$new_end_date', status = 'paid', payment_txid = '$esc_txid', paid_ts = '$now' - WHERE order_id = $existing_order_id"; - if (mysqli_query($db, $updateOrder)) { - if (function_exists('site_log_info')) site_log_info('payment_renewal_processed', ['order_id'=>$existing_order_id, 'invoice_id'=>$invoice_id, 'new_end_date'=>$new_end_date]); - else error_log("[payment_success] Extended order $existing_order_id to $new_end_date for invoice $invoice_id"); + if ($months > 0) $dt->modify('+' . intval($months) . ' months'); + $new_end = $dt->format('Y-m-d H:i:s'); + $update = "UPDATE `" . $TABLE_PREFIX . "billing_orders` SET end_date = ?, status='paid', payment_txid = ?, paid_ts = ? WHERE order_id = ?"; + if ($u = mysqli_prepare($db, $update)) { + mysqli_stmt_bind_param($u, 'sssi', $new_end, $esc_txid, $now, $order_id); + mysqli_stmt_execute($u); + mysqli_stmt_close($u); $processed_count++; } } + mysqli_stmt_close($stmt); + } + } else { + // Create new order + $months = (stripos($duration, 'year') !== false) ? ($qty * 12) : $qty; + $dt = new DateTime('now'); + if ($months > 0) $dt->modify('+' . intval($months) . ' months'); + $end_date = $dt->format('Y-m-d H:i:s'); + + // Simpler insert using properly escaped values + $esc_home = mysqli_real_escape_string($db, $home_name); + $esc_rcon = mysqli_real_escape_string($db, $rcon_pw); + $esc_ftp = mysqli_real_escape_string($db, $ftp_pw); + $esc_duration = mysqli_real_escape_string($db, $duration); + $price = number_format($invoice_amount, 2, '.', ''); + + $insert2 = sprintf( + "INSERT INTO `%s` (user_id, service_id, home_name, ip, max_players, qty, invoice_duration, price, remote_control_password, ftp_password, status, order_date, end_date, payment_txid, paid_ts) VALUES (%d, %d, '%s', %d, %d, %d, '%s', %s, '%s', '%s', 'paid', '%s', '%s', '%s', '%s')", + $TABLE_PREFIX . 'billing_orders', + $user_id, $service_id, $esc_home, $ip, $max_players, $qty, $esc_duration, $price, $esc_rcon, $esc_ftp, $now, $end_date, $esc_txid, $now + ); + if (mysqli_query($db, $insert2)) { + $new_order_id = mysqli_insert_id($db); + $link = "UPDATE `" . $TABLE_PREFIX . "billing_invoices` SET order_id = ? WHERE invoice_id = ?"; + if ($u = mysqli_prepare($db, $link)) { + mysqli_stmt_bind_param($u, 'ii', $new_order_id, $invoice_id); + mysqli_stmt_execute($u); + mysqli_stmt_close($u); + } + $processed_count++; } else { - // NEW ORDER: Create a new order record - // Calculate months for end_date - $months = 0; - $q = intval($qty); - $invdur = strtolower(trim($duration)); - if (strpos($invdur, 'year') !== false) { - $months = $q * 12; - } else { - $months = $q; - } - - $dt = new DateTime('now'); - if ($months > 0) { - $dt->modify('+' . intval($months) . ' months'); - } - $end_date = $dt->format('Y-m-d H:i:s'); - - // Insert order - $insertOrder = "INSERT INTO " . $table_prefix . "billing_orders ( - user_id, service_id, home_name, ip, max_players, qty, invoice_duration, - price, remote_control_password, ftp_password, status, order_date, end_date, - payment_txid, paid_ts - ) VALUES ( - $user_id, $service_id, '$home_name', $ip, $max_players, $qty, '$duration', - $invoice_amount, '$rcon_pw', '$ftp_pw', 'paid', '$now', '$end_date', - '$esc_txid', '$now' - )"; - - if (mysqli_query($db, $insertOrder)) { - $new_order_id = mysqli_insert_id($db); - - // Link invoice to order - $linkInvoice = "UPDATE " . $table_prefix . "billing_invoices SET order_id = $new_order_id WHERE invoice_id = $invoice_id"; - mysqli_query($db, $linkInvoice); - - if (function_exists('site_log_info')) site_log_info('payment_new_order_created', ['order_id'=>$new_order_id, 'invoice_id'=>$invoice_id, 'end_date'=>$end_date]); - else error_log("[payment_success] Created order $new_order_id for invoice $invoice_id"); - $processed_count++; - } + error_log('[payment_processor] Failed to insert order: ' . mysqli_error($db)); } } - - mysqli_close($db); - - if ($processed_count > 0) { - if (function_exists('site_log_info')) site_log_info('payment_success_processed', ['count'=>$processed_count,'invoice'=>$invoice,'custom'=>$custom]); - else error_log('[payment_success] Processed ' . $processed_count . ' invoice(s) - invoice=' . $invoice . ' custom=' . $custom); - return true; - } else { - if (function_exists('site_log_warn')) site_log_warn('payment_success_no_match', ['invoice'=>$invoice,'custom'=>$custom]); - else error_log('[payment_success] No matching invoices found for invoice=' . $invoice . ' custom=' . $custom); - return false; - } } + + mysqli_close($db); + + if ($processed_count > 0) { + error_log('[payment_processor] Processed ' . $processed_count . ' invoice(s)'); + return true; + } + + error_log('[payment_processor] No matching invoices processed for record: ' . json_encode($record)); + return false; } + ?> diff --git a/modules/billing/login.php b/modules/billing/login.php index a47a2d40..0177da21 100644 --- a/modules/billing/login.php +++ b/modules/billing/login.php @@ -131,56 +131,59 @@ mysqli_close($db); } .login-container { - background: white; + background: #ffffff; /* explicit white */ border-radius: 12px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.28); width: 100%; max-width: 420px; padding: 40px; + border: 1px solid rgba(0,0,0,0.06); } - + .login-header { text-align: center; margin-bottom: 30px; } - + .login-header h1 { font-size: 1.8rem; - color: #333; + color: #111111; /* high contrast */ margin-bottom: 8px; } - + .login-header p { - color: #666; + color: #4b5563; /* neutral dark gray */ font-size: 0.95rem; } - + .form-group { margin-bottom: 20px; } - + .form-group label { display: block; margin-bottom: 8px; - color: #333; - font-weight: 500; + color: #111111 !important; /* ensure labels are dark and readable (override external styles) */ + font-weight: 600; font-size: 0.95rem; } - + .form-group input { width: 100%; padding: 12px 16px; - border: 2px solid #e1e8ed; + border: 1px solid #cbd5e1; border-radius: 8px; font-size: 1rem; - transition: border-color 0.3s; - color: #333; - background-color: #fff; + transition: border-color 0.15s, box-shadow 0.15s; + color: #111111; + background-color: #ffffff; + -webkit-appearance: none; } - + .form-group input:focus { outline: none; - border-color: #667eea; + border-color: #4f46e5; + box-shadow: 0 6px 20px rgba(79,70,229,0.12); } .btn-login { @@ -273,12 +276,12 @@ mysqli_close($db);
- +
- +