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

# 商品の作成

> マルチ通貨対応の新しい単発購入商品を作成する

マルチ通貨対応の新しい単発購入商品を作成します。

```
POST /v1/actions/onetime-product/create-product
```

**認証：** API Key

## リクエストボディ

| フィールド         | 型              | 必須  | 説明                                                                     |
| ------------- | -------------- | --- | ---------------------------------------------------------------------- |
| `storeId`     | string         | Yes | Store ID（Short ID フォーマット `STO_xxx`）                                    |
| `name`        | string         | Yes | 商品名（64 文字以下）                                                           |
| `description` | string \| null | No  | 商品説明（Markdown 対応；`null` または `""` で消去可能）                                |
| `prices`      | object         | Yes | マルチ通貨価格マップ（以下参照）                                                       |
| `media`       | array          | No  | 商品画像/動画（以下参照）                                                          |
| `successUrl`  | string \| null | No  | 購入成功後のリダイレクト URL（512 文字以下、有効な http(s) URL であること；`null` または `""` で消去可能） |
| `metadata`    | object         | No  | カスタムキー値データ（最大 50 キー）                                                   |

## 価格オブジェクトフォーマット

価格は ISO 4217 通貨コードから価格設定オブジェクトへのマップです。少なくとも 1 つの通貨が必要です。

```json theme={"system"}
{
  "USD": {
    "amount": "29.00",
    "taxIncluded": false,
    "taxCategory": "saas"
  },
  "EUR": {
    "amount": "27.00",
    "taxIncluded": true,
    "taxCategory": "saas"
  }
}
```

| フィールド         | 型       | 説明                                       |
| ------------- | ------- | ---------------------------------------- |
| `amount`      | string  | 表示フォーマット文字列としての価格（例：`"29.00"` = \$29.00） |
| `taxIncluded` | boolean | 金額に税が含まれているかどうか                          |
| `taxCategory` | string  | 税計算用のカテゴリ                                |

**サポートされる `taxCategory` 値:**

| 値                      | 説明                    |
| ---------------------- | --------------------- |
| `digital_goods`        | 一般的なデジタル商品            |
| `saas`                 | Software as a Service |
| `software`             | ダウンロード可能なソフトウェア       |
| `ebook`                | 電子書籍                  |
| `online_course`        | オンラインコースと教育           |
| `consulting`           | コンサルティングサービス          |
| `professional_service` | プロフェッショナルサービス         |

## メディアアイテムフォーマット

```json theme={"system"}
{
  "type": "image",
  "url": "https://example.com/product.png",
  "alt": "Product screenshot",
  "thumbnail": "https://example.com/product-thumb.png"
}
```

| フィールド       | 型      | 説明                        |
| ----------- | ------ | ------------------------- |
| `type`      | string | `image` または `video`       |
| `url`       | string | メディア URL                  |
| `alt`       | string | アクセシビリティ用の代替テキスト          |
| `thumbnail` | string | サムネイル URL（画像はオプション、動画は推奨） |

## リクエスト例

<CodeGroup>
  ```typescript SDK theme={"system"}
  import { WaffoPancake, TaxCategory } from "@waffo/pancake-ts";

  const client = new WaffoPancake({
    merchantId: process.env.WAFFO_MERCHANT_ID!,
    privateKey: process.env.WAFFO_PRIVATE_KEY!,
  });

  const { product } = await client.onetimeProducts.create({
    storeId: "STO_2aUyqjCzEIiEcYMKj7TZtw",
    name: "Premium Template Pack",
    description: "50 premium design templates for your next project.",
    prices: {
      USD: { amount: "49.00", taxIncluded: false, taxCategory: TaxCategory.DigitalGoods },
      EUR: { amount: "45.00", taxIncluded: true, taxCategory: TaxCategory.DigitalGoods },
    },
    media: [
      { type: "image", url: "https://example.com/templates-preview.png", alt: "Template preview" },
    ],
    successUrl: "https://example.com/thank-you",
    metadata: { category: "design", fileCount: "50" },
  });
  ```

  ```bash cURL theme={"system"}
  MERCHANT_ID="MER_2aUyqjCzEIiEcYMKj7TZtw"
  TIMESTAMP=$(date +%s)
  BODY='{
    "storeId": "STO_2aUyqjCzEIiEcYMKj7TZtw",
    "name": "Premium Template Pack",
    "description": "50 premium design templates for your next project.",
    "prices": {
      "USD": { "amount": "49.00", "taxIncluded": false, "taxCategory": "digital_goods" },
      "EUR": { "amount": "45.00", "taxIncluded": true, "taxCategory": "digital_goods" }
    },
    "successUrl": "https://example.com/thank-you"
  }'
  BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -binary | base64 -w 0)
  CANONICAL_REQUEST="POST
  /v1/actions/onetime-product/create-product
  $TIMESTAMP
  $BODY_HASH"

  SIGNATURE=$(echo -n "$CANONICAL_REQUEST" | openssl dgst -sha256 -sign private_key.pem | base64 -w 0)

  curl -X POST "https://api.waffo.ai/v1/actions/onetime-product/create-product" \
    -H "Content-Type: application/json" \
    -H "X-Merchant-Id: $MERCHANT_ID" \
    -H "X-Timestamp: $TIMESTAMP" \
    -H "X-Signature: $SIGNATURE" \
    -d "$BODY"
  ```

  ```python Python theme={"system"}
  import hashlib, time, base64, json, requests
  from cryptography.hazmat.primitives import hashes, serialization
  from cryptography.hazmat.primitives.asymmetric import padding

  merchant_id = "MER_2aUyqjCzEIiEcYMKj7TZtw"
  timestamp = str(int(time.time()))
  body = {
      "storeId": "STO_2aUyqjCzEIiEcYMKj7TZtw",
      "name": "Premium Template Pack",
      "description": "50 premium design templates for your next project.",
      "prices": {
          "USD": {"amount": "49.00", "taxIncluded": False, "taxCategory": "digital_goods"},
          "EUR": {"amount": "45.00", "taxIncluded": True, "taxCategory": "digital_goods"},
      },
      "successUrl": "https://example.com/thank-you",
  }

  body_json = json.dumps(body, separators=(",", ":"))
  body_hash = base64.b64encode(hashlib.sha256(body_json.encode()).digest()).decode()
  path = "/v1/actions/onetime-product/create-product"
  canonical = f"POST\n{path}\n{timestamp}\n{body_hash}"

  with open("private_key.pem", "rb") as f:
      private_key = serialization.load_pem_private_key(f.read(), password=None)

  signature = base64.b64encode(
      private_key.sign(canonical.encode(), padding.PKCS1v15(), hashes.SHA256())
  ).decode()

  resp = requests.post(
      f"https://api.waffo.ai{path}",
      headers={
          "Content-Type": "application/json",
          "X-Merchant-Id": merchant_id,
          "X-Timestamp": timestamp,
          "X-Signature": signature,
      },
      data=body_json,
  )
  print(resp.json())
  ```
</CodeGroup>

## 成功レスポンス (200)

```json theme={"system"}
{
  "data": {
    "product": {
      "id": "PROD_3kF9mNpQrStUvWxYz1A2bC",
      "storeId": "STO_2aUyqjCzEIiEcYMKj7TZtw",
      "name": "Premium Template Pack",
      "description": "50 premium design templates for your next project.",
      "prices": {
        "USD": { "amount": "49.00", "taxCategory": "digital_goods" },
        "EUR": { "amount": "45.00", "taxCategory": "digital_goods" }
      },
      "media": [
        { "type": "image", "url": "https://example.com/templates-preview.png", "alt": "Template preview" }
      ],
      "successUrl": "https://example.com/thank-you",
      "metadata": { "category": "design", "fileCount": "50" },
      "status": "active",
      "createdAt": "2026-01-15T10:30:00.000Z",
      "updatedAt": "2026-01-15T10:30:00.000Z"
    }
  }
}
```

## レスポンスフィールド

レスポンスは `data.product` でラップされます。商品オブジェクトは、商品とバージョンのフィールドを結合したフラット化された詳細ビューです。

| フィールド         | 型              | 説明                                    |
| ------------- | -------------- | ------------------------------------- |
| `id`          | string         | 商品 ID（`PROD_xxx`）                     |
| `storeId`     | string         | Store ID（`STO_xxx`）                   |
| `name`        | string         | 商品名（現在のバージョンから）                       |
| `description` | string \| null | 商品説明（現在のバージョンから）                      |
| `prices`      | object         | マルチ通貨価格マップ（以下参照）                      |
| `media`       | array          | メディアアイテム（現在のバージョンから）                  |
| `successUrl`  | string \| null | 成功時のリダイレクト URL（現在のバージョンから）            |
| `metadata`    | object         | カスタムメタデータ（現在のバージョンから）                 |
| `status`      | string         | 現在の環境でのステータス: `active` または `inactive` |
| `createdAt`   | string         | 商品作成タイムスタンプ（ISO 8601）                 |
| `updatedAt`   | string         | 商品最終更新タイムスタンプ（ISO 8601）               |

**レスポンスの価格オブジェクト**（通貨ごと）:

| フィールド         | 型      | 説明                             |
| ------------- | ------ | ------------------------------ |
| `amount`      | string | 表示フォーマット文字列としての価格（例：`"49.00"`） |
| `taxCategory` | string | 税計算用のカテゴリ                      |

<Note>
  `status` フィールドは操作対象の環境を反映します。**test** 環境（デフォルト）で作成した場合、ステータスは `active` です。**production** 環境で作成した場合、production のステータスが返されます。
</Note>

## エラー

> **リトライポリシー**：4xx は一切リトライしない — リクエストを修正してから再送信。5xx は指数バックオフでリトライ（5s 開始、最大 3 回）。

| ステータス | `errors[0].message`                      | 意味                                                          | 推奨処理                          |
| ----- | ---------------------------------------- | ----------------------------------------------------------- | ----------------------------- |
| 400   | `Missing required field: storeId`        | `storeId` が指定されていません                                        | ボディを修正して再送信                   |
| 400   | `Invalid ID format`                      | 指定された Short ID をデコードできませんでした                                | `storeId` のフォーマットを修正して再送信     |
| 400   | `Missing required field: name`           | `name` が指定されていません                                           | ボディを修正して再送信                   |
| 400   | `Prices must have at least one currency` | `prices` が欠落しているか空のオブジェクトです                                 | 通貨を少なくとも 1 つ指定して再送信           |
| 400   | `Invalid currency code`                  | 通貨コードは ISO 4217 に準拠した3文字の大文字である必要があります（例：`USD`、`EUR`、`JPY`） | 有効な ISO 4217 コードを使用して再送信      |
| 400   | `Invalid amount`                         | 金額は正の数値文字列である必要があります（例：`"9.99"`、`"100"`）                    | 正の数値文字列を使用して再送信               |
| 404   | `Store not found`                        | ストアが存在しないかアクセスできません                                         | `storeId` が現在のマーチャントに属することを確認 |
| 500   | `Internal server error`                  | サーバ側の予期しない障害                                                | 指数バックオフでリトライ（5s 開始、最大 3 回）    |
