Appearance
Commerce モジュール(会員・決済)
決済・会員ドメインは core_src/Commerce/ に集約し、公開画面(View / PostType)は PaidArticleAccessServiceInterface のみを経由して閲覧可否を判定する。
境界
| 層 | パス | 役割 |
|---|---|---|
| Commerce | core_src/Commerce/ | Installer / Entity / Repository / Checkout Service(#2630)/ Webhook(#2631) |
| 公開 API | PaidArticleAccessServiceInterface | 閲覧可否の唯一の窓口(#2599) |
| 公開 API | PaidArticleCheckoutServiceInterface | Checkout Session 作成の窓口(#2630) |
| コンテンツ | core_src/PostType/PaidArticle/ 等 | 編集 UI・テンプレ(頻繁変更可) |
Deptrac の Commerce / CommerceRepositoryInterface レイヤーで、PostType・View・ShortCode から Commerce への直接依存を CI で拒否する。
Stripe SDK(#2629)
- 依存:
core_src/composer.jsonのstripe/stripe-php(lock: v21.0.0) - キー管理:
wp-config.phpのSTRIPE_SECRET_KEY/STRIPE_PUBLISHABLE_KEY/STRIPE_WEBHOOK_SECRET(定数名はStripeConfigConstants) - 初期化:
StripeClientFactoryInterface→StripeClientFactory(DI:config/di/commerce.php) - 明示 API version:
StripeConfigConstants::API_VERSION=2026-06-24.dahlia(StripeClientのstripe_version。アカウント既定に依存しない) - 設定手順: docs/troubleshooting/stripe-api-key-config.md
Stripe Checkout 単号購入(#2630)
- Service:
PaidArticleCheckoutServiceInterface→PaidArticleCheckoutService(DI:config/di/commerce.php) - 導線: リード CTA →
admin-post.php?action=paid_article_checkout→ Stripe Checkout(mode: payment) - Session
metadata:user_id,post_id(Webhook #2631 で購入記録作成に使用) - Session
integration_identifier:paid_article_+ 英小文字・数字 8 文字(Dashboard 上の Checkout フロー識別。#2735) success_url: 記事パーマリンク +paid_article_checkout=success+session_id={CHECKOUT_SESSION_ID}cancel_url: 記事パーマリンク +paid_article_checkout=cancel- 価格:
db_member_plan_master.plan_key = paid_article_singleのprice(円)
Stripe Webhook(#2631)
- エンドポイント:
POST /wp-json/commerce/v1/stripe-webhook - イベント:
checkout.session.completedのみ購入記録をdb_paid_article_purchaseへ冪等 INSERT - 設計: API-002-1 Stripe Webhook 受信
レート制限・アンチスクレイピング対策(#2632)
決済導線への悪質な高頻度アクセスを抑止する。実装は既存 MemberRateLimiter(WordPress Transient API)を再利用する。
Checkout セッション作成(admin-post.php)
| キー | 閾値 | ウィンドウ |
|---|---|---|
ユーザー単位(paid_article_checkout_user_{user_id}) | 10 回 | 15 分 |
IP 単位(paid_article_checkout_ip_{md5(ip)}) | 30 回 | 15 分 |
- 超過時: HTTP 429(
Messages::RATE_LIMIT_EXCEEDED) - カウント対象: 購入済み(
ALREADY_PURCHASED)以外の Checkout 試行(Stripe API 呼び出しまたはその他失敗) - 購入済みユーザー:
PaidArticleAccessService::can_view_full_content()で Service 内判定し、購入済みの場合はレート制限カウンタに加算せず記事へリダイレクト(#2630 実装済み) - IP 取得:
REMOTE_ADDR(会員認証フローと同パターン。Cloudflare 背後ではプロキシ IP になる可能性あり)
Stripe Webhook 受信
| キー | 閾値 | ウィンドウ |
|---|---|---|
IP 単位・署名検証失敗のみ(stripe_webhook_invalid_sig_ip_{md5(ip)}) | 20 回 | 15 分 |
- 一次防御: Stripe 署名検証(
Stripe-Signature+STRIPE_WEBHOOK_SECRET) - 署名検証失敗時:
error_logで IP と理由を記録(WP_DEBUG/WP_DEBUG_LOG有効時のみ) - 閾値超過時: HTTP 429(
reason: rate_limited)。Service / SDK 呼び出し前に遮断 - 正規の Stripe 配信(署名成功)はカウンタに加算されない
Stripe 公式 IP レンジのホワイトリスト
今回は 実装しない。理由:
- Stripe の送信元 IP は変更されうり、リストの定期更新が必要
- 署名検証(
whsec_*)が一次防御として十分機能する - 署名検証失敗の IP ベース遮断でスクレイピング・総当たり攻撃を抑止できる
WAF / CDN レベルの DDoS 対策、reCAPTCHA 導入は別 Issue のスコープとする。
技術選定理由
Payment Link のみではなく Checkout Session API + Webhook + 購入テーブル + 会員ログインを採用した根拠は Stripe決済-技術選定理由.md を参照。
将来: Composer 私有パッケージ化
複数サイトで同一決済基盤を使う、または決済モジュールだけ独立バージョンでリリースする必要が出た場合:
- 推奨:
slot-kouryaku/commerceを Composer 私有パッケージとして切り出し、テーマ側core_src/composer.jsonで semver 固定(例:^1.0.0) - 非推奨: Git サブモジュール(worktree 運用・CI・デプロイとの相性が悪い)
切り出しタイミングの目安:
- #2631 Webhook 実装が安定し、API 境界が固まった後
- マルチサイト or 別プロダクトでの再利用が具体化した後
パッケージ化時は App\Commerce\ PSR-4 をそのまま維持し、テーマ側は DI 登録と PaidArticleAccessService のみ残す構成が移行コストが低い。