JezK
Edit File: roots-program-api.php.save
<?php error_reporting(E_ALL); ini_set('display_errors', 1); // Start session with secure parameters 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')) { require_once('/home/authentica.com/public_html/wp-load.php'); } // Define the allowed origin $allowed_origin = 'https://authentica.com'; // Set fixed price constants for backend validation // Accommodation options with fixed prices $ACCOMMODATION_PRICES = [ 'homestay' => 4480, '3star' => 5210, '4star' => 5960, 'own' => 3890 ]; // Room type additional costs $ROOM_TYPE_PRICES = [ '3star' => [ 'single' => 1320, 'twin' => 0 ], '4star' => [ 'single' => 2070, 'twin' => 0 ] ]; // Origin validation - only accept requests from our domain if (isset($_SERVER['HTTP_ORIGIN'])) { if ($_SERVER['HTTP_ORIGIN'] !== $allowed_origin) { header('Content-Type: application/json'); http_response_code(403); echo json_encode(['error' => 'Origin not allowed']); exit; } } // Debug mode - check if this is a test request if (isset($_GET['action']) && $_GET['action'] === 'test') { header('Content-Type: application/json'); header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: DENY'); // Check if Stripe keys are defined $keys_configured = defined('STRIPE_PUBLISHABLE_KEY') && defined('STRIPE_SECRET_KEY'); echo json_encode([ 'success' => true, 'message' => 'Stripe integration test', 'stripe_keys_configured' => $keys_configured, 'stripe_webhook_configured' => defined('STRIPE_WEBHOOK_SECRET') ]); exit; } // Check for token generation request if (isset($_GET['action']) && $_GET['action'] === 'get_token') { // Generate token directly in this file $token = bin2hex(random_bytes(64)); $_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]); exit; } // Implement rate limiting (simple IP-based) $rate_limit_key = 'rate_limit_' . md5($_SERVER['REMOTE_ADDR']); $rate_limit_time = 'rate_limit_time_' . md5($_SERVER['REMOTE_ADDR']); $max_requests = 10; // Maximum number of requests in time period $time_period = 60*15; // 15 minutes if (isset($_SESSION[$rate_limit_key])) { // Check if time period has passed if (time() - $_SESSION[$rate_limit_time] > $time_period) { // Reset counter $_SESSION[$rate_limit_key] = 1; $_SESSION[$rate_limit_time] = time(); } else { // Increment counter $_SESSION[$rate_limit_key]++; // Check if limit exceeded if ($_SESSION[$rate_limit_key] > $max_requests) { header('Content-Type: application/json'); header('X-Content-Type-Options: nosniff'); http_response_code(429); echo json_encode(['error' => 'Too many requests. Please try again later.']); exit; } } } else { // Initialize counter $_SESSION[$rate_limit_key] = 1; $_SESSION[$rate_limit_time] = time(); } // Get JSON input from the request $request_body = file_get_contents("php://input"); $decoded_input = json_decode($request_body, true); // Set default for Stripe webhook flag $is_stripe_webhook = false; // For normal POST requests, check for token with improved validation if ($_SERVER['REQUEST_METHOD'] === 'POST') { // If this is a webhook from Stripe, skip token validation $headers = getallheaders(); if (isset($headers['Stripe-Signature'])) { $is_stripe_webhook = true; } if (!$is_stripe_webhook) { // If no CSRF token in request if (!isset($decoded_input['csrf_token'])) { header('Content-Type: application/json'); header('X-Content-Type-Options: nosniff'); http_response_code(403); echo json_encode([ 'error' => 'Missing CSRF token', 'http_code' => 403 ]); exit; } // If token doesn't match session with improved validation if (!isset($_SESSION['csrf_token']) || !isset($_SESSION['csrf_token_hash']) || $decoded_input['csrf_token'] !== $_SESSION['csrf_token'] || $_SESSION['csrf_token_hash'] !== hash('sha256', $decoded_input['csrf_token'] . $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR'])) { header('Content-Type: application/json'); header('X-Content-Type-Options: nosniff'); http_response_code(403); echo json_encode([ 'error' => 'Invalid CSRF token', 'http_code' => 403 ]); exit; } // Check token expiration if (!isset($_SESSION['csrf_token_time']) || (time() - $_SESSION['csrf_token_time'] > 1800)) { header('Content-Type: application/json'); header('X-Content-Type-Options: nosniff'); http_response_code(403); echo json_encode([ 'error' => 'Expired CSRF token', 'http_code' => 403 ]); exit; } } } // Input sanitization function function sanitizeInput($input) { if (is_array($input)) { $sanitized = []; foreach ($input as $key => $value) { // Sanitize keys $sanitizedKey = preg_replace('/[^a-zA-Z0-9_]/', '', $key); // Sanitize values if (is_array($value)) { $sanitized[$sanitizedKey] = sanitizeInput($value); } else { // For string values, remove potential XSS and SQL injection characters $sanitized[$sanitizedKey] = filter_var($value, FILTER_SANITIZE_STRING); // Trim whitespace if (is_string($sanitized[$sanitizedKey])) { $sanitized[$sanitizedKey] = trim($sanitized[$sanitizedKey]); } } } return $sanitized; } else { // For non-array input return filter_var($input, FILTER_SANITIZE_STRING); } } // Sanitize input data if ($decoded_input) { $decoded_input = sanitizeInput($decoded_input); } // Process based on request type if ($is_stripe_webhook) { // Handle Stripe webhook if (!defined('STRIPE_WEBHOOK_SECRET')) { http_response_code(500); echo json_encode(['error' => 'Webhook secret not configured']); exit; } // Get the webhook payload and signature header $payload = $request_body; $sig_header = $headers['Stripe-Signature']; $endpoint_secret = STRIPE_WEBHOOK_SECRET; // Verify webhook signature and process the event try { // Include Stripe PHP library require_once(ABSPATH . 'vendor/autoload.php'); // Set up Stripe API key \Stripe\Stripe::setApiKey(STRIPE_SECRET_KEY); // Construct the event $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); // Handle successful payment event if ($event->type == 'payment_intent.succeeded') { $paymentIntent = $event->data->object; // Here you would: // 1. Update your database with payment success // 2. Send confirmation email to customer // 3. Perform any other post-payment actions // For now just log the success error_log('Payment succeeded: ' . $paymentIntent->id); http_response_code(200); echo json_encode(['status' => 'payment_success']); exit; } // Handle failed payment event else if ($event->type == 'payment_intent.payment_failed') { $paymentIntent = $event->data->object; $error = $paymentIntent->last_payment_error; // Log the failure error_log('Payment failed: ' . $error->message); http_response_code(200); echo json_encode(['status' => 'payment_failed']); exit; } // Acknowledge other events else { http_response_code(200); echo json_encode(['status' => 'event_received']); exit; } } catch (\UnexpectedValueException $e) { // Invalid payload http_response_code(400); echo json_encode(['error' => 'Invalid payload']); exit; } catch (\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature http_response_code(400); echo json_encode(['error' => 'Invalid signature']); exit; } catch (\Exception $e) { // General error http_response_code(500); echo json_encode(['error' => $e->getMessage()]); exit; } } else if ($_SERVER['REQUEST_METHOD'] === 'POST') { // This is the main payment processing endpoint // Check if Stripe keys are configured if (!defined('STRIPE_SECRET_KEY') || !defined('STRIPE_PUBLISHABLE_KEY')) { header('Content-Type: application/json'); echo json_encode(['error' => 'Stripe API keys not configured']); exit; } // Validate required fields $required_fields = ['firstName', 'lastName', 'email', 'accommodation']; foreach ($required_fields as $field) { if (!isset($decoded_input[$field]) || empty($decoded_input[$field])) { header('Content-Type: application/json'); echo json_encode(['error' => "Missing required field: $field"]); exit; } } // Validate field lengths if (strlen($decoded_input['firstName']) > 100) { header('Content-Type: application/json'); echo json_encode(['error' => 'First name is too long']); exit; } if (strlen($decoded_input['lastName']) > 100) { header('Content-Type: application/json'); echo json_encode(['error' => 'Last name is too long']); exit; } if (strlen($decoded_input['email']) > 100) { header('Content-Type: application/json'); echo json_encode(['error' => 'Email is too long']); exit; } // Validate email format if (!filter_var($decoded_input['email'], FILTER_VALIDATE_EMAIL)) { header('Content-Type: application/json'); echo json_encode(['error' => 'Invalid email format']); exit; } // Validate accommodation selection if (!array_key_exists($decoded_input['accommodation'], $ACCOMMODATION_PRICES)) { header('Content-Type: application/json'); echo json_encode(['error' => 'Invalid accommodation option']); exit; } // Get base price from accommodation selection $base_price = $ACCOMMODATION_PRICES[$decoded_input['accommodation']]; $additional_price = 0; // Validate room type selection if required if ($decoded_input['accommodation'] === '3star' || $decoded_input['accommodation'] === '4star') { if (!isset($decoded_input['roomType']) || empty($decoded_input['roomType'])) { header('Content-Type: application/json'); echo json_encode(['error' => 'Room type is required for this accommodation']); exit; } // Validate room type is valid for the selected accommodation if (!array_key_exists($decoded_input['roomType'], $ROOM_TYPE_PRICES[$decoded_input['accommodation']])) { header('Content-Type: application/json'); echo json_encode(['error' => 'Invalid room type for the selected accommodation']); exit; } // Add room type cost $additional_price = $ROOM_TYPE_PRICES[$decoded_input['accommodation']][$decoded_input['roomType']]; } // Calculate total price $total_price = $base_price + $additional_price; // Prepare customer data $customer_name = $decoded_input['firstName'] . ' ' . $decoded_input['lastName']; $customer_email = $decoded_input['email']; try { // Include Stripe PHP library require_once(ABSPATH . 'vendor/autoload.php'); // Set up Stripe API key \Stripe\Stripe::setApiKey(STRIPE_SECRET_KEY); // Create or retrieve customer $customers = \Stripe\Customer::all([ 'email' => $customer_email, 'limit' => 1 ]); if (count($customers->data) > 0) { $customer = $customers->data[0]; } else { $customer = \Stripe\Customer::create([ 'name' => $customer_name, 'email' => $customer_email ]); } // Create payment intent $payment_intent = \Stripe\PaymentIntent::create([ 'amount' => $total_price * 100, // Convert to cents 'currency' => 'usd', 'customer' => $customer->id, 'description' => 'Routes to Roots Program: ' . $decoded_input['accommodationText'], 'metadata' => [ 'customer_name' => $customer_name, 'customer_email' => $customer_email, 'accommodation' => $decoded_input['accommodationText'], 'room_type' => isset($decoded_input['roomTypeText']) ? $decoded_input['roomTypeText'] : 'N/A' ] ]); // Return client secret to frontend header('Content-Type: application/json'); echo json_encode([ 'success' => true, 'client_secret' => $payment_intent->client_secret, 'publishable_key' => STRIPE_PUBLISHABLE_KEY ]); exit; } catch (\Exception $e) { // Handle Stripe API errors header('Content-Type: application/json'); echo json_encode([ 'error' => 'Payment setup failed: ' . $e->getMessage() ]); exit; } } else { // Invalid request method header('Content-Type: application/json'); http_response_code(405); echo json_encode(['error' => 'Method not allowed']); exit; }