# 機能設計書 31-支払い処理

## 概要

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

### 本機能の処理概要

支払い処理機能は、事前に認可（Authorize）された支払いに対して実際の金額をキャプチャ（売上確定）する機能である。ECサイトにおいて、注文確定時に顧客のカードから実際に金額を引き落とす処理を担う。

**業務上の目的・背景**：ECサイトでは通常、注文時にカード認可のみを行い、出荷準備完了時に実際の売上確定（キャプチャ）を行う2段階の決済フローが一般的である。この方式により、在庫切れやキャンセル時に不要な決済処理を防ぎ、顧客体験と運営効率を向上させる。

**機能の利用シーン**：
1. 管理者が注文の出荷準備完了時に手動で支払いをキャプチャする場面
2. 自動キャプチャ設定時に注文確定と同時にキャプチャが実行される場面
3. 部分キャプチャとして、認可金額の一部のみを確定する場面

**主要な処理内容**：
1. 支払い情報の取得と検証（キャンセル済み・キャプチャ済みチェック）
2. キャプチャ金額の計算（全額または指定金額）
3. 残りキャプチャ可能金額の検証
4. Captureレコードの作成
5. 支払いプロバイダーへのキャプチャリクエスト送信
6. 支払いコレクションのステータス更新

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

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 43 | 注文詳細画面 | 主機能 | 支払いキャプチャの実行 |
| 51 | 返金作成画面 | 参照画面 | キャプチャ済み金額の参照 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| payment_id | string | Yes | キャプチャ対象の支払いID | 存在する支払いIDであること |
| amount | BigNumberInput | No | キャプチャする金額（未指定時は全額） | 残りキャプチャ可能金額以下であること |
| captured_by | string | No | キャプチャを実行したユーザーID | - |

### 入力データソース

- 画面入力：管理画面からの支払いID、金額指定
- DBテーブル：payment、payment_session、payment_collection
- 認証コンテキスト：req.auth_context.actor_id

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| payment | PaymentDTO | 更新後の支払い情報 |
| payment.id | string | 支払いID |
| payment.amount | BigNumber | 支払い総額 |
| payment.captured_at | Date | 完全キャプチャ時の日時 |
| payment.captures | CaptureDTO[] | キャプチャ履歴の配列 |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. 支払い情報の取得
   └─ paymentService_.retrieve()でpayment_id、関連するcapturesを取得

2. 前提条件チェック
   ├─ キャンセル済み支払いチェック（canceled_at != null → エラー）
   └─ 既に完全キャプチャ済みチェック（captured_at != null → 早期リターン）

3. キャプチャ金額の決定
   └─ amount未指定時はpayment.amountを使用

4. 残りキャプチャ可能金額の検証
   ├─ 既存キャプチャ金額の合計を計算
   ├─ 認可金額 - 既存キャプチャ合計 = 残り可能金額
   └─ 新規キャプチャ金額 > 残り可能金額 → エラー

5. Captureレコード作成
   └─ captureService_.create()でDBにキャプチャ記録を作成

6. 支払いプロバイダーへのキャプチャ実行
   └─ paymentProviderService_.capturePayment()で外部決済実行

7. 支払いステータス更新
   └─ 完全キャプチャ時はcaptured_atを設定

8. 支払いコレクションステータス更新
   └─ maybeUpdatePaymentCollection_()でコレクション全体のステータスを再計算

9. イベント発行
   └─ PaymentEvents.CAPTUREDイベントを発行

10. 結果を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: capturePayment] --> B[支払い情報取得]
    B --> C{キャンセル済み?}
    C -->|Yes| D[エラー: 支払いキャンセル済み]
    C -->|No| E{既にキャプチャ済み?}
    E -->|Yes| F[早期リターン: isFullyCaptured=true]
    E -->|No| G[キャプチャ金額決定]
    G --> H[残りキャプチャ可能金額計算]
    H --> I{金額検証OK?}
    I -->|No| J[エラー: 金額超過]
    I -->|Yes| K[Captureレコード作成]
    K --> L[プロバイダーへキャプチャ実行]
    L --> M{プロバイダー成功?}
    M -->|No| N[Capture削除/ロールバック]
    N --> O[エラースロー]
    M -->|Yes| P{完全キャプチャ?}
    P -->|Yes| Q[captured_at更新]
    P -->|No| R[支払いデータ更新]
    Q --> R
    R --> S[支払いコレクション更新]
    S --> T[イベント発行]
    T --> U[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-001 | キャンセル済み支払いキャプチャ不可 | キャンセル済みの支払いはキャプチャできない | payment.canceled_at != null |
| BR-31-002 | 認可金額超過キャプチャ不可 | 認可金額を超えるキャプチャはできない | 新規キャプチャ金額 + 既存キャプチャ合計 > 認可金額 |
| BR-31-003 | 部分キャプチャ対応 | 認可金額の一部のみをキャプチャ可能 | amount < payment.amount |
| BR-31-004 | 複数回キャプチャ対応 | 同一支払いに対して複数回のキャプチャが可能 | 残りキャプチャ可能金額 > 0 |
| BR-31-005 | 冪等性保証 | 既に完全キャプチャ済みの場合は何もせずに成功を返す | payment.captured_at != null |

### 計算ロジック

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

**完全キャプチャ判定**:
```
完全キャプチャ = (既存キャプチャ合計 + 新規キャプチャ金額) >= 認可金額
```

**通貨精度丸め**: 通貨コードに基づいた小数点以下の桁数で丸め処理を実施。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| キャプチャ実行 | capture | INSERT | キャプチャレコードの新規作成 |
| 支払い更新 | payment | UPDATE | captured_at、dataの更新 |
| コレクション更新 | payment_collection | UPDATE | captured_amount、statusの更新 |

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

#### capture

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

#### payment

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | captured_at | 完全キャプチャ時のみ現在日時 | nullable |
| UPDATE | data | プロバイダーから返却されたdata | JSON |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| INVALID_DATA | MedusaError | キャンセル済み支払いへのキャプチャ | キャンセルされた支払いは処理不可 |
| INVALID_DATA | MedusaError | 認可金額を超えるキャプチャ | キャプチャ金額を減額して再試行 |
| PROVIDER_ERROR | ProviderError | 外部決済プロバイダーでのエラー | Captureレコードを削除してエラーを上位に伝播 |

### リトライ仕様

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

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

- `@InjectTransactionManager`デコレータによりトランザクション管理
- Captureレコード作成は内部トランザクションで実行
- プロバイダー呼び出し失敗時はCaptureレコードを削除（補償トランザクション）
- 支払いコレクション更新は別トランザクションで実行

## パフォーマンス要件

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

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

- 管理者認証必須（AuthenticatedMedusaRequest）
- 支払いプロバイダーへのリクエストにはidempotency_keyを付与
- 決済データ（data）はプロバイダー固有の機密情報を含む可能性があるため、適切なアクセス制御が必要
- captured_byによる操作者の記録（監査ログ目的）

## 備考

- 自動キャプチャ（is_captured=true）の場合は認可時に自動的にキャプチャ処理が実行される
- プロバイダーがキャプチャ処理をサポートしていない場合の挙動はプロバイダー実装依存

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | payment.ts | `packages/modules/payment/src/models/payment.ts` | Paymentエンティティの構造、Capture/Refundとの関連 |
| 1-2 | capture.ts | `packages/modules/payment/src/models/capture.ts` | Captureエンティティの構造、BigNumber型のamount |

**読解のコツ**: Medusaのモデル定義は`model.define()`を使用したDSLで記述される。`model.bigNumber()`は高精度数値型、`model.belongsTo()`は多対一の関連を示す。

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

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

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

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

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

キャプチャ処理の全体的な流れを制御するワークフローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | capture-payment.ts | `packages/core/core-flows/src/payment/workflows/capture-payment.ts` | ワークフロー定義、ステップの連携 |

**主要処理フロー**:
- **52-93行目**: createWorkflowによるワークフロー定義
- **57行目**: capturePaymentStepの実行
- **59-64行目**: 注文との紐付け確認（order_payment_collection）
- **66-85行目**: 注文トランザクションへの記録（when条件付き）
- **87-90行目**: イベント発行

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

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

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

**主要処理フロー**:
- **30-43行目**: createStepによるステップ定義
- **33-35行目**: PaymentModuleServiceの取得
- **37行目**: paymentModule.capturePayment()の呼び出し

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

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

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

**主要処理フロー**:
- **647-705行目**: capturePaymentメソッド（公開インターフェース）
- **654-671行目**: 支払い情報の取得
- **679-683行目**: capturePayment_（内部処理）の呼び出し
- **686-697行目**: capturePaymentFromProvider_（プロバイダー連携）の呼び出し
- **699-702行目**: maybeUpdatePaymentCollection_（コレクション更新）

- **708-771行目**: capturePayment_メソッド（内部処理）
- **716-720行目**: キャンセル済みチェック
- **723-725行目**: 既にキャプチャ済みの場合の早期リターン
- **728-729行目**: デフォルトキャプチャ金額の設定
- **732-758行目**: キャプチャ可能金額の検証
- **761-768行目**: Captureレコードの作成

- **773-817行目**: capturePaymentFromProvider_メソッド（プロバイダー連携）
- **784-791行目**: 外部プロバイダーへのキャプチャリクエスト

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

```
POST /admin/payments/:id/capture (route.ts)
    │
    └─ capturePaymentWorkflow (capture-payment.ts workflow)
           │
           ├─ capturePaymentStep (capture-payment.ts step)
           │      │
           │      └─ PaymentModuleService.capturePayment()
           │             │
           │             ├─ capturePayment_() - 内部処理
           │             │      │
           │             │      ├─ paymentService_.retrieve()
           │             │      ├─ captureService_.create()
           │             │      └─ return { isFullyCaptured, capture }
           │             │
           │             ├─ capturePaymentFromProvider_()
           │             │      │
           │             │      └─ paymentProviderService_.capturePayment()
           │             │              │
           │             │              └─ provider.capturePayment()
           │             │
           │             └─ maybeUpdatePaymentCollection_()
           │
           ├─ useRemoteQueryStep (order_payment_collection取得)
           │
           ├─ when → addOrderTransactionStep
           │
           └─ emitEventStep (PaymentEvents.CAPTURED)
```

### データフロー図

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

payment_id ────────────▶ capturePaymentWorkflow
amount (optional)                │
captured_by (optional)           │
                                 ▼
                         ┌───────────────────┐
                         │ capturePaymentStep │
                         └─────────┬─────────┘
                                   │
                                   ▼
                    ┌──────────────────────────┐
                    │ PaymentModuleService     │
                    │ .capturePayment()        │
                    └──────────┬───────────────┘
                               │
        ┌──────────────────────┼──────────────────────┐
        │                      │                      │
        ▼                      ▼                      ▼
  ┌───────────┐    ┌─────────────────────┐    ┌───────────────┐
  │ payment   │    │ capture             │    │ provider      │
  │ (UPDATE)  │◀───│ (INSERT)            │    │ API呼び出し    │
  └───────────┘    └─────────────────────┘    └───────┬───────┘
        │                                             │
        └─────────────────────┬───────────────────────┘
                              │
                              ▼
                    ┌───────────────────────┐
                    │ payment_collection    │──────▶ PaymentDTO
                    │ (UPDATE)              │        (レスポンス)
                    └───────────────────────┘
```

### 関連ファイル一覧

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