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

# iframe埋め込み決済

> 決済フォームをiframeで埋め込む方法

## 概要

ZAFA PAYの決済ページをiframeで埋め込むことで、ユーザーをリダイレクトせずに自社サイト内で決済を完結させることができます。

## 結果通知方式

決済結果の通知方式は2種類あります。

| 方式              | 説明                       | 用途                      |
| --------------- | ------------------------ | ----------------------- |
| **postMessage** | JavaScriptで親ウィンドウにイベント送信 | SPAなどJavaScript制御が可能な場合 |
| **リダイレクト**      | 親ウィンドウごとリダイレクト           | サーバーサイドで結果を処理したい場合      |

<Note>
  利用する方式は加盟店ごとに事前に設定されます。変更が必要な場合はサポートにお問い合わせください。
</Note>

## 実装手順

### 1. 決済リクエストを作成

通常通り`POST /v1/payments`で決済リクエストを作成します。レスポンスの`payment_url`を取得します。

```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/payments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 100,
    "currency": "usd",
    "external_id": "order_12345",
    "success_redirect_url": "https://your-site.com/success",
    "failure_redirect_url": "https://your-site.com/failure"
  }'
```

### 2. iframeに埋め込む

`payment_url`に`?mode=embedded`パラメータを追加してiframeに設定します。

```html theme={null}
<iframe
  src="https://pay.zafapay.com/checkout/req_xxx?token=xxx&mode=embedded"
  width="100%"
  height="700"
  style="border: none;"
  allow="payment"
></iframe>
```

<Warning>
  `mode=embedded`パラメータは必須です。これがないと、決済完了時にリダイレクトが発生します。
</Warning>

***

## postMessage方式

postMessage方式が設定されている場合、決済の結果はJavaScriptのpostMessageで親ウィンドウに通知されます。

### イベントを受信

```javascript theme={null}
window.addEventListener('message', function(event) {
  // セキュリティ: オリジンを検証
  if (event.origin !== 'https://pay.zafapay.com') {
    return;
  }

  const data = event.data;
  if (!data || !data.type) return;

  switch (data.type) {
    case 'payment_success':
      console.log('決済成功:', data.transactionId);
      // 成功処理
      break;
    case 'payment_failed':
      console.log('決済失敗:', data.error);
      // 失敗処理
      break;
    case 'payment_cancel':
      console.log('キャンセル');
      // キャンセル処理
      break;
    case 'redirect_required':
      console.log('リダイレクトが必要:', data.url);
      // 3Dセキュア等のリダイレクト処理
      window.open(data.url, '_blank');
      break;
  }
});
```

## イベントタイプ

| イベント                | 説明                | 含まれるデータ                      |
| ------------------- | ----------------- | ---------------------------- |
| `payment_success`   | 決済成功              | `transactionId`, `requestId` |
| `payment_failed`    | 決済失敗              | `error`, `code`              |
| `payment_cancel`    | ユーザーがキャンセル        | なし                           |
| `redirect_required` | 3Dセキュア等のリダイレクトが必要 | `url`, `reason`              |

### payment\_success

決済が正常に完了した場合に送信されます。

```json theme={null}
{
  "type": "payment_success",
  "transactionId": "tx_abc123",
  "requestId": "req_xyz789"
}
```

### payment\_failed

決済が失敗した場合に送信されます。

```json theme={null}
{
  "type": "payment_failed",
  "error": "カードが拒否されました",
  "code": "card_declined"
}
```

### payment\_cancel

ユーザーが「キャンセルしてサイトに戻る」をクリックした場合に送信されます。

```json theme={null}
{
  "type": "payment_cancel"
}
```

### redirect\_required

3Dセキュア認証などでリダイレクトが必要な場合に送信されます。

<Note>
  このイベントは特定の決済方法でのみ発生します。決済方法によっては、認証処理が自動的に行われるため、このイベントが発生しない場合があります。
</Note>

```json theme={null}
{
  "type": "redirect_required",
  "url": "https://auth.example.com/3ds/...",
  "reason": "3ds_required"
}
```

<Note>
  リダイレクトが必要な場合、新しいタブ/ウィンドウで開くか、親ウィンドウでナビゲートしてください。iframe内でのリダイレクトはセキュリティ上の制限により機能しない場合があります。
</Note>

## 完全な実装例

```html theme={null}
<!DOCTYPE html>
<html>
<head>
  <title>決済ページ</title>
</head>
<body>
  <h1>お支払い</h1>

  <div id="checkout-container">
    <!-- iframeは動的に挿入 -->
  </div>

  <script>
    // 決済URLを設定（バックエンドから取得）
    const paymentUrl = 'YOUR_PAYMENT_URL';

    // iframe埋め込み
    const container = document.getElementById('checkout-container');
    const iframe = document.createElement('iframe');
    iframe.src = paymentUrl + (paymentUrl.includes('?') ? '&' : '?') + 'mode=embedded';
    iframe.style.width = '100%';
    iframe.style.height = '700px';
    iframe.style.border = 'none';
    iframe.setAttribute('allow', 'payment');
    container.appendChild(iframe);

    // イベントリスナー
    window.addEventListener('message', function(event) {
      if (event.origin !== 'https://pay.zafapay.com') return;

      const data = event.data;
      if (!data || !data.type) return;

      switch (data.type) {
        case 'payment_success':
          alert('決済が完了しました！');
          window.location.href = '/thank-you?tx=' + data.transactionId;
          break;
        case 'payment_failed':
          alert('決済に失敗しました: ' + data.error);
          break;
        case 'payment_cancel':
          if (confirm('決済をキャンセルしますか？')) {
            window.location.href = '/cart';
          }
          break;
        case 'redirect_required':
          window.open(data.url, '_blank');
          break;
      }
    });
  </script>
</body>
</html>
```

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

<Steps>
  <Step title="オリジン検証">
    postMessageを受信する際は、必ず`event.origin`を検証してください。
  </Step>

  <Step title="HTTPS必須">
    iframe埋め込みはHTTPSサイトでのみ動作します。
  </Step>

  <Step title="Webhookとの併用">
    決済結果の確定にはpostMessageだけでなく、必ずサーバーサイドでWebhookを受信して検証してください。
  </Step>
</Steps>

***

## リダイレクト方式

リダイレクト方式が設定されている場合、決済結果は親ウィンドウへのリダイレクトで通知されます。

### リダイレクト先URL

決済作成時に指定した`success_redirect_url`、`failure_redirect_url`、`cancel_redirect_url`にリダイレクトされます。

### URLパラメータ

| ステータス | リダイレクト先                | パラメータ                                               |
| ----- | ---------------------- | --------------------------------------------------- |
| 成功    | `success_redirect_url` | `?request_id=xxx&transaction_id=xxx&status=success` |
| 失敗    | `failure_redirect_url` | `?request_id=xxx&transaction_id=xxx&status=failed`  |
| キャンセル | `cancel_redirect_url`  | `?request_id=xxx&status=cancel`                     |

<Note>
  * キャンセル時は決済実行前のため`transaction_id`は含まれません。
  * `cancel_redirect_url`が設定されていない場合、キャンセル時は`failure_redirect_url`にリダイレクトされます。
</Note>

### パラメータ詳細

| パラメータ            | 説明                              |
| ---------------- | ------------------------------- |
| `request_id`     | 決済リクエストID（`req_`で始まる）           |
| `transaction_id` | トランザクションID（`tx_`で始まる）           |
| `status`         | `success` / `failed` / `cancel` |

### 実装例

```javascript theme={null}
// サーバーサイド（Node.js例）
app.get('/payment/callback', (req, res) => {
  const { request_id, transaction_id, status } = req.query;

  switch (status) {
    case 'success':
      // 成功処理（Webhookでも検証すること）
      res.redirect('/thank-you?order=' + request_id);
      break;
    case 'failed':
      // 失敗処理
      res.redirect('/checkout?error=payment_failed');
      break;
    case 'cancel':
      // キャンセル処理
      res.redirect('/cart');
      break;
  }
});
```

<Warning>
  決済結果の確定には、URLパラメータだけでなく必ずサーバーサイドでWebhookを受信して検証してください。URLパラメータは改ざんされる可能性があります。
</Warning>

***

## テスト

Sandbox環境でテストする場合は、以下のテストカードを使用できます。

| カード番号                 | 結果       |
| --------------------- | -------- |
| `4242 4242 4242 4242` | 成功       |
| `4000 0000 0000 3220` | 3Dセキュア必須 |
| `4000 0000 0000 9995` | 残高不足で拒否  |
| `4000 0000 0000 0002` | 一般的な拒否   |

有効期限は将来の日付、CVCは任意の3桁を使用してください。
