# 機能設計書 21-配送方法管理

## 概要

本ドキュメントは、Medusaコマースプラットフォームにおけるカートへの配送方法設定・更新機能について記載する。

### 本機能の処理概要

本機能は、ECサイトにおける購入フロー中のカートに対して配送方法を追加・更新する機能である。顧客が商品をカートに追加した後、配送先住所に基づいて利用可能な配送オプションの中から配送方法を選択し、カートに設定する処理を提供する。

**業務上の目的・背景**：ECサイトにおいて、顧客が商品を購入する際に配送方法を選択することは必須のプロセスである。配送方法によって配送料金や配送日数が異なるため、顧客のニーズに応じた配送オプションを提供し、適切に選択できる仕組みが必要となる。また、配送方法の選択は注文金額の合計に直接影響するため、カートの合計金額計算と連動する必要がある。

**機能の利用シーン**：顧客がショッピングカートで商品を確認した後、チェックアウトプロセスに進む際に配送方法を選択する場面で利用される。また、配送先住所を変更した場合に利用可能な配送オプションが変わるため、その際にも配送方法の再選択が行われる。

**主要な処理内容**：
1. カートIDと配送オプションIDを受け取り、カート情報を取得
2. 選択された配送オプションの妥当性検証（利用可能かどうか）
3. 配送オプションの価格計算（プライシング連携）
4. 既存の配送方法があれば削除
5. 新しい配送方法をカートに追加
6. カートの税金・プロモーションを再計算
7. カート更新イベントの発行

**関連システム・外部連携**：配送プロバイダー（FedEx、UPS等）との連携により、配送料金の算出やバリデーションが行われる場合がある。また、価格モジュールとの連携により、リージョン・通貨に応じた配送料金が計算される。

**権限による制御**：Store API経由で顧客が自身のカートに対して配送方法を設定する。カートの所有者（セッションまたは顧客ID）のみが操作可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | チェックアウト画面（配送方法選択） | 主画面 | カートへの配送方法設定 |

## 機能種別

CRUD操作 / 計算処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| cart_id | string | Yes | 対象カートのID | カートが存在すること |
| option_id | string | Yes | 配送オプションのID | 配送オプションが存在し、カートの配送先住所に対して利用可能であること |
| data | Record<string, unknown> | No | 配送プロバイダー向けのカスタムデータ | プロバイダーの要件に従う |

### 入力データソース

- Store API: POST /store/carts/{id}/shipping-methods
- リクエストボディ: option_id, data

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| cart | CartDTO | 更新後のカート情報（配送方法を含む） |
| cart.shipping_methods | ShippingMethodDTO[] | 設定された配送方法の配列 |
| cart.shipping_methods[].id | string | 配送方法ID |
| cart.shipping_methods[].name | string | 配送方法名 |
| cart.shipping_methods[].amount | BigNumber | 配送料金 |
| cart.shipping_methods[].is_tax_inclusive | boolean | 税込み価格かどうか |
| cart.shipping_methods[].shipping_option_id | string | 元の配送オプションID |

### 出力先

- APIレスポンス（JSON形式）
- データベース（cart_shipping_methodテーブル）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ cart_idとoption_idを取得

2. カートの取得とバリデーション
   └─ カートの存在確認、状態検証

3. 分散ロックの取得
   └─ カートIDをキーとしてロック取得（並行更新防止）

4. 配送オプションの取得と価格計算
   └─ listShippingOptionsForCartWithPricingWorkflowで価格付き配送オプション取得

5. 配送オプションの妥当性検証
   └─ validateCartShippingOptionsStepで選択されたオプションが利用可能か確認

6. 配送方法データのバリデーション
   └─ 配送プロバイダーによるデータ検証

7. 既存配送方法の削除
   └─ removeShippingMethodFromCartStepで既存の配送方法を削除

8. 新規配送方法の追加
   └─ addShippingMethodToCartStepで新しい配送方法を追加

9. カートの再計算
   └─ refreshCartItemsWorkflowで税金・プロモーションを再計算

10. イベント発行とロック解放
    └─ cart.updatedイベント発行、ロック解放
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[カート取得]
    B --> C{カート存在?}
    C -->|No| D[エラー返却]
    C -->|Yes| E[ロック取得]
    E --> F[配送オプション取得・価格計算]
    F --> G{オプション有効?}
    G -->|No| H[エラー返却]
    G -->|Yes| I[プロバイダー検証]
    I --> J{検証OK?}
    J -->|No| K[エラー返却]
    J -->|Yes| L[既存配送方法削除]
    L --> M[新規配送方法追加]
    M --> N[カート再計算]
    N --> O[イベント発行]
    O --> P[ロック解放]
    P --> Q[レスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-21-01 | 単一配送方法 | 現在、カートには1つの配送方法のみ設定可能 | 常時 |
| BR-21-02 | 配送先住所必須 | 配送方法設定前にカートに配送先住所が設定されている必要がある | 配送方法追加時 |
| BR-21-03 | リージョン整合性 | 配送オプションはカートのリージョンに対応している必要がある | 配送方法追加時 |
| BR-21-04 | 価格必須 | 配送オプションには計算された価格が設定されている必要がある | 配送方法追加時 |

### 計算ロジック

配送料金 = 配送オプションの計算済み価格（calculated_price.calculated_amount）
- 価格はプライシングモジュールにより、リージョン・通貨・顧客グループ等に基づいて計算される

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 既存配送方法削除 | cart_shipping_method | DELETE（論理） | 既存の配送方法をソフトデリート |
| 配送方法追加 | cart_shipping_method | INSERT | 新しい配送方法を追加 |
| 配送方法税行 | cart_shipping_method_tax_line | INSERT/UPDATE | 配送方法の税行を更新 |
| 配送方法調整 | cart_shipping_method_adjustment | INSERT | プロモーション適用による調整 |

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

#### cart_shipping_method

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（casm_プレフィックス） | 主キー |
| INSERT | name | 配送オプション名 | 表示名 |
| INSERT | amount | 計算された配送料金 | BigNumber形式 |
| INSERT | is_tax_inclusive | 配送オプションの税込設定 | boolean |
| INSERT | shipping_option_id | 選択された配送オプションID | 外部参照 |
| INSERT | cart_id | 対象カートID | 外部キー |
| INSERT | data | プロバイダー検証済みデータ | JSON |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NOT_FOUND | MedusaError | カートが存在しない | 有効なカートIDを指定 |
| NOT_FOUND | MedusaError | 配送オプションが存在しない | 有効な配送オプションIDを指定 |
| INVALID_DATA | MedusaError | 配送オプションに価格が設定されていない | 配送オプションの価格設定を確認 |
| INVALID_DATA | MedusaError | 配送オプションがカートの配送先住所に対応していない | 配送先住所を確認 |
| INVALID_DATA | MedusaError | プロバイダーによるデータ検証失敗 | 入力データを確認 |

### リトライ仕様

分散ロック取得時にタイムアウト（2秒）が発生した場合、ロック取得を再試行する。TTLは10秒に設定されている。

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

- ワークフロー内の各ステップはトランザクション管理される
- 配送方法の削除と追加は並列で実行される
- エラー発生時は補償処理により追加した配送方法が削除される

## パフォーマンス要件

- レスポンス時間: 2秒以内
- 配送オプションの価格計算が外部プロバイダーを呼び出す場合は、追加の遅延が発生する可能性がある

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

- カートの所有者のみが配送方法を設定可能
- 分散ロックにより並行更新を防止
- 配送プロバイダーへのデータ送信時は機密情報をフィルタリング

## 備考

- 現在のバージョンではカートに設定できる配送方法は1つのみ（将来的に複数配送方法への対応が予定されている可能性あり）
- 配送方法の追加時には既存の配送方法がすべて削除される

---

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

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

### 推奨読解順序

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

まず、配送方法に関連するデータモデルを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | shipping-method.ts | `packages/modules/cart/src/models/shipping-method.ts` | ShippingMethodエンティティの構造、カートとの関連 |
| 1-2 | shipping-method-tax-line.ts | `packages/modules/cart/src/models/shipping-method-tax-line.ts` | 配送方法の税行データ構造 |
| 1-3 | shipping-method-adjustment.ts | `packages/modules/cart/src/models/shipping-method-adjustment.ts` | 配送方法の調整（割引）データ構造 |

**読解のコツ**: Medusaのモデル定義は`@medusajs/framework/utils`の`model.define`を使用。`belongsTo`、`hasMany`でリレーションを定義している。

**主要処理フロー（shipping-method.ts）**:
- **6-31行目**: ShippingMethodモデル定義。name, amount, shipping_option_id等のフィールド
- **21-23行目**: カートとのbelongsToリレーション
- **24-29行目**: tax_lines, adjustmentsとのhasManyリレーション
- **44行目**: amountは0以上という制約

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | route.ts | `packages/medusa/src/api/store/carts/[id]/shipping-methods/route.ts` | Store APIのエントリーポイント |

**主要処理フロー**:
- **6-30行目**: POST メソッド - 配送方法追加のエントリーポイント
- **15-21行目**: addShippingMethodToCartWorkflowの呼び出し、optionsとcart_idを渡す
- **23-28行目**: 更新後のカートをrefetchして返却

#### Step 3: ワークフローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | add-shipping-method-to-cart.ts | `packages/core/core-flows/src/cart/workflows/add-shipping-method-to-cart.ts` | 配送方法追加のメインワークフロー |

**主要処理フロー**:
- **83-230行目**: addShippingMethodToCartWorkflowの定義
- **88-92行目**: 分散ロックの取得
- **94-100行目**: useRemoteQueryStepでカート情報取得
- **102行目**: validateCartStepでカート検証
- **113-121行目**: listShippingOptionsForCartWithPricingWorkflowで配送オプション価格計算
- **123-128行目**: 配送オプションの妥当性検証
- **152-154行目**: プロバイダーによるデータ検証
- **199-206行目**: 既存配送方法削除と新規追加を並列実行
- **208-214行目**: カート再計算ワークフロー呼び出し
- **216-224行目**: イベント発行とロック解放

#### Step 4: サービス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cart-module.ts | `packages/modules/cart/src/services/cart-module.ts` | CartModuleServiceの配送方法関連メソッド |

**主要処理フロー**:
- **697-774行目**: addShippingMethods - 配送方法追加のサービスメソッド
- **743-763行目**: addShippingMethods_ - 内部実装、カートの存在確認後にバルク作成
- **765-774行目**: addShippingMethodsBulk_ - shippingMethodService_.createを呼び出し

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

```
POST /store/carts/{id}/shipping-methods
    │
    ├─ addShippingMethodToCartWorkflow
    │      │
    │      ├─ acquireLockStep (ロック取得)
    │      │
    │      ├─ useRemoteQueryStep (カート取得)
    │      │
    │      ├─ validateCartStep (カート検証)
    │      │
    │      ├─ listShippingOptionsForCartWithPricingWorkflow (価格計算)
    │      │
    │      ├─ validateCartShippingOptionsStep (オプション検証)
    │      │
    │      ├─ validateCartShippingOptionsPriceStep (価格検証)
    │      │
    │      ├─ validateAndReturnShippingMethodsDataStep (プロバイダー検証)
    │      │
    │      ├─ parallelize
    │      │      ├─ removeShippingMethodFromCartStep
    │      │      └─ addShippingMethodToCartStep
    │      │             └─ CartModuleService.addShippingMethods
    │      │
    │      ├─ refreshCartItemsWorkflow (カート再計算)
    │      │
    │      └─ parallelize
    │             ├─ emitEventStep (イベント発行)
    │             └─ releaseLockStep (ロック解放)
    │
    └─ refetchCart (更新後カート取得)
```

### データフロー図

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

cart_id, option_id ───▶ addShippingMethodToCartWorkflow ───▶ 更新後カート
                              │
                              ├─▶ カート取得
                              │
                              ├─▶ 配送オプション価格計算
                              │
                              ├─▶ 既存配送方法削除
                              │
                              ├─▶ 新規配送方法追加 ───▶ cart_shipping_method
                              │
                              └─▶ 税・プロモーション再計算
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| route.ts | `packages/medusa/src/api/store/carts/[id]/shipping-methods/route.ts` | ソース | Store APIエントリーポイント |
| add-shipping-method-to-cart.ts | `packages/core/core-flows/src/cart/workflows/add-shipping-method-to-cart.ts` | ソース | メインワークフロー |
| cart-module.ts | `packages/modules/cart/src/services/cart-module.ts` | ソース | カートモジュールサービス |
| shipping-method.ts | `packages/modules/cart/src/models/shipping-method.ts` | ソース | 配送方法モデル定義 |
| shipping-method-tax-line.ts | `packages/modules/cart/src/models/shipping-method-tax-line.ts` | ソース | 配送方法税行モデル |
| shipping-method-adjustment.ts | `packages/modules/cart/src/models/shipping-method-adjustment.ts` | ソース | 配送方法調整モデル |
| list-shipping-options-for-cart-with-pricing.ts | `packages/core/core-flows/src/cart/workflows/list-shipping-options-for-cart-with-pricing.ts` | ソース | 価格計算ワークフロー |
| refresh-cart-items.ts | `packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts` | ソース | カート再計算ワークフロー |
