Luồng xác minh khách
Hướng dẫn này giải thích cách dùng danh sách khách đang lưu trú trong captive portal: đối chiếu thông tin khách nhập với danh sách, thời điểm phiên Wi-Fi nên kết thúc, tần suất làm mới danh sách, và những trường cần lưu cho bản ghi theo Luật số 5651 của Thổ Nhĩ Kỳ.
Hỏi gì trên trang đăng nhập#
Vì danh sách cung cấp đồng thời thông tin phòng và nhận dạng, cách phổ biến nhất là hỏi số phòng + họ. Chỉ riêng số phòng là không đủ; bất kỳ ai biết số phòng bên cạnh đều có thể đăng nhập. Các lựa chọn khác là số phòng + số giấy tờ tùy thân/hộ chiếu hoặc họ + ngày sinh; hãy giữ cách sau làm phương án dự phòng cho những khách có số giấy tờ tùy thân trống.
Quy tắc đối chiếu#
Chuẩn hóa cả hai phía trước khi so sánh thông tin khách nhập với các giá trị trong danh sách:
- Số phòng: cắt khoảng trắng và chuyển sang chữ hoa.
roomNamelà chuỗi (có thể là"104","A-12"hoặc"Villa 3"); không chuyển sang số và không bỏ các số 0 ở đầu. - Họ: chuyển sang chữ hoa (lưu ý quy tắc tiếng Thổ Nhĩ Kỳ
i → İ) và gộp các khoảng trắng liên tiếp. Giá trị trong danh sách thường được đọc từ giấy tờ tùy thân dưới dạng chữ hoa. - Số giấy tờ tùy thân: bỏ khoảng trắng và chuyển sang chữ hoa. Số định danh công dân Thổ Nhĩ Kỳ có 11 chữ số; số hộ chiếu gồm cả chữ và số.
- Ngày sinh: danh sách trả về dạng
DD.MM.YYYY; hãy chuyển đổi nếu form của bạn thu thập theo định dạng khác.
const norm = (s) => String(s ?? "")
.replace(/\s+/g, " ")
.trim()
.toLocaleUpperCase("tr-TR");
function findGuest(list, room, lastName) {
return list.find((g) =>
norm(g.roomName) === norm(room) && norm(g.lastName) === norm(lastName)
) || null;
}
const guest = findGuest(data.otelde, form.room, form.lastName);
if (!guest) {
// "Số phòng và họ không khớp" — đếm số lần thất bại, giới hạn sau 5 lần
}Giá trị unique của bản ghi khớp chính là danh tính của người dùng hotspot. Khi cùng một khách đăng nhập từ thiết bị thứ hai, hãy dùng lại tài khoản gắn với giá trị này thay vì tạo tài khoản mới, và áp dụng giới hạn số thiết bị trên tài khoản đó.
Thời hạn phiên#
checkout là ngày trả phòng dự kiến và trỏ đến 00:00 (UTC) của ngày đó; giá trị này không có thông tin giờ. Nếu bạn mở phiên đến đúng thời điểm đó, khách sẽ mất quyền truy cập vào sáng ngày rời đi. Hãy tính thời điểm kết thúc phiên bằng cách cộng giờ trả phòng của khách sạn vào ngày này:
const CHECKOUT_TIME = "12:00"; // giờ trả phòng của khách sạn
const day = new Date(guest.checkout * 1000)
.toLocaleDateString("en-CA", { timeZone: "UTC" }); // "2026-09-10"
const sessionEnd = new Date(`${day}T${CHECKOUT_TIME}:00+03:00`);Nếu khách rời đi trước giờ trả phòng, khách sẽ biến mất khỏi danh sách; hãy đóng tài khoản ở lần đồng bộ tiếp theo. Nếu kỳ lưu trú được gia hạn, checkout sẽ thay đổi; hãy so sánh trường này khi đồng bộ và cập nhật thời điểm kết thúc phiên. Với trường hợp trả phòng muộn, an toàn hơn là giữ quyền truy cập chừng nào khách vẫn còn trong danh sách.
Đồng bộ định kỳ#
Danh sách là một ảnh chụp tại thời điểm truy vấn; không có thông báo thay đổi (webhook). Hãy lấy danh sách theo chu kỳ cố định và so sánh với danh sách trước đó theo unique:
- Chu kỳ: 5 phút là đủ với hầu hết khách sạn. Nếu khách cần truy cập mạng ngay khi nhận phòng, bạn có thể giảm xuống 1–2 phút; không đặt dưới 1 phút.
uniquemới: chuẩn bị tài khoản; khách được đối chiếu ở lần đăng nhập đầu tiên trên trang đăng nhập.uniquebiến mất: khách đã trả phòng hoặc đặt phòng đã bị hủy. Kết thúc phiên và đóng tài khoản.- Cùng
unique, khácroomNamehoặccheckout: khách đổi phòng hoặc gia hạn. Cập nhật tài khoản. - Yêu cầu thất bại: khi hết thời gian chờ, gặp
401hoặc5xx, hãy giữ danh sách thành công gần nhất; tuyệt đối không đóng tài khoản dựa trên phản hồi lỗi.
async function sync(prev) {
const res = await fetch(BASE + "/public/json/customer/inhotel", {
method: "POST",
headers: { "ApiKey": process.env.HMS_API_KEY, "HotelCode": HOTEL_CODE }
});
if (!res.ok) return prev; // 401 / 5xx: giữ danh sách cũ
const { success, otelde } = await res.json();
if (success !== 1) return prev;
const next = new Map(otelde.map((g) => [g.unique, g]));
for (const [id] of prev) {
if (!next.has(id)) await closeAccount(id); // đã trả phòng
}
for (const [id, g] of next) {
const old = prev.get(id);
if (!old) await openAccount(g); // khách mới
else if (old.checkout !== g.checkout || old.roomName !== g.roomName) await updateAccount(g);
}
return next;
}Nhiều khách trong một phòng#
Mỗi khách trong phòng là một bản ghi riêng: rezervasyon_id dùng chung, unique khác nhau. Hãy mở tài khoản theo từng khách thay vì theo phòng, để mỗi khách có thiết bị, phiên và bản ghi theo Luật 5651 của riêng mình. Truy vấn số phòng + họ có thể trả về nhiều bản ghi khi các khách có cùng họ (một gia đình); thay vì lấy kết quả khớp đầu tiên, hãy phân biệt bằng tên hoặc ngày sinh, hoặc mở một tài khoản dùng chung cho họ.
Bản ghi theo Luật 5651#
Nghĩa vụ lưu nhật ký truy cập theo Luật 5651 thuộc về hệ thống hotspot; HMS chỉ cung cấp dữ liệu nhận dạng. Khi mở phiên, hãy lưu các trường sau cùng với địa chỉ MAC/IP và dấu thời gian: unique, rezervasyon_id, firstName, lastName, identityNumber, birthDate, roomName, checkin, checkout. Khi khách đã biến mất khỏi danh sách, HMS không còn cung cấp thông tin này nữa; hãy lưu bản ghi ở phía bạn.