> ## 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.

# 継続決済（リカーリング）

> カード情報を保存して継続的な課金を行う

## 概要

継続決済（リカーリング）機能を使用すると、顧客のカード情報を安全に保存し、2回目以降の決済でカード入力なしに課金できます。サブスクリプションや定期課金に最適です。

<Note>
  継続決済は一部のゲートウェイでのみ利用可能です。利用可否については担当者にお問い合わせください。
</Note>

<Tip>
  自社の決済フォームで[サーバー間決済（S2S）](/ja/guide/s2s-payments#カード保存と継続決済)を使用している場合、`token`、`save_card: true`、`customer_id` を同時に指定してカードを保存することもできます。詳細はS2Sガイドをご覧ください。
</Tip>

## 継続決済フロー

```mermaid theme={null}
sequenceDiagram
    participant M as 加盟店
    participant Z as ZAFA PAY
    participant C as 顧客

    Note over M,C: 初回決済（カード保存）
    M->>Z: POST /v1/payments (save_card=true)
    Z-->>M: payment_url
    M->>C: リダイレクト
    C->>Z: カード入力・決済
    Z-)M: Webhook (payment_method_id付き)

    Note over M,C: 2回目以降の決済
    M->>Z: POST /v1/payments (payment_method_id)
    Z-->>M: 決済結果（即時完了）
    Z-)M: Webhook (is_recurring=true)
```

## 初回決済（カード保存）

初回決済時に `save_card: true` と `customer_id` を指定してカードを保存します。

### リクエスト

```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/payments \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000,
    "currency": "jpy",
    "customer_id": "cust_abc123",
    "save_card": true,
    "external_id": "subscription_initial_001",
    "metadata": {
      "plan": "premium"
    }
  }'
```

<ParamField body="customer_id" type="string" required>
  顧客を識別する一意のID。加盟店側で管理するユーザーIDなどを指定してください。
</ParamField>

<ParamField body="save_card" type="boolean" required>
  継続決済の初回では `true` を指定してください。決済完了後にカード情報が保存されます。
</ParamField>

### レスポンス

```json theme={null}
{
  "id": "req_abc123",
  "status": "pending",
  "amount": 1000,
  "currency": "jpy",
  "payment_type": "initial",
  "payment_url": "https://pay.sandbox.zafapay.com/checkout/req_abc123?token=xxx",
  "created_at": "2025-01-01T00:00:00.000Z"
}
```

<Note>
  `payment_type: "initial"` は、このリクエストがカード保存を伴う初回決済であることを示します。`payment_method_id` は決済完了後の Webhook で受け取ってください。
</Note>

顧客を `payment_url` にリダイレクトして決済を完了させます。

### 決済完了時のWebhook

```json theme={null}
{
  "event": "payment.succeeded",
  "transaction_id": "tx_abc123",
  "status": "succeeded",
  "amount": "1000",
  "currency": "jpy",
  "external_id": "subscription_initial_001",
  "payment_method": "card",
  "payment_method_id": "pmi_xyz789",
  "save_card": true,
  "is_recurring": false,
  "customer_id": "cust_abc123",
  "card_brand": "visa",
  "card_last4": "4242",
  "card_country": "JP",
  "card_funding": "credit",
  "metadata": { "plan": "premium" },
  "created_at": "2025-01-01T00:00:00.000Z",
  "timestamp": "2025-01-01T00:00:05.000Z"
}
```

<Tip>
  `payment_method_id`（`pmi_xxx` 形式）を保存しておくと、2回目以降の決済で使用できます。
</Tip>

## 保存済みカードの確認

顧客の保存済みカードを一覧取得できます。

```bash theme={null}
curl https://api.sandbox.zafapay.com/v1/customers/cust_abc123/payment-methods \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

### レスポンス

```json theme={null}
{
  "customer_id": "cust_abc123",
  "payment_methods": [
    {
      "id": "pmi_xyz789",
      "type": "card",
      "card": {
        "brand": "visa",
        "last4": "4242",
        "exp_month": 12,
        "exp_year": 2028,
        "cardholder_name": "TARO YAMADA",
        "country": "JP",
        "funding": "credit"
      },
      "is_default": true,
      "status": "active",
      "created_at": "2025-01-01T00:00:00.000Z"
    }
  ]
}
```

## 継続決済の実行

保存済みの `payment_method_id` を使用して、カード入力なしで決済できます。

### リクエスト

```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/payments \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000,
    "currency": "jpy",
    "customer_id": "cust_abc123",
    "payment_method_id": "pmi_xyz789",
    "external_id": "subscription_renewal_002",
    "metadata": {
      "plan": "premium",
      "billing_cycle": 2
    }
  }'
```

<ParamField body="payment_method_id" type="string" required>
  保存済みの決済方法ID（`pmi_xxx` 形式）
</ParamField>

### レスポンス

継続決済は即時処理されます（`payment_url` は返りません）。

```json theme={null}
{
  "id": "req_def456",
  "transaction_id": "tx_def456",
  "status": "succeeded",
  "amount": 1000,
  "currency": "jpy",
  "payment_type": "recurring",
  "created_at": "2025-02-01T00:00:00.000Z"
}
```

<Note>
  `payment_type: "recurring"` は、保存済みカードを使用した継続決済であることを示します。
</Note>

### 決済完了時のWebhook（成功）

```json theme={null}
{
  "event": "payment.succeeded",
  "transaction_id": "tx_def456",
  "status": "succeeded",
  "amount": "1000",
  "currency": "jpy",
  "external_id": "subscription_renewal_002",
  "payment_method": "card",
  "payment_method_id": "pmi_xyz789",
  "is_recurring": true,
  "customer_id": "cust_abc123",
  "card_brand": "visa",
  "card_last4": "4242",
  "card_country": "JP",
  "card_funding": "credit",
  "metadata": { "plan": "premium", "billing_cycle": 2 },
  "created_at": "2025-02-01T00:00:00.000Z",
  "timestamp": "2025-02-01T00:00:05.000Z"
}
```

<Note>
  継続決済のWebhookでは `is_recurring: true` が設定されます。
</Note>

### 決済失敗時のWebhook

継続決済が失敗した場合、`payment.failed` イベントが送信されます。`error` オブジェクトに失敗の詳細が含まれます。

```json theme={null}
{
  "event": "payment.failed",
  "transaction_id": "tx_def456",
  "status": "failed",
  "amount": "1000",
  "currency": "jpy",
  "external_id": "subscription_renewal_002",
  "payment_method": "card",
  "payment_method_id": "pmi_xyz789",
  "is_recurring": true,
  "customer_id": "cust_abc123",
  "card_brand": "visa",
  "card_last4": "4242",
  "error": {
    "code": "card_declined",
    "category": "soft_decline",
    "message": "The card was declined",
    "recommended_action": "use_different_card"
  },
  "created_at": "2025-02-01T00:00:00.000Z",
  "timestamp": "2025-02-01T00:00:05.000Z"
}
```

<Note>
  `error.recommended_action` の値に応じて、適切な対応を行ってください。

  | 値                    | 説明                   |
  | -------------------- | -------------------- |
  | `retry`              | 一時的なエラー。時間をおいてリトライ可能 |
  | `contact_customer`   | 顧客への連絡が必要（残高不足等）     |
  | `use_different_card` | 別のカードでの決済が必要（期限切れ等）  |
  | `none`               | 特別な対応は不要             |
</Note>

## 決済方法の管理

### カードのブロック

不正利用の疑いがある場合など、特定のカードをブロックできます。

```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/customers/payment-methods/pmi_xyz789/block \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "不正利用の疑い"
  }'
```

### ブロック解除

```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/customers/payment-methods/pmi_xyz789/unblock \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

### カードの削除（Detach）

顧客の要求によりカード情報を完全に削除する場合は、detachを使用します。

```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/customers/payment-methods/pmi_xyz789/detach \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

#### レスポンス

```json theme={null}
{
  "id": "pmi_xyz789",
  "deleted": true
}
```

<Warning>
  削除されたカードは復元できません。再度利用するには、顧客にカードを再登録してもらう必要があります。
</Warning>

<Note>
  **ブロックと削除の違い:**

  * **ブロック**: 一時的に使用を停止（不正対策）。後でアンブロック可能
  * **削除（Detach）**: 完全に削除（GDPR対応、顧客の削除要求）。復元不可
</Note>

## エラーハンドリング

### 継続決済がサポートされていない場合

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "recurring_not_supported",
    "message": "Recurring payments are not supported for this connector",
    "request_id": "req_xxx"
  }
}
```

### カードの有効期限切れ

```json theme={null}
{
  "error": {
    "type": "payment_error",
    "code": "expired_card",
    "message": "The card has expired",
    "request_id": "req_xxx"
  }
}
```

<Warning>
  カードの有効期限が切れた場合や、カード会社によって拒否された場合は、顧客に新しいカードを登録してもらう必要があります。
</Warning>

## ベストプラクティス

<Steps>
  <Step title="customer_id を一意に管理">
    加盟店側のユーザーIDと `customer_id` を1対1で紐付けてください。同じ顧客に異なる `customer_id` を使用すると、カード情報が分散してしまいます。
  </Step>

  <Step title="payment_method_id を永続化">
    Webhookで受け取った `payment_method_id` をデータベースに保存し、次回以降の決済に使用してください。
  </Step>

  <Step title="失敗時のリトライ戦略">
    カード決済は一時的なエラーで失敗することがあります。指数バックオフでリトライし、複数回失敗した場合は顧客に通知してください。
  </Step>

  <Step title="有効期限の管理">
    保存済みカードの有効期限を監視し、期限が近づいたら顧客にカード更新を促してください。
  </Step>
</Steps>
