> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zafapay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# エラーコード

> APIエラーレスポンスの詳細

## エラーレスポンス形式

APIエラーは以下の形式で返されます。

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_error",
    "message": "エラーの説明",
    "request_id": "req_abc123xyz789"
  }
}
```

<ResponseField name="type" type="string">
  エラーの種類（`authentication_error`, `invalid_request_error`, `payment_error`, `api_error`）
</ResponseField>

<ResponseField name="code" type="string">
  エラーコード（下記参照）
</ResponseField>

<ResponseField name="message" type="string">
  エラーの説明文
</ResponseField>

<ResponseField name="request_id" type="string">
  リクエストID（サポートへの問い合わせ時に使用）
</ResponseField>

<ResponseField name="param" type="string">
  エラーの原因となったパラメータ名（バリデーションエラー時）
</ResponseField>

<ResponseField name="details" type="array">
  詳細なエラー情報（バリデーションエラー時）
</ResponseField>

## 認証エラー

| コード                   | HTTPステータス | 説明                  |
| --------------------- | --------- | ------------------- |
| `unauthorized`        | 401       | 認証ヘッダーが不正または不足しています |
| `merchant_not_active` | 403       | 加盟店アカウントが無効化されています  |

## バリデーションエラー

| コード                    | HTTPステータス | 説明                                      |
| ---------------------- | --------- | --------------------------------------- |
| `validation_error`     | 400       | リクエストパラメータが不正です。`details`フィールドに詳細が含まれます |
| `invalid_amount`       | 400       | 金額が不正です                                 |
| `amount_below_minimum` | 400       | 金額が最小値を下回っています                          |
| `amount_above_maximum` | 400       | 金額が最大値を超えています                           |
| `unsupported_currency` | 400       | サポートされていない通貨です                          |

## 決済エラー

| コード                       | HTTPステータス | 説明                     |
| ------------------------- | --------- | ---------------------- |
| `payment_failed`          | 400       | 決済処理に失敗しました            |
| `invalid_status`          | 400       | この操作は現在のステータスでは実行できません |
| `invalid_state`           | 400       | トランザクションの状態が不正です       |
| `invalid_transaction`     | 400       | トランザクションが不正です          |
| `not_supported`           | 400       | この操作はサポートされていません       |
| `capture_failed`          | 400       | キャプチャに失敗しました           |
| `refund_failed`           | 400       | 返金に失敗しました              |
| `card_limit_exceeded`     | 400       | 月間カード決済上限を超過しました       |
| `email_limit_exceeded`    | 400       | 月間メール決済上限を超過しました       |
| `recurring_not_supported` | 400       | コネクタが継続決済をサポートしていません   |

## トークンエラー

| コード                          | HTTPステータス | 説明                                        |
| ---------------------------- | --------- | ----------------------------------------- |
| `tokenization_failed`        | 400       | カードデータのトークン化に失敗しました                       |
| `payment_token_expired`      | 400       | トークンの有効期限が切れています。新しいトークンを作成してください。（30分制限） |
| `payment_token_already_used` | 400       | トークンは既に決済に使用されています                        |

## Webhook決済エラーコード

決済が失敗した場合、`payment.failed` Webhookには、すべての決済プロバイダーで統一された形式の詳細なエラー情報が含まれます。

### エラー構造

```json theme={null}
{
  "event": "payment.failed",
  "error": {
    "code": "card_declined",
    "category": "soft_decline",
    "message": "カードが拒否されました",
    "recommended_action": "use_different_card"
  }
}
```

### エラーカテゴリ

| カテゴリ             | 説明                        | 推奨アクション         |
| ---------------- | ------------------------- | --------------- |
| `authentication` | 3DS認証が必要                  | 顧客を認証ページにリダイレクト |
| `soft_decline`   | 一時的な失敗                    | リトライまたは別のカードを使用 |
| `hard_decline`   | 永続的な失敗                    | 同じカードでリトライしない   |
| `gateway_error`  | 決済ゲートウェイの問題               | 後でリトライ          |
| `validation`     | APIリクエスト検証失敗（Webhook発火なし） | リクエストを修正して再試行   |

### 統一エラーコード

| コード                       | カテゴリ           | 説明               | 推奨アクション              |
| ------------------------- | -------------- | ---------------- | -------------------- |
| `authentication_required` | authentication | 3DS認証が必要         | `contact_customer`   |
| `authentication_failed`   | soft\_decline  | 3DS認証に失敗         | `retry`              |
| `authentication_timeout`  | gateway\_error | 3DS認証タイムアウト      | `retry`              |
| `card_declined`           | soft\_decline  | カードが拒否されました      | `use_different_card` |
| `insufficient_funds`      | soft\_decline  | 残高不足             | `use_different_card` |
| `expired_card`            | hard\_decline  | カードの有効期限切れ       | `use_different_card` |
| `invalid_card`            | hard\_decline  | カード番号が無効         | `use_different_card` |
| `invalid_cvc`             | soft\_decline  | セキュリティコードが無効     | `retry`              |
| `invalid_expiry`          | soft\_decline  | 有効期限が無効          | `retry`              |
| `fraud_detected`          | hard\_decline  | 不正利用の疑い          | `none`               |
| `stolen_card`             | hard\_decline  | 盗難カード            | `none`               |
| `lost_card`               | hard\_decline  | 紛失カード            | `none`               |
| `processing_error`        | gateway\_error | 処理エラー            | `retry`              |
| `gateway_timeout`         | gateway\_error | ゲートウェイタイムアウト     | `retry`              |
| `gateway_unavailable`     | gateway\_error | ゲートウェイ利用不可       | `retry`              |
| `payment_failed`          | soft\_decline  | 決済失敗（汎用）         | `use_different_card` |
| `three_ds_required`       | authentication | 3DS認証が必要だがカード非対応 | `use_different_card` |
| `refund_failed`           | gateway\_error | 返金操作失敗           | `contact_customer`   |
| `void_failed`             | gateway\_error | 取消操作失敗           | `contact_customer`   |
| `status_check_failed`     | gateway\_error | 決済ステータス確認失敗      | `retry`              |
| `unknown_error`           | soft\_decline  | 不明なエラー           | `use_different_card` |

**バリデーションエラー**（APIリクエスト失敗 — 400レスポンスに表示、**Webhookは発火しない**）:

| コード                             | 説明                  |
| ------------------------------- | ------------------- |
| `unsupported_currency`          | この決済方法で未対応の通貨       |
| `invalid_amount`                | 金額が正の整数でない          |
| `invalid_parameter_combination` | パラメータの組み合わせが不正      |
| `payment_method_not_found`      | 保存された決済方法が見つからない    |
| `customer_not_found`            | カスタマーが見つからない        |
| `missing_card`                  | カード情報が必要だが欠けている     |
| `not_supported`                 | このコネクタでサポートされていない操作 |

### ハンドリング例

```javascript theme={null}
// Webhookハンドラー
app.post('/webhooks/zafapay', (req, res) => {
  const { event, error } = req.body;

  if (event === 'payment.failed' && error) {
    switch (error.category) {
      case 'authentication':
        // 顧客に認証リンク付きメールを送信
        notifyCustomerForAuth(error.message);
        break;
      case 'soft_decline':
        // リトライまたは別のカードを要求
        if (error.recommended_action === 'retry') {
          scheduleRetry();
        } else {
          requestNewCard();
        }
        break;
      case 'hard_decline':
        // リトライしない、顧客に通知
        notifyCustomerCardInvalid(error.message);
        break;
      case 'gateway_error':
        // 一時的な問題、リトライをスケジュール
        scheduleRetryWithBackoff();
        break;
    }
  }

  res.json({ received: true });
});
```

<Note>
  元の決済プロバイダーのエラー詳細（PSP固有コード）は、APIレスポンスやWebhookには含まれません。統一された`code`と`category`フィールドを使用して、適切なアクションを判断してください。
</Note>

## 返金エラー

| コード              | HTTPステータス | 説明                                   |
| ---------------- | --------- | ------------------------------------ |
| `invalid_amount` | 400       | 返金額が決済額を超えています                       |
| `invalid_status` | 400       | 返金可能なステータスではありません（`completed`のみ返金可能） |

## リソースエラー

| コード                          | HTTPステータス | 説明                    |
| ---------------------------- | --------- | --------------------- |
| `not_found`                  | 404       | 指定されたリソースが見つかりません     |
| `flow_not_found`             | 404       | 決済フローが見つかりません         |
| `no_default_flow`            | 404       | デフォルトの決済フローが設定されていません |
| `flow_not_active`            | 400       | 決済フローが無効化されています       |
| `connector_config_not_found` | 404       | コネクタ設定が見つかりません        |

## 冪等性エラー

| コード                        | HTTPステータス | 説明                      |
| -------------------------- | --------- | ----------------------- |
| `idempotency_key_mismatch` | 400       | 同じ冪等キーで異なるリクエストが送信されました |
| `idempotency_key_in_use`   | 409       | 冪等キーが現在処理中です            |

## システムエラー

| コード              | HTTPステータス | 説明                           |
| ---------------- | --------- | ---------------------------- |
| `internal_error` | 500       | 内部エラーが発生しました。サポートにお問い合わせください |

## エラーハンドリングのベストプラクティス

<Steps>
  <Step title="HTTPステータスコードを確認">
    * `4xx`: クライアントエラー（リクエストの修正が必要）
    * `5xx`: サーバーエラー（リトライ可能）
  </Step>

  <Step title="エラーコードで処理を分岐">
    `error.code`を使用して、エラータイプに応じた処理を実装してください
  </Step>

  <Step title="リトライ戦略">
    `5xx`エラーや`idempotency_key_in_use`の場合は、指数バックオフでリトライしてください
  </Step>
</Steps>

## サンプルコード

```javascript Node.js theme={null}
async function createPayment(data) {
  const response = await fetch('https://api.sandbox.zafapay.com/v1/payments', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
      'Content-Type': 'application/json',
      'Idempotency-Key': generateIdempotencyKey()
    },
    body: JSON.stringify(data)
  });

  if (!response.ok) {
    const { error } = await response.json();

    // request_id をログに記録（サポートへの問い合わせ時に使用）
    console.error(`API Error [${error.request_id}]:`, error.code, error.message);

    switch (error.type) {
      case 'authentication_error':
        // トークンの再取得が必要
        throw new AuthError(error.message);

      case 'invalid_request_error':
        // リクエストパラメータを確認
        if (error.details) {
          console.error('Validation errors:', error.details);
        }
        throw new ValidationError(error.message);

      case 'payment_error':
        // 決済失敗（別の決済方法を試す等）
        throw new PaymentError(error.message);

      case 'api_error':
        // リトライ可能
        throw new RetryableError(error.message);

      default:
        throw new Error(error.message);
    }
  }

  return response.json();
}
```
