はじめに
「8月1日から3泊、6名」と入力したら、空いているかどうかと合計金額がすぐ出る。予約サイトでは当たり前の機能ですが、裏側では毎回、予約管理システムに問い合わせが飛んでいます。
この記事では、空室と料金を表示する部分で実際にぶつかった落とし穴と、その対処を紹介します。宿泊料金は思っているよりずっと複雑で、素直に作ると必ず金額がずれます。
料金を自分で計算してはいけない
日別料金の足し算は必ずずれる
最初に作ったとき、私は日別の料金を取得して足し算していました。1泊19万円なら3泊で57万円のはずです。ところが実際に予約管理システムが提示する金額は、まったく違う数字でした。
人数追加料金・連泊割引・最低宿泊日数のルールが反映されず、実際の請求額とずれる
「この日程・この人数」の総額をシステム側が計算して返す。ずれようがない
宿泊料金には、人数による追加料金、連泊での割引、季節ごとの料金設定、最低宿泊日数といったルールが幾重にも重なっています。これらは予約管理システムの中に設定されており、外から日別料金だけを見ても再現できません。
そこで「宿泊期間と人数を渡すと総額が返ってくる」見積もりAPIを使い、金額の計算は一切自前でやらない方針に切り替えました。カレンダーに表示する日別の目安価格はあくまで参考値で、請求には一切使いません。
人数の数え方も揃える
この宿では「5歳以下のお子様は添い寝で2名まで無料」という料金ルールがあります。つまり画面上の「大人4名+お子様2名」は、料金計算上は「4名」として扱う必要があります。
厄介なのは、この変換を表示するときと予約を確定するときの両方で行う点です。片方だけ直すと、画面には57万円と出ているのに確定時は60万円になり、「金額が変わりました」というエラーで予約が止まります。
そこで人数変換のロジックを1つの関数にまとめ、どちらの場面からも同じものを呼ぶようにしました。同じ計算を2か所に書かない。当たり前のようで、実際に事故が起きてから気づくことが多い部分です。
「取得できなかった」を「満室」にしない
4つの状態を区別する
空室状況の表示は、単純な「空き」「満室」の2択ではありません。実際には次の4状態を区別しています。
一番大事なのは、最後の「確認できない」を絶対に「満室」と表示しないことです。通信エラーで満室と表示してしまうと、本当は空いているのにお客様が諦めてしまいます。エラーを在庫状況として翻訳しない。これは他の外部連携にも当てはまる原則だと思います。
満室のときは代替を出す
満室と表示するだけでは、お客様はそこで引き返してしまいます。そこで満室を検出したら、前後の日程で空いている期間を自動で探し、「この日程なら空いています」と提案するようにしました。
行き止まりを作らない。予約サイトでは、これだけでコンバージョンが変わってきます。
キャッシュとの付き合い方
60秒だけキャッシュする
同じ日程の検索が連続すると、そのたびにAPIを呼ぶのは無駄ですし、回数制限にも近づきます。そこで検索結果を60秒だけキャッシュしました。
宿泊在庫は秒単位で変動するものではないので、60秒古い情報でも実用上は問題ありません。ただし予約を確定する直前だけは、キャッシュを使わず必ず取り直します。表示は多少古くてもよいが、お金が動く瞬間は最新でなければならない、という線引きです。
キャッシュを消し忘れない
キャッシュで一番怖いのは、在庫が動いたのに古い情報が残り続けることです。実際、決済画面から戻ってきた直後に「満室」と表示され続ける不具合が起きました。仮押さえで在庫が動いたのに、キャッシュが更新されていなかったのです。
キャッシュ削除は「在庫が動いた場所」に書く
キャッシュを消す処理を、呼び出し側にコピーして回ると必ず書き忘れが出ます。仮押さえの作成・解放といった「在庫を動かす処理そのもの」の中に削除を組み込むことで、書き忘れの余地をなくしました。
在庫が1つ減るので即座にキャッシュを破棄
在庫が戻るので即座にキャッシュを破棄
OTAの予約かもしれないので、内容にかかわらず破棄
在庫に影響する変更なら破棄
まとめ
空室と料金の表示で押さえたのは、次の3点です。
- 料金は自分で計算しない — 総額を返すAPIに任せ、日別価格は目安表示にとどめる
- エラーを満室と訳さない — 4つの状態を区別し、確認できないときは正直に伝える
- キャッシュは在庫を動かす処理の中で消す — 呼び出し側に任せず、書き忘れを構造的に防ぐ
予約を確定するときの在庫確保については「ダブルブッキングを防ぐ予約登録フロー」で解説しています。