# 機能設計書 55-ニュースレター登録確認

## 概要

本ドキュメントは、Shopware Storefrontにおける「ニュースレター登録確認」機能の設計仕様を定義する。ニュースレター購読のダブルオプトイン（二重確認）プロセスにおける、確認リンククリック時の登録完了処理を担う。

### 本機能の処理概要

**業務上の目的・背景**：GDPR等の法規制に準拠するため、ニュースレター登録にはダブルオプトイン（DOI）プロセスが必要となる。ユーザーがニュースレター登録フォームに入力後、確認メールが送信され、メール内の確認リンクをクリックすることで初めて登録が完了する。本機能はその確認リンクからの登録完了処理を担う。

**機能の利用シーン**：ユーザーがニュースレター登録フォームに入力し、送信された確認メール内のリンクをクリックした際に本機能が呼び出される。リンクにはハッシュ値とメールアドレスのハッシュ（em）が含まれ、これらを検証して登録を完了する。

**主要な処理内容**：
1. HEADリクエストの場合は無視（メールクライアントのプレビュー対策）
2. ハッシュ値でニュースレター受信者レコードを検索
3. ステータスと確認状態のバリデーション
4. ステータスを「OPT_IN」に更新、確認日時を記録
5. NewsletterConfirmEventイベント発火
6. 登録完了ページを表示

**関連システム・外部連携**：本機能は Store API (`/store-api/newsletter/confirm`) を内部的に利用する。また、`NewsletterConfirmEvent`イベントを発火し、確認完了後のフロー管理やプラグインによる拡張を可能にする。

**権限による制御**：本機能はログイン不要で利用可能（確認リンクはメール受信者のみが持つため）。ただし、ハッシュ値とメールアドレスハッシュの検証により、不正なアクセスを防止する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 24 | ニュースレター購読確認ページ | 主画面 | 確認リンククリック後の表示ページ |

## 機能種別

CRUD操作（Update）/ バリデーション / ダブルオプトイン

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| hash | string | Yes | ニュースレター受信者を特定するハッシュ値 | 存在するレコードのハッシュであること |
| em | string | Yes | メールアドレスのSHA1ハッシュ | hash値で取得したレコードのメールアドレスと一致 |

### 入力データソース

- URLクエリパラメータ: `?hash=xxx&em=yyy`

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | NewsletterSubscribePage | 登録完了ページデータ |

### 出力先

- HTTPレスポンス: Twigテンプレートによる確認完了ページ表示
- 失敗時: フラッシュメッセージを設定してホームページへリダイレクト

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ NewsletterController::subscribeMail が呼び出される

2. HEADリクエストチェック
   └─ REQUEST_METHODがHEADの場合は204 No Contentを返却
   └─ メールクライアントのプレビュー機能対策

3. Store API呼び出し
   └─ NewsletterConfirmRoute::confirm を実行

4. ニュースレター受信者取得
   └─ hash値でnewsletter_recipientテーブルを検索
   └─ 存在しない場合は NewsletterException をスロー

5. バリデーション実行
   └─ id: 必須チェック
   └─ status: STATUS_NOT_SETであること
   └─ em: メールアドレスのSHA1ハッシュと一致すること

6. データ更新
   └─ status を STATUS_OPT_IN に更新
   └─ confirmedAt を現在日時で更新

7. イベント発火
   └─ NewsletterConfirmEvent をディスパッチ

8. レスポンス返却
   └─ 成功時: 確認完了ページを表示
   └─ 失敗時: エラーフラッシュメッセージを設定してホームへリダイレクト
```

### フローチャート

```mermaid
flowchart TD
    A[開始: subscribeMail呼び出し] --> B{リクエストメソッドはHEAD?}
    B -->|Yes| C[204 No Content返却]
    B -->|No| D[NewsletterConfirmRoute::confirm呼び出し]
    D --> E{受信者レコードは存在?}
    E -->|No| F[NewsletterException]
    E -->|Yes| G{ステータスはNOT_SET?}
    G -->|No| H[バリデーションエラー]
    G -->|Yes| I{emハッシュは一致?}
    I -->|No| H
    I -->|Yes| J[ステータスをOPT_INに更新]
    J --> K[confirmedAtを設定]
    K --> L[NewsletterConfirmEvent発火]
    L --> M[確認完了ページ表示]
    C --> N[終了]
    M --> N
    F --> O[エラーフラッシュメッセージ設定]
    H --> O
    O --> P[ホームページへリダイレクト]
    P --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-55-01 | HEADリクエスト無視 | HEADリクエストは処理せず204を返却 | メールクライアントプレビュー対策 |
| BR-55-02 | ハッシュ検証 | hash値で受信者レコードを特定 | 全リクエスト |
| BR-55-03 | メールハッシュ検証 | emパラメータがメールアドレスのSHA1と一致必須 | バリデーション時 |
| BR-55-04 | ステータス確認 | 確認前はSTATUS_NOT_SETであること | バリデーション時 |
| BR-55-05 | 二重確認防止 | 既にOPT_INのレコードは再確認不可 | バリデーション時 |

### 計算ロジック

- emハッシュ計算: `SHA1(email_address)`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 受信者取得 | newsletter_recipient | SELECT | ハッシュ値で受信者レコードを検索 |
| ステータス更新 | newsletter_recipient | UPDATE | status、confirmedAtを更新 |

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

#### newsletter_recipient

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, status, confirmed_at, email, hash | hash = ? | 受信者レコード取得 |
| UPDATE | status | 'optIn' | ステータス更新 |
| UPDATE | confirmed_at | CURRENT_TIMESTAMP | 確認日時設定 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NEWSLETTER_RECIPIENT_NOT_FOUND | 受信者未存在 | 指定hashの受信者が存在しない | 正しいリンクを使用 |
| VALIDATION_ERROR | バリデーションエラー | status/em の検証失敗 | 登録フローを最初からやり直し |

### リトライ仕様

特になし（確認リンクは一度のみ有効な設計）

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

- `newsletterRecipientRepository.update` 内でトランザクションが管理される
- 失敗時は自動ロールバック

## パフォーマンス要件

- レスポンス時間: 500ms以内
- 確認リンクは一度のクリックで完了するため、高頻度アクセスは想定しない

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

- ハッシュ値による受信者特定（URLからのID推測不可）
- メールアドレスハッシュ（em）による二重検証
- HEADリクエスト無視によるプレビュー攻撃防止
- 確認済みレコードの再確認不可

## 備考

- ダブルオプトイン（DOI）はGDPRコンプライアンスの重要要素
- STATUS_NOT_SET → STATUS_OPT_IN への遷移のみ許可
- 確認メール送信は別機能（NewsletterSubscribeRoute）で実装

---

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

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

### 推奨読解順序

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

まず、ニュースレター受信者のデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | NewsletterRecipientDefinition.php | `src/Core/Content/Newsletter/Aggregate/NewsletterRecipient/NewsletterRecipientDefinition.php` | 受信者エンティティ定義。hash、email、status、confirmedAtフィールドを確認 |

**読解のコツ**: ステータス値は`NewsletterSubscribeRoute`クラスの定数（STATUS_NOT_SET、STATUS_OPT_IN等）で定義されている。

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

処理の起点となるコントローラーを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NewsletterController.php | `src/Storefront/Controller/NewsletterController.php` | Storefront側のエントリーポイント。subscribeMailメソッドが確認処理の入口 |

**主要処理フロー**:
1. **38-42行目**: subscribeMailメソッドのルート定義。GET `/newsletter-subscribe`
2. **43-70行目**: subscribeMailメソッドの実装
3. **51-53行目**: HEADリクエストチェック。$_SERVER['REQUEST_METHOD']を直接参照（Symfonyがルーティング時にGETに変換するため）
4. **55-65行目**: try-catchブロック内でnewsletterConfirmRouteを呼び出し
5. **57-60行目**: NewsletterException発生時はフラッシュメッセージを設定してホームへフォワード
6. **67-69行目**: 成功時は確認完了ページをレンダリング

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

実際のビジネスロジックはStore APIルートに実装されている。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NewsletterConfirmRoute.php | `src/Core/Content/Newsletter/SalesChannel/NewsletterConfirmRoute.php` | 確認処理の核心ロジック |

**主要処理フロー**:
- **49行目**: ルート定義。POST `/store-api/newsletter/confirm`
- **50-72行目**: confirmメソッドの実装
- **52行目**: hash値で受信者レコードを取得（getNewsletterRecipient）
- **54-59行目**: バリデーション用データを構築
- **61行目**: バリデーション実行（getBeforeConfirmSubscribeValidation）
- **63-64行目**: ステータスをOPT_INに更新、confirmedAtを設定
- **66行目**: newsletterRecipientRepositoryでデータ更新
- **68-69行目**: NewsletterConfirmEventを発火

#### Step 4: バリデーションロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | NewsletterConfirmRoute.php | `src/Core/Content/Newsletter/SalesChannel/NewsletterConfirmRoute.php` | getNewsletterRecipient、getBeforeConfirmSubscribeValidationメソッド |

**主要処理フロー**:
- **74-88行目**: getNewsletterRecipientメソッド。hash値で受信者を検索、salutationをアソシエーション
- **90-98行目**: getBeforeConfirmSubscribeValidationメソッド
  - **93行目**: id必須チェック（NotBlank）
  - **94行目**: statusがSTATUS_NOT_SETであることをチェック（EqualTo）
  - **95行目**: emパラメータがメールのSHA1ハッシュと一致することをチェック（EqualTo）

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

```
NewsletterController::subscribeMail (Storefront)
    │
    ├─ HEADリクエストチェック (プレビュー対策)
    │      └─ 204 No Content返却
    │
    └─ NewsletterConfirmRoute::confirm (Store API)
           │
           ├─ getNewsletterRecipient (受信者取得)
           │      └─ EntityRepository::search
           │             └─ 例外: RecipientNotFound
           │
           ├─ DataValidator::validate (バリデーション)
           │      └─ getBeforeConfirmSubscribeValidation
           │
           ├─ EntityRepository::update (ステータス更新)
           │
           └─ EventDispatcher::dispatch (イベント発火)
                  └─ NewsletterConfirmEvent
```

### データフロー図

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

hash (クエリパラメータ) ──▶ NewsletterController::subscribeMail
                              │
em (クエリパラメータ) ─────▶ │
                              │
SalesChannelContext ───────▶ │
                              ▼
                         NewsletterConfirmRoute::confirm
                              │
                              ├─▶ newsletter_recipient テーブル (SELECT)
                              │
                              └─▶ newsletter_recipient テーブル (UPDATE)
                                          │
                                          ▼
                                   Twigテンプレート ──────▶ 確認完了ページ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| NewsletterController.php | `src/Storefront/Controller/NewsletterController.php` | ソース | Storefrontコントローラー、エントリーポイント |
| NewsletterConfirmRoute.php | `src/Core/Content/Newsletter/SalesChannel/NewsletterConfirmRoute.php` | ソース | Store APIルート、ビジネスロジック |
| AbstractNewsletterConfirmRoute.php | `src/Core/Content/Newsletter/SalesChannel/AbstractNewsletterConfirmRoute.php` | ソース | 抽象クラス、デコレーションパターン用 |
| NewsletterRecipientDefinition.php | `src/Core/Content/Newsletter/Aggregate/NewsletterRecipient/NewsletterRecipientDefinition.php` | ソース | 受信者エンティティ定義 |
| NewsletterSubscribeRoute.php | `src/Core/Content/Newsletter/SalesChannel/NewsletterSubscribeRoute.php` | ソース | ステータス定数定義 |
| NewsletterConfirmEvent.php | `src/Core/Content/Newsletter/Event/NewsletterConfirmEvent.php` | ソース | 確認完了イベントクラス |
| NewsletterSubscribePageLoader.php | `src/Storefront/Page/Newsletter/Subscribe/NewsletterSubscribePageLoader.php` | ソース | 確認完了ページローダー |
| confirm-subscribe.html.twig | `src/Storefront/Resources/views/storefront/page/newsletter/confirm-subscribe.html.twig` | テンプレート | 確認完了ページテンプレート |
