Skip to content

Commerce モジュール(会員・決済)

決済・会員ドメインは core_src/Commerce/ に集約し、公開画面(View / PostType)は PaidArticleAccessServiceInterface のみを経由して閲覧可否を判定する。

境界

パス役割
Commercecore_src/Commerce/Installer / Entity / Repository / Checkout Service(#2630)/ Webhook(#2631)
公開 APIPaidArticleAccessServiceInterface閲覧可否の唯一の窓口(#2599)
公開 APIPaidArticleCheckoutServiceInterfaceCheckout Session 作成の窓口(#2630)
コンテンツcore_src/PostType/PaidArticle/編集 UI・テンプレ(頻繁変更可)

Deptrac の Commerce / CommerceRepositoryInterface レイヤーで、PostType・View・ShortCode から Commerce への直接依存を CI で拒否する。

Stripe SDK(#2629)

  • 依存: core_src/composer.jsonstripe/stripe-php(lock: v21.0.0)
  • キー管理: wp-config.phpSTRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEY / STRIPE_WEBHOOK_SECRET(定数名は StripeConfigConstants
  • 初期化: StripeClientFactoryInterfaceStripeClientFactory(DI: config/di/commerce.php
  • 明示 API version: StripeConfigConstants::API_VERSION = 2026-06-24.dahliaStripeClientstripe_version。アカウント既定に依存しない)
  • 設定手順: docs/troubleshooting/stripe-api-key-config.md

Stripe Checkout 単号購入(#2630)

  • Service: PaidArticleCheckoutServiceInterfacePaidArticleCheckoutService(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_singleprice(円)

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 のみ残す構成が移行コストが低い。