diff --git a/modules/billing/api/capture_order.php b/modules/billing/api/capture_order.php index d2bf8896..38efb165 100644 --- a/modules/billing/api/capture_order.php +++ b/modules/billing/api/capture_order.php @@ -78,7 +78,7 @@ if ($captureStatus === 'COMPLETED' && $custom_id) { $db = createDatabaseConnection($db_host, $db_user, $db_pass, $db_name, $db_port); if (!$db) { error_log('capture_order.php: DB connection failed'); - echo $res; + echo json_encode(['error' => 'db_connection_failed', 'status' => $captureStatus]); exit; } @@ -86,7 +86,9 @@ if ($captureStatus === 'COMPLETED' && $custom_id) { // For now, we'll mark ALL due invoices for the logged-in user as paid // TODO: Improve to match specific invoice_id from custom_id if cart sends it session_start(); - $user_id = isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0; + // Check both website_user_id and user_id for compatibility + $user_id = isset($_SESSION['website_user_id']) ? intval($_SESSION['website_user_id']) : + (isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0); if ($user_id > 0) { // Mark all due invoices for this user as paid @@ -102,9 +104,10 @@ if ($captureStatus === 'COMPLETED' && $custom_id) { $getInvoices = "SELECT * FROM ogp_billing_invoices WHERE user_id=$user_id AND payment_txid='$esc_txid'"; $invoicesResult = mysqli_query($db, $getInvoices); - // For each invoice, create an order + // For each invoice, either create a new order or extend existing one (renewal) while ($inv = mysqli_fetch_assoc($invoicesResult)) { $invoice_id = intval($inv['invoice_id']); + $existing_order_id = intval($inv['order_id'] ?? 0); $service_id = intval($inv['service_id']); $home_name = mysqli_real_escape_string($db, $inv['home_name']); $ip = intval($inv['ip']); @@ -115,30 +118,72 @@ if ($captureStatus === 'COMPLETED' && $custom_id) { $rcon_pw = mysqli_real_escape_string($db, $inv['remote_control_password']); $ftp_pw = mysqli_real_escape_string($db, $inv['ftp_password']); - // Calculate end_date based on qty * duration - $end_date = date('Y-m-d H:i:s', strtotime("+$qty $duration")); - - // Insert order - $insertOrder = "INSERT INTO ogp_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', - $amount, '$rcon_pw', '$ftp_pw', 'paid', '$now', '$end_date', - '$esc_txid', '$now' - )"; - - if (mysqli_query($db, $insertOrder)) { - $new_order_id = mysqli_insert_id($db); + // 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 based on qty and duration + $months = 0; + $q = intval($qty); + $invdur = strtolower(trim($duration)); + if (strpos($invdur, 'year') !== false) { + $months = $q * 12; + } else { + // default to months for anything else (month, monthly, etc.) + $months = $q; + } - // Link invoice to order - $linkInvoice = "UPDATE ogp_billing_invoices SET order_id=$new_order_id WHERE invoice_id=$invoice_id"; - mysqli_query($db, $linkInvoice); - - error_log("capture_order.php: Created order $new_order_id for invoice $invoice_id"); + // Get current end_date and extend it + $getEndDate = "SELECT end_date FROM ogp_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) + $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 mark as paid/active + $updateOrder = "UPDATE ogp_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)) { + error_log("capture_order.php: Extended order $existing_order_id end_date to $new_end_date for invoice $invoice_id"); + } else { + error_log("capture_order.php: Failed to extend order $existing_order_id: " . mysqli_error($db)); + } + } } else { - error_log("capture_order.php: Failed to create order for invoice $invoice_id: " . mysqli_error($db)); + // NEW ORDER: Create a new order record + // Calculate end_date based on qty * duration + $end_date = date('Y-m-d H:i:s', strtotime("+$qty $duration")); + + // Insert order + $insertOrder = "INSERT INTO ogp_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', + $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 ogp_billing_invoices SET order_id=$new_order_id WHERE invoice_id=$invoice_id"; + mysqli_query($db, $linkInvoice); + + error_log("capture_order.php: Created order $new_order_id for invoice $invoice_id"); + } else { + error_log("capture_order.php: Failed to create order for invoice $invoice_id: " . mysqli_error($db)); + } } } diff --git a/modules/billing/payment_success.php b/modules/billing/payment_success.php index 224c026b..01245636 100644 --- a/modules/billing/payment_success.php +++ b/modules/billing/payment_success.php @@ -8,6 +8,184 @@ session_start(); require_once(__DIR__ . '/includes/header.php'); require_once(__DIR__ . '/includes/config.inc.php'); require_once(__DIR__ . '/../../includes/database_mysqli.php'); +require_once(__DIR__ . '/includes/log.php'); + +/** + * Process payment record from webhook or capture + * Marks invoices as paid and creates/extends orders + * + * @param array $record Payment record with invoice, custom, amount, txid, etc. + * @return bool True if successful, false otherwise + */ +function process_payment_record($record) { + global $db_host, $db_user, $db_pass, $db_name, $db_port, $table_prefix; + + // Extract payment details + $invoice = $record['invoice'] ?? null; + $custom = $record['custom'] ?? null; + $txid = $record['resource_id'] ?? null; + $amount = $record['amount'] ?? 0; + + // Require database connection + $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; + } + + $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(); + } + } + + // 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; + } + } + } + + // 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) + $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"); + $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++; + } + } + } + + 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; + } +} $invoice_ref = isset($_GET['invoice']) ? $_GET['invoice'] : ''; $user_id = isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0; @@ -88,104 +266,3 @@ $user_id = isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0; - $end_date_val = null; - if ($has_finish) { - // Attempt to find the target order's qty/invoice_duration using the same where clause but without LIMIT - $sel_sql = "SELECT qty, invoice_duration FROM ogp_billing_orders WHERE " . str_replace(' AND status <> \"paid\" LIMIT 1', '', $where_sql) . " LIMIT 1"; - // Note: this simple substitution assumes the where_sql is of the form 'col = ?' used earlier - if ($sel_stmt = $db->prepare($sel_sql)) { - // bind where params - if ($bind_types) { - $refs = []; - $vals = $bind_vals; - foreach ($vals as $k => $v) $refs[$k] = &$vals[$k]; - array_unshift($refs, $bind_types); - call_user_func_array([$sel_stmt, 'bind_param'], $refs); - } - $sel_stmt->execute(); - $sel_stmt->bind_result($sel_qty, $sel_invdur); - if ($sel_stmt->fetch()) { - // compute months - $months = 0; - $q = intval($sel_qty ?? 0); - $invdur = strtolower(trim($sel_invdur ?? '')); - if (strpos($invdur, 'year') !== false) { - $months = $q * 12; - } else { - $months = $q; - } - if ($months <= 0) $months = 0; - $dt = new DateTime('now'); - if ($months > 0) $dt->modify('+' . intval($months) . ' months'); - $end_date_val = $dt->format('Y-m-d H:i:s'); - } - $sel_stmt->close(); - } - if ($end_date_val !== null) { - $sql = str_replace(' WHERE ', ', end_date = ? WHERE ', $sql); - } - } - - if ($stmt = $db->prepare($sql)) { - // Build params: first any where params, then txid/ts values if present, then end_date if present - $types = $bind_types; - $vals = $bind_vals; - if ($cols) { - foreach ($cols as $c) { - $types .= 's'; - if ($c === 'payment_txid') $vals[] = $txid; - else $vals[] = $ts; - } - } - if ($end_date_val !== null) { - $types .= 's'; - $vals[] = $end_date_val; - } - // bind dynamically - if ($types) { - $refs = []; - foreach ($vals as $k => $v) $refs[$k] = &$vals[$k]; - array_unshift($refs, $types); - call_user_func_array([$stmt, 'bind_param'], $refs); - } - $stmt->execute(); - $affected = $stmt->affected_rows; - $stmt->close(); - return $affected; - } - return 0; - }; - - $affected = 0; - // Try match by invoice column (if present) - if ($invoice) { - // some invoices may include paths or file names; use exact match - $affected = $update_paid('invoice = ?', 's', [$invoice]); - } - - // If not matched, try numeric custom (order_id) - if (!$affected && $custom) { - if (ctype_digit((string)$custom)) { - $affected = $update_paid('order_id = ?', 'i', [(int)$custom]); - } - } - - // If still not matched, try matching the custom text field - if (!$affected && $custom) { - $affected = $update_paid('custom = ?', 's', [$custom]); - } - - mysqli_close($db); - - if ($affected) { - if (function_exists('site_log_info')) site_log_info('payment_success_marked_paid', ['affected'=>intval($affected),'invoice'=>$invoice,'custom'=>$custom]); - else error_log('[payment_success] Marked order paid (affected=' . intval($affected) . ') 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 order found for invoice=' . $invoice . ' custom=' . $custom); - return false; - } -} - -?>