pdo = $pdo; $this->apiKeys = $apiKeys; } /** * Validate API request * Checks Content-Type and optionally validates API key * * @param array $allowedMethods Array of allowed HTTP methods (e.g. ['POST', 'GET']) * @return array Validation result */ public function validateRequest($allowedMethods = ['POST']) { // Check request method $method = $_SERVER['REQUEST_METHOD']; if (!in_array($method, $allowedMethods)) { return [ 'valid' => false, 'error' => "Invalid request method. Allowed: " . implode(', ', $allowedMethods), 'http_code' => 405 ]; } // Check Content-Type header (only for methods with body) if (in_array($method, ['POST', 'PUT', 'PATCH'])) { $contentType = $_SERVER['CONTENT_TYPE'] ?? ''; if (stripos($contentType, 'application/json') === false) { return [ 'valid' => false, 'error' => 'Invalid Content-Type. Expected application/json', 'http_code' => 400 ]; } } // Validate API key if configured if (!empty($this->apiKeys)) { $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (empty($authHeader)) { return [ 'valid' => false, 'error' => 'Missing Authorization header', 'http_code' => 401 ]; } // Extract Bearer token if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) { return [ 'valid' => false, 'error' => 'Invalid Authorization header format. Expected: Bearer ', 'http_code' => 401 ]; } $apiKey = $matches[1]; if (!in_array($apiKey, $this->apiKeys)) { return [ 'valid' => false, 'error' => 'Invalid API key', 'http_code' => 401 ]; } } return ['valid' => true]; } /** * Parse and validate JSON payload * * @return array Parsed data or error */ public function parseJsonPayload() { $rawInput = file_get_contents('php://input'); if (empty($rawInput)) { return [ 'valid' => false, 'error' => 'Empty request body', 'http_code' => 400 ]; } $data = json_decode($rawInput, true); if (json_last_error() !== JSON_ERROR_NONE) { return [ 'valid' => false, 'error' => 'Invalid JSON: ' . json_last_error_msg(), 'http_code' => 400 ]; } return [ 'valid' => true, 'data' => $data ]; } /** * Validate payment request fields * * @param array $data Request data * @return array Validation result with errors */ public function validatePaymentRequest($data) { $errors = []; // Validate student_id if (empty($data['student_id'])) { $errors['student_id'] = 'Student ID is required'; } elseif (!is_string($data['student_id']) && !is_numeric($data['student_id'])) { $errors['student_id'] = 'Student ID must be a string or number'; } // Validate teller_no if (empty($data['teller_no'])) { $errors['teller_no'] = 'Teller number is required'; } elseif (!is_string($data['teller_no']) && !is_numeric($data['teller_no'])) { $errors['teller_no'] = 'Teller number must be a string or number'; } // Validate amount if (!isset($data['amount'])) { $errors['amount'] = 'Amount is required'; } elseif (!is_numeric($data['amount'])) { $errors['amount'] = 'Amount must be a number'; } elseif (floatval($data['amount']) <= 0) { $errors['amount'] = 'Amount must be greater than zero'; } // Validate payment_date if (empty($data['payment_date'])) { $errors['payment_date'] = 'Payment date is required'; } elseif (!strtotime($data['payment_date'])) { $errors['payment_date'] = 'Invalid payment date format'; } if (!empty($errors)) { return [ 'valid' => false, 'errors' => $errors, 'http_code' => 400 ]; } return ['valid' => true]; } /** * Validate student exists in database * * @param string $studentId Student ID * @return array Validation result with student data */ public function validateStudentExists($studentId) { $sql = "SELECT sr.id, sr.student_code, CONCAT(sr.last_name, ' ', sr.first_name, ' ', COALESCE(sr.other_name, '')) AS full_name, sr.admission_status, al.level_name FROM tb_student_registrations sr LEFT JOIN tb_academic_levels al ON sr.level_id = al.id WHERE sr.id = :student_id"; $stmt = $this->pdo->prepare($sql); $stmt->execute(['student_id' => $studentId]); $student = $stmt->fetch(); if (!$student) { return [ 'valid' => false, 'error' => "Student not found (ID searched: '$studentId')", 'http_code' => 404 ]; } if ($student['admission_status'] !== 'Active') { return [ 'valid' => false, 'error' => 'Student is not active', 'http_code' => 400 ]; } return [ 'valid' => true, 'student' => $student ]; } /** * Validate teller number exists and has unreconciled amount * * @param string $tellerNo Teller number * @return array Validation result with teller data */ public function validateTellerNumber($tellerNo) { $sql = "SELECT bs.id, bs.description, bs.amount_paid, bs.payment_date, COALESCE(fp.total_registered_fee, 0.00) AS registered_amount, (bs.amount_paid - COALESCE(fp.total_registered_fee, 0.00)) AS unreconciled_amount FROM tb_account_bank_statements bs LEFT JOIN ( SELECT teller_no, SUM(amount_paid) AS total_registered_fee FROM tb_account_school_fee_payments GROUP BY teller_no ) fp ON SUBSTRING_INDEX(bs.description, ' ', -1) = fp.teller_no WHERE SUBSTRING_INDEX(bs.description, ' ', -1) = :teller_number LIMIT 1"; $stmt = $this->pdo->prepare($sql); $stmt->execute(['teller_number' => $tellerNo]); $teller = $stmt->fetch(); if (!$teller) { return [ 'valid' => false, 'error' => 'Teller number not found', 'http_code' => 404 ]; } if ($teller['unreconciled_amount'] <= 0) { return [ 'valid' => false, 'error' => 'Teller number has no unreconciled amount', 'http_code' => 400 ]; } return [ 'valid' => true, 'teller' => $teller ]; } /** * Check for duplicate teller number usage * Prevents the same teller number from being used multiple times * * @param string $tellerNo Teller number * @param string $studentId Student ID (optional, for better error message) * @return array Validation result */ public function checkTellerDuplicate($tellerNo, $studentId = null) { // Note: The system allows the same teller to be used for different students // This check is to ensure the teller has available unreconciled amount // The actual duplicate check is handled by the validateTellerNumber method return ['valid' => true]; } /** * Sanitize input data * * @param array $data Input data * @return array Sanitized data */ public function sanitizeInput($data) { return [ 'student_id' => trim($data['student_id'] ?? ''), 'teller_no' => trim($data['teller_no'] ?? ''), 'amount' => floatval($data['amount'] ?? 0), 'payment_date' => trim($data['payment_date'] ?? '') ]; } }