Skip to main content

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​

  1. Đăng nhập PMS bằng tài khoản Admin.
  2. 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.
  3. Cấp cho tài khoản quyền truy cập khách sạn cần kết nối.
  4. 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.

Kết nối API PMS Skyhotel

Kết nối API PMS Skyhotel

Mỗi yêu cầu gửi ba thông tin sau:

Thông sốNội dung
usernameTên đăng nhập tài khoản API_USER
passwordMật khẩu tài khoản API_USER
hotel_idMã 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ốnghttps://api.skyhotel.vn/booking/get_availability
Tạo đặt phònghttps://api.skyhotel.vn/booking/push_booking

Các endpoint /booking/... được nối trực tiếp trong api.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, thay BASE_URL trong ví dụ.

API này dùng dữ liệu Booking của APP Checkin. Các trường checkin, checkout, rooms, rates, rooms_count củ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
modehour: theo giờ; night: qua đêm; day: theo ngày/nhiều ngày
startAtThời gian nhận phòng, Unix timestamp milliseconds
endAtThời gian trả phòng, Unix timestamp milliseconds
guestsSố 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ới night/day.
  • Chọn mode theo 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àm roomTypeId khi đặ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àm roomId nế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_idMã yêu cầu duy nhất của đơn bên bạn, ví dụ DON-WEB-000001
bookingObject chứa thông tin đặt phòng; không chuyển riêng thành chuỗi JSON
booking.mode/startAt/endAt/guestsCùng ý nghĩa với API kiểm tra phòng trống
booking.roomTypeIdMã 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.roomIdTùy chọn: thay roomTypeId bằng trường này nếu muốn đặt phòng cụ thể
booking.customer.phoneBắt buộc, số điện thoại 8–15 chữ số; cho phép dấu + đầu số
booking.customer.nameHọ tên khách
booking.payMethodlater: thanh toán sau, mặc định; qr: giữ phòng chờ chuyển khoản QR
booking.itemsDị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.couponCodeTùy chọn: mã giảm giá của khách sạn
booking.expectedTotalTù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. confirmed khô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:

  1. 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 :, _, -.
  2. Mất mạng, hết thời gian chờ hoặc nhận HTTP 202: gửi lại đúng request_id và toàn bộ nội dung booking ban đầu.
  3. 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.
  4. 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
  1. Điền tài khoản API_USER và Hotel ID.
  2. Đổi ngày giờ trong stay sang tương lai.
  3. Chạy lần đầu để xem mã hạng phòng, số phòng trống và giá.
  4. Điền roomTypeId, thông tin khách và request_id của đơn.
  5. Bỏ dấu // trước await 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
  1. Điền tài khoản API_USER, Hotel ID và thời gian tương lai.
  2. Chạy get_availability() để lấy roomTypes[].id.
  3. Điền roomTypeId, thông tin khách và request_id trong push_booking().
  4. Đổi $result = get_availability(); thành $result = push_booking(); để tạo đơn.
  5. Kiểm tra order.status theo 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ã HTTPNguyên nhân thường gặpCách xử lý
400Thiếu thông tin, sai thời gian, sai số điện thoạiKiểm tra dữ liệu gửi; thời gian phải là milliseconds
401Sai tài khoản/mật khẩu, tài khoản bị khóa hoặc không có quyền API_USERKiểm tra tài khoản API
403Hotel ID không thuộc tài khoản hoặc khách sạn bị chặnKiểm tra Hotel ID và quyền truy cập khách sạn
409Hế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
422Chưa tính được giá cho thời gian đã chọnKiểm tra bảng giá hoặc đổi thời gian/hình thức ở
429Gọi quá nhiều yêu cầuGiãn thời gian gọi; tránh gửi liên tục
5xxLỗi kết nối hoặc lỗi hệ thốngKhi đặ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.