Skip to content

API-002-1 Stripe Webhook 受信

← API-一覧

概要

Stripe からの Webhook(主に checkout.session.completed)を受信し、署名検証後に db_paid_article_purchase へ購入記録を冪等に INSERT する。

入力(リクエスト)

項目必須型・制約説明
HTTP メソッドはいPOSTStripe からの Webhook のみ
Stripe-Signature ヘッダーはい文字列t=...,v1=... 形式。Stripe\Webhook::constructEvent() で検証
リクエストボディはい生 JSON 文字列Stripe Event オブジェクト。改変前のバイト列で署名検証する

エンドポイント: POST /wp-json/commerce/v1/stripe-webhook

出力(レスポンス)

成功時 data(HTTP 200)

field説明
receivedbool常に true
ignoredbool未対応イベント種別のとき true(購入記録は作成しない)

失敗時 data(HTTP 400)

field説明
receivedbool常に false
reasonstringinvalid_signature / webhook_not_configured / invalid_payload

レート制限時 data(HTTP 429)

field説明
receivedbool常に false
reasonstring常に rate_limited

同一 IP から署名検証失敗が 15 分間に 20 回を超えた場合、Service / SDK 呼び出し前に 429 を返す(#2632)。

失敗・エラー条件

条件HTTPreason
ボディが空400invalid_payload
STRIPE_WEBHOOK_SECRET 未設定400webhook_not_configured
署名検証失敗400invalid_signature
署名検証失敗の高頻度(IP 単位)429rate_limited

権限・nonce

名前空間共通事項は API-一覧 を参照。

  • WP ログイン・REST nonce は不要WordPressPublicPermissionChecker が常に許可)
  • 実際の認証は Stripe 署名検証whsec_* + Stripe-Signature ヘッダー)が担う

レート制限・ログ記録(#2632)

  • 一次防御: Stripe 署名検証(上記)
  • 二次防御: 署名検証失敗を IP 単位で Transient カウント。15 分間に 20 回超過で HTTP 429(reason: rate_limited
  • ログ: 署名検証失敗のたびに error_log で IP を記録(WP_DEBUG / WP_DEBUG_LOG 有効時のみ。ペイロード内容は記録しない)
  • 正規配信: 署名成功リクエストはカウンタに加算されない(Stripe からの高頻度 Webhook バーストに影響なし)
  • Stripe IP ホワイトリスト: 今回は見送り(署名検証 + 失敗時遮断で十分。IP リストの保守負担を回避)

処理フロー

  1. StripeWebhookHandlercommerce/v1/stripe-webhook を登録
  2. WordPressRestApiAdapterinclude_raw_body 指定時に $request->get_body()_raw_body として Controller に渡す
  3. StripeWebhookServiceStripe\Webhook::constructEvent() で署名検証
  4. checkout.session.completed のみ購入記録を作成
    • metadata.user_id / metadata.post_id(#2630 Checkout 作成時に付与)
    • external_provider = stripe
    • external_transaction_id = session.iduk_external_transaction_id で冪等)
  5. 購入記録が新規 INSERT された場合のみ、管理者(admin_email)へ決済控えメールを wp_mail で送信する(#2703, PaidArticlePurchaseAdminNotifier
    • 重複 INSERT(null)ではメールを送らない
    • メール送信の成否は Webhook 応答に影響しない(失敗しても後続の HTTP 200 を維持し、error_log に記録)
  6. 処理成功時は 常に HTTP 200(Stripe の再送を防ぐ)。重複 INSERT 時も 200

決済未反映時の手動補正手順

決済は Stripe 側で完了しているが、サイト上で購入済みにならない場合の確認・補正手順。

1. 状況確認

  1. Stripe Dashboard(テスト/本番)→ Payments で該当決済が Succeeded か確認
  2. Developers → Webhooks でエンドポイント .../wp-json/commerce/v1/stripe-webhook の配信ログを確認
    • 400: 署名シークレット不一致・STRIPE_WEBHOOK_SECRET 未設定の可能性
    • 200 だが未反映: DB または metadata 不整合の可能性
  3. WordPress DB の wp_*_db_paid_article_purchaseexternal_transaction_id = cs_* の行があるか確認

2. metadata の確認

Checkout Session の metadatauser_idpost_id が入っていること(#2630 実装どおり)。欠落している場合は Webhook では購入記録を作成せず 200 を返す(再送しても解消しない)。

3. 手動 INSERT(最終手段)

Stripe Dashboard で Session ID(cs_*)・金額・user_idpost_id を特定したうえで、管理者が DB に 1 件 INSERT する。

sql
INSERT INTO wp_db_paid_article_purchase
  (user_id, post_id, price, external_provider, external_transaction_id, purchased_at)
VALUES
  (<user_id>, <post_id>, <amount_jpy>, 'stripe', '<cs_session_id>', NOW());
  • uk_external_transaction_id 重複時は INSERT しない(既に反映済み)
  • 補正後、該当ユーザーで有料記事全文が閲覧できることを確認(PaidArticleAccessService

4. Webhook 再送

署名・シークレット修正後は Stripe Dashboard の Webhook ログから Resend で再送可能。冪等キーにより二重購入にはならない。

関連実装

コンポーネントパス
Handlercore_src/Handler/stripe_webhook_handler/StripeWebhookHandler.php
Controllercore_src/Controller/stripe_webhook_controller/StripeWebhookController.php
Servicecore_src/Commerce/Service/stripe_webhook_service/StripeWebhookService.php
Gatewaycore_src/Commerce/Infrastructure/Stripe/StripeWebhookEventGateway.php

ローカル検証(Stripe CLI)

bash
stripe listen --forward-to https://<local-site>/wp-json/commerce/v1/stripe-webhook
# 表示される whsec_* を wp-config.php の STRIPE_WEBHOOK_SECRET に設定
stripe trigger checkout.session.completed