概要
ユーザーがプランを購入/上位プランへアップグレードするときに使うエンドポイントです。
有料時はチェックアウトセッション(Stripe)を生成し、差額のみを請求します。成功時はredirect_url(Stripe Checkout)を返します。フリー配布は別エンドポイント(/licenses/request-free)で処理します。なお、フロントの購入ボタン(.appcotton-buy-button)は、このチェックアウト開始APIを叩く実装が同梱されています。 appcotton-public
エンドポイント
- HTTP:
POST - Path:
/wp-json/appcotton/v1/checkout/create-session - 用途:
- 新規購入:指定プロダクト+プランのチェックアウトを作成
- アップグレード:既存ライセンスを指定し、差額のみで上位プランへ
認証
- ログインユーザー向け:
X-WP-Nonce(wp_create_nonce( 'wp_rest' )) - ゲスト購入を許可する場合:サーバ側設定による(メール収集はチェックアウト側で実施)
リクエスト(JSON)
{
"product_id": 123, // 必須: プロダクトID(または product_slug を許可する実装も可)
"plan_id": 456, // 必須: 対象プランID
"license_key": "xxxxx", // 既存ライセンスのアップグレード時に必須
"success_url": "https://example.com/thanks",
"cancel_url": "https://example.com/canceled",
"customer_email": "buyer@example.com" // 任意: ゲスト購入時のプリフィル
}
備考
既存のフロント実装は有料時に{ product_id }を送信し、サーバ側でデフォルトプランを選ぶ実装が含まれています。複数プランを正しく選べるよう、plan_idを必須にする方針でクライアント(ショートコード出力 or JS)を拡張してください。 appcotton-public
レスポンス(成功)
{
"success": true,
"mode": "payment", // or "subscription"
"redirect_url": "https://checkout.stripe.com/c/pay_cs_..."
}
レスポンス(失敗)
{
"success": false,
"error": "plan_not_found",
"message": "選択したプランが見つかりません。"
}
ステータスコード
200 OK:セッション生成成功400 Bad Request:入力不備(missing_param,invalid_plan,license_mismatch等)401 Unauthorized:認証エラー(invalid_nonce)404 Not Found:product_id/plan_id不在409 Conflict:同時処理・二重要求(duplicate_checkout)500 Internal Server Error:決済ゲートウェイ等の内部失敗
差額課金(プロレーション)仕様
- 買い切り→上位買い切り:
請求額 = max( 0, 新プラン価格 - 現プラン価格 ) - サブスクリプション:
Stripe の**プラン変更の即時課金(proration)**を採用。残存期間の未使用分が自動按分(クレジット or 差額加算)されます。 - ハイブリッド(買い切り + サブスク):
いずれか一方のモードへ正規化してから差額算定(設計ポリシーに従う)
実装指針
- 買い切りは**センチ金額(最小通貨単位)**でDB保存
- サブスクは**Stripe Price(subscription)**を明示
- 同一プロダクト内の
activation_limit上限のみ変更するアップグレードは、plan_id差し替えと注文レコード生成で整合性を担保
決済完了後(Webhook連携)
Stripe 側イベント(例):
checkout.session.completedpayment_intent.succeeded(支払い確定)invoice.paid(サブスク更新含む)
AppCotton で行う処理:
- 注文状態を
paidに更新(新規 or アップグレード) - ライセンスの
plan_id/activation_limitを対象プラン値に更新 - ライセンス状態
activeを維持(失効スケジュールがある場合は解除) - 監査ログへ追記(ユーザーID/IP/UA/旧→新プラン)
エラーコード(例)
missing_param:必須パラメータ不足(product_id/plan_id/ URL)product_not_found:プロダクト不在plan_not_found/plan_not_in_product:プラン不在/プロダクト未紐付けlicense_not_found:アップグレード対象ライセンスなしlicense_product_mismatch:ライセンスとプロダクト不一致already_on_plan:既に同一 or 上位プランstripe_error:ゲートウェイ連携失敗invalid_nonce/unauthorized:認証不正
フロント実装メモ
- ショートコード
で生成される購入ボタンは、同梱の JS([AppCotton] エラー: product 属性を指定してください。
.appcotton-buy-buttonハンドラ)が価格問い合わせ→有料なら checkout APIへPOSTの順で処理します。複数プラン表示に対応させるには、プラン選択UIをショートコード側で描画し、選択したplan_idを一緒にPOSTするようにJSを拡張してください。 appcotton-public - 無料時は
/licenses/request-freeを使用(メール入力→即時発行の現在実装)。 appcotton-public
実行例
cURL
curl -X POST "https://YOUR_SITE/wp-json/appcotton/v1/checkout/create-session" \
-H "Content-Type: application/json" \
-H "X-WP-Nonce: YOUR_NONCE" \
-d '{
"product_id": 123,
"plan_id": 456,
"success_url": "https://example.com/thanks",
"cancel_url": "https://example.com/canceled",
"license_key": "AC-XXXX-YYYY" // 既存アップグレード時
}'
JavaScript(ブラウザ)
const res = await fetch('/wp-json/appcotton/v1/checkout/create-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': window.appcotton_params?.appcotton_nonce || ''
},
body: JSON.stringify({
product_id: 123,
plan_id: 456,
success_url: location.origin + '/thanks',
cancel_url: location.origin + '/cancel'
})
});
const data = await res.json();
if (data.success) location.href = data.redirect_url;
PHP(サーバー to サーバー)
$response = wp_remote_post(
home_url('/wp-json/appcotton/v1/checkout/create-session'),
[
'headers' => [
'Content-Type' => 'application/json',
'X-WP-Nonce' => wp_create_nonce('wp_rest'),
],
'body' => wp_json_encode([
'product_id' => 123,
'plan_id' => 456,
'success_url' => home_url('/thanks'),
'cancel_url' => home_url('/cancel'),
'license_key' => 'AC-XXXX-YYYY', // アップグレード時
]),
'timeout' => 20,
]
);
Idempotency(冪等)推奨
- 同一ユーザーが短時間に複数回クリックした場合に備え、
license_key + plan_id + user_idなどで冪等キーを生成し、Stripe API 呼び出し時に付与してください。 - API レスポンスには、既存の未使用セッションがある場合はそれを再利用した
redirect_urlを返すと安全。
テスト
- Stripe テストモード(公開鍵/秘密鍵)で動作確認
- テストカード例:
4242 4242 4242 4242、任意の将来日、CVC任意 - Webhook を**受信できる環境(トンネルや Stripe CLI)**で確認し、ライセンス更新の副作用(
plan_id/activation_limit/ 注文レコード)が正しく反映されることを確認
既知の落とし穴
- フロントが
product_idのみを送る旧ロジックのままだと、複数プランがあっても最初のプランが選ばれがち。plan_id必須化とUIのプラン選択対応が必要です。 appcotton-public - 無料配布と有料チェックアウトの分岐はクライアント側実装に依存します。価格確認 → 分岐呼び出しの順を維持してください。 appcotton-public
これで、チェックアウト開始 APIの仕様はひととおりです。次に「プラン選択UI」側(ショートコード出力 or ブロック)をplan_id付きでPOSTできるように拡張すると、複数プラン販売と差額アップグレードの導線が揃います。