# 店頭購入キーコード登録フロー 設定手順書

店頭で **WEBROOT＋端末保険** を購入したお客様が、OPCON に会員登録してキーコードを登録すると、
マイページで商品情報（インストール方法・保険請求フォーム等）を確認できるようになる機能です。

- 店頭購入は買い切りのため、**登録した商品はマイページから退会（解約）できません**。
- 月額課金は発生しません（`members_deals.res_pay_method = 'store'` は各課金バッチの抽出対象外）。

## お客様の利用の流れ

1. 店頭の案内（QRコード等）から `https://opcon.jp/store_regist/?shop_cd=<店舗のmed_id>` にアクセス
2. 会員登録（メールアドレス＋パスワード＋認証コード、または Google / Apple）。既に会員ならログイン
3. キーコード入力画面で店頭購入のキーコードを入力（ハイフン・大文字小文字・全角は区別しない）
4. 登録完了 → マイページに「WEBROOT」と「端末保険」が表示される

## 初期設定（1回のみ）

### 1. テーブル作成

```
psql -f src/release/store_regist/dbsetting/01_store_keycode_courses_create.sql
```

- `store_keycode_courses`（キーコード種別 → 購入扱いにするコース）を作成
- キーコード照合用の式インデックスを `CREATE INDEX CONCURRENTLY` で作成（トランザクション外で実行）

### 2. 商品設定

`02_store_keycode_setup_template.sql` の `<...>` を置き換えて実行します。

| 設定 | 内容 |
|------|------|
| 店頭専用のキーコード種別 | `licence_types` に新規追加（例: 「WEBROOT（店頭販売）」） |
| WEBROOT コース | 店頭販売する WEBROOT のコース。マイページのインストールURL・マニュアルはこのコースの設定が表示される |
| 端末保険コース | `insurance_claim` に保険請求フォームを設定するとマイページに表示される |
| 種別の対応 | `store_keycode_courses` に（種別, WEBROOTコース, 端末保険コース）を登録 |

> **重要:** 店頭用キーコードは必ず **店頭専用の licence_type** で在庫登録すること。
> オンライン販売と同じ種別にすると、オンライン購入時の自動払い出し（`licenceUpdate()`）で
> 店頭販売済みのキーコードが別のお客様に払い出される恐れがある。

### 3. 店舗の販売価格（任意）

`media_courses` に店舗×コースの `media_course_price` を登録すると `members_deals.deals_price` に記録される（未設定時は 0 円）。

## 運用

- **キーコード在庫登録:** 管理画面の「ライセンスCSV投入」で、店頭専用の licence_type を指定して登録
- **受付の一時停止:** `UPDATE store_keycode_courses SET is_active = false, uts = now() WHERE licence_type_cd = <種別>;`
- **登録状況の確認:**

```sql
SELECT md.mem_deal_no, md.mem_no, c.course_name, md.med_no, md.res_payment_date, l.licence_cd
FROM members_deals md
INNER JOIN courses c USING (course_no)
LEFT JOIN licences l USING (mem_deal_no)
WHERE md.res_pay_method = 'store'
ORDER BY md.mem_deal_no DESC;
```

店舗ごとの登録者は、代理店管理画面の会員一覧（`members_deals.med_no`）にも表示される。

## 仕様メモ

| 項目 | 内容 |
|------|------|
| 照合 | 入力値と `licence_cd` を英数字以外除去・大文字化して比較 |
| 受付条件 | 未割当（`mem_no`/`mem_deal_no` NULL、`licence_use_flg = false`）、未解約（`licence_withdrawal_ts` NULL）、`store_keycode_courses` に有効登録された種別。`licence_status_flg` は問わず割当時に true にする（CSV投入在庫は DEFAULT false のため） |
| 不正対策 | 照合失敗を `email_verification_attempts`（`code_type = 'store_keycode'`）に記録し、既存2段階認証と同じ上限（10分間に会員5回／IP上限）でロック |
| 購入扱いデータ | WEBROOT・端末保険それぞれに `members_deals`（`res_pay_method='store'`, `res_result='OK'`）、`members_courses`、`official_deals` を作成し、WEBROOT の取引にキーコードを紐付け（1トランザクション） |
| 退会不可 | マイページの退会ボタンを非表示。`/withdrawal/confirm`・`/withdrawal/complete`（取引番号・キーコード単位）、旧キャリア経路 `/withdrawal/complete_other`（コース単位）への直接アクセスも拒否。管理画面・パートナーAPI（`userTaikai`）からの退会は従来どおり可能 |
| 同時登録 | 未割当条件付き UPDATE を最初の更新系SQLとして実行し行ロックで排他。後発は「既に登録されています」 |
| 店舗 | 入口URLの `shop_cd` で特定した店舗を `members_deals.med_no` に記録 |
