View file File name : api.php Content :<?php // Use folder name to generate a unique session name $folder = basename(dirname($_SERVER['PHP_SELF'])); $session_name = 'SESS_' . $folder; session_name($session_name); // Dynamically set session cookie path for this business's folder $cookie_path = rtrim(dirname($_SERVER['PHP_SELF']), '/\\') . '/'; session_set_cookie_params(0, $cookie_path); session_start(); date_default_timezone_set('America/Belize'); require 'db_config.php'; // --- HELPER FUNCTIONS --- function send_json_error($message, $code = 400) { http_response_code($code); echo json_encode(['success' => false, 'message' => $message]); exit(); } function send_json_success($data = []) { echo json_encode(['success' => true, 'data' => $data]); exit(); } function generate_referral_code($conn) { $is_unique = false; while (!$is_unique) { $referral_code = 'REF' . strtoupper(substr(md5(uniqid(rand(), true)), 0, 6)); $stmt = $conn->prepare("SELECT id FROM customers WHERE referral_code = ?"); $stmt->bind_param("s", $referral_code); $stmt->execute(); $stmt->store_result(); if ($stmt->num_rows == 0) { $is_unique = true; } $stmt->close(); } return $referral_code; } // --- DATABASE CONNECTION --- try { $conn = new mysqli($servername, $username, $password, $dbname); if ($conn->connect_error) { throw new Exception('Database connection failed: ' . $conn->connect_error); } } catch (Exception $e) { send_json_error($e->getMessage(), 500); } // --- MANUAL GET_RESULT FOR COMPATIBILITY --- function get_stmt_result($stmt) { $result = []; $stmt->store_result(); if ($stmt->num_rows === 0) return $result; $meta = $stmt->result_metadata(); $fields = []; $row = []; if (!$meta) { return []; } while ($field = $meta->fetch_field()) { $fields[] = &$row[$field->name]; } call_user_func_array([$stmt, 'bind_result'], $fields); while ($stmt->fetch()) { $result_row = []; foreach ($row as $key => $val) { $result_row[$key] = $val; } $result[] = $result_row; } $stmt->free_result(); return $result; } $action = $_POST['action'] ?? ''; if (empty($action)) { send_json_error('No action specified.'); } // --- ACTION ROUTER --- switch ($action) { case 'get_customer_updates': $customerId = $_POST['customer_id'] ?? null; if (!$customerId) { send_json_error('Customer ID is required.'); } $stmt = $conn->prepare("SELECT points, rewards FROM customers WHERE id = ?"); $stmt->bind_param("s", $customerId); $stmt->execute(); $result = get_stmt_result($stmt); $stmt->close(); if (empty($result)) { send_json_error('Customer not found.', 404); } $customer_data = $result[0]; // Add active gift cards count for real-time UI updates $stmt_gc = $conn->prepare("SELECT COUNT(*) as count FROM gift_cards WHERE customer_id = ? AND status = 'active'"); $stmt_gc->bind_param("s", $customerId); $stmt_gc->execute(); $gc_result = get_stmt_result($stmt_gc); $customer_data['active_gift_cards_count'] = (int)($gc_result[0]['count'] ?? 0); $stmt_gc->close(); send_json_success($customer_data); break; case 'register_customer': $name = $_POST['name'] ?? null; $phone = $_POST['phone'] ?? null; $password = $_POST['password'] ?? null; if (!$name || !$phone || !$password) { send_json_error('All fields are required.'); } if (strlen($password) < 6) { send_json_error('Password must be at least 6 characters long.'); } $stmt = $conn->prepare("SELECT id FROM customers WHERE phone = ?"); $stmt->bind_param("s", $phone); $stmt->execute(); $stmt->store_result(); if ($stmt->num_rows > 0) { $stmt->close(); send_json_error('A customer with this phone number already exists.'); } $stmt->close(); $id = 'cust' . time(); $qrCodeValue = $id; $points = 0; $rewards = 0; $joinedDate = date("Y-m-d H:i:s"); $stmt = $conn->prepare("INSERT INTO customers (id, name, phone, password, qrCodeValue, points, rewards, joinedDate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->bind_param("sssssiis", $id, $name, $phone, $password, $qrCodeValue, $points, $rewards, $joinedDate); if ($stmt->execute()) { $new_customer = [ 'id' => $id, 'name' => $name, 'phone' => $phone, 'password' => $password, 'qrCodeValue' => $qrCodeValue, 'points' => $points, 'rewards' => $rewards, 'joinedDate' => $joinedDate, 'history' => [] ]; // Automatically log in the new user $_SESSION['user_id'] = $id; $_SESSION['user_type'] = 'customer'; $_SESSION['user_name'] = $name; send_json_success(['user' => $new_customer, 'message' => "Registration successful. Welcome, {$name}!"]); } else { send_json_error('Failed to create customer account.', 500); } $stmt->close(); break; case 'get_report_data': $report_type = $_POST['report_type'] ?? ''; $start_date_str = $_POST['start_date'] ?? ''; $end_date_str = $_POST['end_date'] ?? ''; $response_data = []; try { switch($report_type) { case 'points_by_customer': $customer_id = $_POST['customer_id'] ?? null; if (!$customer_id) send_json_error('Customer ID must be provided for this report.'); $start_date = date('Y-m-d 00:00:00', strtotime($start_date_str)); $end_date = date('Y-m-d 23:59:59', strtotime($end_date_str)); // Fetch customer name first $stmt = $conn->prepare("SELECT name FROM customers WHERE id = ?"); $stmt->bind_param("s", $customer_id); $stmt->execute(); $customer_result = get_stmt_result($stmt); $stmt->close(); if (empty($customer_result)) send_json_error('Customer not found.'); $response_data['customer_name'] = $customer_result[0]['name']; // Fetch history $stmt = $conn->prepare(" SELECT date, description, points, CASE WHEN description = 'Reward Redeemed' THEN 1 ELSE 0 END as rewards_redeemed FROM point_history WHERE customer_id = ? AND date BETWEEN ? AND ? ORDER BY date DESC "); $stmt->bind_param("sss", $customer_id, $start_date, $end_date); $stmt->execute(); $records = get_stmt_result($stmt); $stmt->close(); foreach ($records as &$record) { preg_match('/by (.*)$/', $record['description'], $matches); $record['admin_name'] = isset($matches[1]) ? trim($matches[1]) : 'N/A'; } unset($record); $response_data['records'] = $records; break; case 'yearly_points': $year = date('Y', strtotime($start_date_str)); $response_data['year'] = $year; $stmt = $conn->prepare(" SELECT c.id as customer_id, c.name as customer_name, MONTHNAME(ph.date) as month_name, MONTH(ph.date) as month_number, SUM(CASE WHEN ph.points > 0 THEN ph.points ELSE 0 END) as points_earned, SUM(CASE WHEN ph.description = 'Reward Redeemed' THEN 1 ELSE 0 END) as rewards_redeemed FROM point_history ph JOIN customers c ON ph.customer_id = c.id WHERE YEAR(ph.date) = ? GROUP BY c.id, c.name, MONTH(ph.date), MONTHNAME(ph.date) ORDER BY c.name, MONTH(ph.date) "); $stmt->bind_param("i", $year); $stmt->execute(); $monthly_records = get_stmt_result($stmt); $stmt->close(); $customers_data = []; foreach ($monthly_records as $record) { $customerId = $record['customer_id']; if (!isset($customers_data[$customerId])) { $customers_data[$customerId] = [ 'customer_id' => $customerId, 'customer_name' => $record['customer_name'], 'monthly_data' => [], 'yearly_total_points' => 0, 'yearly_total_rewards' => 0 ]; } $customers_data[$customerId]['monthly_data'][] = [ 'month_number' => $record['month_number'], 'month' => $record['month_name'], 'points_earned' => (int)$record['points_earned'], 'rewards_redeemed' => (int)$record['rewards_redeemed'] ]; $customers_data[$customerId]['yearly_total_points'] += (int)$record['points_earned']; $customers_data[$customerId]['yearly_total_rewards'] += (int)$record['rewards_redeemed']; } $response_data['customers'] = array_values($customers_data); break; case 'monthly_points': $month_name = date('F Y', strtotime($start_date_str)); $response_data['month'] = $month_name; $start_date = date('Y-m-d 00:00:00', strtotime($start_date_str)); $end_date = date('Y-m-d 23:59:59', strtotime($end_date_str)); $stmt = $conn->prepare(" SELECT c.id as customer_id, c.name as customer_name, ph.date, ph.description, ph.points, CASE WHEN ph.description = 'Reward Redeemed' THEN 1 ELSE 0 END as rewards_redeemed FROM point_history ph JOIN customers c ON ph.customer_id = c.id WHERE ph.date BETWEEN ? AND ? ORDER BY c.name, ph.date "); $stmt->bind_param("ss", $start_date, $end_date); $stmt->execute(); $records = get_stmt_result($stmt); $stmt->close(); $customers_transactions = []; foreach ($records as $record) { $customerId = $record['customer_id']; if (!isset($customers_transactions[$customerId])) { $customers_transactions[$customerId] = [ 'customer_name' => $record['customer_name'], 'transactions' => [] ]; } preg_match('/by (.*)$/', $record['description'], $matches); $admin_name = isset($matches[1]) ? trim($matches[1]) : 'N/A'; $customers_transactions[$customerId]['transactions'][] = [ 'date' => $record['date'], 'description' => $record['description'], 'points' => $record['points'], 'admin_name' => $admin_name, 'rewards_redeemed' => $record['rewards_redeemed'] ]; } $response_data['customers'] = array_values($customers_transactions); break; default: send_json_error('Invalid report type specified.'); } send_json_success($response_data); } catch (Exception $e) { send_json_error('Failed to generate report: ' . $e->getMessage(), 500); } break; case 'get_chart_data': $monthly_data = []; for ($i = 5; $i >= 0; $i--) { $date = new DateTime("first day of -$i months"); $month_key = $date->format('Y-m'); $monthly_data[$month_key] = ['totalPoints' => 0, 'rewardsClaimed' => 0]; } $five_months_ago = new DateTime("first day of -5 months"); $start_date = $five_months_ago->format('Y-m-d 00:00:00'); $stmt = $conn->prepare(" SELECT DATE_FORMAT(date, '%Y-%m') as month, SUM(CASE WHEN points > 0 THEN points ELSE 0 END) as total_points, SUM(CASE WHEN description = 'Reward Redeemed' THEN 1 ELSE 0 END) as rewards_claimed_count FROM point_history WHERE date >= ? GROUP BY month "); $stmt->bind_param("s", $start_date); $stmt->execute(); $results = get_stmt_result($stmt); $stmt->close(); foreach ($results as $row) { if (isset($monthly_data[$row['month']])) { $monthly_data[$row['month']]['totalPoints'] = (int)$row['total_points']; $monthly_data[$row['month']]['rewardsClaimed'] = (int)$row['rewards_claimed_count']; } } $chart_data = []; foreach($monthly_data as $month => $data) { $date_obj = DateTime::createFromFormat('Y-m', $month); $chart_data[] = [ 'month' => $date_obj->format('M'), 'totalPoints' => $data['totalPoints'], 'rewardsClaimed' => $data['rewardsClaimed'] ]; } send_json_success($chart_data); break; case 'check_session': if (isset($_SESSION['user_id']) && isset($_SESSION['user_type'])) { $user_id = $_SESSION['user_id']; $user_type = $_SESSION['user_type']; $user = null; if ($user_type === 'customer') { $stmt = $conn->prepare("SELECT id, name, phone, password, qrCodeValue, points, rewards, joinedDate FROM customers WHERE id = ?"); $stmt->bind_param("s", $user_id); $stmt->execute(); $result_array = get_stmt_result($stmt); if (!empty($result_array)) { $user = $result_array[0]; } $stmt->close(); } elseif ($user_type === 'admin') { $stmt = $conn->prepare("SELECT id, name, username, password FROM admins WHERE id = ?"); $stmt->bind_param("s", $user_id); $stmt->execute(); $result_array = get_stmt_result($stmt); if (!empty($result_array)) { $user = $result_array[0]; } $stmt->close(); } if ($user) { send_json_success(['userType' => $user_type, 'user' => $user]); } else { send_json_error('Session invalid, user not found.', 401); } } else { send_json_error('No active session.', 401); } break; case 'login': $type = $_POST['type'] ?? ''; $user = null; if ($type === 'customer') { $phone = $_POST['customer_phone'] ?? ''; $pass = $_POST['customer_password'] ?? ''; $stmt = $conn->prepare("SELECT id, name, phone, password, qrCodeValue, points, rewards, joinedDate FROM customers WHERE phone = ? AND password = ?"); if (!$stmt) send_json_error('Database error preparing customer login.', 500); $stmt->bind_param("ss", $phone, $pass); $stmt->execute(); $result_array = get_stmt_result($stmt); if (!empty($result_array)) { $user = $result_array[0]; } $stmt->close(); } elseif ($type === 'admin') { $user_in = $_POST['admin_username'] ?? ''; $pass_in = $_POST['admin_password'] ?? ''; $stmt = $conn->prepare("SELECT id, name, username, password FROM admins WHERE username = ? AND password = ?"); if (!$stmt) send_json_error('Database error preparing admin login.', 500); $stmt->bind_param("ss", $user_in, $pass_in); $stmt->execute(); $result_array = get_stmt_result($stmt); if (!empty($result_array)) { $user = $result_array[0]; } $stmt->close(); } else { send_json_error('Invalid login type.'); } if ($user) { $_SESSION['user_id'] = $user['id']; $_SESSION['user_type'] = $type; $_SESSION['user_name'] = $user['name']; send_json_success(['userType' => $type, 'user' => $user]); } else { send_json_error('Invalid credentials.', 401); } break; case 'logout': session_unset(); session_destroy(); send_json_success(['message' => 'Logged out successfully.']); break; case 'get_customers': $customers_result = $conn->query("SELECT id, name FROM customers ORDER BY name ASC"); $customers_array = []; while ($row = $customers_result->fetch_assoc()) { $customers_array[] = $row; } send_json_success($customers_array); break; case 'getData': $response_data = []; // Fetch Settings $settings_result = $conn->query("SELECT * FROM business_settings LIMIT 1"); $response_data['settings'] = $settings_result->fetch_assoc(); // Fetch Customers and calculate VIP status $customers_query = " SELECT c.*, (SELECT COUNT(*) FROM point_history ph WHERE ph.customer_id = c.id AND ph.description = 'Reward Redeemed') as redeemed_rewards_count FROM customers c "; $customers_result = $conn->query($customers_query); $customers_array = []; while ($row = $customers_result->fetch_assoc()) { $row['isVIP'] = ($row['redeemed_rewards_count'] >= 5); $customers_array[] = $row; } $response_data['customers'] = $customers_array; // Fetch Admins $admins_result = $conn->query("SELECT id, name, username, password FROM admins"); $admins_array = []; while($row = $admins_result->fetch_assoc()) { $admins_array[] = $row; } $response_data['admins'] = $admins_array; // Calculate Stats $stats = [ 'totalCustomers' => count($customers_array), 'pointsThisMonth' => 0, 'totalPoints' => 0, 'rewardsClaimed' => 0 ]; $current_month_start = date('Y-m-01 00:00:00'); $stmt_month = $conn->prepare("SELECT SUM(points) as total FROM point_history WHERE points > 0 AND date >= ?"); $stmt_month->bind_param("s", $current_month_start); $stmt_month->execute(); $month_result = get_stmt_result($stmt_month); if (!empty($month_result)) { $stats['pointsThisMonth'] = (int)($month_result[0]['total'] ?? 0); } $stmt_month->close(); $stmt_total = $conn->query("SELECT SUM(points) as total FROM point_history WHERE points > 0"); $total_result = $stmt_total->fetch_assoc(); $stats['totalPoints'] = (int)($total_result['total'] ?? 0); $stmt_claimed = $conn->query("SELECT COUNT(*) as total FROM point_history WHERE description = 'Reward Redeemed'"); $claimed_result = $stmt_claimed->fetch_assoc(); $stats['rewardsClaimed'] = (int)($claimed_result['total'] ?? 0); $response_data['stats'] = $stats; send_json_success($response_data); break; case 'get_customer_history': $customer_id = $_POST['customer_id'] ?? null; if (!$customer_id) { send_json_error('Customer ID not provided.'); } $stmt = $conn->prepare("SELECT * FROM point_history WHERE customer_id = ? ORDER BY date DESC"); $stmt->bind_param("s", $customer_id); $stmt->execute(); $history = get_stmt_result($stmt); $stmt->close(); send_json_success($history); break; case 'add_point_from_scan': $qrCodeValue = $_POST['qrCodeValue'] ?? null; if (!$qrCodeValue) send_json_error('QR Code value not provided.'); $stmt = $conn->prepare("SELECT id FROM customers WHERE qrCodeValue = ?"); $stmt->bind_param("s", $qrCodeValue); $stmt->execute(); $result_array = get_stmt_result($stmt); if (empty($result_array)) { $stmt->close(); send_json_error('No customer found for this QR code.'); } $customer_id = $result_array[0]['id']; $stmt->close(); $_POST['customerId'] = $customer_id; $_POST['method'] = 'QR Scan'; // The break is intentionally omitted to fall through to add_point case 'add_point': case 'remove_point': $customerId = $_POST['customerId'] ?? null; if (!$customerId) send_json_error('Customer ID not provided.'); $conn->begin_transaction(); try { $stmt = $conn->prepare("SELECT name, points, rewards FROM customers WHERE id = ? FOR UPDATE"); $stmt->bind_param("s", $customerId); $stmt->execute(); $result_array = get_stmt_result($stmt); if (empty($result_array)) { throw new Exception('Customer not found.'); } $customer = $result_array[0]; $stmt->close(); $name = $customer['name']; $points = $customer['points']; $rewards = $customer['rewards']; $new_rewards_earned = 0; $points_change = 0; $desc = ''; $admin_name = $_SESSION['user_name'] ?? 'Admin'; if ($action === 'add_point' || $action === 'add_point_from_scan') { $points_change = 1; $desc = "Point added by {$admin_name}"; $points++; $new_rewards_earned = floor($points / 10); $points = $points % 10; $rewards += $new_rewards_earned; } else { // remove_point if ($points <= 0) { throw new Exception("{$name} has no points to remove."); } $points_change = -1; $desc = "Point removed by {$admin_name}"; $points--; } $stmt = $conn->prepare("UPDATE customers SET points = ?, rewards = ? WHERE id = ?"); $stmt->bind_param("iis", $points, $rewards, $customerId); $stmt->execute(); $stmt->close(); $history_id = 'h_' . time() . rand(100,999); $date = date("Y-m-d H:i:s"); $stmt = $conn->prepare("INSERT INTO point_history (id, customer_id, date, description, points) VALUES (?, ?, ?, ?, ?)"); $stmt->bind_param("ssssi", $history_id, $customerId, $date, $desc, $points_change); $stmt->execute(); $stmt->close(); $conn->commit(); $message = $action === 'remove_point' ? "Removed 1 point from {$name}." : "Added 1 point to {$name}."; if (($action === 'add_point' || $action === 'add_point_from_scan') && $new_rewards_earned > 0) { $message .= " They earned a reward!"; } send_json_success(['message' => $message]); } catch (Exception $e) { $conn->rollback(); send_json_error($e->getMessage(), 500); } break; case 'redeem_reward': $customerId = $_POST['customerId'] ?? null; if (!$customerId) send_json_error('Customer ID not provided.'); $conn->begin_transaction(); try { $stmt = $conn->prepare("SELECT name, rewards FROM customers WHERE id = ? FOR UPDATE"); $stmt->bind_param("s", $customerId); $stmt->execute(); $result_array = get_stmt_result($stmt); if (empty($result_array)) { throw new Exception('Customer not found.'); } $customer = $result_array[0]; $stmt->close(); if ($customer['rewards'] < 1) { throw new Exception("{$customer['name']} has no rewards to redeem."); } $newRewards = $customer['rewards'] - 1; $stmt = $conn->prepare("UPDATE customers SET rewards = ? WHERE id = ?"); $stmt->bind_param("is", $newRewards, $customerId); $stmt->execute(); $stmt->close(); $history_id = 'h_' . time() . rand(100,999); $date = date("Y-m-d H:i:s"); $admin_name = $_SESSION['user_name'] ?? 'Admin'; $desc = "Reward Redeemed"; $points_change = -10; $stmt = $conn->prepare("INSERT INTO point_history (id, customer_id, date, description, points) VALUES (?, ?, ?, ?, ?)"); $stmt->bind_param("ssssi", $history_id, $customerId, $date, $desc, $points_change); $stmt->execute(); $stmt->close(); $conn->commit(); send_json_success(['message' => "Reward redeemed for {$customer['name']}."]); } catch (Exception $e) { $conn->rollback(); send_json_error($e->getMessage(), 500); } break; case 'add_customer': $name = $_POST['name'] ?? null; $phone = $_POST['phone'] ?? null; $password = $_POST['password'] ?? null; if (!$name || !$phone || !$password) { send_json_error('All fields are required.'); } $stmt = $conn->prepare("SELECT id FROM customers WHERE phone = ?"); $stmt->bind_param("s", $phone); $stmt->execute(); $stmt->store_result(); if ($stmt->num_rows > 0) { $stmt->close(); send_json_error('A customer with this phone number already exists.'); } $stmt->close(); $id = 'cust' . time(); $qrCodeValue = $id; $points = 0; $rewards = 0; $joinedDate = date("Y-m-d H:i:s"); $stmt = $conn->prepare("INSERT INTO customers (id, name, phone, password, qrCodeValue, points, rewards, joinedDate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->bind_param("sssssiis", $id, $name, $phone, $password, $qrCodeValue, $points, $rewards, $joinedDate); if ($stmt->execute()) { $new_customer = [ 'id' => $id, 'name' => $name, 'phone' => $phone, 'password' => $password, 'qrCodeValue' => $qrCodeValue, 'points' => $points, 'rewards' => $rewards, 'joinedDate' => $joinedDate, 'history' => [] ]; send_json_success(['customer' => $new_customer, 'message' => "Customer {$name} added successfully."]); } else { send_json_error('Failed to create customer.', 500); } $stmt->close(); break; case 'reset_points': $customerId = $_POST['customerId'] ?? null; if (!$customerId) send_json_error('Customer ID not provided.'); $conn->begin_transaction(); try { $stmt = $conn->prepare("SELECT points FROM customers WHERE id = ?"); $stmt->bind_param("s", $customerId); $stmt->execute(); $result_array = get_stmt_result($stmt); $stmt->close(); if (empty($result_array)) { throw new Exception("Customer not found."); } $points_lost = $result_array[0]['points']; if ($points_lost > 0) { $stmt = $conn->prepare("UPDATE customers SET points = 0 WHERE id = ?"); $stmt->bind_param("s", $customerId); $stmt->execute(); $stmt->close(); $history_id = 'h_' . time() . rand(100,999); $date = date("Y-m-d H:i:s"); $admin_name = $_SESSION['user_name'] ?? 'Admin'; $desc = "Points reset by {$admin_name}"; $points_change = -$points_lost; $stmt_history = $conn->prepare("INSERT INTO point_history (id, customer_id, date, description, points) VALUES (?, ?, ?, ?, ?)"); $stmt_history->bind_param("ssssi", $history_id, $customerId, $date, $desc, $points_change); $stmt_history->execute(); $stmt_history->close(); } $conn->commit(); send_json_success(['message' => 'Points reset successfully.']); } catch (Exception $e) { $conn->rollback(); send_json_error($e->getMessage(), 500); } break; case 'reset_password': $customerId = $_POST['customerId'] ?? null; if (!$customerId) send_json_error('Customer ID not provided.'); $new_password = substr(md5(rand()), 0, 8); $stmt = $conn->prepare("UPDATE customers SET password = ? WHERE id = ?"); $stmt->bind_param("ss", $new_password, $customerId); if ($stmt->execute()) { send_json_success(['newPassword' => $new_password, 'message' => 'Password has been reset.']); } else { send_json_error('Failed to reset password.', 500); } $stmt->close(); break; case 'update_customer_details': if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'customer') { send_json_error('Unauthorized', 403); } $customerId = $_SESSION['user_id']; $update_type = $_POST['update_type'] ?? ''; if ($update_type === 'phone') { $new_phone = $_POST['new_phone'] ?? null; if (!$new_phone) send_json_error('Phone number not provided.'); $stmt = $conn->prepare("UPDATE customers SET phone = ? WHERE id = ?"); $stmt->bind_param("ss", $new_phone, $customerId); if ($stmt->execute()) { send_json_success(['message' => 'Phone number updated successfully.']); } else { send_json_error('Failed to update phone number.', 500); } $stmt->close(); } elseif ($update_type === 'password') { $new_password = $_POST['new_password'] ?? null; $confirm_password = $_POST['confirm_password'] ?? null; if (!$new_password || !$confirm_password) send_json_error('Password fields are required.'); if ($new_password !== $confirm_password) send_json_error('Passwords do not match.'); if (strlen($new_password) < 6) send_json_error('Password must be at least 6 characters.'); $stmt = $conn->prepare("UPDATE customers SET password = ? WHERE id = ?"); $stmt->bind_param("ss", $new_password, $customerId); if ($stmt->execute()) { send_json_success(['message' => 'Password updated successfully.']); } else { send_json_error('Failed to update password.', 500); } $stmt->close(); } else { send_json_error('Invalid update type specified.'); } break; case 'delete_customer': $customerId = $_POST['customerId'] ?? null; if (!$customerId) send_json_error('Customer ID not provided.'); $conn->begin_transaction(); try { $stmt = $conn->prepare("DELETE FROM point_history WHERE customer_id = ?"); $stmt->bind_param("s", $customerId); $stmt->execute(); $stmt->close(); $stmt = $conn->prepare("DELETE FROM customers WHERE id = ?"); $stmt->bind_param("s", $customerId); $stmt->execute(); $stmt->close(); $conn->commit(); send_json_success(['message' => 'Customer deleted.']); } catch(Exception $e) { $conn->rollback(); send_json_error('Failed to delete customer.', 500); } break; case 'update_settings': if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') { send_json_error('Unauthorized', 403); } $current_admin_id = $_SESSION['user_id']; $business_name = $_POST['businessName'] ?? null; $admin_username = $_POST['adminUsername'] ?? null; $new_password = $_POST['newPassword'] ?? null; $primary_color = $_POST['primaryColor'] ?? null; if (!$business_name || !$admin_username) { send_json_error('Business Name and Admin Username are required.'); } $conn->begin_transaction(); try { $result = $conn->query("SELECT * FROM business_settings LIMIT 1"); $current_settings = $result->fetch_assoc(); $logo_url_to_save = $current_settings['logoUrl'] ?? ''; $favicon_url_to_save = $current_settings['faviconUrl'] ?? ''; $upload_dir = 'uploads/'; if (!is_dir($upload_dir)) { mkdir($upload_dir, 0755, true); } $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/x-icon', 'image/svg+xml']; if (isset($_FILES['logoFile']) && $_FILES['logoFile']['error'] == 0) { if (!in_array($_FILES['logoFile']['type'], $allowed_types)) send_json_error('Invalid logo file type.'); $file_extension = pathinfo($_FILES['logoFile']['name'], PATHINFO_EXTENSION); $new_filename = 'logo.' . $file_extension; $destination = $upload_dir . $new_filename; if (move_uploaded_file($_FILES['logoFile']['tmp_name'], $destination)) { $logo_url_to_save = $destination . '?v=' . time(); } else { throw new Exception('Failed to move uploaded logo file.'); } } if (isset($_FILES['faviconFile']) && $_FILES['faviconFile']['error'] == 0) { if (!in_array($_FILES['faviconFile']['type'], $allowed_types)) send_json_error('Invalid favicon file type.'); $file_extension = pathinfo($_FILES['faviconFile']['name'], PATHINFO_EXTENSION); $new_filename = 'favicon.' . $file_extension; $destination = $upload_dir . $new_filename; if (move_uploaded_file($_FILES['faviconFile']['tmp_name'], $destination)) { $favicon_url_to_save = $destination . '?v=' . time(); } else { throw new Exception('Failed to move uploaded favicon file.'); } } $stmt = $conn->prepare("UPDATE business_settings SET businessName = ?, logoUrl = ?, faviconUrl = ?, primaryColor = ?"); if (!$stmt) { throw new Exception("Prepare failed: (" . $conn->errno . ") " . $conn->error); } $stmt->bind_param("ssss", $business_name, $logo_url_to_save, $favicon_url_to_save, $primary_color); $stmt->execute(); $stmt->close(); if (!empty($new_password)) { $stmt = $conn->prepare("UPDATE admins SET username = ?, password = ? WHERE id = ?"); $stmt->bind_param("sss", $admin_username, $new_password, $current_admin_id); } else { $stmt = $conn->prepare("UPDATE admins SET username = ? WHERE id = ?"); $stmt->bind_param("ss", $admin_username, $current_admin_id); } $stmt->execute(); $stmt->close(); $conn->commit(); send_json_success(['message' => 'Settings updated successfully.']); } catch (Exception $e) { $conn->rollback(); send_json_error('Failed to update settings: ' . $e->getMessage(), 500); } break; case 'add_admin': $name = $_POST['name'] ?? null; $username = $_POST['username'] ?? null; $password = $_POST['password'] ?? null; if (!$name || !$username || !$password) send_json_error('All fields are required.'); $id = 'admin' . time(); $stmt = $conn->prepare("INSERT INTO admins (id, name, username, password) VALUES (?, ?, ?, ?)"); $stmt->bind_param("ssss", $id, $name, $username, $password); if ($stmt->execute()) { send_json_success(['message' => "Admin {$name} added."]); } else { send_json_error('Failed to add admin.', 500); } break; case 'delete_admin': $adminId = $_POST['adminId'] ?? null; if (!$adminId) send_json_error('Admin ID not provided.'); $stmt = $conn->prepare("DELETE FROM admins WHERE id = ?"); $stmt->bind_param("s", $adminId); if ($stmt->execute()) { send_json_success(['message' => 'Admin deleted.']); } else { send_json_error('Failed to delete admin.', 500); } break; case 'delete_all_customers': $conn->begin_transaction(); try { $conn->query("DELETE FROM point_history"); $conn->query("DELETE FROM customers"); $conn->commit(); send_json_success(['message' => 'All customer data has been deleted.']); } catch (Exception $e) { $conn->rollback(); send_json_error('Failed to delete customer data.', 500); } break; // --- GIFT CARD ACTIONS --- case 'create_gift_card': if ($_SESSION['user_type'] !== 'admin') send_json_error('Unauthorized', 403); $customerId = $_POST['customerId'] ?? null; $amount = (float)($_POST['amount'] ?? 0); $title = $_POST['title'] ?? 'Gift Card'; $description = $_POST['description'] ?? ''; $adminId = $_SESSION['user_id']; if (!$customerId || $amount <= 0 || !$title) send_json_error('Missing required gift card details.'); $stmt = $conn->prepare("INSERT INTO gift_cards (customer_id, amount, title, description, admin_id) VALUES (?, ?, ?, ?, ?)"); $stmt->bind_param("sdsss", $customerId, $amount, $title, $description, $adminId); if ($stmt->execute()) { send_json_success(['message' => 'Gift card successfully assigned.']); } else { send_json_error('Failed to create gift card.', 500); } $stmt->close(); break; case 'get_gift_cards': $customerId = $_POST['customer_id'] ?? $_SESSION['user_id']; $status = $_POST['status'] ?? 'all'; // active, redeemed, all $sql = "SELECT * FROM gift_cards WHERE customer_id = ?"; if ($status !== 'all') $sql .= " AND status = '$status'"; $sql .= " ORDER BY created_at DESC"; $stmt = $conn->prepare($sql); $stmt->bind_param("s", $customerId); $stmt->execute(); $result = get_stmt_result($stmt); $stmt->close(); send_json_success($result); break; case 'redeem_gift_card': if ($_SESSION['user_type'] !== 'admin') send_json_error('Unauthorized', 403); $giftCardId = (int)($_POST['giftCardId'] ?? 0); $redeemedAt = date("Y-m-d H:i:s"); $stmt = $conn->prepare("UPDATE gift_cards SET status = 'redeemed', redeemed_at = ? WHERE id = ? AND status = 'active'"); $stmt->bind_param("si", $redeemedAt, $giftCardId); if ($stmt->execute() && $conn->affected_rows > 0) { send_json_success(['message' => 'Gift card successfully redeemed.']); } else { send_json_error('Failed to redeem gift card or already redeemed.', 400); } $stmt->close(); break; // --- BIOMETRIC ACTIONS --- case 'enable_biometric': if (!isset($_SESSION['user_id'])) send_json_error('Unauthorized', 403); $userId = $_SESSION['user_id']; $userType = $_SESSION['user_type']; $table = ($userType === 'admin') ? 'admins' : 'customers'; $token = bin2hex(random_bytes(32)); $stmt = $conn->prepare("UPDATE $table SET biometric_token = ? WHERE id = ?"); $stmt->bind_param("ss", $token, $userId); if ($stmt->execute()) { send_json_success(['token' => $token]); } else { send_json_error('Failed to enable biometric login.', 500); } $stmt->close(); break; case 'login_biometric': $token = $_POST['token'] ?? ''; $userId = $_POST['userId'] ?? ''; $userType = $_POST['userType'] ?? ''; if (!$token || !$userId || !$userType) send_json_error('Missing biometric login data.'); $table = ($userType === 'admin') ? 'admins' : 'customers'; $stmt = $conn->prepare("SELECT * FROM $table WHERE id = ? AND biometric_token = ?"); $stmt->bind_param("ss", $userId, $token); $stmt->execute(); $res = get_stmt_result($stmt); $stmt->close(); if (!empty($res)) { $user = $res[0]; $_SESSION['user_id'] = $user['id']; $_SESSION['user_type'] = $userType; $_SESSION['user_name'] = $user['name']; unset($user['password'], $user['biometric_token']); send_json_success(['userType' => $userType, 'user' => $user]); } else { send_json_error('Invalid or expired biometric token.', 401); } break; default: send_json_error('Invalid action specified.'); break; } $conn->close(); ?>