Add comprehensive logging to PayPal payment flow for debugging errors

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-10-29 21:38:02 +00:00
parent de3db4e247
commit 4dba46d11c
4 changed files with 542 additions and 68 deletions

View file

@ -11,12 +11,49 @@ ini_set('display_errors', '0');
error_reporting(E_ALL);
header('Content-Type: application/json');
$in = json_decode(file_get_contents('php://input'), true) ?: [];
// Read and parse input
$rawInput = file_get_contents('php://input');
capture_log('RAW_INPUT', substr($rawInput, 0, 1000));
$in = json_decode($rawInput, true);
if (json_last_error() !== JSON_ERROR_NONE) {
capture_log('JSON_DECODE_ERROR', [
'error' => json_last_error_msg(),
'raw_input_length' => strlen($rawInput),
'raw_input_preview' => substr($rawInput, 0, 500)
]);
http_response_code(400);
echo json_encode(['error' => 'invalid_json', 'message' => json_last_error_msg(), 'request_id' => $requestId]);
exit;
}
if (!$in) {
$in = [];
}
$order_id = $in['order_id'] ?? null;
if (!$order_id) { http_response_code(400); echo json_encode(['error'=>'missing order_id']); exit; }
capture_log('PARSED_INPUT', ['order_id' => $order_id]);
if (!$order_id) {
capture_log('MISSING_ORDER_ID', ['input' => $in]);
http_response_code(400);
echo json_encode(['error' => 'missing_order_id', 'request_id' => $requestId]);
exit;
}
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
capture_log('PAYPAL_API_CONFIG', [
'sandbox_mode' => $sandbox,
'api_base' => $api,
'has_client_id' => !empty($client_id),
'has_client_secret' => !empty($client_secret)
]);
// Step 1: Get OAuth token
capture_log('OAUTH_REQUEST_START', ['endpoint' => "$api/v1/oauth2/token"]);
$ch = curl_init("$api/v1/oauth2/token");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
@ -27,9 +64,47 @@ curl_setopt_array($ch, [
]);
$tok = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$oauth_curl_errno = curl_errno($ch);
$oauth_curl_error = curl_error($ch);
curl_close($ch);
if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_fail']); exit; }
capture_log('OAUTH_RESPONSE', [
'http_code' => $http,
'curl_errno' => $oauth_curl_errno,
'curl_error' => $oauth_curl_error,
'response_length' => strlen($tok),
'response_preview' => substr($tok, 0, 200)
]);
if ($oauth_curl_errno !== 0) {
capture_log('OAUTH_CURL_ERROR', ['errno' => $oauth_curl_errno, 'error' => $oauth_curl_error]);
http_response_code(502);
echo json_encode(['error' => 'oauth_curl_fail', 'details' => $oauth_curl_error, 'request_id' => $requestId]);
exit;
}
if ($http !== 200) {
capture_log('OAUTH_HTTP_ERROR', ['http_code' => $http, 'response' => $tok]);
http_response_code(500);
echo json_encode(['error' => 'oauth_fail', 'http_code' => $http, 'request_id' => $requestId]);
exit;
}
$access = json_decode($tok, true)['access_token'] ?? null;
if (!$access) {
capture_log('OAUTH_NO_TOKEN', ['response' => $tok]);
http_response_code(500);
echo json_encode(['error' => 'oauth_no_token', 'request_id' => $requestId]);
exit;
}
capture_log('OAUTH_SUCCESS', ['token_length' => strlen($access)]);
// Step 2: Capture the PayPal order
capture_log('CAPTURE_REQUEST_START', [
'endpoint' => "$api/v2/checkout/orders/$order_id/capture",
'order_id' => $order_id
]);
$ch = curl_init("$api/v2/checkout/orders/$order_id/capture");
curl_setopt_array($ch, [
@ -40,30 +115,33 @@ curl_setopt_array($ch, [
$res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_err = curl_error($ch);
$curl_errno = curl_errno($ch);
curl_close($ch);
// Ensure logs folder exists and provide a helper to write debug info
$logDir = __DIR__ . '/../logs';
if (!is_dir($logDir)) @mkdir($logDir, 0755, true);
$logFile = $logDir . '/paypal_capture.log';
function capture_log($label, $data) {
global $logFile;
$entry = '[' . date('Y-m-d H:i:s') . '] ' . $label . "\n";
if (is_array($data) || is_object($data)) $entry .= print_r($data, true);
else $entry .= (string)$data;
$entry .= "\n---\n";
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
}
// Log the raw curl response for debugging
capture_log('paypal_curl_response_http_' . $http, $res === false ? "(curl failed) " . $curl_err : $res);
capture_log('CAPTURE_RESPONSE', [
'http_code' => $http,
'curl_errno' => $curl_errno,
'curl_error' => $curl_err,
'response_length' => strlen($res),
'response_preview' => substr($res, 0, 1000)
]);
// Check for curl-level errors
if ($curl_errno !== 0) {
capture_log('CAPTURE_CURL_ERROR', ['errno' => $curl_errno, 'error' => $curl_err]);
http_response_code(502);
$out = ['error' => 'capture_curl_fail', 'details' => $curl_err, 'request_id' => $requestId];
echo json_encode($out);
exit;
}
// Normalize response: ensure we always return valid JSON to the caller
if ($res === false || $res === '') {
// Curl-level failure or empty body
capture_log('CAPTURE_EMPTY_RESPONSE', ['http' => $http]);
http_response_code(502);
$out = ['error' => 'paypal_empty_response', 'http' => $http, 'curl_error' => $curl_err];
capture_log('paypal_empty_response', $out);
$out = ['error' => 'paypal_empty_response', 'http' => $http, 'request_id' => $requestId];
echo json_encode($out);
exit;
}
@ -72,25 +150,36 @@ if ($res === false || $res === '') {
$capture = json_decode($res, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// PayPal returned non-JSON / malformed response — return it as raw string inside JSON
capture_log('CAPTURE_INVALID_JSON', [
'json_error' => json_last_error_msg(),
'http' => $http,
'raw_preview' => substr($res, 0, 500)
]);
http_response_code(502);
$out = ['error' => 'paypal_invalid_json', 'http' => $http, 'raw' => $res];
capture_log('paypal_invalid_json', $out);
$out = ['error' => 'paypal_invalid_json', 'http' => $http, 'request_id' => $requestId];
echo json_encode($out);
exit;
}
if ($http !== 201 && $http !== 200) {
capture_log('CAPTURE_HTTP_ERROR', [
'http_code' => $http,
'response' => $capture
]);
http_response_code($http);
// Return structured JSON with PayPal's decoded response
$out = ['error' => 'paypal_capture_failed', 'http' => $http, 'response' => $capture];
capture_log('paypal_capture_failed', $out);
$out = ['error' => 'paypal_capture_failed', 'http' => $http, 'paypal_error' => $capture, 'request_id' => $requestId];
echo json_encode($out);
exit;
}
// Extract payment details
$txid = null;
capture_log('paypal_capture_success', $capture);
capture_log('CAPTURE_SUCCESS', [
'status' => $capture['status'] ?? 'UNKNOWN',
'id' => $capture['id'] ?? 'UNKNOWN'
]);
if (isset($capture['purchase_units'][0]['payments']['captures'][0])) {
$txid = $capture['purchase_units'][0]['payments']['captures'][0]['id'] ?? null;
}
@ -99,28 +188,48 @@ if (isset($capture['purchase_units'][0]['payments']['captures'][0])) {
$custom_id = $capture['purchase_units'][0]['custom_id'] ?? null;
$captureStatus = $capture['status'] ?? null;
capture_log('PAYMENT_DETAILS', [
'txid' => $txid,
'custom_id' => $custom_id,
'status' => $captureStatus
]);
if ($captureStatus === 'COMPLETED' && $custom_id) {
capture_log('STARTING_DB_PROCESSING', ['custom_id' => $custom_id, 'status' => $captureStatus]);
// Connect to database using mysqli (standalone - no panel dependencies)
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$db) {
error_log('capture_order.php: DB connection failed - ' . mysqli_connect_error());
echo json_encode(['error' => 'db_connection_failed', 'status' => $captureStatus]);
$dbError = mysqli_connect_error();
capture_log('DB_CONNECTION_FAILED', [
'error' => $dbError,
'host' => $db_host,
'db_name' => $db_name
]);
error_log('capture_order.php: DB connection failed - ' . $dbError);
echo json_encode(['error' => 'db_connection_failed', 'status' => $captureStatus, 'request_id' => $requestId]);
exit;
}
capture_log('DB_CONNECTED', ['database' => $db_name]);
// Get coupon information from session if available
session_start();
$applied_coupon = isset($_SESSION['applied_coupon']) ? $_SESSION['applied_coupon'] : null;
$coupon_id = $applied_coupon ? intval($applied_coupon['coupon_id']) : null;
// Find all invoices with status='due' for this user (cart session)
// 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();
// 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);
capture_log('SESSION_INFO', [
'user_id' => $user_id,
'coupon_id' => $coupon_id,
'session_keys' => array_keys($_SESSION)
]);
if ($user_id > 0) {
capture_log('PROCESSING_INVOICES', ['user_id' => $user_id, 'custom_id' => $custom_id]);
// Mark all due invoices for this user as paid, including coupon_id if applicable
$now = date('Y-m-d H:i:s');
$esc_txid = mysqli_real_escape_string($db, $txid);
@ -131,26 +240,52 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
$updateInvoices .= ", coupon_id=$coupon_id";
}
$updateInvoices .= " WHERE user_id=$user_id AND status='due'";
mysqli_query($db, $updateInvoices);
capture_log('UPDATE_INVOICES_QUERY', ['sql' => $updateInvoices]);
$updateResult = mysqli_query($db, $updateInvoices);
if (!$updateResult) {
capture_log('UPDATE_INVOICES_FAILED', ['error' => mysqli_error($db)]);
} else {
$affectedRows = mysqli_affected_rows($db);
capture_log('UPDATE_INVOICES_SUCCESS', ['affected_rows' => $affectedRows]);
}
// Update coupon usage count if a coupon was applied
if ($coupon_id) {
$updateCoupon = "UPDATE {$table_prefix}billing_coupons
SET current_uses = current_uses + 1
WHERE coupon_id = $coupon_id";
mysqli_query($db, $updateCoupon);
capture_log('UPDATE_COUPON_QUERY', ['sql' => $updateCoupon]);
$couponResult = mysqli_query($db, $updateCoupon);
if (!$couponResult) {
capture_log('UPDATE_COUPON_FAILED', ['error' => mysqli_error($db)]);
} else {
capture_log('UPDATE_COUPON_SUCCESS', ['affected_rows' => mysqli_affected_rows($db)]);
}
// Clear coupon from session after use (for one-time coupons)
if ($applied_coupon && $applied_coupon['usage_type'] === 'one_time') {
unset($_SESSION['applied_coupon']);
capture_log('COUPON_CLEARED', ['type' => 'one_time']);
}
}
// Get all invoices we just marked paid
$getInvoices = "SELECT * FROM {$table_prefix}billing_invoices WHERE user_id=$user_id AND payment_txid='$esc_txid'";
capture_log('GET_INVOICES_QUERY', ['sql' => $getInvoices]);
$invoicesResult = mysqli_query($db, $getInvoices);
if (!$invoicesResult) {
capture_log('GET_INVOICES_FAILED', ['error' => mysqli_error($db)]);
} else {
$invoiceCount = mysqli_num_rows($invoicesResult);
capture_log('GET_INVOICES_SUCCESS', ['count' => $invoiceCount]);
}
// For each invoice, either create a new order or extend existing one (renewal)
$processedInvoices = 0;
while ($inv = mysqli_fetch_assoc($invoicesResult)) {
$invoice_id = intval($inv['invoice_id']);
$existing_order_id = intval($inv['order_id'] ?? 0);
@ -166,8 +301,17 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
$ftp_pw = mysqli_real_escape_string($db, $inv['ftp_password']);
$inv_coupon_id = intval($inv['coupon_id'] ?? 0);
capture_log('PROCESSING_INVOICE', [
'invoice_id' => $invoice_id,
'existing_order_id' => $existing_order_id,
'service_id' => $service_id,
'home_name' => $home_name,
'amount' => $amount
]);
// Check if this is a renewal (existing order_id > 0) or new order (order_id = 0)
if ($existing_order_id > 0) {
capture_log('RENEWAL_DETECTED', ['order_id' => $existing_order_id, 'invoice_id' => $invoice_id]);
// RENEWAL: Extend the existing order's end_date
// Calculate months to add based on qty and duration
$months = 0;
@ -199,13 +343,27 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
$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";
capture_log('UPDATE_ORDER_QUERY', ['sql' => $updateOrder]);
if (mysqli_query($db, $updateOrder)) {
capture_log('ORDER_EXTENDED_SUCCESS', [
'order_id' => $existing_order_id,
'new_end_date' => $new_end_date,
'invoice_id' => $invoice_id
]);
error_log("capture_order.php: Extended order $existing_order_id end_date to $new_end_date for invoice $invoice_id");
$processedInvoices++;
} else {
error_log("capture_order.php: Failed to extend order $existing_order_id: " . mysqli_error($db));
$dbError = mysqli_error($db);
capture_log('ORDER_EXTENDED_FAILED', [
'order_id' => $existing_order_id,
'error' => $dbError
]);
error_log("capture_order.php: Failed to extend order $existing_order_id: " . $dbError);
}
}
} else {
capture_log('NEW_ORDER_DETECTED', ['invoice_id' => $invoice_id]);
// 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"));
@ -221,25 +379,58 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
'$esc_txid', '$now'" . ($inv_coupon_id ? ", $inv_coupon_id" : "") . "
)";
capture_log('INSERT_ORDER_QUERY', ['sql' => substr($insertOrder, 0, 500)]);
if (mysqli_query($db, $insertOrder)) {
$new_order_id = mysqli_insert_id($db);
capture_log('ORDER_CREATED_SUCCESS', [
'new_order_id' => $new_order_id,
'invoice_id' => $invoice_id
]);
// 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);
capture_log('LINK_INVOICE_QUERY', ['sql' => $linkInvoice]);
if (mysqli_query($db, $linkInvoice)) {
capture_log('INVOICE_LINKED_SUCCESS', ['invoice_id' => $invoice_id, 'order_id' => $new_order_id]);
} else {
capture_log('INVOICE_LINK_FAILED', ['error' => mysqli_error($db)]);
}
error_log("capture_order.php: Created order $new_order_id for invoice $invoice_id");
$processedInvoices++;
} else {
error_log("capture_order.php: Failed to create order for invoice $invoice_id: " . mysqli_error($db));
$dbError = mysqli_error($db);
capture_log('ORDER_CREATE_FAILED', [
'invoice_id' => $invoice_id,
'error' => $dbError
]);
error_log("capture_order.php: Failed to create order for invoice $invoice_id: " . $dbError);
}
}
}
capture_log('PROCESSING_COMPLETE', [
'processed_invoices' => $processedInvoices,
'user_id' => $user_id
]);
mysqli_close($db);
} else {
capture_log('NO_USER_ID', ['session_data' => $_SESSION]);
}
} else {
capture_log('SKIP_PROCESSING', [
'captureStatus' => $captureStatus,
'custom_id' => $custom_id,
'reason' => !$captureStatus ? 'no_status' : (!$custom_id ? 'no_custom_id' : 'status_not_completed')
]);
}
// Return the full PayPal response (normalized JSON) for proper processing
capture_log('REQUEST_COMPLETE', ['returning_status' => $captureStatus]);
echo json_encode($capture);
?>

View file

@ -1,13 +1,65 @@
<?php
/**
* PayPal Create Order API Endpoint
* Enhanced with comprehensive logging for debugging
*/
// Ensure all errors are logged, not displayed (to prevent JSON corruption)
ini_set('display_errors', '0');
error_reporting(E_ALL);
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';
// Setup comprehensive logging
$logDir = __DIR__ . '/../logs';
@mkdir($logDir, 0755, true);
$logFile = $logDir . '/paypal_create_order.log';
$requestId = uniqid('req_', true); // Unique request identifier for tracking
function create_order_log($label, $data) {
global $logFile, $requestId;
$timestamp = date('Y-m-d H:i:s');
$entry = "[$timestamp] [$requestId] $label\n";
if (is_array($data) || is_object($data)) {
$entry .= print_r($data, true);
} else {
$entry .= (string)$data;
}
$entry .= "\n" . str_repeat('-', 80) . "\n";
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
}
create_order_log('REQUEST_START', [
'method' => $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN',
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN',
]);
header('Content-Type: application/json');
$in = json_decode(file_get_contents('php://input'), true) ?: [];
// Read and parse input
$rawInput = file_get_contents('php://input');
create_order_log('RAW_INPUT', substr($rawInput, 0, 2000)); // Log first 2000 chars
$in = json_decode($rawInput, true);
if (json_last_error() !== JSON_ERROR_NONE) {
create_order_log('JSON_DECODE_ERROR', [
'error' => json_last_error_msg(),
'raw_input_length' => strlen($rawInput),
'raw_input_preview' => substr($rawInput, 0, 500)
]);
http_response_code(400);
echo json_encode(['error' => 'invalid_json', 'message' => json_last_error_msg(), 'request_id' => $requestId]);
exit;
}
if (!$in) {
$in = [];
}
$amount_in = $in['amount'] ?? '0.00';
$currency = $in['currency'] ?? 'USD';
@ -19,6 +71,15 @@ $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;
create_order_log('PARSED_INPUT', [
'amount' => $amount_in,
'currency' => $currency,
'invoice_id' => $invoice_id,
'custom_id' => $custom_id,
'items_count' => $items ? count($items) : 0,
'line_invoices_count' => $line_invoices ? count($line_invoices) : 0
]);
$amount_value = number_format((float)$amount_in, 2, '.', '');
if ($items) {
$sum = 0.00;
@ -28,9 +89,23 @@ if ($items) {
$sum += $qty * $val;
}
$amount_value = number_format($sum, 2, '.', '');
create_order_log('AMOUNT_CALCULATED', [
'original_amount' => $amount_in,
'calculated_from_items' => $amount_value,
'items_sum' => $sum
]);
}
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
create_order_log('PAYPAL_API_CONFIG', [
'sandbox_mode' => $sandbox,
'api_base' => $api,
'has_client_id' => !empty($client_id),
'has_client_secret' => !empty($client_secret)
]);
// Step 1: Get OAuth token
create_order_log('OAUTH_REQUEST_START', ['endpoint' => "$api/v1/oauth2/token"]);
$ch = curl_init("$api/v1/oauth2/token");
curl_setopt_array($ch, [
@ -42,14 +117,51 @@ curl_setopt_array($ch, [
]);
$tok = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_errno = curl_errno($ch);
$curl_error = curl_error($ch);
curl_close($ch);
if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_fail']); exit; }
create_order_log('OAUTH_RESPONSE', [
'http_code' => $http,
'curl_errno' => $curl_errno,
'curl_error' => $curl_error,
'response_length' => strlen($tok),
'response_preview' => substr($tok, 0, 200)
]);
if ($curl_errno !== 0) {
create_order_log('OAUTH_CURL_ERROR', ['errno' => $curl_errno, 'error' => $curl_error]);
http_response_code(502);
echo json_encode(['error' => 'oauth_curl_fail', 'details' => $curl_error, 'request_id' => $requestId]);
exit;
}
if ($http !== 200) {
create_order_log('OAUTH_HTTP_ERROR', ['http_code' => $http, 'response' => $tok]);
http_response_code(500);
echo json_encode(['error' => 'oauth_fail', 'http_code' => $http, 'request_id' => $requestId]);
exit;
}
$access = json_decode($tok, true)['access_token'] ?? null;
if (!$access) { http_response_code(500); echo json_encode(['error'=>'oauth_no_token']); exit; }
if (!$access) {
create_order_log('OAUTH_NO_TOKEN', ['response' => $tok]);
http_response_code(500);
echo json_encode(['error' => 'oauth_no_token', 'request_id' => $requestId]);
exit;
}
create_order_log('OAUTH_SUCCESS', ['token_length' => strlen($access)]);
// Update site base URL to exclude 'modules/billing'
$siteBaseUrl = 'http://gameservers.world';
create_order_log('URL_PROCESSING_BEFORE', [
'return_url' => $return_url,
'cancel_url' => $cancel_url,
'site_base' => $siteBaseUrl
]);
// Ensure return_url and cancel_url are absolute URLs (relative to site root)
if (strpos($return_url, 'http') !== 0) {
$return_url = $siteBaseUrl . '/' . ltrim($return_url, '/');
@ -58,6 +170,11 @@ if (strpos($cancel_url, 'http') !== 0) {
$cancel_url = $siteBaseUrl . '/' . ltrim($cancel_url, '/');
}
create_order_log('URL_PROCESSING_AFTER', [
'return_url' => $return_url,
'cancel_url' => $cancel_url
]);
$purchaseUnit = [
'amount' => [ 'currency_code' => $currency, 'value' => $amount_value ],
'description' => $description,
@ -75,17 +192,10 @@ $body = [
'application_context' => [ 'return_url'=>$return_url, 'cancel_url'=>$cancel_url, 'user_action'=>'PAY_NOW' ]
];
// Log the payload for debugging
$logDir = __DIR__ . '/../data';
@mkdir($logDir, 0775, true);
$logFile = $logDir . '/create_order_payload.log';
$logEntry = date('Y-m-d H:i:s') . "\n" . json_encode($body, JSON_PRETTY_PRINT) . "\n\n";
@file_put_contents($logFile, $logEntry, FILE_APPEND);
create_order_log('PAYPAL_ORDER_PAYLOAD', $body);
// Log corrected URLs for debugging
$logFile = $logDir . '/corrected_urls.log';
$logEntry = date('Y-m-d H:i:s') . "\nReturn URL: $return_url\nCancel URL: $cancel_url\n\n";
@file_put_contents($logFile, $logEntry, FILE_APPEND);
// Step 2: Create PayPal order
create_order_log('CREATE_ORDER_REQUEST_START', ['endpoint' => "$api/v2/checkout/orders"]);
$ch = curl_init("$api/v2/checkout/orders");
curl_setopt_array($ch, [
@ -96,20 +206,61 @@ curl_setopt_array($ch, [
]);
$res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_errno = curl_errno($ch);
$curl_error = curl_error($ch);
curl_close($ch);
if ($http !== 201) {
// Log error for debugging
$logDir = __DIR__ . '/../data';
@mkdir($logDir, 0775, true);
$logFile = $logDir . '/create_order_errors.log';
$logEntry = date('Y-m-d H:i:s') . " HTTP $http: " . substr($res, 0, 500) . "\n";
@file_put_contents($logFile, $logEntry, FILE_APPEND);
http_response_code($http);
echo $res;
exit;
create_order_log('CREATE_ORDER_RESPONSE', [
'http_code' => $http,
'curl_errno' => $curl_errno,
'curl_error' => $curl_error,
'response_length' => strlen($res),
'response' => substr($res, 0, 1000) // First 1000 chars of response
]);
if ($curl_errno !== 0) {
create_order_log('CREATE_ORDER_CURL_ERROR', ['errno' => $curl_errno, 'error' => $curl_error]);
http_response_code(502);
echo json_encode(['error' => 'create_order_curl_fail', 'details' => $curl_error, 'request_id' => $requestId]);
exit;
}
if ($http !== 201) {
create_order_log('CREATE_ORDER_HTTP_ERROR', [
'http_code' => $http,
'response' => $res,
'payload_sent' => $body
]);
// Try to parse PayPal error response
$errorData = json_decode($res, true);
http_response_code($http);
echo json_encode([
'error' => 'create_order_failed',
'http_code' => $http,
'paypal_error' => $errorData,
'request_id' => $requestId
]);
exit;
}
// Success - parse and validate response
$orderData = json_decode($res, true);
if (json_last_error() !== JSON_ERROR_NONE) {
create_order_log('CREATE_ORDER_INVALID_JSON', [
'json_error' => json_last_error_msg(),
'response' => $res
]);
http_response_code(502);
echo json_encode(['error' => 'invalid_paypal_response', 'request_id' => $requestId]);
exit;
}
create_order_log('CREATE_ORDER_SUCCESS', [
'order_id' => $orderData['id'] ?? 'UNKNOWN',
'status' => $orderData['status'] ?? 'UNKNOWN'
]);
echo $res;
?>

View file

@ -0,0 +1,44 @@
<?php
/**
* Client-side error logging endpoint
* Logs JavaScript errors from the cart page for debugging
*/
// Ensure all errors are logged, not displayed
ini_set('display_errors', '0');
error_reporting(E_ALL);
header('Content-Type: application/json');
// Setup logging
$logDir = __DIR__ . '/../logs';
@mkdir($logDir, 0755, true);
$logFile = $logDir . '/client_errors.log';
function log_client_error($data) {
global $logFile;
$timestamp = date('Y-m-d H:i:s');
$entry = "[$timestamp] CLIENT ERROR\n";
$entry .= "IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN') . "\n";
$entry .= "User Agent: " . ($_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN') . "\n";
if (is_array($data) || is_object($data)) {
$entry .= print_r($data, true);
} else {
$entry .= (string)$data;
}
$entry .= "\n" . str_repeat('-', 80) . "\n";
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
}
// Read and parse input
$rawInput = file_get_contents('php://input');
$data = json_decode($rawInput, true);
if ($data) {
log_client_error($data);
echo json_encode(['status' => 'logged']);
} else {
log_client_error(['raw_input' => $rawInput, 'error' => 'Invalid JSON']);
echo json_encode(['status' => 'error', 'message' => 'Invalid JSON']);
}
?>

View file

@ -597,12 +597,37 @@ $apiBase = 'api';
return_url, cancel_url
});
function setStatus(msg){ if(statusEl) statusEl.textContent = msg; }
function setStatus(msg, isError = false){
if(statusEl) {
statusEl.textContent = msg;
statusEl.style.color = isError ? '#dc3545' : '#000';
statusEl.style.fontWeight = isError ? 'bold' : 'normal';
}
}
function logError(context, error) {
const errorData = {
timestamp: new Date().toISOString(),
context: context,
error: error,
invoice_id: invoice_id,
amount: amount
};
console.error('PayPal Error:', errorData);
// Try to send error to server for logging
fetch("<?= $apiBase ?>/log_error.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify(errorData)
}).catch(e => console.error('Failed to log error to server:', e));
}
paypal.Buttons({
createOrder: function() {
setStatus('Creating order…');
console.log('createOrder: Starting request to create_order.php');
return fetch("<?= $apiBase ?>/create_order.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
@ -615,51 +640,114 @@ $apiBase = 'api';
})
})
.then(res => {
console.log('createOrder: Response received', { status: res.status, ok: res.ok });
if (!res.ok) {
return res.text().then(errText => {
throw new Error('API error ' + res.status + ': ' + errText.substring(0, 200));
console.error('createOrder: Error response text:', errText);
logError('create_order_http_error', { status: res.status, response: errText });
// Try to parse as JSON for better error display
let errorMsg = 'API error ' + res.status;
try {
const errJson = JSON.parse(errText);
if (errJson.error) errorMsg += ': ' + errJson.error;
if (errJson.request_id) errorMsg += ' (Ref: ' + errJson.request_id + ')';
} catch (e) {
errorMsg += ': ' + errText.substring(0, 100);
}
throw new Error(errorMsg);
});
}
return res.json();
})
.then(data => {
if (!data.id) {
throw new Error(JSON.stringify(data).substring(0, 200) || 'No order id');
console.log('createOrder: Parsed response', data);
if (!data.id) {
const errMsg = 'No order ID in response';
console.error('createOrder:', errMsg, data);
logError('create_order_no_id', { response: data });
throw new Error(errMsg + (data.request_id ? ' (Ref: ' + data.request_id + ')' : ''));
}
setStatus('Order created.');
console.log('createOrder: Success, order ID:', data.id);
return data.id;
})
.catch(err => {
setStatus('PayPal error: ' + err.message);
console.error('createOrder: Caught error', err);
const errorMsg = 'Failed to create order: ' + err.message;
setStatus(errorMsg, true);
logError('create_order_exception', { message: err.message, stack: err.stack });
throw err;
});
},
onApprove: function(data) {
setStatus('Capturing payment…');
console.log('onApprove: Starting capture for order', data.orderID);
return fetch("<?= $apiBase ?>/capture_order.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ order_id: data.orderID })
})
.then(res => res.json())
.then(res => {
console.log('onApprove: Capture response received', { status: res.status, ok: res.ok });
if (!res.ok) {
return res.text().then(errText => {
console.error('onApprove: Error response text:', errText);
logError('capture_order_http_error', { status: res.status, response: errText });
let errorMsg = 'Capture failed (HTTP ' + res.status + ')';
try {
const errJson = JSON.parse(errText);
if (errJson.error) errorMsg += ': ' + errJson.error;
if (errJson.request_id) errorMsg += ' (Ref: ' + errJson.request_id + ')';
} catch (e) {
errorMsg += ': ' + errText.substring(0, 100);
}
throw new Error(errorMsg);
});
}
return res.json();
})
.then(capture => {
console.log('onApprove: Parsed capture response', capture);
if (capture.status === 'COMPLETED') {
console.log('onApprove: Payment completed, redirecting to success page');
setStatus('Payment completed! Redirecting...');
// go to your return page; webhook will fill data/<invoice_id>.json
window.location.href = return_url;
} else if (capture.error) {
const errorMsg = 'Capture error: ' + capture.error + (capture.request_id ? ' (Ref: ' + capture.request_id + ')' : '');
console.error('onApprove:', errorMsg);
logError('capture_order_error_response', capture);
setStatus(errorMsg, true);
} else {
setStatus('Capture status: ' + capture.status);
const statusMsg = 'Unexpected capture status: ' + (capture.status || 'UNKNOWN');
console.warn('onApprove:', statusMsg, capture);
logError('capture_order_unexpected_status', capture);
setStatus(statusMsg, true);
}
})
.catch(err => setStatus('Error: ' + err.message));
.catch(err => {
console.error('onApprove: Caught error', err);
const errorMsg = 'Payment capture failed: ' + err.message;
setStatus(errorMsg, true);
logError('capture_order_exception', { message: err.message, stack: err.stack });
});
},
onCancel: function() {
console.log('onCancel: User cancelled payment');
logError('payment_cancelled', { invoice_id: invoice_id });
window.location.href = cancel_url;
},
onError: function(err){
setStatus('PayPal error: ' + (err && err.message ? err.message : err));
console.error('onError: PayPal SDK error', err);
const errorMsg = 'PayPal error: ' + (err && err.message ? err.message : JSON.stringify(err));
setStatus(errorMsg, true);
logError('paypal_sdk_error', { error: err });
}
}).render('#paypal-button-container');
})();