この章では、AppCotton での課金フロー(ワンタイム/サブスクリプション)を Stripe と接続するための設計と実装ポイントをまとめます。
実運用に必要な 前提設定 → チェックアウト生成 → Webhook 処理 → ライセンス発行/反映 → アップグレード(差額課金) の順で整理します。サンプルは プラグイン内クラス(Stripe Manager)+ REST エンドポイント+ Webhook を想定しています。
1. 前提:Stripe 側の準備
- アカウント作成:本番用/テスト用の双方を用意(テストで十分に検証してから本番切替)。
- API キー:
- 公開可能キー(
pk_live_.../pk_test_...) - シークレットキー(
sk_live_.../sk_test_...)
- 公開可能キー(
- プロダクト&価格(Price):
- ワンタイム販売:
Price(type=one_time)を作成し、プランごとに 1 つ対応させる。 - サブスクリプション:
Price(type=recurring、interval=month/year等)。 - AppCotton の プラン(plan_id) ⇔ Stripe price_id を1対1でマッピング。
- ワンタイム販売:
- Webhook エンドポイント(後述):
- テスト/本番で 別 URL を登録。
- 受け取るイベント:最低限
checkout.session.completed、(サブスクなら)invoice.payment_succeeded/customer.subscription.updatedなど。
2. 価格モデル(AppCotton × Stripe の対応)
| AppCottonのプラン属性 | Stripeの対応例 |
|---|---|
billing_type = one_time | Checkout Session(mode=payment、line_items=[{price: <price_id>, quantity:1}]) |
billing_type = subscription | Checkout Session(mode=subscription、line_items=[{price: <price_id>, quantity:1}]) |
price(最小通貨単位) | StripeのPriceに合わせる(AppCotton側は参照用/差額計算用) |
activation_limit(1/3/5/…/無制限) | 価格自体と紐づけ。アップグレード時は target_plan により差額決済 |
stripe_price_id, stripe_price_id_subscription | AppCotton のプランに保持し、Checkout 生成時に使用 |
推奨:表示価格や税計算は Stripe に寄せ、AppCotton は “どの Stripe Price で決済したか” を正として記録します。
3. チェックアウトの生成(フロント → REST → Stripe)
3.1 フロント(例:ショートコード出力のボタン/UI)
- 「購入」「プラン選択」「アップグレード」などのボタンから AppCotton REST を呼び出し、Checkout Session を生成してリダイレクト。
- 必須パラメータ:
product_id(slug から解決済みでも良い)plan_id(複数プランがある前提)success_url/cancel_url(決済後遷移先)- (任意)
client_reference_id(user_id:license_id:plan_idなどを埋める) - (任意)
customer_email(収集/固定) - (任意)
allow_promotion_codes,automatic_tax,billing_address_collectionなど
3.2 REST(サーバ) → Stripe Checkout
モード別の生成例(擬似PHP):
// mode=payment(ワンタイム)
$params = [
'mode' => 'payment',
'line_items' => [[ 'price' => $price_id, 'quantity' => 1 ]],
'success_url' => $success_url . '?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $cancel_url,
'client_reference_id' => $client_ref, // 重要:後続紐付けに使う
'metadata' => [
'appcotton_product_id' => $product_id,
'appcotton_plan_id' => $plan_id,
'appcotton_user_id' => $user_id,
'action' => 'purchase', // or 'upgrade'
],
'customer_email' => $email, // 収集方針に応じて
'allow_promotion_codes' => true,
'locale' => 'auto',
];
// mode=subscription(サブスク)
$params = [
'mode' => 'subscription',
'line_items' => [[ 'price' => $recurring_price_id, 'quantity' => 1 ]],
'success_url' => $success_url . '?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $cancel_url,
'client_reference_id' => $client_ref,
'metadata' => [
'appcotton_product_id' => $product_id,
'appcotton_plan_id' => $plan_id,
'appcotton_user_id' => $user_id,
'action' => 'purchase',
],
'subscription_data' => [
'metadata' => [
'appcotton_plan_id' => $plan_id,
],
// proration_behavior はサブスク内アップグレード運用時に利用
],
'automatic_tax' => ['enabled' => true],
'billing_address_collection' => 'auto',
];
- Idempotency-Key を必ず付与(重複決済防止)。
client_reference_idとmetadataは Webhook 側で AppCotton 注文/ライセンスに正確に紐付けるカギ。
4. Webhook 設計(注文最終化・ライセンス発行)
4.1 受信イベント(最低限)
checkout.session.completed
→ 一括の“成立”トリガー。session.mode、client_reference_id、metadata、amount_total、payment_statusを確認。payment_intent.succeeded(mode=payment の場合の冪等性補助)- (サブスク導入時)
invoice.payment_succeeded、customer.subscription.updated、customer.subscription.deleted
4.2 署名検証
- Stripe で発行される Webhook Signing Secret を用い、
Stripe-Signatureヘッダで検証。 - 失敗時は
400を返し、絶対に処理しない。
4.3 冪等化(重複イベント対策)
event.idを 専用テーブル またはwp_options(autoload off)に記録し、一度処理したら二度と処理しない。
4.4 典型フロー(成功時)
- イベント検証 OK →
checkout.session.completedをパース client_reference_id/metadataから ユーザー/プロダクト/プラン を特定- AppCotton 注文レコードを作成(
order_id) - ライセンス新規発行 or 既存にプラン反映
- 購入:新規ライセンス生成(
activation_limitをプランに合わせて設定) - アップグレード:差額計算済の注文として記録し、既存ライセンスの
activation_limitを上書き
- 購入:新規ライセンス生成(
- 顧客通知(メールでライセンスキー送付/マイページへ誘導)
- 200 OK を返す
5. 差額課金(アップグレード)の扱い
5.1 ロジック(ワンタイム課金の場合)
- 原則:旧プラン価格 と 新プラン価格 の差額 = 請求額。
diff_cents = max(new_price_cents - old_price_cents, 0)- 返金は行わず、上位への差分請求のみ許容。
- REST:
/checkout/upgrade/start(例)license_key,target_plan_id,success_url,cancel_url- サーバ側で現在プランの
price_centsを取得し、差額 Price を 動的計算 → Checkout(mode=payment、unit_amount = diff_centsの Dynamic Price を使う場合は、Payment Links でなく Checkout Session API を利用)。
- Webhook:成功後に ライセンスの
activation_limitとplan_idを上書き。
注:Stripeに差額用の Price を都度生成する運用は非推奨。
Checkout のline_itemsにprice_data(custom unit_amount) を使い、一時的な差額アイテムとして決済するのがシンプルです。
5.2 サブスクリプションの場合
- Stripe のサブスク プラン変更(Upgrade) を利用
subscriptionのitemsをnew_price_idに差し替えproration_behavior='create_prorations'を活用して 自動按分(推奨)- あるいは
noneで按分なし運用
- 変更直後に
customer.subscription.updated/invoice.payment_succeededをフックして AppCotton ライセンス側の上限更新。
6. 失敗・キャンセル時の扱い
checkout.session.completedが来ない限り 「未完」 として扱い、ライセンスは発行しない。expired/canceledは クリーンアップ対象(必要であれば “未完注文” として残す)。- ネットワーク/Stripe エラー時は Idempotency-Key で再試行可能に。
7. 税・住所・領収書
- 税計算:
automatic_tax.enabled = trueを推奨(Stripe Tax を契約している場合)。そうでない場合は Price 側の税込/税抜設計を明確に。 billing_address_collection:autoorrequired。領収書用途や国別の税制要件に応じて。- Receipt(顧客メール)は Stripe 側で送付。AppCotton では 注文番号やライセンス情報の通知に専念。
8. メタデータ設計(必須)
Stripe への metadata / client_reference_id に 最低限以下を載せます:
appcotton_user_idappcotton_product_idappcotton_plan_id- (アップグレード時)
appcotton_license_id/action = upgrade - (任意)
order_hint(内部整合の補助)
これにより Webhook で 外部キー無しに正確に復元できます。
9. セキュリティ
- Webhook 署名検証は必須。
- REST で Checkout 生成時は 現在ログイン中のユーザー(またはEメール) を検証し、他人の license_id を使った Upgrade を弾く。
- Idempotency-Key を毎リクエスト発行(
license_id + target_plan_id + timestampなど)して重複防止。 - 金額は サーバ側で計算(フロントの値は信用しない)。
10. 返金・キャンセルポリシー
- 返金を行う場合は Stripe ダッシュボード/API で行い、AppCotton 注文ステータスを
refundedに同期。 - サブスクの解約は
cancel_at_period_end=trueを基本にし、ライセンス有効期限(expires_at) と同期。
11. 実装の目安(疑似コード)
11.1 Checkout セッション生成(REST エンドポイント)
// 1) 入力: product_id, plan_id, action(purchase|upgrade), success_url, cancel_url
// 2) プランを取得 → price_id(or recurring_price_id)を取り出し
// 3) (upgradeなら)差額centsを計算 → custom price_data で line_items 構築
// 4) CheckoutSession を作成 → URL を返す
11.2 Webhook 受信
// 1) 署名検証
// 2) idempotency チェック(event.id)
// 3) event.type に応じて分岐
// 4) checkout.session.completed → metadata / client_reference_id を解決
// 5) AppCotton 注文作成+ライセンス発行/更新
// 6) メール通知
12. 動作確認チェック(本番前)
- テストキーで
purchase(ワンタイム)完了 → Webhook 受信 → ライセンス発行 OK - テストキーで
upgrade(差額) → 既存ライセンスのactivation_limit更新 - サブスク(導入時)で
upgrade/downgrade→ 按分の反映とライセンス上限同期 - Webhook の冪等化(再送でも重複発行しない)
- 署名検証の失敗時は 400
- 本番キーへ切替、成功/キャンセルURLの最終確認
13. よくある落とし穴
- フロントから差額額面を渡してしまう(サーバで再計算せず信頼)→ 悪用される
client_reference_id未設定 → Webhookで紐付け不能- Idempotency 未使用 → 重複課金
- テスト用 Webhook URL のまま本番運用 → 本番イベントが届かない
success_urlでの “購入済み処理” を書いてしまう(Webhook基準にする)
まとめ
- Price(Stripe)= プラン(AppCotton) を厳密に紐づけ、Checkout → Webhook → ライセンスという一筆書きを守る。
- **アップグレード(差額請求)**は サーバ計算×Checkout(custom
price_data) で実装し、成功後に 既存ライセンスを書き換える。 - 失敗/重複/署名不正を防ぐために Idempotency / 署名検証 / 冪等化 を必須化する。
この方針で実装しておけば、プロダクト横断で同じ決済・発行体験を提供できます。