イベント物販モード(予約 → 受取時に検品+決済)
イベント会場の物販で、**コンテンツを見ながら注文(予約)**し、受取時に検品してから決済するモード。事前決済は行わない。
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. 未確定
- 予約 TTL(受取枠+猶予の長さ)
- 受取枠を設けるか(開演前/休憩/終演後)
- 残数の表示粒度(「わずか」のみ/正確な数)
- 交換・追加のルール(同一商品のみ/サイズ交換可)
- 1人あたりの数量制限
- イベントを Square の Location として切るか、店舗 Location のまま自前で在庫を持つか
14. 店舗モードとの関係
- カタログ・カート・注文・スタッフ確認・Terminal 決済は design.md の共通部分を流用
- 追加するのは「予約・引当・受取枠・検品・残数表示」
- 店舗モードの V1 設計は変更しない(別モードとして実装)