# 機能設計書 8-登録確認

## 概要

本ドキュメントは、Shopware 6 Storefrontにおける登録確認機能の設計仕様を記述する。ダブルオプトインの登録確認を行う機能である。

### 本機能の処理概要

**業務上の目的・背景**：ダブルオプトイン機能が有効な場合、顧客登録時に確認メールが送信される。本機能は、その確認メールに記載されたリンクをクリックした際に、登録を確定させる処理を行う。これにより、スパム登録を防止し、メールアドレスの有効性を確認できる。

**機能の利用シーン**：新規登録した顧客が確認メールを受け取り、メール内のリンクをクリックして登録を確定する場合に使用される。ゲスト注文時のダブルオプトイン確認にも使用される。

**主要な処理内容**：
1. 確認リンクからハッシュとメールハッシュを取得
2. ハッシュで顧客を検索
3. メールハッシュの一致を確認
4. ダブルオプトイン確認日時を設定
5. 自動ログイン処理
6. 登録完了イベントの発行
7. リダイレクト先へ遷移

**関連システム・外部連携**：特になし

**権限による制御**：有効な確認ハッシュとメールハッシュを持つ顧客のみが使用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 登録確認（メールリンク） | 主機能 | ダブルオプトインの登録確認処理（RegisterController::confirmRegistration） |

## 機能種別

認証処理 / データ更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| hash | string | Yes | 登録確認ハッシュ | NotBlank、顧客存在確認 |
| em | string | Yes | メールアドレスのSHA1ハッシュ | メールハッシュ一致確認 |
| redirectTo | string | No | 確認後のリダイレクト先ルート名 | - |

### 入力データソース

- クエリパラメータ（GETリクエスト）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| customer | CustomerEntity | 確認された顧客エンティティ |
| token | string | 新しいセッショントークン |

### 出力先

- データベース（customer.double_opt_in_confirm_date更新）
- セッション（コンテキストトークン）
- HTTPレスポンス（リダイレクト）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ RegisterController::confirmRegistration がGETリクエストを受け取る

2. パラメータ取得
   └─ hash, em, redirectToをクエリパラメータから取得

3. 確認ルート呼び出し
   └─ RegisterConfirmRoute::confirm を実行

4. ハッシュ検証
   └─ hashパラメータの存在確認
   └─ hashで顧客を検索

5. メールハッシュ検証
   └─ emパラメータと顧客メールのSHA1ハッシュを比較
   └─ doubleOptInRegistration=trueを確認

6. 既確認チェック
   └─ doubleOptInConfirmDateがnullでないことを確認
   └─ 既確認の場合はエラー

7. 確認日時更新
   └─ doubleOptInConfirmDateに現在日時を設定

8. セッション更新
   └─ コンテキストトークンを置換
   └─ 顧客IDをコンテキストに設定

9. イベント発行
   └─ CustomerRegisterEvent または GuestCustomerRegisterEvent
   └─ CustomerLoginEvent

10. レスポンス生成
    └─ ゲストの場合はチェックアウト確認ページへ
    └─ 会員の場合はアカウントホーム（またはredirectTo）へ
```

### フローチャート

```mermaid
flowchart TD
    A[確認開始] --> B{hashあり?}
    B -->|No| C[エラー: ハッシュなし]
    B -->|Yes| D[顧客検索]
    D --> E{顧客存在?}
    E -->|No| F[エラー: 顧客なし]
    E -->|Yes| G{emハッシュ一致?}
    G -->|No| H[エラー: ハッシュ不一致]
    G -->|Yes| I{doubleOptInRegistration?}
    I -->|No| J[エラー: 設定不一致]
    I -->|Yes| K{既に確認済み?}
    K -->|Yes| L[エラー: 既確認]
    K -->|No| M[確認日時更新]
    M --> N[セッション更新]
    N --> O{ゲスト?}
    O -->|Yes| P[GuestCustomerRegisterEvent]
    O -->|No| Q[CustomerRegisterEvent]
    P --> R[CustomerLoginEvent]
    Q --> R
    R --> S{ゲスト?}
    S -->|Yes| T[チェックアウト確認へ]
    S -->|No| U{redirectToあり?}
    U -->|Yes| V[redirectTo先へ]
    U -->|No| W[アカウントホームへ]
    C --> X[登録ページへ]
    F --> X
    H --> X
    J --> X
    L --> X
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | ハッシュ必須 | 確認にはhashパラメータが必須 | 常時 |
| BR-002 | メールハッシュ一致 | emパラメータが顧客メールのSHA1ハッシュと一致すること | 常時 |
| BR-003 | ダブルオプトイン必須 | doubleOptInRegistration=trueの顧客のみ確認可能 | 常時 |
| BR-004 | 重複確認禁止 | doubleOptInConfirmDateが既に設定されている場合はエラー | 常時 |
| BR-005 | ゲストリダイレクト | ゲスト顧客はチェックアウト確認ページへリダイレクト | ゲスト確認時 |
| BR-006 | 会員リダイレクト | 会員はアカウントホームまたはredirectTo先へリダイレクト | 会員確認時 |

### 計算ロジック

- メールハッシュ検証: `Hasher::hash(customer.email, 'sha1') === em`

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 顧客検索 | customer | SELECT | ハッシュで顧客を検索 |
| 確認日時更新 | customer | UPDATE | doubleOptInConfirmDateを設定 |
| コンテキスト更新 | sales_channel_api_context | INSERT/UPDATE | セッションに顧客IDを設定 |

### テーブル別操作詳細

#### customer

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, email, hash, double_opt_in_registration, double_opt_in_confirm_date, guest | hash = 入力値 | 顧客検索 |
| UPDATE | double_opt_in_confirm_date | new DateTimeImmutable() | 確認日時更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| CHECKOUT__CUSTOMER_NO_HASH_PROVIDED | NoHashProvidedException | hashパラメータなし | 登録ページへリダイレクト |
| CHECKOUT__CUSTOMER_NOT_FOUND_BY_HASH | CustomerNotFoundByHashException | ハッシュに対応する顧客なし | 登録ページへリダイレクト |
| CHECKOUT__CUSTOMER_ALREADY_CONFIRMED | CustomerAlreadyConfirmedException | 既に確認済み | 登録ページへリダイレクト |
| - | ConstraintViolationException | バリデーションエラー | 登録ページへリダイレクト |

### リトライ仕様

- 確認済みの場合は再確認不可
- ハッシュ不正の場合は再登録が必要

## トランザクション仕様

- 顧客更新とコンテキスト更新は個別トランザクション

## パフォーマンス要件

- 確認処理全体で500ms以内のレスポンスを目標

## セキュリティ考慮事項

- ハッシュはUUID形式で推測困難
- メールハッシュ（em）により、ハッシュのみでの不正確認を防止
- 確認済みの顧客は再確認不可

## 備考

- 確認後は自動的にログイン状態になる
- CustomerLoginEventが発行されるため、通常のログインと同様にイベントリスナーが動作する

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

確認に関連する顧客プロパティを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerEntity.php | `src/Core/Checkout/Customer/CustomerEntity.php` | hash, doubleOptInRegistration, doubleOptInConfirmDateプロパティ |

#### Step 2: エントリーポイントを理解する

Storefrontの確認リクエストはRegisterControllerで受け付けられる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RegisterController.php | `src/Storefront/Controller/RegisterController.php` | confirmRegistration()メソッド（246-283行目） |

**主要処理フロー**:
1. **253-257行目**: registerConfirmRoute.confirm呼び出し
2. **258-261行目**: 例外キャッチしてエラーメッセージ追加
3. **264行目**: 顧客情報を再取得
4. **267-270行目**: ゲストの場合はチェックアウト確認ページへ
5. **273行目**: 会員の場合は成功メッセージ追加
6. **275-279行目**: redirectToがある場合はそちらへ

#### Step 3: Store-APIルートを理解する

実際の確認ロジックはRegisterConfirmRouteに実装されている。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | RegisterConfirmRoute.php | `src/Core/Checkout/Customer/SalesChannel/RegisterConfirmRoute.php` | confirm()メソッド（53-136行目） |

**主要処理フロー**:
- **56-58行目**: hashパラメータの存在確認
- **60-68行目**: ハッシュで顧客を検索
- **70-76行目**: emハッシュとdoubleOptInRegistrationの検証
- **78-80行目**: 既確認チェック
- **82-86行目**: doubleOptInConfirmDateを更新
- **88-99行目**: セッション更新
- **115-119行目**: 登録イベント発行
- **130-131行目**: ログインイベント発行

### プログラム呼び出し階層図

```
RegisterController::confirmRegistration (Storefront)
    │
    └─ RegisterConfirmRoute::confirm (Store-API)
           │
           ├─ hashパラメータ検証
           │
           ├─ CustomerRepository::search (hash検索)
           │
           ├─ DataValidator::validate
           │      └─ em, doubleOptInRegistration検証
           │
           ├─ 既確認チェック
           │
           ├─ CustomerRepository::update (doubleOptInConfirmDate)
           │
           ├─ SalesChannelContextPersister::replace
           │
           ├─ SalesChannelContextPersister::save
           │
           ├─ SalesChannelContextService::get
           │
           ├─ [ゲスト] GuestCustomerRegisterEvent (dispatch)
           │  [会員] CustomerRegisterEvent (dispatch)
           │
           └─ CustomerLoginEvent (dispatch)
```

### データフロー図

```
[入力]                    [処理]                         [出力]

hash, em ───▶ RegisterController::confirmRegistration
               │
               ▼
         RegisterConfirmRoute::confirm
               │
               ▼
         ハッシュで顧客検索
               │
               ▼
         em検証、doubleOptInRegistration確認
               │
               ▼
         既確認チェック
               │
               ▼
         customer.double_opt_in_confirm_date ───▶ [UPDATE]
               │
               ▼
         セッション更新
               │
               ▼
         CustomerRegisterEvent / GuestCustomerRegisterEvent
               │
               ▼
         CustomerLoginEvent
               │
         ┌─────┴─────┐
         ▼           ▼
      [ゲスト]    [会員]
         │           │
         ▼           ▼
   チェックアウト  アカウントホーム
   確認ページへ    へリダイレクト
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| RegisterController.php | `src/Storefront/Controller/RegisterController.php` | コントローラ | 確認エンドポイント |
| RegisterConfirmRoute.php | `src/Core/Checkout/Customer/SalesChannel/RegisterConfirmRoute.php` | ルート | 確認のStore-APIルート |
| AbstractRegisterConfirmRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractRegisterConfirmRoute.php` | 抽象クラス | 確認ルートの抽象定義 |
| CustomerEntity.php | `src/Core/Checkout/Customer/CustomerEntity.php` | エンティティ | 顧客データモデル |
| CustomerRegisterEvent.php | `src/Core/Checkout/Customer/Event/CustomerRegisterEvent.php` | イベント | 会員登録イベント |
| GuestCustomerRegisterEvent.php | `src/Core/Checkout/Customer/Event/GuestCustomerRegisterEvent.php` | イベント | ゲスト登録イベント |
| CustomerLoginEvent.php | `src/Core/Checkout/Customer/Event/CustomerLoginEvent.php` | イベント | ログインイベント |
| Hasher.php | `src/Core/Framework/Util/Hasher.php` | ユーティリティ | ハッシュ計算 |
