イベント物販モード(予約 → 受取時に検品+決済)

  • 調査日: 2026-10-29
  • データ時点: 2026年10月(Square Inventory / Terminal API の公式ドキュメント)
  • 状態: 構想(V1 店舗設計の派生モード)
  • 親: README.md / 関連: design.md / square-terminal.md

イベント会場の物販で、**コンテンツを見ながら注文(予約)**し、受取時に検品してから決済するモード。事前決済は行わない。


1. 位置づけ

店舗モード(design.md) イベントモード(本ドキュメント)
注文 その場でカート→決済 予約(在庫引当)→ 後で受取
決済 受取時に Terminal 同じ(受取時に Terminal)
検品 決済前 決済前(受取時)
在庫 Square の実在庫 実在庫+自前の引当(hold)
差分 — 予約 TTL、受取枠、残数表示、交換
  • 共通:カタログ、カート、注文、スタッフ確認、Terminal 決済、Square 連携
  • 差分:予約・引当・受取・検品・残数表示

2. フロー(正常系)

1. 客: コンテンツを見ながらアプリで商品を選ぶ(サイズ・数量)
2. 客: 受取枠(開演前/休憩/終演後 など)を選んで予約
3. BE: 在庫を引当(hold, TTL)→ 予約コード(QR/6桁)を発行
4. 客: 受取窓口へ。予約コードを提示
5. 店員: 予約を検索 → 商品・サイズ・数量を検品
6. 差異があれば修正(交換・追加・取消)
7. 店員: Terminal で決済
8. BE: 引当を消費 → 在庫を確定 → 受取完了
9. 客: 完了通知(ポーリング)

例外

事象 挙動
予約 TTL 切れ 引当を解放(EXPIRED)。在庫に戻す
ノーショー 同上。受取枠終了+猶予で解放
検品でサイズ違い 旧SKUの引当を解放し、新SKUを引当(在庫があれば)。なければ交換不可
検品で追加 追加分を引当。合計を再計算
予約後に売り切れ 受取時に代替・返金・取消を提示
端末オフライン 決済を保留し、復旧後に再試行(予約は保持)

3. 在庫の「キープ」設計(要)

Square の Inventory API は実在庫のみで、予約(引当)は持てない。したがって引当は自前DBで管理する。

available = on_hand - reserved - safety_stock
要素 持ち主 更新
on_hand Square Inventory inventory.count.updated webhook + 定期同期
reserved 自前DB 予約の作成・期限切れ・取消・決済で増減
safety_stock 自前DB 店舗/イベント設定(例: 0〜数点)
available 計算値 カタログ表示・予約可否の判定
  • 予約は条件付き書き込みで取得(available >= 要求数 のときのみ成功)→ オーバーセル防止
  • 予約に TTL を設定(例: 受取枠の終了+猶予、最大2時間)
  • 決済完了時に引当を消費し、Square 側の在庫減算と突合
  • 在庫追跡が OFF の商品は、自前で on_hand を減算する運用にする
  • 定期的に on_hand と reserved を突合し、ズレをアラート

「キープできるか」は設計次第で、Square に予約機能はないため自前 hold が必須。ここがイベントモードの核心。


4. 受取枠(ピックアップスロット)

  • 予約時に「開演前/休憩/終演後」などの枠を選ばせる
  • 窓口の混雑を平準化し、スタッフ配置を決めやすくする
  • 枠ごとの予約数・検品待ち数をスタッフ画面に表示
  • 枠を過ぎたら TTL で解放(ただし会場運用に合わせて猶予を持たせる)

5. 「あとわずか」表示

  • 正確な残数をそのまま出さず、バケットで表示する
    • available > threshold → 通常表示
    • 1 <= available <= threshold → 「残りわずか」
    • available = 0 → 「売り切れ」
  • 閾値は商品/イベント単位(例: 5)
  • 予約が入るたびに available が減るので、表示は参照時点の値
  • 正確な残数を出すかは店舗判断(競合への情報・心理効果・問い合わせ対応のバランス)

6. 状態遷移

RESERVED → READY(受取枠内) → INSPECTING → PAID → PICKED_UP
     │            │                 │
     │            │                 └→ ADJUSTED(交換・追加後)
     │            └→ EXPIRED / CANCELED
     └→ EXPIRED / CANCELED
状態 意味
RESERVED 引当済み。受取待ち
READY 受取枠内。窓口で検品可能
INSPECTING 店員が検品中(ロック)
PAID 決済完了
PICKED_UP 受け渡し完了
EXPIRED / CANCELED 引当解放
  • 検品中のロックは予約単位(同じ予約を二重に処理しない)
  • 決済は既存の Terminal フローを流用(order_id + show_itemized_cart)

7. データモデル(追加分)

PK SK 主な属性
EVENT#{eventId} META 会場、期間、locationId、受取枠
EVENT#{eventId} SLOT#{slotId} 開始/終了、上限
EVENT#{eventId} STOCK#{variationId} onHand、reserved、safetyStock、lowStockThreshold
EVENT#{eventId} RES#{reservationId} 客トークン、slotId、状態、TTL、明細
RES#{reservationId} ITEM#{n} variationId、数量、引当数
EVENT#{eventId} CODE#{code} reservationId(TTL)
  • 予約は EVENT# スコープ。店舗モードの SHOP# とは分離
  • 決済時の Square Order は受取時(検品後)に作成(予約時点では作らない)
    • 予約時点で作ると、検品での交換・追加・取消で古い注文が残るため
  • 監査ログ:検品・交換・取消・値引きの操作を記録

8. API(追加・差分)

メソッド パス 認証 用途
GET /api/events/{eventId}/catalog なし 商品+available/lowStock
POST /api/reservations なし 引当+予約コード発行
GET /api/reservations/{id} res_token 予約状態(ポーリング)
POST /api/reservations/{id}/cancel res_token 客の取消(引当解放)
POST /api/staff/reservations/lookup JWT 予約コードで検索
POST /api/staff/reservations/{id}/inspect JWT 検品開始(ロック)
POST /api/staff/reservations/{id}/adjust JWT 交換・追加・取消
POST /api/staff/reservations/{id}/checkout JWT Terminal 決済
POST /webhooks/square 署名 在庫・決済の更新

POST /api/reservations

{
  "eventId": "ev-01",
  "slotId": "slot-2",
  "items": [{ "variationId": "VAR_ID", "quantity": 1 }],
  "clientToken": "..."
}
{
  "reservationId": "res_...",
  "code": "731904",
  "expiresAt": "2026-10-29T20:30:00Z",
  "total": 3500,
  "resToken": "eyJ..."
}
  • 引当は条件付き更新。available 不足なら 409 SOLD_OUT
  • 合計はあくまで概算。確定額は受取時の Square Order total_money

9. 受取時の検品

  • 検品項目:商品、サイズ(バリエーション)、数量、状態
  • 差異の扱い
    • 交換:旧SKUの引当を解放 → 新SKUを引当(在庫があれば)
    • 追加:追加分を引当
    • 取消:引当を解放
  • 検品後に Square Order を作成 → Terminal 決済
  • 検品・交換の操作は監査ログに残す(スタッフ・時刻・差分)
  • 検品ロックは予約単位。二重処理を防ぐ

10. 運用

項目 方針
受取窓口 受取枠で平準化。スタッフ画面に枠ごとの待ち数を表示
ノーショー TTL で解放。人気商品は早めに戻る
スタッフ 短期雇用前提。検品・交換・決済を1画面で完結
通信 会場の混雑を前提。Terminal はオンライン必須。予約は事前に済ませられる
返金 Refunds API / Square Dashboard(現金は店舗)
転売対策 数量制限、会員コード、予約枠の制限
ライセンス アーティスト/チームの許諾・販売権を事前確認

11. 差別化

  • コンテンツ文脈:アーティスト/チームのアプリに埋め込み、見ながら注文
  • 限定商品・サイズ:イベント限定・数量限定
  • 「あとわずか」:意思決定を促し、機会損失を回復
  • 行列削減:ピークを分散
  • 購買データ:何がいつ売れたか(次回の仕入れ・限定商品に活用)

12. リスク

リスク 対策
Square と自前 hold の在庫ズレ 定期突合、inventory.count.updated、アラート
ノーショー TTL・受取枠・数量制限
会場の通信不良 事前予約、Terminal のオフライン非対応を運用でカバー
スマホ禁止の公演 事前予約・物販列のQR・紙の整理券で代替
転売 数量制限、会員コード、当日受取のみ
スタッフ教育 1画面完結、検品チェックリスト
返金・交換 ルールを事前に明文化

13. 未確定

  1. 予約 TTL(受取枠+猶予の長さ)
  2. 受取枠を設けるか(開演前/休憩/終演後)
  3. 残数の表示粒度(「わずか」のみ/正確な数)
  4. 交換・追加のルール(同一商品のみ/サイズ交換可)
  5. 1人あたりの数量制限
  6. イベントを Square の Location として切るか、店舗 Location のまま自前で在庫を持つか

14. 店舗モードとの関係

  • カタログ・カート・注文・スタッフ確認・Terminal 決済は design.md の共通部分を流用
  • 追加するのは「予約・引当・受取枠・検品・残数表示」
  • 店舗モードの V1 設計は変更しない(別モードとして実装)