PMS 與通路串接 HUIDUN 房量中台
這份規格給 PMS 廠商與技術窗口。PMS 端只需要實作四支 OpenTravel 2003/05 標準訊息;下單、預留、防超賣、對帳由中台處理。範例都是沙盒的真實請求與回應。
1. 總覽
HUIDUN 有兩種串接模式。絕大多數 PMS 使用模式 A,可以自助完成;模式 B 適用於只能提供即時查詢、沒有推送能力的系統,逐案開通。
| 模式 A:推送與拉單(預設) | 模式 B:即時連接器(逐案) | |
|---|---|---|
| 誰呼叫誰 | PMS 呼叫 HUIDUN | HUIDUN 呼叫 PMS |
| 協定 | OpenTravel 2003/05 XML,HTTP Basic over TLS | JSON,HMAC-SHA256 機器簽章 |
| 房量主人 | PMS(串接後 HUIDUN 端手動改價改量會被擋下) | PMS |
| PMS 要做的事 | 取房型、推房價、推房量、拉訂單四支訊息 | 提供查詢、報價、預留、下單、取消、查單端點 |
沙盒
獨立的測試資料庫,申請後當下開通,30 天後停用。
https://huidun-demo.luma-e61.workers.dev/ota/cm正式環境
通過認證後發給正式帳密;路徑與沙盒相同,只換主機名稱。
https://app.hui-dun.com/ota/cm2. 認證
- 每一個請求都帶
Authorization: Basic base64(帳號:密碼),全程 HTTPS。HUIDUN 只保存密碼的雜湊值,遺失只能重設。 HotelCode(拉單時是hotel_id)是飯店在 HUIDUN 後台「通路串接」選擇您的 PMS 時設定的代碼。飯店沒有選擇您的 PMS,這個代碼對您的帳號就不存在(回 392)。- 房型代碼
InvTypeCode/RoomTypeCode與方案代碼RatePlanCode以取房型回傳的為準,對應表建在 PMS 端。 - 每個帳號每分鐘上限 1,200 次請求,超過回 366;單一請求本文上限 1 MB。
3. 取房型與方案 OTA_HotelProduct
取得這間飯店在 HUIDUN 上的房型與方案組合,每一組「房型 × 方案」一個 HotelProduct。PaymentCollection 為 HotelCollect(到店付款)或 OTACollect(線上預付)。
POST https://huidun-demo.luma-e61.workers.dev/ota/cm/OTA_HotelProduct
Authorization: Basic base64(帳號:密碼)
Content-Type: application/xml
<OTA_HotelProductRQ xmlns="http://www.opentravel.org/OTA/2003/05" Version="1.0">
<HotelProducts>
<HotelProduct HotelCode="SBX-LUVQ9U"/>
</HotelProducts>
</OTA_HotelProductRQ><OTA_HotelProductRS Version="1.0" TimeStamp="2026-10-25T06:01:40Z" xmlns="http://www.opentravel.org/OTA/2003/05">
<Success />
<HotelProducts HotelCode="SBX-LUVQ9U">
<HotelProduct>
<RoomTypes><RoomType RoomTypeCode="STD" RoomTypeName="標準雙人房" MaxOccupancy="2" /></RoomTypes>
<RatePlans><RatePlan RatePlanCode="RO" RatePlanName="純住宿" PaymentCollection="HotelCollect" /></RatePlans>
</HotelProduct>
<HotelProduct>
<RoomTypes><RoomType RoomTypeCode="STD" RoomTypeName="標準雙人房" MaxOccupancy="2" /></RoomTypes>
<RatePlans><RatePlan RatePlanCode="BB" RatePlanName="含早餐" PaymentCollection="OTACollect" /></RatePlans>
</HotelProduct>
<!-- 每一組「房型 × 方案」一個 HotelProduct -->
</HotelProducts>
<TPA_Extensions><Pricing Type="Standard" /></TPA_Extensions>
</OTA_HotelProductRS>4. 推每日房價 OTA_HotelRateAmountNotif
Start/End為入住日(含頭尾),格式YYYY-MM-DD,一段最多 365 天。AmountAfterTax為每間每晚含稅價;不帶NumberOfGuests的那一筆是基本價,帶了就是該人數的價格。CurrencyCode必須與飯店設定的幣別一致(目前為 TWD),不一致回 61。- 同一個請求可以放多個
RateAmountMessage;任一筆有錯,整個請求都不寫入,並在Errors列出每一筆錯誤(最多 20 筆)。
POST https://huidun-demo.luma-e61.workers.dev/ota/cm/OTA_HotelRateAmountNotif
<OTA_HotelRateAmountNotifRQ xmlns="http://www.opentravel.org/OTA/2003/05" Version="1.0">
<RateAmountMessages HotelCode="SBX-LUVQ9U">
<RateAmountMessage>
<StatusApplicationControl InvTypeCode="STD" RatePlanCode="BB"/>
<Rates>
<Rate CurrencyCode="TWD" Start="2026-11-04" End="2026-11-05">
<BaseByGuestAmts>
<BaseByGuestAmt AmountAfterTax="3500"/>
<BaseByGuestAmt NumberOfGuests="1" AmountAfterTax="3200"/>
</BaseByGuestAmts>
</Rate>
</Rates>
</RateAmountMessage>
</RateAmountMessages>
</OTA_HotelRateAmountNotifRQ><OTA_HotelRateAmountNotifRS Version="1.0" TimeStamp="2026-10-25T06:01:40Z" xmlns="http://www.opentravel.org/OTA/2003/05">
<Success />
<UniqueID ID="1792908100105976" Type="506" />
</OTA_HotelRateAmountNotifRS>5. 推房量與限制 OTA_HotelAvailNotif
BookingLimit:這段期間每晚可賣幾間(0 至 9999)。中台成立訂單時從這個數字逐晚扣量。RestrictionStatus:Status="Close"/"Open";不帶Restriction為關房,Arrival為該日不可入住,Departure為該日不可退房。LengthsOfStay:SetMinLOS、SetMaxLOS(以入住日計),SetMinLOS_Through、SetMaxLOS_Through(以住宿期間每一晚計)。- 限制類不帶
RatePlanCode時,套用到該房型當下所有適用的方案。
POST https://huidun-demo.luma-e61.workers.dev/ota/cm/OTA_HotelAvailNotif
<OTA_HotelAvailNotifRQ xmlns="http://www.opentravel.org/OTA/2003/05" Version="1.0">
<AvailStatusMessages HotelCode="SBX-LUVQ9U">
<!-- 房量:BookingLimit=這幾天每晚可賣幾間 -->
<AvailStatusMessage BookingLimit="3">
<StatusApplicationControl Start="2026-11-04" End="2026-11-05" InvTypeCode="STD"/>
</AvailStatusMessage>
<!-- 關房/不可入住/最少住幾晚(不帶 RatePlanCode=套用到該房型所有方案) -->
<AvailStatusMessage>
<StatusApplicationControl Start="2026-12-31" End="2026-12-31" InvTypeCode="DLX" RatePlanCode="RO"/>
<RestrictionStatus Status="Close" Restriction="Arrival"/>
<LengthsOfStay><LengthOfStay MinMaxMessageType="SetMinLOS" Time="2"/></LengthsOfStay>
</AvailStatusMessage>
</AvailStatusMessages>
</OTA_HotelAvailNotifRQ><OTA_HotelAvailNotifRS Version="1.0" TimeStamp="2026-10-25T06:01:40Z" xmlns="http://www.opentravel.org/OTA/2003/05">
<Success />
<UniqueID ID="1792908100106824" Type="505" />
</OTA_HotelAvailNotifRS>6. 拉訂單 OTA_HotelResNotif
依「最後異動時間」區間拉回訂單,或用 id 查單一訂單。建議每 1 至 5 分鐘拉一次,區間首尾互相重疊一點,以訂單編號去重。
| 參數 | 說明 |
|---|---|
hotel_id | 必填,飯店代碼 |
from/to | ISO 8601 時間,依訂單最後異動時間篩選;未帶 id 時必填 |
limit | 10 至 200,預設 10;依異動時間由舊到新排序 |
id | 選填,訂單編號(ResID_Value) |
ResStatus:Book成立(含預留中,見第 7 節)、Modify已變更、Cancel已取消。- 訂單編號在
HotelReservationID ResID_Value,同一筆訂單的編號不會改變;變更與取消都以同一個編號再出現一次。 PrepaidIndicator="true"表示旅客已線上付款,飯店不必再向旅客收房費。
GET https://huidun-demo.luma-e61.workers.dev/ota/cm/OTA_HotelResNotif?hotel_id=SBX-LUVQ9U&from=2026-10-25T06:00:00Z&to=2026-10-25T07:00:00Z&limit=50
Authorization: Basic base64(帳號:密碼)
# 只查單一訂單:
GET .../ota/cm/OTA_HotelResNotif?hotel_id=SBX-LUVQ9U&id=6193082730<OTA_HotelResNotifRS Version="1.0" TimeStamp="2026-10-25T06:01:40Z" xmlns="http://www.opentravel.org/OTA/2017/05">
<HotelReservations>
<HotelReservation CreateDateTime="2026-10-25T06:01:40+00:00" LastModifyDateTime="2026-10-25T06:01:40+00:00" ResStatus="Book">
<RoomStays><RoomStay>
<RoomTypes><RoomType RoomTypeCode="STD" NonSmoking="true"><RoomDescription Name="標準雙人房" /></RoomType></RoomTypes>
<RatePlans><RatePlan RatePlanCode="BB" PrepaidIndicator="true"><RatePlanDescription Name="含早餐" /><MealsIncluded MealPlanCodes="19" /></RatePlan></RatePlans>
<RoomRates><RoomRate RoomTypeCode="STD" RatePlanCode="BB" NumberOfUnits="1"><Rates>
<Rate UnitMultiplier="1" RateTimeUnit="Day" EffectiveDate="2026-11-04" ExpireDate="2026-11-05"><Total AmountAfterTax="3500.00" CurrencyCode="TWD" /></Rate>
<Rate UnitMultiplier="1" RateTimeUnit="Day" EffectiveDate="2026-11-05" ExpireDate="2026-11-06"><Total AmountAfterTax="3500.00" CurrencyCode="TWD" /></Rate>
</Rates></RoomRate></RoomRates>
<BasicPropertyInfo HotelCode="SBX-LUVQ9U" HotelName="沙盒飯店 LUVQ9U" />
</RoomStay></RoomStays>
<ResGuests><ResGuest><Profiles><ProfileInfo><Profile><Customer>
<PersonName><GivenName>旅客</GivenName><Surname>測</Surname></PersonName>
<ContactPerson><PersonName><GivenName>旅客</GivenName><Surname>測</Surname></PersonName><Telephone PhoneNumber="0900000000" /><Email /></ContactPerson>
</Customer></Profile></ProfileInfo></Profiles></ResGuest></ResGuests>
<ResGlobalInfo>
<GuestCounts><GuestCount AgeQualifyingCode="10" Count="2" /></GuestCounts>
<TimeSpan Start="2026-11-04" End="2026-11-06" />
<Total AmountAfterTax="7000.00" CurrencyCode="TWD" />
<HotelReservationIDs><HotelReservationID ResID_Type="14" ResID_Value="6193082730" /></HotelReservationIDs>
</ResGlobalInfo>
</HotelReservation>
</HotelReservations>
</OTA_HotelResNotifRS>7. 預留、確認與防超賣
通路下單一律走三段式,名額在預留當下就扣住。兩個通路同時搶最後一間時,輸的一方在付款之前就收到「已客滿」,不會發生旅客付了錢卻沒有房。
| 階段 | 中台做什麼 | PMS 拉單看到 |
|---|---|---|
| 預留 | 逐晚條件式扣量,任一晚不足整筆不成立;預留預設 10 分鐘、最長 30 分鐘 | Book(付款那幾分鐘您就能先在其他通路扣掉) |
| 確認 | 付款成功後確認,訂單成立並記入飯店服務費帳 | 同一筆,狀態不變 |
| 逾時或取消 | 房量回補 | 看過的那一筆會以 Cancel 再出現;從未被拉到的逾時預留不會出現 |
- 每間飯店可設定保留間數(預設 1 間):PMS 推進來的房量保留這幾間不賣,吸收兩邊同步之間的時間差。
- 串接後房量的主人是 PMS:HUIDUN 後台的手動改價改量會被擋下,請在 PMS 端修改再推送。
- PMS 拉到訂單後再推送房量時,中台會辨識已被拉走的訂單,不會重複扣量。
8. 通路 API(賣方使用;沙盒用來模擬訂單)
賣方通路透過 /api/mp 查房、報價與下單。PMS 不需要實作這一節;沙盒會給您一把測試用的通路金鑰,讓您不必等真實訂單就能看到訂單如何出現在拉單結果裡。
POST /api/mp/quote
X-API-Key: 沙盒通路金鑰
{"channel":"sbx_luvq9u","source":"huidun","merchant":"PROP-SBX-LUVQ9U",
"unit":"STD","plan":"BB","day":"2026-11-04","nights":2,"guests":2}
200 {"ok":true,"quote":{"ok":true,"sellMinor":7000,"currency":"TWD",
"detail":{"roomTypeName":"標準雙人房","ratePlanName":"含早餐","cancelPolicy":"不可取消",
"nightly":[{"date":"2026-11-04","sell":3500},{"date":"2026-11-05","sell":3500}]}}}
POST /api/mp/bookings
{ ...同上, "hold":true, "holdMinutes":10, "idemKey":"order-10086",
"guest":{"name":"王小明","phone":"0900000000"} }
201 {"ok":true,"booking":{"bookingKey":"mpb-2cf63b70-…","status":"held",
"holdUntil":"2026-10-25T06:11:40.112Z","grossMinor":7000,"currency":"TWD","reused":false}}
409 {"ok":false,"error":"quote_failed","problems":["已客滿"]}
POST /api/mp/bookings/{bookingKey}/confirm → 200 {"ok":true,"status":"confirmed","already":false}
POST /api/mp/bookings/{bookingKey}/cancel → 200 {"ok":true,"status":"cancelled"}- 金額單位為最小貨幣單位(TWD 即元)。
idemKey全域唯一:同一個idemKey重送回同一筆(reused:true),不會變成兩筆。- 入住人至少要有姓名,以及電話或 Email 其中一項。
9. 即時連接器與機器簽章(模式 B)
當 PMS 只能提供即時查詢時,由 HUIDUN 呼叫 PMS 的端點。PMS 需提供:查詢可售量、報價、預留、下單、取消、查單,以及宣告能力(是否支援預留、取消、即時確認)。這個模式需要逐案開通,請在申請表選擇「由我方提供即時查詢與下單 API」。
HUIDUN 送出的每一個請求都帶 Authorization: Bearer 權杖 與兩個簽章標頭,PMS 端驗證後才處理:
x-hd-timestamp: 1792908100105 (Unix 毫秒)
x-hd-signature: hex( HMAC-SHA256( 共用密鑰, timestamp + "." + 原始請求本文 ) )
驗證規則:
1. |現在時間 − timestamp| ≤ 5 分鐘,否則拒絕(防重放)
2. 用原始位元組計算,不要先解析再序列化 JSON
3. 以常數時間比較簽章字串// Node.js 驗證範例
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, ts, body, sig) {
if (Math.abs(Date.now() - Number(ts)) > 300000) return false;
const want = createHmac("sha256", secret).update(ts + "." + body).digest("hex");
return want.length === sig.length && timingSafeEqual(Buffer.from(want), Buffer.from(sig));
}共用密鑰在開通時另行交付,不會出現在任何頁面或信件內文。
10. 錯誤碼
解析前錯誤:HTTP 505+JSON
HTTP/1.1 505
Content-Type: application/json
{"error":{"code":"497","message":"Authentication error","timestamp":"2026-10-25T14:01:40+08:00"}}| 代碼 | 意義 | 處理方式 |
|---|---|---|
| 497 | 認證失敗 | 檢查帳密;不要自動重試 |
| 450 | 無法解析、不支援的訊息或本文過大 | 修正請求內容 |
| 366 | 超過每分鐘請求上限 | 退避後重試 |
| 448 | 系統錯誤 | 稍後重試 |
業務錯誤:HTTP 200+Errors
HTTP/1.1 200
<OTA_HotelRateAmountNotifRS Version="1.0" TimeStamp="2026-10-25T06:01:40Z" xmlns="http://www.opentravel.org/OTA/2003/05">
<Errors>
<Error Type="3" ShortText="Invalid room type" Code="402">Room type SUITE is not defined for this hotel.</Error>
</Errors>
<UniqueID ID="1792908100109112" Type="506" />
</OTA_HotelRateAmountNotifRS>| 代碼 | 意義 |
|---|---|
| 392 | 飯店代碼無效,或這間飯店沒有選擇您的 PMS |
| 402 | 房型代碼不存在 |
| 425 | 方案代碼不存在,或不適用於這個房型 |
| 15 | 日期格式錯誤、結束早於開始,或區間超過 365 天 |
| 61 | 幣別與飯店設定不符 |
| 321 | 缺少必填欄位 |
| 448 | 單一請求的區段太多,請分批 |
通路 API(JSON)
| HTTP | error | 意義 |
|---|---|---|
| 401 | unauthorized | 金鑰無效 |
| 403 | scope_denied/channel_forbidden | 金鑰沒有這個權限,或不是這個通路的金鑰 |
| 409 | quote_failed | 已客滿、關房、不符最少住宿晚數等,原因列在 problems |
| 409 | idem_key_conflict | 這個 idemKey 已被別的通路使用 |
| 400 | need_contact/bad_request | 入住人缺電話與 Email,或請求格式錯誤 |
| 404 | booking_not_found | 訂單不存在或不屬於這個通路 |
11. 認證檢核七項
沙盒的檢核表依沙盒資料庫的實際紀錄自動判定,七項全部完成即可申請正式認證。
- 取房型與方案(OTA_HotelProduct)成功
- 推房價(OTA_HotelRateAmountNotif)成功
- 推房量(OTA_HotelAvailNotif)成功
- 通路預留成立一筆訂單
- 預留確認成立
- 取消一筆訂單,或讓預留逾時釋放
- 有訂單之後拉單(OTA_HotelResNotif)成功
檢核表網址:https://huidun-demo.luma-e61.workers.dev/sandbox/pms/progress?ref=您的飯店代碼(只顯示完成與否與時間,不含任何帳密或個資);沙盒一鍵跑通 每次執行後也會在頁面上顯示同一張表。
12. 沙盒限制
- 每次開通一間沙盒飯店:2 個房型(STD、DLX)× 2 個方案(RO 純住宿到店付、BB 含早餐線上預付),預先放好未來 30 天的房價房量,保留間數 1 間。
- 沙盒 30 天後停用;同一個網路一小時最多開通 3 個沙盒。
- 沙盒通路的服務費為 0,不代表正式費率。
- 沙盒是獨立的測試資料庫,與正式環境沒有任何資料往來;請勿放入真實旅客資料。