JezK
Edit File: roots-program-api.php
<?php // Disable error display in production ini_set('display_errors', 0); ini_set('log_errors', 1); error_reporting(E_ALL); error_log("PHP script started"); // Secure session handling if (!session_id() && !headers_sent()) { ini_set('session.cookie_httponly', 1); if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') { ini_set('session.cookie_secure', 1); } ini_set('session.use_only_cookies', 1); ini_set('session.cookie_samesite', 'Strict'); session_start(); } // Ensure this file is accessed within WordPress if (!defined('ABSPATH')) { define('ABSPATH', dirname(__FILE__) . '/../../../'); require_once(ABSPATH . 'wp-load.php'); } // Allowed Origin $allowed_origin = 'https://authentica.com'; // Accommodation prices $ACCOMMODATION_PRICES = [ 'homestay' => 4480, '3star' => 5210, '4star' => 5960, 'own' => 3890, 'test' => 1 ]; // Room type additional costs $ROOM_TYPE_PRICES = [ '3star' => ['single' => 1320, 'twin' => 0], '4star' => ['single' => 2070, 'twin' => 0] ]; // Enable CORS for allowed origin header("Access-Control-Allow-Origin: $allowed_origin"); header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); header("Access-Control-Allow-Headers: Content-Type"); // Handle preflight OPTIONS request if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; } // Validate request origin if (isset($_SERVER['HTTP_ORIGIN']) && $_SERVER['HTTP_ORIGIN'] !== $allowed_origin) { header('Content-Type: application/json'); http_response_code(403); echo json_encode(['error' => 'Origin not allowed']); exit; } // CSRF Token Handling if (isset($_GET['action']) && $_GET['action'] === 'get_token') { ob_clean(); // Ensure no unexpected output before JSON response $token = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = $token; $_SESSION['csrf_token_time'] = time(); $_SESSION['csrf_token_hash'] = hash('sha256', $token . $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']); header('Content-Type: application/json'); header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: DENY'); echo json_encode(['csrf_token' => $token]); error_log("CSRF token generated successfully"); exit; } // Rate limiting $rate_limit_key = 'rate_limit_' . md5($_SERVER['REMOTE_ADDR']); $rate_limit_time = 'rate_limit_time_' . md5($_SERVER['REMOTE_ADDR']); $max_requests = 10; $time_period = 900; // 15 minutes if (isset($_SESSION[$rate_limit_key])) { if (time() - $_SESSION[$rate_limit_time] > $time_period) { $_SESSION[$rate_limit_key] = 1; $_SESSION[$rate_limit_time] = time(); } else { $_SESSION[$rate_limit_key]++; if ($_SESSION[$rate_limit_key] > $max_requests) { header('Content-Type: application/json'); http_response_code(429); echo json_encode(['error' => 'Too many requests. Try again later.']); exit; } } } else { $_SESSION[$rate_limit_key] = 1; $_SESSION[$rate_limit_time] = time(); } // Process request $request_body = file_get_contents("php://input"); $decoded_input = json_decode($request_body, true); // Input sanitization function sanitizeInput($input) { if (is_array($input)) { $sanitized = []; foreach ($input as $key => $value) { $sanitizedKey = preg_replace('/[^a-zA-Z0-9_]/', '', $key); $sanitized[$sanitizedKey] = is_array($value) ? sanitizeInput($value) : filter_var(trim($value), FILTER_SANITIZE_STRING); } return $sanitized; } return filter_var($input, FILTER_SANITIZE_STRING); } // Sanitize input data if ($decoded_input) { $decoded_input = sanitizeInput($decoded_input); error_log("Received input data: " . json_encode($decoded_input)); } // Validate CSRF token (if not a Stripe webhook) $is_stripe_webhook = false; $headers = getallheaders(); if (isset($headers['Stripe-Signature'])) { $is_stripe_webhook = true; } if (!$is_stripe_webhook) { if (!isset($decoded_input['csrf_token']) || !isset($_SESSION['csrf_token']) || !isset($_SESSION['csrf_token_hash']) || $decoded_input['csrf_token'] !== $_SESSION['csrf_token'] || $_SESSION['csrf_token_hash'] !== hash('sha256', $_SESSION['csrf_token'] . $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']) || (time() - $_SESSION['csrf_token_time'] > 1800)) { error_log("CSRF token validation failed"); header('Content-Type: application/json'); http_response_code(403); echo json_encode(['error' => 'Invalid or expired CSRF token']); exit; } } // Handle Stripe webhook if ($is_stripe_webhook) { if (!defined('STRIPE_WEBHOOK_SECRET')) { http_response_code(500); echo json_encode(['error' => 'Webhook secret not configured']); exit; } require_once(__DIR__ . '/stripe-lib/stripe-php-master/init.php'); \Stripe\Stripe::setApiKey(STRIPE_SECRET_KEY); try { $event = \Stripe\Webhook::constructEvent( $request_body, $headers['Stripe-Signature'], STRIPE_WEBHOOK_SECRET ); if ($event->type == 'payment_intent.succeeded') { error_log('Payment succeeded: ' . $event->data->object->id); http_response_code(200); echo json_encode(['status' => 'payment_success']); exit; } elseif ($event->type == 'payment_intent.payment_failed') { error_log('Payment failed: ' . $event->data->object->last_payment_error->message); http_response_code(200); echo json_encode(['status' => 'payment_failed']); exit; } else { http_response_code(200); echo json_encode(['status' => 'event_received']); exit; } } catch (\Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); exit; } } // Handle form submission for payment processing if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($decoded_input['firstName']) && isset($decoded_input['lastName']) && isset($decoded_input['email']) && isset($decoded_input['accommodation'])) { error_log("Processing payment form submission"); // Validate required fields if (empty($decoded_input['firstName']) || empty($decoded_input['lastName']) || empty($decoded_input['email']) || empty($decoded_input['accommodation'])) { header('Content-Type: application/json'); echo json_encode(['success' => false, 'message' => 'Missing required fields']); exit; } // Validate email if (!filter_var($decoded_input['email'], FILTER_VALIDATE_EMAIL)) { header('Content-Type: application/json'); echo json_encode(['success' => false, 'message' => 'Invalid email address']); exit; } // Check if accommodation type is valid if (!array_key_exists($decoded_input['accommodation'], $ACCOMMODATION_PRICES)) { header('Content-Type: application/json'); echo json_encode(['success' => false, 'message' => 'Invalid accommodation type']); exit; } // Calculate total price $basePrice = $ACCOMMODATION_PRICES[$decoded_input['accommodation']]; $additionalPrice = 0; // Add room type price if applicable if (isset($decoded_input['roomType']) && $decoded_input['roomType'] && isset($ROOM_TYPE_PRICES[$decoded_input['accommodation']]) && isset($ROOM_TYPE_PRICES[$decoded_input['accommodation']][$decoded_input['roomType']])) { $additionalPrice = $ROOM_TYPE_PRICES[$decoded_input['accommodation']][$decoded_input['roomType']]; } $totalPrice = $basePrice + $additionalPrice; // Create session data to pass to Stripe checkout $session_data = [ 'firstName' => $decoded_input['firstName'], 'lastName' => $decoded_input['lastName'], 'email' => $decoded_input['email'], 'accommodation' => $decoded_input['accommodation'], 'accommodationText' => $decoded_input['accommodationText'], 'roomType' => isset($decoded_input['roomType']) ? $decoded_input['roomType'] : '', 'roomTypeText' => isset($decoded_input['roomTypeText']) ? $decoded_input['roomTypeText'] : '', 'totalPrice' => $totalPrice ]; // Include the checkout session creation file require_once(__DIR__ . '/create-stripe-checkout-session.php'); // The checkout session file will handle the rest of the response exit; } // If we get here, it's an invalid request header('Content-Type: application/json'); http_response_code(400); echo json_encode(['error' => 'Invalid request']); exit; ?>