/products と /plans

AppCotton の公開 REST では、販売ページや購入導線(UI/ショートコード/SDK) が参照する読み取り系エンドポイントとして /products/plans を提供します。ここでは「何が取得できるか」「どう使うか」「型・例外・設計指針」をまとめます。
(※ 管理者による作成・更新は管理画面 or 管理用RESTで行い、本ページは主に公開読み取り前提です)


1. 目的と前提

  • Webサイト側のLP/購入ページ/マイページで「表示用データ」を取得する。
  • Product は販売対象(例: “CombPass Premium”)。
  • Plan はその商品の料金/上限/課金モードのバリエーション(例: 1サイト/3サイト/5サイト、買い切り/サブスクなど)。
  • 価格は最小通貨単位(例: JPYなら円、USDならセント) で返します。

2. エンドポイント一覧(読み取り)

2.1 製品一覧

GET /wp-json/appcotton/v1/products

用途: 一覧表示、商品カードの生成、プラン導線へ遷移の起点。
主なクエリ:

  • active=1 … 公開中のプロダクトのみ
  • slug=combpass_premium … スラッグでの絞り込み
  • per_page, page … ページング

レスポンス(要点)

[
  {
    "id": 1,
    "name": "CombPass Premium",
    "product_slug": "combpass_premium",
    "description": "予約・ライセンス認証プラグイン",
    "is_active": true,
    "created_at": "2025-10-01 00:00:00",
    "updated_at": "2025-11-01 00:00:00"
  }
]

2.2 製品詳細

GET /wp-json/appcotton/v1/products/{product_id}
GET /wp-json/appcotton/v1/products/by-slug/{product_slug}

用途: ランディングで 1 商品の詳細をレンダリング。

レスポンス(要点)

{
  "id": 1,
  "name": "CombPass Premium",
  "product_slug": "combpass_premium",
  "description": "…",
  "is_active": true
}

2.3 製品に紐づくプラン一覧

GET /wp-json/appcotton/v1/products/{product_id}/plans
GET /wp-json/appcotton/v1/products/by-slug/{product_slug}/plans

用途: 「購入」ボタンを“プランごと”に複数表示するためのデータ源。
標準並び順: sort_order ASCprice ASC を推奨。

レスポンス(要点)

[
  {
    "id": 101,
    "product_id": 1,
    "plan_name": "1サイト",
    "activation_limit": 1,
    "billing_type": "one_time",         // one_time | subscription
    "subscription_interval": null,      // day|week|month|year
    "price": 9800,                      // 最小通貨単位
    "currency": "JPY",
    "stripe_price_id": "price_xxx",     // 買い切り用
    "stripe_price_id_subscription": null,
    "sort_order": 10,
    "is_active": true
  },
  {
    "id": 102,
    "product_id": 1,
    "plan_name": "3サイト",
    "activation_limit": 3,
    "billing_type": "one_time",
    "price": 19800,
    "currency": "JPY",
    "stripe_price_id": "price_xxx_3",
    "sort_order": 20,
    "is_active": true
  }
]

2.4 プラン詳細

GET /wp-json/appcotton/v1/plans/{plan_id}

用途: 単一プランの確認、価格・上限の再取得(購入直前の再確認など)。
レスポンスは 2.3 の各要素と同等。


3. 表示ロジック指針(UI/ショートコード/SDK)

  • 「購入」ボタンを 1 つだけにしない
    /products/{id or slug}/plans を必ず呼び、プランごとにボタンを並べる
    例)「1サイト」「3サイト」「5サイト」「無制限」… を同一商品ページで横並び。
  • 課金モード表示
    • billing_type=one_time → 「買い切り」「¥9,800」
    • billing_type=subscription → 「¥1,000/月」 (subscription_interval をサフィックス表示)
  • 無効(非公開)プランの抑止
    is_active=false は非表示。管理画面でドラフト化→非表示にできる設計に。
  • 並び順
    sort_orderprice の順で安定表示。UIブロックの並びと一致させる。

4. 差額課金(アップグレード)との関係

  • アップグレードは「現在のライセンスのプラン」と「目標プラン」を比較し、
    差額のみを生成するチェックアウトへ誘導(詳細は「/checkout(差額課金)」章に委譲)。
  • このページでは**「どのプランに上げられるか」**を把握するため、
    一覧取得→上限(activation_limit)や価格(price)の比較に使う。

5. クエリ・ページング・並びの仕様(提案)

  • 共通クエリ: per_page(default 20), page(default 1), orderby, order
  • orderby 候補
    • Products: created_at, updated_at, name, id
    • Plans: sort_order, price, activation_limit, id
  • order: asc | desc

応答ヘッダ(推奨)

  • X-Total-Count … 総件数
  • X-Total-Pages … 総ページ数

6. 例:表示用コード断片(参考)

PHP (ショートコード/テーマ側)
商品スラッグからプランを列挙してボタンを生成(イメージ)

// 1) プロダクトをスラッグで取得
$product = appcotton_get_product_by_slug('combpass_premium');
if ( $product ) {
    // 2) プラン一覧を取得
    $plans = appcotton_get_plans_by_product_slug('combpass_premium'); // SDK/ヘルパ層でラップ
    foreach ( $plans as $plan ) {
        // 3) 各プランのボタンを出力(例:data-plan-idを載せる)
        printf(
            '<button class="appcotton-buy-button" data-product-id="%d" data-plan-id="%d">%s - ¥%s</button>',
            $product['id'],
            $plan['id'],
            esc_html($plan['plan_name']),
            number_format_i18n($plan['price'])
        );
    }
}

JavaScript (フロントUI)
ボタン押下で plan_id を渡し、チェックアウトへ(差額課金フローは別章)

document.addEventListener('click', async (e) => {
  const btn = e.target.closest('.appcotton-buy-button');
  if (!btn) return;
  const productId = btn.dataset.productId;
  const planId    = btn.dataset.planId; // ← プラン必須

  // ここで /checkout/create-session 等へ POST し、result.redirect_url へ遷移
  // ※ 詳細は「/checkout」ページへ
});

7. エラーとバリデーション(抜粋)

  • 404product_id / product_slug / plan_id が存在しない
  • 400 … クエリ不正(例: per_page が負数、orderby が未サポート)
  • 410 … 取得はできるが非公開(廃止) の場合の通知に使うことを推奨
  • 200 でも is_active:false は UI 側で非表示にする

8. 設計の注意

  • 安定キーとして product_slug を運用:テーマ・ショートコード・外部配布SDKは slug 参照が基本。
  • 価格は常にバックエンドの値を最優先:UI表示前に /plans で再取得、チェックアウト直前にも再確認。
  • プランの整合:プラン削除/非公開により UI 側の plan_id が無効化される場合があるため、
    その際は「在庫切れ/販売停止」扱いのメッセージを表示。

9. 典型フロー

  1. LP 表示時に GET /products/by-slug/{slug} → 商品存在確認
  2. GET /products/by-slug/{slug}/plans → ボタンをプラン分レンダリング
  3. クリック時に product_id + plan_id を持ってチェックアウト開始
  4. 完了後に order / license が作成され、ユーザーへライセンス通知

10. チェックリスト

  • product_slug はユニークか
  • plan の sort_orderis_active を設定済みか
  • billing_typesubscription_interval の整合が取れているか
  • price は最小通貨単位か(UI で整形表示)
  • LP/ウィジェットは必ず /plans を呼び、複数ボタンを出す実装になっているか