# 機能設計書 32-返金処理

## 概要

本ドキュメントは、Medusaの支払いモジュールにおける返金処理機能の設計を記載する。

### 本機能の処理概要

返金処理機能は、キャプチャ済み（売上確定済み）の支払いに対して、全額または部分的な返金を実行する機能である。ECサイトにおいて、返品・キャンセル・クレーム対応時に顧客へ金額を返金する処理を担う。

**業務上の目的・背景**：顧客満足度の維持とコンプライアンス遵守のため、返品や注文キャンセル時に迅速かつ正確な返金処理が必要である。部分返金にも対応することで、一部商品のみの返品や値引き対応など柔軟な運用を可能にする。

**機能の利用シーン**：
1. 顧客からの返品リクエスト承認後に返金を実行する場面
2. 注文の一部キャンセルに伴い部分返金を行う場面
3. クレーム対応として一部金額を返金する場面
4. 商品不良等に対する補償として返金を行う場面

**主要な処理内容**：
1. 支払い情報の取得と返金可能金額の検証
2. 返金金額がキャプチャ済み金額を超えないことの検証
3. Refundレコードの作成
4. 支払いプロバイダーへの返金リクエスト送信
5. 注文トランザクションへの返金記録
6. 返金理由に基づくクレジットライン作成（該当時）

**関連システム・外部連携**：Stripe、PayPal等の外部支払いプロバイダーと連携し、実際の返金処理を委譲する。

**権限による制御**：管理者（Admin）権限を持つユーザーのみがAPIエンドポイント経由で返金を実行可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 51 | 返金作成画面 | 主機能 | 返金の作成・実行 |
| 43 | 注文詳細画面 | 参照画面 | 返金履歴の表示 |
| 182 | 返金理由一覧画面 | 参照画面 | 返金理由の選択 |

## 機能種別

計算処理 / データ連携 / CRUD操作

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| payment_id | string | Yes | 返金対象の支払いID | 存在する支払いIDであること |
| amount | BigNumberInput | No | 返金する金額（未指定時は全額） | キャプチャ済み金額 - 既返金金額 以下であること |
| created_by | string | No | 返金を実行したユーザーID | - |
| note | string | No | 返金に関するメモ | - |
| refund_reason_id | string | No | 返金理由のID | 存在する返金理由IDであること |

### 入力データソース

- 画面入力：管理画面からの支払いID、金額、理由等
- DBテーブル：payment、capture、refund、refund_reason
- 認証コンテキスト：req.auth_context.actor_id

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| payment | PaymentDTO | 更新後の支払い情報 |
| payment.id | string | 支払いID |
| payment.refunds | RefundDTO[] | 返金履歴の配列 |
| payment.refunds[].amount | BigNumber | 返金金額 |
| payment.refunds[].note | string | 返金メモ |
| payment.refunds[].refund_reason | RefundReasonDTO | 返金理由 |

### 出力先

- 画面表示：管理画面の支払い詳細
- DBテーブル：payment、refund、order_transaction、order_credit_line
- イベント発行：PaymentEvents.REFUNDED

## 処理フロー

### 処理シーケンス

```
1. 支払い情報の取得
   └─ useRemoteQueryStepでpayment、captures、refundsを取得

2. 返金金額の検証（amount指定時）
   └─ validateRefundPaymentExceedsCapturedAmountStepで検証

3. 注文支払いコレクション情報の取得
   └─ order_payment_collectionから注文IDを取得

4. 注文情報の取得
   └─ order.summary.pending_differenceの確認

5. 返金理由の取得（refund_reason_id指定時）
   └─ refund_reasonテーブルから理由情報を取得

6. 返金処理の実行
   └─ refundPaymentStep → PaymentModuleService.refundPayment()

7. クレジットライン金額の計算
   └─ pending_differenceを考慮した返金額の計算

8. 注文トランザクションの記録
   └─ addOrderTransactionStepで返金トランザクション記録

9. クレジットライン作成（該当時）
   └─ createOrderRefundCreditLinesWorkflowの実行

10. イベント発行
    └─ PaymentEvents.REFUNDEDイベントを発行
```

### フローチャート

```mermaid
flowchart TD
    A[開始: refundPayment] --> B[支払い情報取得]
    B --> C{amount指定?}
    C -->|Yes| D[返金可能金額検証]
    D --> E{検証OK?}
    E -->|No| F[エラー: キャプチャ金額超過]
    E -->|Yes| G[注文情報取得]
    C -->|No| G
    G --> H{refund_reason_id指定?}
    H -->|Yes| I[返金理由取得]
    H -->|No| J[Refundレコード作成]
    I --> J
    J --> K[プロバイダーへ返金実行]
    K --> L{プロバイダー成功?}
    L -->|No| M[Refund削除/ロールバック]
    M --> N[エラースロー]
    L -->|Yes| O[クレジットライン金額計算]
    O --> P{注文紐付けあり?}
    P -->|Yes| Q[注文トランザクション記録]
    P -->|No| R{クレジットライン必要?}
    Q --> R
    R -->|Yes| S[クレジットライン作成]
    R -->|No| T[イベント発行]
    S --> T
    T --> U[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-32-001 | キャプチャ金額超過返金不可 | キャプチャ済み金額を超える返金はできない | 新規返金金額 + 既存返金合計 > キャプチャ合計 |
| BR-32-002 | 部分返金対応 | キャプチャ済み金額の一部のみを返金可能 | amount < captured_amount - refunded_amount |
| BR-32-003 | 複数回返金対応 | 同一支払いに対して複数回の返金が可能 | 残り返金可能金額 > 0 |
| BR-32-004 | 返金理由の任意指定 | 返金理由は任意で指定可能 | refund_reason_idが渡された場合のみ適用 |
| BR-32-005 | クレジットライン自動作成 | pending_differenceがマイナスの場合、返金額との差額でクレジットライン作成 | creditLineAmount > 0 |

### 計算ロジック

**返金可能金額の計算**:
```
返金可能金額 = キャプチャ合計(SUM(captures.raw_amount)) - 既存返金合計(SUM(refunds.raw_amount))
```

**クレジットライン金額の計算**:
```
if (pending_difference < 0):
    amountOwed = pending_difference * -1
    if (amountToRefund > amountOwed):
        creditLineAmount = amountToRefund - amountOwed
    else:
        creditLineAmount = 0
else:
    creditLineAmount = amountToRefund
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 返金実行 | refund | INSERT | 返金レコードの新規作成 |
| 支払い更新 | payment | UPDATE | dataの更新 |
| コレクション更新 | payment_collection | UPDATE | refunded_amountの更新 |
| トランザクション記録 | order_transaction | INSERT | 返金トランザクションの記録 |
| クレジットライン作成 | order_credit_line | INSERT | クレジットラインの作成（該当時） |

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

#### refund

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（prefix: ref） | - |
| INSERT | amount | 返金金額 | BigNumber型 |
| INSERT | payment_id | 対象の支払いID | 外部キー |
| INSERT | refund_reason_id | 返金理由ID | nullable、外部キー |
| INSERT | note | 返金メモ | nullable |
| INSERT | created_by | 実行者のユーザーID | nullable |
| INSERT | created_at | 現在日時 | 自動設定 |

#### order_transaction

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | order_id | 注文ID | 外部キー |
| INSERT | amount | 返金金額 * -1 | マイナス値で記録 |
| INSERT | currency_code | 通貨コード | - |
| INSERT | reference_id | refund.id | - |
| INSERT | reference | "refund" | 固定値 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| INVALID_DATA | MedusaError | キャプチャ金額を超える返金 | 返金金額を減額して再試行 |
| NOT_FOUND | MedusaError | 存在しない支払いIDへの返金 | 正しい支払いIDを指定 |
| NOT_FOUND | MedusaError | 存在しない返金理由IDの指定 | 正しい返金理由IDを指定 |
| PROVIDER_ERROR | ProviderError | 外部決済プロバイダーでのエラー | Refundレコードを削除してエラーを上位に伝播 |

### リトライ仕様

- プロバイダーエラー時はRefundレコードを削除してエラーをスローする
- 呼び出し元で必要に応じてリトライ処理を実装
- 冪等性キーとしてrefund.idを使用

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

- `@InjectTransactionManager`デコレータによりトランザクション管理
- Refundレコード作成は内部トランザクションで実行
- プロバイダー呼び出し失敗時はRefundレコードを削除（補償トランザクション）
- 注文トランザクション記録、クレジットライン作成は別ステップで実行

## パフォーマンス要件

- レスポンス時間：3秒以内（プロバイダー応答時間依存）
- 外部プロバイダーとの通信タイムアウト：30秒
- BigNumber演算による高精度計算の実施

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

- 管理者認証必須（AuthenticatedMedusaRequest）
- 支払いプロバイダーへのリクエストにはidempotency_keyを付与
- created_byによる操作者の記録（監査ログ目的）
- 返金履歴は監査目的で永続保存

## 備考

- 返金処理には補償トランザクション（自動ロールバック）が設定されていない
- これは一度返金された金額を自動的に再チャージすることが不適切なため
- 返金の取り消しが必要な場合は手動対応が必要

---

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

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

### 推奨読解順序

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

まず、返金に関するデータモデルを理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | refund.ts | `packages/modules/payment/src/models/refund.ts` | Refundエンティティの構造、Payment・RefundReasonとの関連 |
| 1-2 | refund-reason.ts | `packages/modules/payment/src/models/refund-reason.ts` | RefundReasonエンティティの構造 |
| 1-3 | payment.ts | `packages/modules/payment/src/models/payment.ts` | Paymentエンティティとrefundsの関連 |

**読解のコツ**: `model.belongsTo()`は多対一、`model.hasMany()`は一対多の関連を示す。Refundは1つのPaymentに属し、オプションで1つのRefundReasonに属する。

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

処理の起点となるAPIルートを特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | route.ts | `packages/medusa/src/api/admin/payments/[id]/refund/route.ts` | POST /admin/payments/:id/refund エンドポイント |

**主要処理フロー**:
1. **9-14行目**: リクエスト型定義とレスポンス型定義
2. **17-23行目**: refundPaymentWorkflowの呼び出し
3. **25-27行目**: 更新後の支払い情報を取得して返却

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

返金処理の全体的な流れを制御するワークフローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | refund-payment.ts | `packages/core/core-flows/src/payment/workflows/refund-payment.ts` | ワークフロー定義、検証ステップ、複合処理 |

**主要処理フロー**:
- **50-88行目**: validateRefundPaymentExceedsCapturedAmountStep（検証ステップ）
- **110-234行目**: createWorkflowによるワークフロー定義
- **113-127行目**: 支払い情報の取得
- **129-134行目**: 金額指定時の検証ステップ実行（when条件）
- **136-151行目**: 注文・支払いコレクション情報の取得
- **152-164行目**: 返金理由の取得（when条件）
- **166行目**: refundPaymentStepの実行
- **168-188行目**: クレジットライン金額の計算（transform）
- **190-212行目**: 注文トランザクションの記録（when条件）
- **214-225行目**: クレジットライン作成（when条件）
- **227-230行目**: イベント発行

#### Step 4: ステップ層を理解する

ワークフローから呼び出される個別ステップを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | refund-payment.ts | `packages/core/core-flows/src/payment/steps/refund-payment.ts` | 返金ステップの実装 |

**主要処理フロー**:
- **30-43行目**: createStepによるステップ定義
- **33-35行目**: PaymentModuleServiceの取得
- **37行目**: paymentModule.refundPayment()の呼び出し
- **41-42行目**: 補償アクションなしの理由コメント

#### Step 5: モジュールサービス層を理解する

実際のビジネスロジックを実装するモジュールサービスを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | payment-module.ts | `packages/modules/payment/src/services/payment-module.ts` | refundPayment メソッドの実装 |

**主要処理フロー**:
- **819-859行目**: refundPaymentメソッド（公開インターフェース）
- **825-839行目**: 支払い情報の取得（captures、refundsを含む）
- **840行目**: refundPayment_（内部処理）の呼び出し
- **842-847行目**: refundPaymentFromProvider_（プロバイダー連携）の呼び出し
- **849-852行目**: maybeUpdatePaymentCollection_（コレクション更新）

- **861-900行目**: refundPayment_メソッド（内部処理）
- **867-869行目**: デフォルト返金金額の設定
- **871-886行目**: 返金可能金額の検証
- **888-898行目**: Refundレコードの作成

- **902-925行目**: refundPaymentFromProvider_メソッド（プロバイダー連携）
- **908-917行目**: 外部プロバイダーへの返金リクエスト

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

```
POST /admin/payments/:id/refund (route.ts)
    │
    └─ refundPaymentWorkflow (refund-payment.ts workflow)
           │
           ├─ useRemoteQueryStep (payment取得)
           │
           ├─ when → validateRefundPaymentExceedsCapturedAmountStep
           │
           ├─ useRemoteQueryStep (order_payment_collection取得)
           │
           ├─ useRemoteQueryStep (order取得)
           │
           ├─ when → useRemoteQueryStep (refund_reason取得)
           │
           ├─ refundPaymentStep
           │      │
           │      └─ PaymentModuleService.refundPayment()
           │             │
           │             ├─ refundPayment_() - 内部処理
           │             │      │
           │             │      ├─ 返金可能金額検証
           │             │      └─ refundService_.create()
           │             │
           │             ├─ refundPaymentFromProvider_()
           │             │      │
           │             │      └─ paymentProviderService_.refundPayment()
           │             │
           │             └─ maybeUpdatePaymentCollection_()
           │
           ├─ transform (creditLineAmount計算)
           │
           ├─ when → addOrderTransactionStep
           │
           ├─ when → createOrderRefundCreditLinesWorkflow
           │
           └─ emitEventStep (PaymentEvents.REFUNDED)
```

### データフロー図

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

payment_id ────────────▶ refundPaymentWorkflow
amount (optional)                │
created_by (optional)            │
note (optional)                  │
refund_reason_id (optional)      │
                                 ▼
                    ┌───────────────────────────┐
                    │ 検証ステップ               │
                    │ (キャプチャ金額超過チェック) │
                    └──────────┬────────────────┘
                               │
                               ▼
                    ┌───────────────────────────┐
                    │ refundPaymentStep         │
                    └──────────┬────────────────┘
                               │
        ┌──────────────────────┼──────────────────────┐
        │                      │                      │
        ▼                      ▼                      ▼
  ┌───────────┐    ┌─────────────────────┐    ┌───────────────┐
  │ payment   │    │ refund              │    │ provider      │
  │ (UPDATE)  │◀───│ (INSERT)            │    │ API呼び出し    │
  └───────────┘    └─────────────────────┘    └───────┬───────┘
        │                                             │
        └─────────────────────┬───────────────────────┘
                              │
                              ▼
                    ┌───────────────────────┐
                    │ order_transaction     │
                    │ (INSERT)              │
                    └──────────┬────────────┘
                               │
                               ▼
                    ┌───────────────────────┐
                    │ order_credit_line     │──────▶ PaymentDTO
                    │ (INSERT - 該当時)     │        (レスポンス)
                    └───────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| route.ts | `packages/medusa/src/api/admin/payments/[id]/refund/route.ts` | APIルート | HTTPエンドポイント定義 |
| refund-payment.ts | `packages/core/core-flows/src/payment/workflows/refund-payment.ts` | ワークフロー | 返金処理の全体フロー制御 |
| refund-payment.ts | `packages/core/core-flows/src/payment/steps/refund-payment.ts` | ステップ | 返金処理のステップ実装 |
| payment-module.ts | `packages/modules/payment/src/services/payment-module.ts` | サービス | 返金ビジネスロジック |
| payment-provider.ts | `packages/modules/payment/src/services/payment-provider.ts` | サービス | 外部プロバイダー連携 |
| refund.ts | `packages/modules/payment/src/models/refund.ts` | モデル | 返金エンティティ定義 |
| refund-reason.ts | `packages/modules/payment/src/models/refund-reason.ts` | モデル | 返金理由エンティティ定義 |
| payment.ts | `packages/modules/payment/src/models/payment.ts` | モデル | 支払いエンティティ定義 |
| validators.ts | `packages/medusa/src/api/admin/payments/validators.ts` | バリデータ | 入力値検証 |
