本番移行設計(環境分離・Cognito・マルチ店舗)

デモ(mock)で作ったものを、設定変更中心で本番に上げるための設計。


1. 環境

環境 用途 ADAPTER AUTH_MODE DynamoDB Square
dev ローカル・デモ mock pin 使い捨て なし
staging 結合テスト sandbox cognito(テストプール) 別テーブル Sandbox
production 本番 production cognito 別テーブル 本番
  • 同一コード、環境差分は設定のみ(SSM パスを /{env}/shoptools/... に)
  • スタックも環境ごとに分離(CloudFront・Webhook 購読・Cognito も別)
  • 機能フラグ:ADAPTER / AUTH_MODE / DEMO_ENABLED / FEATURE_SCAN / FEATURE_CASH / CHECKOUT_ENABLED
  • 本番は DEMO_ENABLED=false でモック用 API を 404 にする

2. 秘密情報

既定:SSM Parameter Store(SecureString)

パラメータ 内容
/{env}/shoptools/square/access_token Square アクセストークン(本番のみ)
/{env}/shoptools/square/webhook_signature_key Webhook 署名キー
/{env}/shoptools/auth/jwt_secret デモ JWT 署名鍵(本番は未使用)
/{env}/shoptools/staff/pin_hash デモ PIN ハッシュ
/{env}/shoptools/demo/token モック API 用トークン
  • AWS 管理キー(aws/ssm)を使えば KMS 費用も無料
  • Lambda 起動時に取得してメモリキャッシュ。失敗時のみ再取得
  • 本番で OAuth(店舗ごとトークン)に進む段階で Secrets Manager へ移行
    • 理由:ローテーション、監査、店舗数分のシークレット管理

アクセストークンの種類

方式 用途 備考
Personal Access Token(PAT) 自社アカウントのみ 長期。失効は手動
OAuth 他社・複数店舗 有効期限あり。リフレッシュ必要
  • 初期(1店舗・既存アカウント)は PAT
  • 店舗が別アカウントになる、または SaaS 化する段階で OAuth
    • スコープ:DEVICE_CREDENTIAL_MANAGEMENT / MERCHANT_PROFILE_READ / PAYMENTS_READ / PAYMENTS_WRITE

3. Cognito(店員認証)

構成

User Pool(1つ)
 ├ Group: admin
 ├ Group: staff
 └ App Client(PWA 用、SRP)
  • ユーザーは管理者が作成(セルフサインアップ不可)
  • MFA(TOTP)は任意
  • PWA は Cognito SDK(amazon-cognito-identity-js 等)でログインし、ID トークンを API に付与

認可

  • Lambda は JWKS で JWT を検証(鍵はキャッシュ)
  • sub → STAFF#{sub} を引き、shopId / registerId / role / active を取得
  • JWT にレジ情報を詰めない(異動・兼務に強い、トークン肥大を避ける)
  • 端末はサーバー側で解決。クライアント指定を信用しない
  • 退職・異動は DB の active / registerId を更新(トークン失効を待たない)

デモとの差

デモ 本番
ログイン 店舗PIN Cognito
トークン BE 署名 JWT Cognito ID トークン
認可 PIN に紐づく固定レジ DB のスタッフ情報
監査 なし audit_log(誰が・いつ・どの注文)

4. マルチ店舗設計(今は1店舗)

Merchant(Square アカウント)
 └ Shop(Square Location)
    └ Register(Terminal)
  • 全データに merchantId / shopId / registerId
  • 店舗追加=同じ Square アカウントに Location を追加し shopId を発行
  • 別アカウントなら OAuth(TokenStore を店舗ごとに)
  • URL は /s/{shopId}/...。将来は店舗ドメイン
    • CloudFront の別名 + ACM(us-east-1)+ Route 53
  • 完全隔離が必要なら店舗ごとにスタック分割(データモデルは同一)

5. 既存 Square アカウントを使う場合

  1. 既存 Terminal を Square POS(スタンドアロン)からサインアウトし、CreateDeviceCode で本アプリにペアリングする
    • 同時併用は不可。既存 POS 運用との競合を事前に合意する
  2. 既存の Location ID を使用
  3. 既存カタログ・在庫・税設定はそのまま(BE は参照のみ)
  4. 商品バーコードが登録済みか確認(未登録ならスキャン不可。MVP はタイル+検索で回避)
  5. 領収書・返金・在庫は従来どおり Square Dashboard / 端末
  6. 税は order.total_money を正とする(BE で再計算しない)
  7. Square for Retail 等の端末上機能は、Terminal API モードでは使えなくなる点に注意

6. 切替フェーズとロールバック

Phase 内容 完了条件 戻し方
P1 mock デモ 先方が再現できる —
P2 sandbox 結合 契約テスト全通過 ADAPTER=mock
P3 本番パイロット(1レジ・少額) 実機 E2E・返金確認 CHECKOUT_ENABLED=false(新規決済停止)
P4 本番運用 監視・運用定着 同上
P5 店舗追加 shopId 追加のみ(別アカウントは OAuth) 店舗単位で停止
  • 切替は設定変更のみ(コード再デプロイ不要)にしておく
  • ロールバックは「新規決済の受付停止(CHECKOUT_ENABLED=false)」まで。処理中の決済は完了させる。完了済み決済は Square の記録が正(返金は Dashboard)
  • パイロット中は 1 レジ・少額・有人で実施

7. 監視・運用

項目 内容
ログ 構造化 JSON、correlationId(注文IDで追跡)
アラーム webhook 失敗、SENT 滞留、checkout 失敗率、金額不一致、カタログ同期失敗
非同期 SQS + DLQ(webhook 後処理、キュー送出)
ヘルス GET /api/health(DynamoDB / SSM / Square 到達性)
バックアップ PITR、日次エクスポート
ランブック 端末オフライン、端末使用中、放置 checkout、返金、障害時の停止手順

8. 本番化チェックリスト

  • 金額は常に Square の total_money(JPY はゼロ小数)
  • Payment の実額照合、paymentIds を永続保存(checkout は30日で消える)
  • webhook 署名検証(生ボディ)+event_id 冪等
  • 二重押下・同時 claim・レジロックを条件付き書き込みで担保
  • 秘密は SSM、ログに PII / 秘密を出さない
  • PITR・バックアップ・保持期間
  • レート制限(lookup / 注文作成)
  • エラー設計の統一(400/401/403/404/409/422/500)
  • 領収書・レシート運用の合意
  • 実機ペアリング手順の文書化(Sandbox では不可)
  • Cognito のユーザー作成・退職時の無効化手順
  • DEMO_ENABLED=false でモック API が 404 になること
  • 店舗追加手順(Location・shopId・Register・端末ペアリング)
  • 現金を有効化する場合:FEATURE_CASH、CreatePayment(CASH)、ドロワー運用、返金、日締めを確認

9. リスク

リスク 影響 対策
既存 Terminal の再ペアリング 既存 POS が使えなくなる 事前合意、必要なら端末追加
Sandbox でペアリング不可 検証不足 本番パイロットで実機検証
単一アカウントで複数店舗の売上集約 資金移動・収納代行の論点 店舗ごとに OAuth、資金は店舗口座へ
Cognito 設定ミス ログイン不可 staging で検証、管理者作成手順を文書化
OAuth トークン失効 決済不可 リフレッシュ実装、失効アラート
税設定のズレ 金額不一致 total_money を正、差分検知
カタログ同期失敗 商品が出ない 前回キャッシュ保持+アラート
現金のドロワーが自動で開かない 運用負荷・釣銭ミス 手動ドロワー/Square POS 併用。現金管理は別運用