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

# モード C：即時履行 API

> URL 署名リダイレクト、即時履行、即時履行結果照会、履行結果通知 Webhook。ユーザーが支払いを完了した後、Waffo Point Topup がサプライヤーのエンドポイントを呼び出してユーザーアカウントを直接チャージします。

モード C は、ユーザーアカウントに直接クレジットをチャージできるサプライヤー向けです。ユーザーが支払いを完了すると、Waffo Point Topup がサプライヤーの API を呼び出してチャージを実行するため、ユーザーはポイントコードを扱う必要がありません。

<CardGroup cols={2}>
  <Card title="メリット" icon="circle-check">
    * シームレスなユーザー体験（ポイントコード不要）
    * 即時アカウントクレジット
    * 高いコンバージョン率
  </Card>

  <Card title="ユースケース" icon="users">
    * アカウントチャージシステムを持つゲームパブリッシャー
    * 会員 / サブスクリプションサービス
  </Card>
</CardGroup>

## 連携フロー

<Tabs>
  <Tab title="シンプルなフロー">
    <Frame>
      <img src="https://mintcdn.com/waffo-docs/hUiobY-hbNbq3QYe/images/developer-docs/point-topup/mode-c-flow-simple.png?fit=max&auto=format&n=hUiobY-hbNbq3QYe&q=85&s=abc2f4eabfba9391ae31235d0407ff4c" alt="モード C のシンプルなフロー：サプライヤーが署名付きリダイレクトリンクを生成、ユーザーが Waffo で決済、Waffo がサプライヤーの履行エンドポイントを呼び出す" width="3244" height="2933" data-path="images/developer-docs/point-topup/mode-c-flow-simple.png" />
    </Frame>
  </Tab>

  <Tab title="詳細なフロー">
    <Frame>
      <img src="https://mintcdn.com/waffo-docs/hUiobY-hbNbq3QYe/images/developer-docs/point-topup/mode-c-flow-detailed.png?fit=max&auto=format&n=hUiobY-hbNbq3QYe&q=85&s=72a90991e77f8e9d54d5545498ae843c" alt="モード C の詳細シーケンス：署名検証、セッション確立、決済、履行呼び出し、履行結果照会、Webhook 通知" width="4347" height="7001" data-path="images/developer-docs/point-topup/mode-c-flow-detailed.png" />
    </Frame>
  </Tab>
</Tabs>

## API 一覧

| No. | API 名                                     | 説明                                                                      | 提供元       | 連携要否         |
| --- | ----------------------------------------- | ----------------------------------------------------------------------- | --------- | ------------ |
| 1   | [URL 署名とリダイレクト](#url-署名とリダイレクト)           | URL 署名認証                                                                | Waffo     | **必須**       |
| 2   | [即時履行](#即時履行)                             | 即時履行の注文作成                                                               | サプライヤー    | 任意連携（サンプル仕様） |
| 3   | [即時履行結果照会](#即時履行結果照会)                     | 即時履行の注文照会。チャージリクエスト自体が冪等であれば不要です。Waffo は同じ注文 ID で再試行し、サプライヤーが冪等処理を保証します | サプライヤー    | 任意連携（サンプル仕様） |
| 4   | [履行結果通知 Webhook](#履行結果通知-webhook)         | Waffo が最終的な履行ステータスをサプライヤーに通知                                            | Waffo が送信 | 任意           |
| 5   | [Waffo 即時履行結果照会 API](#waffo-即時履行結果照会-api) | サプライヤーが Waffo から履行結果を能動的に取得                                             | Waffo     | 任意           |

<Note>
  最小構成は 1 番と 4 番だけです。署名リダイレクト + 履行結果通知 Webhook で、履行 API を実装しない形になります。この場合 Webhook が返すのは `PAY_SUCCESS` と `PAYMENT_FAILED` のみで、`PAY_SUCCESS` を受け取った後にサプライヤー側で発送処理を行います。
</Note>

## URL 署名とリダイレクト

パラメータの完全性を保証するため、署名検証付きの URL パラメータでユーザー情報を渡します。これは Waffo が提供するエンドポイントです。

**パラメータの一覧、型、必須区分、例は [API リファレンス：署名リダイレクト](/docs/api-reference/point-topup-redirect/signed-redirect-to-checkout)を参照してください。** 本セクションでは署名の計算方法を説明します。

<Warning>
  **署名は必ずサプライヤーのバックエンドで生成してください。`SECRET_KEY` をフロントエンドで公開してはなりません。** 署名の有効性はタイムスタンプで制御され、2 時間以上経過したリクエストは拒否されます。
</Warning>

URL 形式：

```text theme={null}
https://{supplier}.waffoplay.com/redirect?amount={amount}&currency={currency}&faceValue={faceValue}&notifyUrl={notifyUrl}&productId={productId}&returnUrl={returnUrl}&salesOrderId={salesOrderId}&siteCode={siteCode}&supplierId={supplierId}&supplierUserAccount={supplierUserAccount}&theme={theme}&timestamp={timestamp}&signature={signature}
```

`{supplier}` は、オンボーディング時に Waffo から割り当てられる専用サブドメインです。

<Info>
  **サプライヤー独自のカスタムドメインも利用できます。** その場合、サプライヤー側で当該ドメインを Waffo のエンドポイントに向ける必要があります。カスタムドメインを使う場合、署名対象文字列のベース URL もそのカスタムドメインに置き換えてください。署名はベース URL を含むため、ドメインが一致しないと検証に失敗します。
</Info>

### 署名アルゴリズム

<Steps>
  <Step title="パラメータの除外">
    `signature` パラメータを除外し、**値が空（空文字・空白）のパラメータをすべて除きます**。
  </Step>

  <Step title="パラメータのソート">
    残りのパラメータを、キー名の ASCII 昇順で並べ替えます。
  </Step>

  <Step title="文字列の連結">
    署名対象文字列を `{baseUrl}?key1=value1&key2=value2&...` の形式で構築します。**ベース URL（スキーム + ホスト + パス、例：`https://supplier.waffoplay.com/redirect`）を必ず含めてください。**
  </Step>

  <Step title="署名の計算">
    共有の `SECRET_KEY` を HMAC キーとして、署名対象文字列に対し HMAC-SHA256 を計算します。
  </Step>

  <Step title="大文字への変換">
    16 進数（HEX）にエンコードし、大文字に変換します。
  </Step>
</Steps>

### 署名例

指定されたパラメータ：

| パラメータ                   | 値                                                      |
| ----------------------- | ------------------------------------------------------ |
| `supplierUserAccount`   | `supplier_user_12345`                                  |
| `supplierUserEmail`     | `test@supplier.com`                                    |
| `supplierId`            | `WAFFO_POINT_TOPUP_001`                                |
| `faceValue`（額面）         | `3000`                                                 |
| `amount`（金額）            | `3000`                                                 |
| `currency`（通貨）          | `JPY`                                                  |
| `salesOrderId`（販売注文 ID） | `A123456`                                              |
| `siteCode`（サイトコード）      | `US`                                                   |
| `returnUrl`（リターン URL）   | `https://example.com/return_url_page?salesOrderId=XXX` |
| `theme`（テーマ）            | `light`                                                |
| `timestamp`（タイムスタンプ）    | `1713483091000`                                        |
| `SECRET_KEY`（秘密鍵）       | `your_secret_key_here`                                 |

ステップ 1 - パラメータのソートと連結：

```text theme={null}
https://supplier.waffoplay.com/redirect?amount=3000&currency=JPY&faceValue=3000&returnUrl=https://example.com/return_url_page?salesOrderId=XXX&salesOrderId=A123456&siteCode=US&supplierId=WAFFO_POINT_TOPUP_001&supplierUserAccount=supplier_user_12345&supplierUserEmail=test@supplier.com&theme=light&timestamp=1713483091000
```

ステップ 2 - HMAC-SHA256 を計算し、大文字に変換：

```text theme={null}
signature = HMAC_SHA256(上記の文字列).toUpperCase()
```

完全な URL の例：

```text theme={null}
https://supplier.waffoplay.com/redirect?amount=3000&currency=JPY&faceValue=3000&returnUrl=https%3A%2F%2Fexample.com%2Freturn_url_page%3FsalesOrderId%3DXXX&salesOrderId=A123456&siteCode=US&supplierId=WAFFO_POINT_TOPUP_001&supplierUserAccount=supplier_user_12345&supplierUserEmail=test@supplier.com&theme=light&timestamp=1713483091000&signature=ABC123XYZ456DEF789
```

<Tip>
  署名対象文字列では `returnUrl` は**エンコードしない値**を使い、最終的な URL では **URL エンコードした値**を使います。署名と送信は別の工程なので、両者を混同しないでください。
</Tip>

### Java 実装例

```java theme={null}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;

public class SignatureUtil {

    private static final String HMAC_SHA256 = "HmacSHA256";

    /**
     * Generate signature using HMAC-SHA256 algorithm (including base URL)
     *
     * @param baseUrl   base URL including host and path (e.g., https://supplier.waffoplay.com/redirect)
     * @param params    request parameters
     * @param secretKey secret key
     * @return signature string (uppercase hexadecimal)
     */
    public static String generateSignature(String baseUrl, Map<String, String> params, String secretKey) {
        try {
            // Use TreeMap for automatic alphabetical sorting
            TreeMap<String, String> sortedParams = new TreeMap<>(params);

            // Remove existing signature parameter if present
            sortedParams.remove("signature");

            // Build full URL with sorted parameters
            StringBuilder fullUrl = new StringBuilder(baseUrl);
            fullUrl.append("?");
            for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
                if (fullUrl.charAt(fullUrl.length() - 1) != '?') {
                    fullUrl.append("&");
                }
                fullUrl.append(entry.getKey())
                       .append("=")
                       .append(entry.getValue());
            }

            // Calculate signature using HMAC-SHA256 algorithm
            Mac mac = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec secretKeySpec = new SecretKeySpec(
                secretKey.getBytes(StandardCharsets.UTF_8),
                HMAC_SHA256
            );
            mac.init(secretKeySpec);
            byte[] hash = mac.doFinal(fullUrl.toString().getBytes(StandardCharsets.UTF_8));

            // Convert to hexadecimal string and uppercase
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            return hexString.toString().toUpperCase();
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate HMAC-SHA256 signature", e);
        }
    }

    /**
     * Verify signature
     */
    public static boolean verifySignature(String baseUrl, Map<String, String> params, String secretKey) {
        String receivedSignature = params.get("signature");
        if (receivedSignature == null || receivedSignature.isEmpty()) {
            return false;
        }
        String calculatedSignature = generateSignature(baseUrl, params, secretKey);
        return calculatedSignature.equals(receivedSignature);
    }

    /**
     * Build the final signed URL
     */
    public static String buildSignedUrl(String baseUrl, Map<String, String> params, String secretKey) {
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        sortedParams.remove("signature");

        String signature = generateSignature(baseUrl, sortedParams, secretKey);
        sortedParams.put("signature", signature);

        StringBuilder url = new StringBuilder(baseUrl);
        url.append("?");
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            if (url.charAt(url.length() - 1) != '?') {
                url.append("&");
            }
            url.append(entry.getKey())
               .append("=")
               .append(entry.getValue());
        }

        return url.toString();
    }
}
```

<Card title="Node.js 署名実装ガイド（HMAC-SHA256 & RSA-SHA256）" icon="file-archive" href="/docs/files/developer-docs/point-topup/waffoplay-sign-methods.zip">
  `Waffoplay Sign Methods.zip` をダウンロードしてください。Node.js による HMAC-SHA256 と RSA-SHA256 の署名実装が含まれます。
</Card>

### 検証フロー

ユーザーが署名付き URL にアクセスすると、Waffo Point Topup バックエンドは以下を実行します。

1. すべての URL パラメータを受信
2. 必須パラメータ（`faceValue`、`salesOrderId`、`supplierId`、`supplierUserAccount`、`siteCode`、`timestamp`、`signature`）が全て存在することを検証
3. `timestamp` が有効期間内（2 時間）であることを確認
4. 同じアルゴリズムで署名を再計算
5. リクエスト内の署名と計算した署名を比較
6. 検証成功時、JWT トークンを生成しユーザーセッションに設定
7. 対象の `siteCode` 購入ページへリダイレクト

エラー処理：

| 状況          | 結果                               |
| ----------- | -------------------------------- |
| パラメータ不足     | 必須パラメータ不足を示すメッセージ付きエラーページへリダイレクト |
| 署名検証失敗      | 無効な署名を示すメッセージ付きエラーページへリダイレクト     |
| タイムスタンプ期限切れ | リンク期限切れを示すメッセージ付き期限切れページへリダイレクト  |

## 即時履行

ユーザーが支払いを完了した後、Waffo Point Topup によって呼び出されます。

| 項目     | 値                                                                                            |
| ------ | -------------------------------------------------------------------------------------------- |
| Method | `POST`                                                                                       |
| Path   | サプライヤーが定義                                                                                    |
| 認証     | 共通 API 署名（SHA256WithRSA）。[API 共通仕様](/docs/ja/developer-docs/point-topup/api-common#api-セキュリティ)を参照 |

<Warning>
  本セクションで定義する API 仕様は**サンプル／参考用のみ**です。すでにポイント直接チャージ用の API を有している場合、既存の API ドキュメントを Waffo Point Topup に提供すれば統合を行うことができます。Waffo Point Topup はサプライヤーの既存 API インターフェースに適合します。
</Warning>

<Warning>
  **冪等性の要件：** サプライヤーは `salesOrderId` + `supplierId` を使用して冪等性をサポートする必要があります。同じリクエストが複数回受信された場合、同じ成功レスポンスを返してください。
</Warning>

<Tip>
  **推奨事項：** 同一の `salesOrderId` において、`faceValue`、`amount`、`currency` が内部システムの注文情報と厳密に一致しているかを確認することを強く推奨します。業務チェック（バリデーション）に失敗した場合は、該当する取引のフルフィルメント（注文履行）を拒否してください。
</Tip>

### リクエスト

| フィールド名         |                       | 説明                                                                              | 型             | 必須 |
| -------------- | --------------------- | ------------------------------------------------------------------------------- | ------------- | -- |
| `supplierId`   |                       | Waffo からサプライヤーに割り当てられた固有の ID                                                    | String(64)    | 必須 |
| `salesOrderId` |                       | 署名 URL で提供されるサプライヤーの注文 ID。`supplierId` と併せて冪等キーとして使用                            | String(64)    | 必須 |
| `faceValue`    |                       | 額面金額                                                                            | String(32)    | 必須 |
| `amount`       |                       | 注文に対して実際に支払われる金額。**額面**と**注文金額**の両方を受け取る場合、これら 2 つのフィールド間で**検証チェック**を行うことを推奨します | DECIMAL(20,8) | 任意 |
| `currency`     |                       | 注文価格の通貨                                                                         | String(3)     | 任意 |
| `requestedAt`  |                       | Waffo 側リクエスト時間。ISO 8601 拡張形式（UTC）、`YYYY-MM-DDThh:mm:ss.sssZ`                    | String(32)    | 必須 |
| `buyerInfo`    |                       | 購入者情報。直接チャージを伴うシナリオでは、このフィールドは必須です                                              | Object        | 任意 |
|                | `supplierUserAccount` | 課金アカウント ID                                                                      | String(64)    | 任意 |

<Note>
  `requestedAt` において、`T` は日付と時刻の区切り記号、`.000` はミリ秒、`Z` は協定世界時（UTC）を示します。例：`2025-01-05T10:30:00.000Z` は、UTC の 2025 年 1 月 5 日 10 時 30 分 00 秒を指します。
</Note>

### レスポンス

| フィールド名         | 説明                                                        | 型          | 必須 |
| -------------- | --------------------------------------------------------- | ---------- | -- |
| `salesOrderId` | 署名 URL で提供されるサプライヤーの注文 ID                                 | String(32) | 必須 |
| `status`       | 注文ステータス：`IN_PROGRESS` 購入処理中、`SUCCESS` 購入成功、`FAILURE` 購入失敗 | String(24) | 必須 |

レスポンス例：

```json theme={null}
{
  "code": "0",
  "msg": "success",
  "data": {
    "salesOrderId": "M202504160311311156635",
    "status": "IN_PROGRESS"
  }
}
```

## 即時履行結果照会

Waffo Point Topup がサプライヤーに履行結果を照会するために呼び出します。

| 項目     | 値                                                                                            |
| ------ | -------------------------------------------------------------------------------------------- |
| Method | `POST`                                                                                       |
| Path   | サプライヤーが定義                                                                                    |
| 認証     | 共通 API 署名（SHA256WithRSA）。[API 共通仕様](/docs/ja/developer-docs/point-topup/api-common#api-セキュリティ)を参照 |

<Note>
  即時履行エンドポイント自体が冪等であれば、この照会エンドポイントは不要です。Waffo は同じ `salesOrderId` で再試行し、サプライヤーが冪等処理を保証します。
</Note>

### リクエスト

| フィールド名         | 説明                           | 型          | 必須 |
| -------------- | ---------------------------- | ---------- | -- |
| `salesOrderId` | 署名 URL で提供されるサプライヤーの注文 ID    | String(64) | 必須 |
| `supplierId`   | Waffo からサプライヤーに割り当てられた固有の ID | String(64) | 必須 |

### レスポンス

| フィールド名          |   | 説明                                        | 型          | 必須 |
| --------------- | - | ----------------------------------------- | ---------- | -- |
| `salesOrderId`  |   | 署名 URL で提供されるサプライヤーの注文 ID                 | String(64) | 必須 |
| `supplierId`    |   | Waffo からサプライヤーに割り当てられた固有の ID              | String(64) | 必須 |
| `status`        |   | 注文ステータス：`IN_PROGRESS`、`SUCCESS`、`FAILURE` | String(24) | 必須 |
| `faceValue`     |   | 額面金額。動的価値を持つゲームポイントコード商品を指定する場合は明示が必要です   | String(32) | 任意 |
| `buyerInfo`     |   | 購入者情報。オブジェクト構造は即時履行リクエストと同様です             | Object     | 任意 |
| `requestedAt`   |   | Waffo 側のリクエスト日時（履行リクエスト時と同一）              | String(32) | 必須 |
| `completedTime` |   | 注文完了時刻                                    | String(32) | 任意 |
| `failureCode`   |   | 注文失敗コード                                   | String(32) | 任意 |
| `failureReason` |   | 注文失敗理由                                    | String(64) | 任意 |

レスポンス例：

```json theme={null}
{
  "code": "0",
  "msg": "success",
  "data": {
    "salesOrderId": "M202504160311311156635",
    "supplierId": "123",
    "status": "SUCCESS",
    "faceValue": 1000,
    "buyerInfo": {
      "supplierUserAccount": "user@example.com"
    },
    "requestedAt": "2024-04-16T03:11:31.000Z",
    "completedTime": "2024-04-16T03:12:31.000Z"
  }
}
```

## 履行結果通知 Webhook

Waffo Point Topup は、署名リダイレクトの `notifyUrl` パラメータで指定したエンドポイントに最終的な履行ステータスを POST します。「決済成功後の履行失敗」「決済失敗」「タイムアウトによる未払い」の各ケースに対応しています。

**全フィールド、`failureCode` の一覧、リクエスト例は [API リファレンス：履行結果 Webhook](/docs/api-reference/ja/point-topup-fulfillment-webhook)を参照してください。**

<Warning>
  **冪等性の要件：** サプライヤーは `salesOrderId` + `supplierId` を使用して冪等性をサポートする必要があります。同じ通知が複数回受信された場合、同じ成功レスポンスを返してください。
</Warning>

連携ロジックを決めるのは `fulfillmentStatus` です。

| 値                           | 意味                                                        |
| --------------------------- | --------------------------------------------------------- |
| `SUCCESS`                   | 履行成功                                                      |
| `PAY_SUCCESS`               | 支払い成功。フルフィルメント API を未実装のサプライヤーに通知され、自社の発送処理を開始するトリガーとなります |
| `PAY_SUCCESS_SUPPLY_FAILED` | 支払い成功、但し履行失敗。返金または再発行の確認が必要です                             |
| `PAYMENT_FAILED`            | 支払い失敗（ユーザーの支払いタイムアウトを含む）                                  |

<Note>
  最小構成（署名リダイレクト + 本 Webhook のみ、履行 API なし）を選択した場合、Waffo が返すのは `PAY_SUCCESS` と `PAYMENT_FAILED` のみです。
</Note>

### レスポンスと再試行ポリシー

履行コールバックを受信した後、正常に処理できた場合は HTTP `200 OK` を返し、メッセージ本文に `success` を含めてください。Waffo Point Topup はこれをもって履行結果がサプライヤーに正常に通知されたと判断します。**それ以外の場合、Waffo Point Topup は履行通知を再試行します。**

```json theme={null}
{
    "message": "success"
}
```

| 項目      | 値                                                       |
| ------- | ------------------------------------------------------- |
| 再試行間隔   | 5 秒、30 秒、1 分、5 分、30 分、1 時間、2 時間、4 時間、8 時間、24 時間、24 時間…… |
| 最大再試行回数 | 15 回                                                    |
| 最大再試行後  | 通知は失敗としてマークされ、手動処理が必要になります                              |

## Waffo 即時履行結果照会 API

サプライヤーが Waffo Point Topup に対してモード C 注文の履行結果を能動的に照会するためのエンドポイントです。履行結果通知 Webhook の**プル型の対応版**であり、通常は消込（リコンサイル）や Webhook 通知を受信できなかった場合に使用します。レスポンスの `data` 構造は Webhook の `data` と同一です。

**完全なパラメータ、レスポンス例、オンライン実行は [API リファレンス：即時履行結果照会](/docs/api-reference/point-topup-fulfillment-inquiry/fulfillment-inquiry)を参照してください。**

| 項目     | 値                                                                                            |
| ------ | -------------------------------------------------------------------------------------------- |
| Method | `POST`                                                                                       |
| Path   | `/api/v1/gamepin/fulfillment-inquiry`                                                        |
| 認証     | 共通 API 署名（SHA256WithRSA）。[API 共通仕様](/docs/ja/developer-docs/point-topup/api-common#api-セキュリティ)を参照 |

照会キー：`supplierId` は必須、`salesOrderId` と `payOrderId` のいずれか一方は必須です。注文が存在しない、または呼び出し元のサプライヤーに属さない場合は、[エラーコード](/docs/ja/developer-docs/point-topup/api-common#エラーコード)仕様に従いエラーレスポンスを返します。

```json theme={null}
{
    "supplierId": "WAFFO_POINT_TOPUP_001",
    "salesOrderId": "A123456"
}
```

## 次のステップ

<CardGroup cols={2}>
  <Card title="API 共通仕様" icon="shield-check" href="/docs/ja/developer-docs/point-topup/api-common">
    メッセージ構造、RSA 署名と検証、エラーコード、鍵の生成。
  </Card>

  <Card title="連携の概要" icon="map" href="/docs/ja/developer-docs/point-topup/overview">
    3つのモードの詳細比較と準備チェックリスト。
  </Card>
</CardGroup>
