# 機能設計書 24-カート割引適用

## 概要

本ドキュメントは、Medusaコマースプラットフォームにおけるカートへのプロモーションコード（割引・クーポン）適用機能について記載する。

### 本機能の処理概要

本機能は、ECサイトの購入フローにおいて、顧客がプロモーションコード（クーポンコード）をカートに適用し、割引を受けるための機能である。プロモーションモジュールと連携して、適用可能なプロモーションルールを評価し、ラインアイテムや配送方法に対する調整（Adjustment）を計算・適用する。

**業務上の目的・背景**：ECサイトにおいてプロモーションやクーポンは売上促進の重要な手段である。顧客が割引コードを入力することで、商品価格の割引や配送料の無料化などの特典を適用できる仕組みが必要となる。また、複数のプロモーションルールが存在する場合の適用優先順位や、適用条件の判定ロジックも重要である。

**機能の利用シーン**：
- 顧客がチェックアウト画面でプロモーションコードを入力する時
- マーケティングキャンペーンで自動適用されるプロモーションがある時
- カート内容の変更により適用プロモーションの再評価が必要な時

**主要な処理内容**：
1. プロモーションコードの受付と検証
2. プロモーションルールの評価（適用条件の判定）
3. 割引アクションの計算（金額割引、パーセント割引等）
4. ラインアイテム調整（LineItemAdjustment）の作成
5. 配送方法調整（ShippingMethodAdjustment）の作成
6. カートの合計金額への反映
7. 支払いコレクションの更新（金額変更時）

**関連システム・外部連携**：
- プロモーションモジュール（Promotion Module）: プロモーションルールの管理、適用条件の評価
- 価格モジュール（Pricing Module）: 割引後価格の計算
- 支払いモジュール（Payment Module）: 支払いコレクションの金額更新

**権限による制御**：Store APIから顧客が自身のカートにプロモーションコードを適用可能。Admin APIでは管理者が任意のカートにプロモーションを適用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | チェックアウト画面 | 主画面 | プロモーションコード入力・適用 |
| - | カート画面 | 主画面 | 割引額の表示 |

## 機能種別

CRUD操作 / 計算処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| cart_id | string | Yes | 対象カートのID | カートが存在すること |
| promo_codes | string[] | Yes | 適用するプロモーションコードの配列 | 有効なプロモーションコードであること |
| action | PromotionActions | No | 操作種別（ADD/REMOVE/REPLACE） | デフォルト: ADD |

### 入力データソース

- Store API: POST /store/carts/{id}/promotions（プロモーション追加）
- Store API: DELETE /store/carts/{id}/promotions（プロモーション削除）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| cart | CartDTO | 更新後のカート情報 |
| cart.items[].adjustments | LineItemAdjustmentDTO[] | ラインアイテムの調整 |
| cart.items[].adjustments[].amount | BigNumber | 調整金額（負の値で割引） |
| cart.items[].adjustments[].code | string | プロモーションコード |
| cart.shipping_methods[].adjustments | ShippingMethodAdjustmentDTO[] | 配送方法の調整 |
| cart.discount_total | BigNumber | 割引合計 |
| cart.total | BigNumber | 割引後合計 |

### 出力先

- APIレスポンス（JSON形式）
- データベース（cart_line_item_adjustment, cart_shipping_method_adjustmentテーブル）
- データベース（cartテーブルのpromotion_codes）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ cart_id, promo_codes, actionを取得

2. カートの取得と検証
   └─ カート情報、既存のプロモーションコードを取得

3. 分散ロックの取得
   └─ カートIDをキーとしてロック取得

4. プロモーションコードの処理
   ├─ ADD: 既存コードに新規コードを追加
   ├─ REMOVE: 指定コードを削除
   └─ REPLACE: 既存コードを指定コードで置換

5. プロモーションルールの評価
   └─ getActionsToComputeFromPromotionsStepでアクション計算
      ├─ 適用条件の判定（金額条件、商品条件等）
      └─ 割引アクションの計算

6. 調整データの準備
   └─ prepareAdjustmentsFromPromotionActionsStepで調整データ作成

7. 既存調整の削除
   └─ removeLineItemAdjustmentsStep
   └─ removeShippingMethodAdjustmentsStep

8. 新規調整の作成
   └─ createLineItemAdjustmentsStep
   └─ createShippingMethodAdjustmentsStep

9. カートのプロモーションコード更新
   └─ updateCartPromotionsStep

10. 支払いコレクションの更新
    └─ refreshPaymentCollectionForCartWorkflow（金額変更時）

11. ロック解放
    └─ releaseLockStep
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[カート取得]
    B --> C{カート存在?}
    C -->|No| D[エラー返却]
    C -->|Yes| E[ロック取得]
    E --> F{アクション判定}
    F -->|ADD| G[コード追加]
    F -->|REMOVE| H[コード削除]
    F -->|REPLACE| I[コード置換]
    G --> J[プロモーション評価]
    H --> J
    I --> J
    J --> K[調整データ計算]
    K --> L[既存調整削除]
    L --> M[新規調整作成]
    M --> N[カート更新]
    N --> O{金額変更?}
    O -->|Yes| P[支払いコレクション更新]
    O -->|No| Q[ロック解放]
    P --> Q
    Q --> R[レスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-24-01 | コード重複禁止 | 同一プロモーションコードは重複適用不可 | コード追加時 |
| BR-24-02 | 適用条件判定 | プロモーションの適用条件（最低金額、対象商品等）を満たす必要がある | 常時 |
| BR-24-03 | 排他制御 | 特定のプロモーション同士は併用不可の場合がある | プロモーション設定による |
| BR-24-04 | 調整額制限 | 調整後の金額が0未満にならない | 調整計算時 |
| BR-24-05 | 支払い更新 | プロモーションにより金額が変わった場合、支払いコレクションを更新 | 金額変更時 |

### 計算ロジック

```
■ 固定金額割引
調整額 = -discount_amount

■ パーセント割引
調整額 = -(item_price * discount_percentage / 100)

■ 配送無料
調整額 = -shipping_amount

■ カート全体の割引合計
discount_total = |Σ(ラインアイテム調整額)| + |Σ(配送方法調整額)|

■ 割引後合計
total = subtotal + shipping_total + tax_total - discount_total
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 調整削除 | cart_line_item_adjustment | DELETE | 既存のラインアイテム調整を削除 |
| 調整作成 | cart_line_item_adjustment | INSERT | 新規ラインアイテム調整を作成 |
| 配送調整削除 | cart_shipping_method_adjustment | DELETE | 既存の配送方法調整を削除 |
| 配送調整作成 | cart_shipping_method_adjustment | INSERT | 新規配送方法調整を作成 |
| カート更新 | cart | UPDATE | promotion_codesを更新 |

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

#### cart_line_item_adjustment

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（caliajプレフィックス） | 主キー |
| INSERT | item_id | ラインアイテムID | 外部キー |
| INSERT | amount | 調整金額（負の値） | BigNumber |
| INSERT | code | プロモーションコード | 文字列 |
| INSERT | description | 調整の説明 | 文字列 |
| INSERT | promotion_id | プロモーションID | 外部参照 |

#### cart_shipping_method_adjustment

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（casmajプレフィックス） | 主キー |
| INSERT | shipping_method_id | 配送方法ID | 外部キー |
| INSERT | amount | 調整金額（負の値） | BigNumber |
| INSERT | code | プロモーションコード | 文字列 |
| INSERT | description | 調整の説明 | 文字列 |
| INSERT | promotion_id | プロモーションID | 外部参照 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NOT_FOUND | MedusaError | カートが存在しない | 有効なカートIDを指定 |
| INVALID_DATA | MedusaError | プロモーションコードが無効 | 有効なコードを指定 |
| INVALID_DATA | MedusaError | プロモーションの適用条件を満たさない | 条件を満たす商品を追加 |

### リトライ仕様

分散ロック取得時にタイムアウトが発生した場合、再試行可能。

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

- 調整の削除と作成は並列で実行される
- カートのプロモーションコード更新も同一トランザクション内
- エラー発生時はロールバック

## パフォーマンス要件

- レスポンス時間: 1秒以内
- プロモーションルールの評価は複雑なルールがある場合に時間がかかる可能性

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

- プロモーションコードの総当たり攻撃を防ぐためのレート制限
- 有効期限切れコードや使用回数制限のあるコードの検証

## 備考

- force_refresh_payment_collection=trueで支払いコレクションを強制更新
- PromotionActions列挙型: ADD, REMOVE, REPLACEの3種類

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | line-item-adjustment.ts | `packages/modules/cart/src/models/line-item-adjustment.ts` | ラインアイテム調整モデル |
| 1-2 | shipping-method-adjustment.ts | `packages/modules/cart/src/models/shipping-method-adjustment.ts` | 配送方法調整モデル |

**読解のコツ**: 調整（Adjustment）はプロモーション適用による価格変更を表す。amount, code, descriptionを持つ。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | route.ts | `packages/medusa/src/api/store/carts/[id]/promotions/route.ts` | Store APIプロモーション操作 |

**主要処理フロー**:
- **7-33行目**: POST メソッド - プロモーション追加
- **14-24行目**: updateCartPromotionsWorkflowId呼び出し、action=ADD or REPLACE
- **35-63行目**: DELETE メソッド - プロモーション削除
- **47-54行目**: updateCartPromotionsWorkflowId呼び出し、action=REMOVE

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | update-cart-promotions.ts | `packages/core/core-flows/src/cart/workflows/update-cart-promotions.ts` | プロモーション更新ワークフロー |

**主要処理フロー**:
- **83-179行目**: updateCartPromotionsWorkflowの定義
- **89-100行目**: カート取得（cart_idから、またはcart引数を使用）
- **106行目**: validateCartStep
- **108-112行目**: ロック取得
- **127-131行目**: getPromotionCodesToApply - 適用すべきコードの決定
- **133-136行目**: getActionsToComputeFromPromotionsStep - アクション計算
- **138-144行目**: prepareAdjustmentsFromPromotionActionsStep - 調整データ準備
- **146-160行目**: parallelize - 調整の削除/作成/カート更新を並列実行
- **162-169行目**: 支払いコレクション更新（force_refresh_payment_collection時）
- **171-173行目**: ロック解放

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

```
POST /store/carts/{id}/promotions
    │
    ├─ updateCartPromotionsWorkflow
    │      │
    │      ├─ useQueryGraphStep (カート取得)
    │      │
    │      ├─ validateCartStep (カート検証)
    │      │
    │      ├─ acquireLockStep (ロック取得)
    │      │
    │      ├─ getPromotionCodesToApply (コード決定)
    │      │
    │      ├─ getActionsToComputeFromPromotionsStep (アクション計算)
    │      │      └─ Promotion Module
    │      │
    │      ├─ prepareAdjustmentsFromPromotionActionsStep (調整準備)
    │      │
    │      ├─ parallelize
    │      │      ├─ removeLineItemAdjustmentsStep
    │      │      ├─ removeShippingMethodAdjustmentsStep
    │      │      ├─ createLineItemAdjustmentsStep
    │      │      ├─ createShippingMethodAdjustmentsStep
    │      │      └─ updateCartPromotionsStep
    │      │
    │      ├─ refreshPaymentCollectionForCartWorkflow (条件付き)
    │      │
    │      └─ releaseLockStep (ロック解放)
    │
    └─ refetchCart
```

### データフロー図

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

cart_id          ───┐
promo_codes      ───┼──▶ updateCartPromotionsWorkflow ───▶ 更新後カート
action           ───┘         │
                              ├─▶ Promotion Module ───▶ 適用ルール評価
                              │
                              ├─▶ 調整計算
                              │
                              ├─▶ 調整削除/作成 ───▶ cart_line_item_adjustment
                              │                  ───▶ cart_shipping_method_adjustment
                              │
                              └─▶ カート更新 ───▶ cart.promotion_codes
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| route.ts | `packages/medusa/src/api/store/carts/[id]/promotions/route.ts` | ソース | Store APIエントリーポイント |
| update-cart-promotions.ts | `packages/core/core-flows/src/cart/workflows/update-cart-promotions.ts` | ソース | プロモーション更新ワークフロー |
| line-item-adjustment.ts | `packages/modules/cart/src/models/line-item-adjustment.ts` | ソース | ラインアイテム調整モデル |
| shipping-method-adjustment.ts | `packages/modules/cart/src/models/shipping-method-adjustment.ts` | ソース | 配送方法調整モデル |
| cart-module.ts | `packages/modules/cart/src/services/cart-module.ts` | ソース | 調整操作メソッド |
