# 機能設計書 26-GoCardless連携

## 概要

本ドキュメントは、Actual Budget における GoCardless連携機能の設計仕様を記述する。この機能は、GoCardless（旧Nordigen）APIを使用してヨーロッパの銀行口座と連携し、取引データを自動的に取得することを目的とする。

### 本機能の処理概要

GoCardless連携機能は、Open Banking APIを通じてヨーロッパの銀行口座にアクセスし、取引データを自動取得する。ユーザーは銀行を選択して認証を行い、口座をActualの口座にリンクすることで、定期的に取引が同期される。

**業務上の目的・背景**：手動での取引入力は時間がかかり、入力ミスも発生しやすい。銀行との自動連携により、正確な取引データを効率的に取得でき、家計管理の精度と利便性が向上する。特にPSD2規制下のヨーロッパでは、Open Banking APIが広く利用可能である。

**機能の利用シーン**：
- ヨーロッパの銀行口座をActualに連携する
- 定期的に銀行取引を自動取得する
- 連携の更新（認証の再実行）
- 連携の解除

**主要な処理内容**：
1. 銀行リストの取得：指定国の利用可能な銀行を取得
2. Requisition（認可リクエスト）の作成：銀行認証URLを生成
3. 口座一覧の取得：認証後、連携された口座を取得
4. 取引の取得：指定期間の取引データを取得
5. 残高の取得：口座の現在残高を取得
6. 連携の削除：Requisitionを削除

**関連システム・外部連携**：
- GoCardless Bank Account Data API（旧Nordigen）
- 各銀行のOpen Banking API
- 同期サーバー（プロキシとして動作）

**権限による制御**：GoCardless APIキーはサーバー側で管理され、クライアントからは直接アクセスしない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | 銀行同期管理画面（デスクトップ） | 主画面 | GoCardless APIによる銀行連携設定 |
| 26 | 銀行同期管理画面（モバイル） | 主画面 | モバイルでの銀行連携管理 |
| 28 | GoCardlessリンク画面 | 主画面 | GoCardless銀行連携の初期設定・認証処理 |

## 機能種別

データ連携 / 外部API連携

## 入力仕様

### 入力パラメータ

#### /status

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| - | - | - | パラメータなし | - |

#### /create-web-token

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| institutionId | string | Yes | 銀行識別子 | GoCardless形式 |

#### /get-accounts

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| requisitionId | string | Yes | RequisitionのID | UUID形式 |

#### /get-banks

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| country | string | Yes | 国コード | ISO 3166-1 alpha-2 |
| showDemo | boolean | No | デモ銀行を表示 | デフォルトfalse |

#### /transactions

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| requisitionId | string | Yes | RequisitionのID | UUID形式 |
| accountId | string | Yes | 口座ID | GoCardless形式 |
| startDate | string | No | 取得開始日 | YYYY-MM-DD |
| endDate | string | No | 取得終了日 | YYYY-MM-DD |
| includeBalance | boolean | No | 残高を含める | デフォルトtrue |

### 入力データソース

- ユーザー選択（銀行、口座）
- サーバー設定（GoCardless APIキー）
- 銀行の認証画面でのユーザー認証

## 出力仕様

### 出力データ

#### /status レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| configured | boolean | GoCardlessが設定されているか |

#### /create-web-token レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| link | string | 銀行認証URL |
| requisitionId | string | RequisitionのID |

#### /get-accounts レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | Requisition ID |
| accounts | Account[] | 連携された口座リスト |
| requisitionStatus | string | Requisitionのステータス（未リンク時） |

#### /transactions レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| balances | Balance[] | 口座残高（includeBalance時） |
| startingBalance | number | 開始残高 |
| institutionId | string | 銀行識別子 |
| transactions.booked | Transaction[] | 確定取引 |
| transactions.pending | Transaction[] | 保留取引 |
| transactions.all | Transaction[] | 全取引 |

### 出力先

- クライアント：取引一覧、残高情報
- ローカルDB：インポートされた取引

## 処理フロー

### 処理シーケンス

```
1. 銀行連携の初期設定
   └─ /get-banks で利用可能な銀行を取得
   └─ ユーザーが銀行を選択
   └─ /create-web-token でRequisitionを作成
   └─ ユーザーを銀行認証URLにリダイレクト

2. 認証コールバック
   └─ 銀行での認証完了
   └─ ActualにリダイレクトバックしてRequisitionIDを渡す

3. 口座リンク
   └─ /get-accounts で連携された口座を取得
   └─ ユーザーがActualの口座と紐付け

4. 取引同期
   └─ /transactions で取引データを取得
   └─ 銀行固有の正規化処理を適用
   └─ Actualの取引として登録
```

### フローチャート

```mermaid
flowchart TD
    A[銀行同期開始] --> B[/get-banks]
    B --> C[銀行選択]
    C --> D[/create-web-token]
    D --> E[銀行認証画面へリダイレクト]
    E --> F[ユーザー認証]
    F --> G[Actualにリダイレクト]
    G --> H[/get-accounts]
    H --> I{Requisitionリンク済み?}
    I -->|No| J[再認証が必要]
    I -->|Yes| K[口座選択・紐付け]
    K --> L[/transactions]
    L --> M[取引正規化]
    M --> N[Actualに取引登録]
    N --> O[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-26-1 | APIキー必須 | GoCardless APIキーがサーバーに設定されている必要がある | 全API呼び出し時 |
| BR-26-2 | Requisition有効期限 | 銀行によりRequisitionの有効期限が異なる | 取引取得時 |
| BR-26-3 | IBANハッシュ化 | IBANはSHA-256でハッシュ化して返却 | /get-accounts時 |
| BR-26-4 | レート制限 | APIレート制限に従う | 全API呼び出し時 |
| BR-26-5 | 銀行固有正規化 | 銀行ごとに取引データの正規化処理が異なる | 取引取得時 |

### 計算ロジック

**IBANハッシュ化**:
```javascript
account.iban ? { ...account, iban: await sha256String(account.iban) } : account
```

**取引正規化**:
- 各銀行ごとにbankファクトリで正規化クラスを取得
- 支払先名、取引日、金額などを統一形式に変換

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 取引インポート | transactions | INSERT | 取得した取引を登録 |
| 口座リンク設定 | account_mapping | INSERT/UPDATE | GoCardless口座とActual口座の紐付け |

### サーバー側の状態管理

GoCardless連携はサーバー側で状態を管理しない。Requisition IDはクライアントで保持し、必要に応じてGoCardless APIに問い合わせる。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ITEM_LOGIN_REQUIRED | 認証エラー | Requisitionの有効期限切れ | 再認証が必要 |
| INVALID_ACCESS_TOKEN | 認証エラー | 口座がRequisitionにリンクされていない | 再リンクが必要 |
| RATE_LIMIT_EXCEEDED | レート制限 | APIレート制限超過 | 時間をおいて再試行 |
| NORDIGEN_ERROR | 同期エラー | GoCardless API側のエラー | ログを確認 |
| UNKNOWN | 不明エラー | 想定外のエラー | サポートに連絡 |

### リトライ仕様

- レート制限時：rateLimitHeadersの情報に基づいて待機後リトライ
- ネットワークエラー時：クライアント側で自動リトライ

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

- GoCardless APIへのリクエストはトランザクション外
- 取引のActualへのインポートはバッチ処理

## パフォーマンス要件

- 銀行リスト取得：5秒以内
- 取引取得：銀行のレスポンス時間に依存（通常10秒以内）

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

- GoCardless APIキーはサーバー側でのみ管理
- IBANはハッシュ化してクライアントに返却
- 認証はOAuth2ベースで銀行側で実施
- セッション認証必須

## 備考

- 銀行ごとに取引データの形式が異なるため、bankファクトリで正規化処理を行う
- デモ銀行（SANDBOXFINANCE_SFIN0000）でテスト可能
- 一部の銀行は特定の機能をサポートしていない場合がある

---

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

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

### 推奨読解順序

#### Step 1: APIエンドポイントを理解する

GoCardless連携のAPIエンドポイントを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-gocardless.js | `packages/sync-server/src/app-gocardless/app-gocardless.js` | 全APIエンドポイント |

**読解のコツ**:
- **32-39行目**: `/status` - GoCardless設定確認
- **41-60行目**: `/create-web-token` - Requisition作成
- **62-95行目**: `/get-accounts` - 口座一覧取得
- **97-118行目**: `/get-banks` - 銀行リスト取得
- **120-141行目**: `/remove-account` - Requisition削除
- **143-274行目**: `/transactions` - 取引取得（メイン処理）

#### Step 2: GoCardlessサービスを理解する

GoCardless APIとの通信を担当するサービスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | gocardless-service.js | `packages/sync-server/src/app-gocardless/services/gocardless-service.js` | API通信サービス |

**主要関数**:
- `isConfigured()`: APIキーの設定確認
- `setToken()`: アクセストークンの取得・設定
- `getInstitutions()`: 銀行リスト取得
- `createRequisition()`: Requisition作成
- `getRequisitionWithAccounts()`: Requisitionと口座取得
- `getTransactionsWithBalance()`: 取引と残高取得
- `getNormalizedTransactions()`: 正規化された取引取得
- `deleteRequisition()`: Requisition削除

#### Step 3: 銀行固有の正規化を理解する

銀行ごとの取引データ正規化処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | bank-factory.js | `packages/sync-server/src/app-gocardless/bank-factory.js` | 銀行ファクトリ |
| 3-2 | bank.interface.ts | `packages/sync-server/src/app-gocardless/banks/bank.interface.ts` | 銀行インターフェース |

**読解のコツ**:
- `getBankHandler()`で銀行IDに応じた正規化クラスを取得
- 各銀行ファイル（例：`ing_ingddeff.js`）で固有の正規化ロジックを実装

#### Step 4: エラー処理を理解する

エラーの種類と処理方法を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | errors.js | `packages/sync-server/src/app-gocardless/errors.js` | カスタムエラークラス |
| 4-2 | handle-error.js | `packages/sync-server/src/app-gocardless/util/handle-error.js` | エラーハンドリング |

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

```
Client
    │
    ├─ POST /gocardless/status
    │       │
    │       └─ goCardlessService.isConfigured()
    │
    ├─ POST /gocardless/get-banks
    │       │
    │       ├─ goCardlessService.setToken()
    │       │
    │       └─ goCardlessService.getInstitutions()
    │
    ├─ POST /gocardless/create-web-token
    │       │
    │       └─ goCardlessService.createRequisition()
    │               │
    │               └─ GoCardless API (POST /requisitions)
    │
    ├─ POST /gocardless/get-accounts
    │       │
    │       └─ goCardlessService.getRequisitionWithAccounts()
    │               │
    │               └─ GoCardless API (GET /requisitions/{id})
    │
    └─ POST /gocardless/transactions
            │
            └─ goCardlessService.getTransactionsWithBalance()
                    │
                    ├─ GoCardless API (GET /accounts/{id}/transactions)
                    │
                    ├─ GoCardless API (GET /accounts/{id}/balances)
                    │
                    └─ bankFactory.normalize()
                            │
                            └─ 銀行固有の正規化処理
```

### データフロー図

```
[ユーザー]               [Actualサーバー]            [GoCardless API]

銀行選択
    │
    ▼
POST /create-web-token ──▶ createRequisition() ──▶ POST /requisitions
                                                         │
    ◀──────────────── { link, requisitionId } ◀──────────┘

銀行認証URL
    │
    └──────────────────────────────────────────▶ [銀行認証画面]
                                                         │
    ◀────────────────────────────── redirect with code ──┘

POST /get-accounts ────▶ getRequisitionWithAccounts() ──▶ GET /requisitions/{id}
                                                                │
    ◀──────────────────────── { accounts[] } ◀──────────────────┘

POST /transactions ────▶ getTransactionsWithBalance()
                                │
                                ├──▶ GET /accounts/{id}/transactions
                                │
                                ├──▶ GET /accounts/{id}/balances
                                │
                                └──▶ bankFactory.normalize()
                                            │
    ◀──────────────── { transactions, balances } ◀──┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app-gocardless.js | `packages/sync-server/src/app-gocardless/app-gocardless.js` | ソース | APIエンドポイント |
| gocardless-service.js | `packages/sync-server/src/app-gocardless/services/gocardless-service.js` | ソース | GoCardless API通信 |
| bank-factory.js | `packages/sync-server/src/app-gocardless/bank-factory.js` | ソース | 銀行ハンドラファクトリ |
| bank.interface.ts | `packages/sync-server/src/app-gocardless/banks/bank.interface.ts` | ソース | 銀行インターフェース定義 |
| errors.js | `packages/sync-server/src/app-gocardless/errors.js` | ソース | カスタムエラー定義 |
| handle-error.js | `packages/sync-server/src/app-gocardless/util/handle-error.js` | ソース | エラーハンドリング |
| utils.js | `packages/sync-server/src/app-gocardless/utils.js` | ソース | ユーティリティ関数 |
| gocardless.types.ts | `packages/sync-server/src/app-gocardless/gocardless.types.ts` | ソース | 型定義 |
| link.html | `packages/sync-server/src/app-gocardless/link.html` | テンプレート | 認証リンク画面 |
