Kết nối API Booking Skyhotel
API Booking Skyhotel giúp website hoặc ứng dụng của bạn kiểm tra số phòng trống và tạo đặt phòng trên PMS, sử dụng dữ liệu và cách tính giá của APP Checkin.
Hỗ trợ đặt theo giờ, qua đêm và ngày. Mỗi yêu cầu đặt một phòng. Giá do Skyhotel tính theo bảng giá khách sạn.
1. Chuẩn bị tài khoản API và Hotel ID
- Đăng nhập PMS bằng tài khoản Admin.
- Vào Nhân viên > Quản lý Nhân viên > Thêm, tạo tài khoản có nhóm quyền API_USER.
- Cấp cho tài khoản quyền truy cập khách sạn cần kết nối.
- Vào Khách sạn > Danh sách khách sạn > Sửa, sao chép mã Hotel ID.
Nếu chưa thấy quyền API_USER hoặc Hotel ID, vui lòng liên hệ hỗ trợ Skyhotel để kích hoạt.


Mỗi yêu cầu gửi ba thông tin sau:
| Thông số | Nội dung |
|---|---|
username | Tên đăng nhập tài khoản API_USER |
password | Mật khẩu tài khoản API_USER |
hotel_id | Mã Hotel ID sao chép từ PMS, giữ nguyên chuỗi mã hóa |
Gọi API từ máy chủ của bạn, không đưa tài khoản API vào JavaScript công khai trên trình duyệt.
2. Địa chỉ API
Gửi bằng phương thức POST, nội dung JSON, header Content-Type: application/json.
| Chức năng | Địa chỉ |
|---|---|
| Kiểm tra phòng trống | https://api.skyhotel.vn/booking/get_availability |
| Tạo đặt phòng | https://api.skyhotel.vn/booking/push_booking |
Các endpoint
/booking/...được nối trực tiếp trongapi.js, dùng chung core với APP Checkin. Các endpoint/pms/...cũ giữ nguyên. Cần cập nhật core và khởi động lại cả dịch vụ app lẫn API trước khi sử dụng. Nếu Skyhotel cấp địa chỉ khác, thayBASE_URLtrong ví dụ.API này dùng dữ liệu Booking của APP Checkin. Các trường
checkin,checkout,rooms,rates,rooms_countcủa API theo đêm cũ không áp dụng cho API mới.
3. Kiểm tra phòng trống — get_availability
Dữ liệu gửi lên
{
"username": "TEN_TAI_KHOAN_API",
"password": "MAT_KHAU_API",
"hotel_id": "HOTEL_ID_TU_PMS",
"mode": "hour",
"startAt": 1792126800000,
"endAt": 1792134000000,
"guests": 2
}
| Thông số | Nội dung |
|---|---|
mode | hour: theo giờ; night: qua đêm; day: theo ngày/nhiều ngày |
startAt | Thời gian nhận phòng, Unix timestamp milliseconds |
endAt | Thời gian trả phòng, Unix timestamp milliseconds |
guests | Số khách trong một phòng, số nguyên từ 1 đến 20 |
Ví dụ trên là 16/10/2026, từ 12:00 đến 14:00, giờ Việt Nam (UTC+7). Khi sử dụng, đổi sang thời gian tương lai phù hợp.
- Giờ nhận phòng không được ở quá khứ; giờ trả phải sau giờ nhận.
- Mặc định cho đặt trước tối đa 30 ngày, tùy cấu hình khách sạn.
- Thời gian ở tối thiểu 30 phút; tối đa 48 giờ với
hour, hoặc 31 ngày vớinight/day. - Chọn
modetheo hình thức tính giá muốn sử dụng; thời gian gửi lên vẫn phải có đủ ngày, giờ nhận và trả phòng.
Kết quả trả về
Ví dụ rút gọn, ID và giá chỉ để minh họa:
{
"rooms": [
{
"id": 101,
"name": "101",
"typeId": 27,
"typeName": "Standard",
"capacity": 2,
"beds": 1
}
],
"roomTypes": [
{
"id": 27,
"name": "Standard",
"available": 1,
"stayPrice": 200000,
"stayMethod": "Theo giờ"
}
],
"stay": {
"mode": "hour",
"startAt": 1792126800000,
"endAt": 1792134000000,
"guests": 2,
"checkinNow": false
}
}
Cách đọc kết quả:
roomTypes[].id: mã hạng phòng, dùng làmroomTypeIdkhi đặt.roomTypes[].available: số phòng trống trong toàn bộ khoảng thời gian đã chọn.roomTypes[].stayPrice: tổng tiền một phòng cho thời gian và số khách đã chọn, đơn vị VND; chưa cộng dịch vụ hoặc trừ mã giảm giá.rooms[].id: mã phòng cụ thể, dùng làmroomIdnếu muốn chọn đúng phòng đó.available = 0: hạng phòng đã hết chỗ. Hạng phòng ngừng mở bán không xuất hiện.
Nếu không có
stayPrice, hệ thống chưa tính được giá cho lựa chọn đó. Không hiểu trường bị thiếu là giá 0. Phòng trống có thể thay đổi trước khi bạn gửi yêu cầu đặt.
4. Tạo đặt phòng — push_booking
Dữ liệu gửi lên
{
"username": "TEN_TAI_KHOAN_API",
"password": "MAT_KHAU_API",
"hotel_id": "HOTEL_ID_TU_PMS",
"request_id": "DON-WEB-000001",
"booking": {
"mode": "hour",
"startAt": 1792126800000,
"endAt": 1792134000000,
"guests": 2,
"roomTypeId": 27,
"items": [],
"payMethod": "later",
"customer": {
"name": "Nguyễn Văn A",
"phone": "0901234567",
"email": "",
"address": "",
"country": "VN"
}
}
}
| Thông số | Nội dung |
|---|---|
request_id | Mã yêu cầu duy nhất của đơn bên bạn, ví dụ DON-WEB-000001 |
booking | Object chứa thông tin đặt phòng; không chuyển riêng thành chuỗi JSON |
booking.mode/startAt/endAt/guests | Cùng ý nghĩa với API kiểm tra phòng trống |
booking.roomTypeId | Mã hạng phòng lấy từ kết quả phòng trống; server tự chọn một phòng còn trống |
booking.roomId | Tùy chọn: thay roomTypeId bằng trường này nếu muốn đặt phòng cụ thể |
booking.customer.phone | Bắt buộc, số điện thoại 8–15 chữ số; cho phép dấu + đầu số |
booking.customer.name | Họ tên khách |
booking.payMethod | later: thanh toán sau, mặc định; qr: giữ phòng chờ chuyển khoản QR |
booking.items | Dịch vụ, mặc định []; nếu có: [{"id": 10, "qty": 2}], dùng ID dịch vụ hợp lệ do khách sạn cung cấp |
booking.couponCode | Tùy chọn: mã giảm giá của khách sạn |
booking.expectedTotal | Tùy chọn: tổng tiền VND muốn đối chiếu; khác giá server tính thì báo lỗi 409 |
Ví dụ cơ bản dùng items: [], payMethod: "later". Khách sạn phải cho phép thanh toán sau. Muốn dùng qr, khách sạn cần cấu hình tài khoản nhận tiền trong APP Checkin.
Server tự tính tiền phòng, dịch vụ và giảm giá theo cài đặt hiện có. Không gửi giá tự nhập bằng rates.
Kết quả trả về
Ví dụ rút gọn khi đặt phòng thanh toán sau thành công:
{
"result": {
"code_book": 12345,
"id": 501053303
},
"order": {
"id": "ABC123",
"status": "confirmed",
"bookingCode": 12345,
"billId": 501053303,
"booking": true,
"checkinNow": false,
"room": { "id": 101, "name": "101", "typeId": 27 },
"roomTotal": 200000,
"serviceTotal": 0,
"discount": 0,
"total": 200000,
"payLater": true,
"qr": null
}
}
result.code_book: mã đặt phòng trên PMS.result.id: ID nội bộ của đơn trên PMS.order.id: mã thao tác của app, đồng thời là nội dung chuyển khoản khi thanh toán QR.order.total: tổng tiền VND sau khi cộng dịch vụ và trừ giảm giá.
order.status | Ý nghĩa và cách xử lý |
|---|---|
confirmed | Đã xác nhận đặt phòng trước; khách đến sẽ nhận phòng tại quầy |
pending | Đã giữ phòng, đang chờ thanh toán QR; ảnh QR ở order.qr, hạn giữ phòng ở order.expiresAt |
creating, paid, review | Đang xử lý hoặc cần đối chiếu, API trả HTTP 202; chưa thông báo đặt thành công |
expired, cancelled | Đơn hết hạn hoặc đã hủy; không thông báo đặt thành công |
API luôn tạo đặt phòng trước, không tự chuyển sang nhận phòng.
confirmedkhông có nghĩa khách đã nhận phòng hay đã thanh toán nếu chọn thanh toán sau.
Gửi lại khi mất mạng hoặc kiểm tra trạng thái
request_id giúp tránh tạo đơn trùng:
- Tạo mã từ đơn của bạn và lưu trước khi gọi API. Mã dài 1–80 ký tự, gồm chữ, số, dấu
:,_,-. - Mất mạng, hết thời gian chờ hoặc nhận HTTP 202: gửi lại đúng
request_idvà toàn bộ nội dungbookingban đầu. - Server trả cùng đơn và trạng thái hiện tại. Bạn cũng dùng cách này để kiểm tra đơn đang chờ QR.
- Dùng lại mã với nội dung booking khác sẽ nhận lỗi 409. Một đơn mới phải dùng mã mới.
Không tạo request_id mới để thử lại đơn chưa rõ kết quả. Không tính lại giờ nhận/trả hoặc chọn hạng phòng khác khi đang gửi lại một đơn cũ.
5. Ví dụ đầy đủ Node.js sử dụng fetch
Yêu cầu Node.js 20 trở lên, không cần cài thêm thư viện.
Lưu mã sau thành skyhotel-api.mjs:
const BASE_URL = 'https://api.skyhotel.vn/booking';
const account = {
username: 'TEN_TAI_KHOAN_API',
password: 'MAT_KHAU_API',
hotel_id: 'HOTEL_ID_TU_PMS'
};
// Đổi sang thời gian tương lai. +07:00 là múi giờ Việt Nam.
const stay = {
mode: 'day',
startAt: new Date('2026-10-20T14:00:00+07:00').getTime(),
endAt: new Date('2026-10-21T12:00:00+07:00').getTime(),
guests: 2
};
async function callApi(endpoint, payload) {
const response = await fetch(`${BASE_URL}/${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...account, ...payload }),
signal: AbortSignal.timeout(30000)
});
const text = await response.text();
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error(`HTTP ${response.status}: máy chủ không trả JSON`);
}
if (!response.ok || data.error) {
throw new Error(data.error || `HTTP ${response.status}`);
}
return { httpStatus: response.status, data };
}
async function getAvailability() {
const { data } = await callApi('get_availability', stay);
console.table(data.roomTypes.map(type => ({
roomTypeId: type.id,
name: type.name,
available: type.available,
stayPrice: type.stayPrice ?? 'Chưa tính được giá'
})));
return data;
}
async function pushBooking() {
// Thay 27 bằng roomTypes[].id vừa tra được.
// Lưu request_id và payload này cùng đơn bên bạn trước khi gửi.
const payload = {
request_id: 'DON-WEB-000001',
booking: {
...stay,
roomTypeId: 27,
items: [],
payMethod: 'later',
customer: {
name: 'Nguyễn Văn A',
phone: '0901234567',
email: '',
address: '',
country: 'VN'
}
}
};
const { httpStatus, data } = await callApi('push_booking', payload);
console.log(JSON.stringify(data, null, 2));
if (data.order.status === 'confirmed') {
console.log('Đặt phòng thành công. Mã PMS:', data.result.code_book);
} else if (data.order.status === 'pending') {
console.log('Đang giữ phòng chờ thanh toán QR.');
} else if (httpStatus === 202) {
console.log('Đang xử lý/cần đối chiếu. Gửi lại đúng payload để kiểm tra.');
} else {
console.log('Trạng thái đơn:', data.order.status);
}
}
try {
await getAvailability();
// Sau khi điền đúng hạng phòng, bỏ // dòng dưới để tạo đơn.
// await pushBooking();
} catch (error) {
console.error(error.message);
// Nếu lỗi khi đặt: giữ nguyên request_id và booking để gửi lại.
process.exitCode = 1;
}
Cách chạy:
node skyhotel-api.mjs
- Điền tài khoản API_USER và Hotel ID.
- Đổi ngày giờ trong
staysang tương lai. - Chạy lần đầu để xem mã hạng phòng, số phòng trống và giá.
- Điền
roomTypeId, thông tin khách vàrequest_idcủa đơn. - Bỏ dấu
//trướcawait pushBooking();rồi chạy để đặt phòng.
Khi tích hợp thực tế, đọc dữ liệu đơn đã lưu để gửi lại; không lấy lại thời gian hoặc mã yêu cầu mới. Mỗi yêu cầu tạo một phòng.
6. Ví dụ đầy đủ PHP sử dụng cURL
Yêu cầu PHP 7.4 trở lên, PHP 64-bit và bật extension cURL.
Lưu mã sau thành skyhotel-api.php:
<?php
header('Content-Type: application/json; charset=utf-8');
date_default_timezone_set('Asia/Ho_Chi_Minh');
$baseUrl = 'https://api.skyhotel.vn/booking';
$account = [
'username' => 'TEN_TAI_KHOAN_API',
'password' => 'MAT_KHAU_API',
'hotel_id' => 'HOTEL_ID_TU_PMS'
];
// Đổi sang thời gian tương lai. strtotime trả giây, API cần milliseconds.
$stay = [
'mode' => 'day',
'startAt' => strtotime('2026-10-20 14:00:00') * 1000,
'endAt' => strtotime('2026-10-21 12:00:00') * 1000,
'guests' => 2
];
function call_api($endpoint, $payload) {
global $baseUrl, $account;
$body = json_encode(
array_merge($account, $payload),
JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
$ch = curl_init($baseUrl . '/' . $endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => $body,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30
]);
// Giữ kiểm tra chứng chỉ HTTPS mặc định, không tắt xác minh SSL.
$response = curl_exec($ch);
$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new RuntimeException('Lỗi kết nối API: ' . $error);
}
$data = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($data)) {
throw new RuntimeException('Máy chủ trả dữ liệu không hợp lệ.');
}
if ($httpStatus < 200 || $httpStatus >= 300 || isset($data['error'])) {
throw new RuntimeException($data['error'] ?? ('HTTP ' . $httpStatus));
}
return ['httpStatus' => $httpStatus, 'data' => $data];
}
function get_availability() {
global $stay;
return call_api('get_availability', $stay);
}
function push_booking() {
global $stay;
// Thay 27 bằng roomTypes[].id vừa tra được.
// Lưu request_id và payload này cùng đơn bên bạn trước khi gửi.
$payload = [
'request_id' => 'DON-WEB-000001',
'booking' => array_merge($stay, [
'roomTypeId' => 27,
'items' => [],
'payMethod' => 'later',
'customer' => [
'name' => 'Nguyễn Văn A',
'phone' => '0901234567',
'email' => '',
'address' => '',
'country' => 'VN'
]
])
];
return call_api('push_booking', $payload);
}
try {
$result = get_availability();
// Sau khi điền đúng hạng phòng, thay dòng trên bằng dòng sau để đặt:
// $result = push_booking();
http_response_code($result['httpStatus']);
echo json_encode(
$result['data'],
JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
);
} catch (Throwable $error) {
http_response_code(502);
echo json_encode(['error' => $error->getMessage()], JSON_UNESCAPED_UNICODE);
// Nếu lỗi khi đặt: giữ nguyên request_id và booking để gửi lại.
}
Chạy trên máy chủ PHP hoặc dùng:
php skyhotel-api.php
- Điền tài khoản API_USER, Hotel ID và thời gian tương lai.
- Chạy
get_availability()để lấyroomTypes[].id. - Điền
roomTypeId, thông tin khách vàrequest_idtrongpush_booking(). - Đổi
$result = get_availability();thành$result = push_booking();để tạo đơn. - Kiểm tra
order.statustheo bảng ở mục 4. HTTP 202 là đang xử lý; HTTP 200 vẫn cần đọc trạng thái đơn.
Mã PHP minh họa trả lỗi gọi API bằng HTTP 502 của trang ví dụ. Khi tích hợp, xử lý lỗi theo nhu cầu website. Không kết luận đơn thất bại chỉ vì mất kết nối; gửi lại đúng yêu cầu ban đầu để đối chiếu.
7. Các lỗi thường gặp
API trả lỗi dạng:
{
"error": "Nội dung lỗi"
}
| Mã HTTP | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| 400 | Thiếu thông tin, sai thời gian, sai số điện thoại | Kiểm tra dữ liệu gửi; thời gian phải là milliseconds |
| 401 | Sai tài khoản/mật khẩu, tài khoản bị khóa hoặc không có quyền API_USER | Kiểm tra tài khoản API |
| 403 | Hotel ID không thuộc tài khoản hoặc khách sạn bị chặn | Kiểm tra Hotel ID và quyền truy cập khách sạn |
| 409 | Hết phòng, giá thay đổi, không cho trả sau hoặc request_id trùng nhưng dữ liệu khác | Đọc thông báo và kiểm tra lại dữ liệu trước khi tạo yêu cầu mới |
| 422 | Chưa tính được giá cho thời gian đã chọn | Kiểm tra bảng giá hoặc đổi thời gian/hình thức ở |
| 429 | Gọi quá nhiều yêu cầu | Giãn thời gian gọi; tránh gửi liên tục |
| 5xx | Lỗi kết nối hoặc lỗi hệ thống | Khi đặt phòng, giữ nguyên request_id và booking để gửi lại |
Lưu ý cuối: API mới phải được triển khai và kiểm tra trên máy chủ trước khi cấp địa chỉ cho đối tác sử dụng.