Appearance
有料記事決済 シナリオテスト手順
有料記事の単号購入フローを手動で確認するための手順です。対象は Stripe Checkout、Webhook、購入履歴、閲覧制御、購入 CTA です。
対象範囲
- 未購入ユーザーにはリードと購入 CTA のみ表示される
- ログイン済み・メール認証済み・現行規約同意済みユーザーだけ購入開始できる
- Stripe Checkout で決済成功後、Webhook により
db_paid_article_purchaseへ購入履歴が作成される - 購入履歴が新規作成されたとき、管理者(
admin_email)へ決済控えメールが送信される(#2703) - 購入済みユーザーは有料記事の全文を閲覧できる
- キャンセル、未ログイン、未認証、規約未同意、価格未設定などの分岐が想定通りになる
前提
- Stripe はテストモードのキーを使う。本番カードや live キーでは実施しない。
STRIPE_SECRET_KEYは Restricted API key(rk_test_)を推奨し、従来の Secret key(sk_test_)も利用できる。 wp-config.phpに次の定数が設定されている。
php
define( 'STRIPE_SECRET_KEY', 'rk_test_...' );
define( 'STRIPE_PUBLISHABLE_KEY', 'pk_test_...' );
define( 'STRIPE_WEBHOOK_SECRET', 'whsec_...' );paid_articleの公開済み記事が 1 件以上ある。- 会員ユーザーでログインできる。
- テストには専用のテスト会員と専用のテスト有料記事を使う。実ユーザー・実購入済み記事の組み合わせでは実施しない。
- DB を直接更新する場合は、事前にローカル環境またはステージング環境で行う。本番 DB では事前バックアップを取る。
- 管理者メール通知(S14 / S15)を確認する場合は、WordPress の「設定 → 一般」の管理者メールアドレス(
admin_email)で受信できること。ローカルでは MailHog などのメール補足環境、またはwp_mailの送信ログで確認する。
事前準備
1. Stripe Webhook を受けられる状態にする
ローカルで確認する場合は Stripe CLI で転送する。
bash
stripe listen --forward-to https://example.test/wp-json/commerce/v1/stripe-webhook表示された whsec_... を wp-config.php の STRIPE_WEBHOOK_SECRET に設定する。
ステージングで確認する場合は Stripe Dashboard の Webhook endpoint に次を登録する。
text
https://example.com/wp-json/commerce/v1/stripe-webhookイベントは最低限 checkout.session.completed を送る。
2. 単号購入価格を設定する
初期 seed の paid_article_single は価格 0 円なので、そのままだと Checkout は開始されない。テスト用価格を入れる。
WP-CLI が使える場合:
bash
wp db prefix
wp db query "UPDATE <prefix>db_member_plan_master SET price = 500, is_active = 1 WHERE plan_key = 'paid_article_single';"
wp db query "SELECT plan_key, price, is_active FROM <prefix>db_member_plan_master WHERE plan_key = 'paid_article_single';"<prefix> は wp db prefix の出力に置き換える。例: wp_db_member_plan_master。
3. テスト会員を購入可能状態にする
UI で会員登録、メール認証、規約同意を行うのが本番に近い。準備を短縮する場合は WP-CLI でユーザー meta を更新する。
bash
wp user meta update <user_id> member_email_verified 1
wp user meta update <user_id> member_terms_accepted_version 2026-07-19
wp user meta update <user_id> member_terms_accepted_at "2026-07-16 00:00:00"現行規約バージョンは管理画面「法務文書」の利用規約現行版(または DB 空時の MemberTermsConsentConstants::CURRENT_VERSION)と一致させる。確認例: wp db query "SELECT version FROM <prefix>db_legal_document_version WHERE document_type='terms' AND is_current=1;"
プライバシーポリシー版、同意元 IP / User-Agent、文書 URL / hash は監査証跡用であり、購入可否判定には使わない。短縮準備では省略できるが、本番相当の確認では UI から登録または再同意して保存させる。
4. 購入履歴を初期化する
同じユーザー・同じ記事で再テストする場合は、購入履歴を消して未購入状態に戻す。専用テスト会員・専用テスト記事の組み合わせであることを確認してから実行する。
bash
wp db query "DELETE FROM <prefix>db_paid_article_purchase WHERE user_id = <user_id> AND post_id = <post_id>;"手動シナリオ
S1. 未ログインユーザーはリードとログイン CTA が表示される
- ブラウザをシークレットウィンドウで開く。
- 公開済み有料記事へアクセスする。
- intro セクションだけが表示され、全文は表示されないことを確認する。
- ログインボタンが表示されることを確認する。
- ログインボタン押下後、ログイン画面に遷移することを確認する。
- テスト会員でログインする。
- ログイン後の自動遷移先を確認する。
- Stripe Checkout へ進んだ場合: 未ログイン時に生成された購入開始 URL から、ログイン後も決済開始できることを確認する。
- 対象記事へ戻った場合: 記事上の購入 CTA から Stripe Checkout へ進めることを確認する。
- このシナリオでは支払いを完了せず、Checkout 画面から戻る。
期待結果:
- 有料本文は見えない。
- URL に購入開始用の
admin-post.php?action=paid_article_checkoutが直接露出していても、未ログインならログインへ誘導される。 - 未ログイン入口からログインした後の実際のリダイレクト先で、購入導線が失われない。
S2. ログイン済み未購入ユーザーは購入 CTA が表示される
- テスト会員でログインする。
- 対象の有料記事へアクセスする。
- intro セクションと購入 CTA が表示されることを確認する。
- 価格が事前準備で設定した金額になっていることを確認する。
期待結果:
- 全文は見えない。
- 購入ボタンが表示される。
S3. メール未認証ユーザーは購入開始できない
- テスト会員の
member_email_verifiedを0にする。 - 有料記事の購入ボタンを押す。
- メール認証案内ページへ遷移することを確認する。
member_email_verifiedを1に戻す。
期待結果:
- Stripe Checkout へは遷移しない。
- 購入履歴は作成されない。
S4. 現行規約未同意ユーザーは購入開始できない
- テスト会員の
member_terms_accepted_versionを空、または古い値にする。 - 有料記事の購入ボタンを押す。
- 規約再同意ページへ遷移することを確認する。
- 現行規約に同意し直す、または meta を現行バージョンに戻す。
期待結果:
- Stripe Checkout へは遷移しない。
- 購入履歴は作成されない。
S5. 正常購入できる
- テスト会員をメール認証済み、現行規約同意済み、未購入状態にする。
- 有料記事の購入ボタンを押す。
- Stripe Checkout へ遷移することを確認する。
- テストカードで支払う(Stripe 公式の日本発行カード)。
- 日本 Visa:
4000 0039 2000 0003(自動化 S5 の既定) - 日本 JCB:
3530 1113 3330 0000(手動確認の代替) - 有効期限: 任意の未来日
- CVC: 任意の 3 桁
- 日本 Visa:
- 決済後、有料記事へ戻ることを確認する。
- Stripe CLI または Stripe Dashboard で
checkout.session.completedが送信成功になっていることを確認する。 - Stripe Dashboard または DB の
external_transaction_idから Checkout Session ID を控える。 - 購入履歴を確認する。
bash
wp db query "SELECT user_id, post_id, price, external_provider, external_transaction_id, purchased_at FROM <prefix>db_paid_article_purchase WHERE user_id = <user_id> AND post_id = <post_id>;"期待結果:
external_providerはstripe。external_transaction_idは Checkout Session ID。priceは Stripe から返ったamount_total。- Webhook 反映後、記事全文が閲覧できる。
補足:
- 決済直後に「反映待ち」表示になる場合は、Webhook 到着前の可能性がある。数秒待って記事を再読み込みする。
S6. Webhook 再送で購入履歴が二重作成されない
- S5 で作成された
checkout.session.completedイベントを Stripe Dashboard から再送する。 - 同じ
user_id/post_idの購入履歴件数を確認する。
bash
wp db query "SELECT COUNT(*) AS purchase_count FROM <prefix>db_paid_article_purchase WHERE user_id = <user_id> AND post_id = <post_id>;"期待結果:
purchase_countは1のまま。- 全文閲覧権限は維持される。
- エラーログに未処理の例外が出ない。
S7. 購入済みユーザーは全文閲覧できる
- S5 の購入履歴がある状態で対象記事へアクセスする。
- intro 以外の有料本文も表示されることを確認する。
- 購入 CTA が表示されないことを確認する。
期待結果:
db_paid_article_purchaseに該当レコードがあるユーザーは全文閲覧できる。
S8. 購入済みユーザーが購入 URL を再度踏んだ場合
- 購入済みユーザーで、購入開始 URL を開く。
- Stripe Checkout へ遷移せず、記事へ戻ることを確認する。
期待結果:
- 二重購入に進まない。
S9. Checkout をキャンセルした場合
- 購入履歴を削除し、未購入状態に戻す。
- 購入ボタンを押して Stripe Checkout へ遷移する。
- Checkout 画面で戻る、またはキャンセル導線を選ぶ。
- 有料記事へ戻り、キャンセル通知が表示されることを確認する。
- DB に購入履歴が作成されていないことを確認する。
期待結果:
- 全文は見えない。
db_paid_article_purchaseにレコードは追加されない。
S10. 決済失敗カードの場合
- 購入履歴を削除し、未購入状態に戻す。
- 購入ボタンを押して Stripe Checkout へ遷移する。
- Stripe のテスト用失敗カードで支払いを試す。
- 例:
4000 0000 0000 0002(汎用 decline。日本専用 decline カードは公式に無し)
- 例:
- Checkout 画面上で失敗が表示されることを確認する。
- DB に購入履歴が作成されていないことを確認する。
期待結果:
- サイト側に購入済みアクセスは付与されない。
S11. REST API では未購入ユーザーに本文が返らない
- 未購入ユーザー、または未ログイン状態で REST API から対象記事を取得する。
bash
curl -s https://example.test/wp-json/wp/v2/paid_article/<post_id>期待結果:
content.renderedとexcerpt.renderedが空、または保護状態になっている。
S12. 編集権限者は購入なしで全文閲覧できる
- 管理者または対象投稿を編集できるユーザーでログインする。
- 対象の有料記事へアクセスする。
期待結果:
- 購入履歴がなくても全文閲覧できる。
S13. 価格未設定時は購入 CTA が出ない、または決済不可になる
paid_article_singleの価格を0にする。- 未購入のテスト会員で対象記事へアクセスする。
- 購入 CTA の表示状態を確認する。
- 購入 URL を直接開いた場合も Checkout へ遷移しないことを確認する。
- テスト後、価格を元に戻す。
期待結果:
- 価格
0では Checkout Session は作成されない。
S14. 正常購入時に管理者へ決済控えメールが届く
前提: S5(正常購入)を実施できる状態にする。admin_email が受信可能であること。
- 購入履歴を削除し、テスト会員を未購入状態に戻す。
- S5 と同じ手順で正常購入を完了する。
- Webhook(
checkout.session.completed)が送信成功し、購入履歴が新規作成されたことを確認する。 admin_emailの受信トレイ(またはメール補足環境・送信ログ)を確認する。
期待結果:
- 管理者宛に件名「【スロット攻略】有料記事の購入がありました」のメールが 1 通届く。
- 本文に次が含まれる。
- 購入日時
- ユーザー ID・表示名・メールアドレス
- 記事 ID・記事タイトル
- 金額(円)
- Stripe Checkout Session ID(
cs_...) - Payment Intent ID(Webhook に含まれる場合のみ)
- 購入履歴(
db_paid_article_purchase)が 1 件だけ作成されている。
補足:
- メール送信は購入履歴の新規 INSERT が成功したときだけ行われる。送信に失敗しても Webhook 応答は成功(HTTP 200)で、権利付与(全文閲覧)は維持される。失敗時はサーバーの
error_logにPaidArticlePurchaseAdminNotifierのログが出る。
S15. Webhook 再送では管理者メールが重複送信されない
前提: S14 を実施済みで、購入履歴が 1 件ある状態。
- S14 で作成された
checkout.session.completedイベントを Stripe Dashboard から再送する。 admin_emailの受信トレイ(またはメール補足環境・送信ログ)を確認する。- 購入履歴件数を確認する。
bash
wp db query "SELECT COUNT(*) AS purchase_count FROM <prefix>db_paid_article_purchase WHERE user_id = <user_id> AND post_id = <post_id>;"期待結果:
- 再送分の管理者メールは届かない(メールは増えない)。
purchase_countは1のまま(uk_external_transaction_idにより重複 INSERT はnullとなり、通知も行われない)。
手動以外の確認方法
1. PHPUnit の関連テストだけ実行する
決済フローの分岐、Webhook、アクセス判定、戻り通知、管理者メール通知はユニットテストで確認できる。
bash
composer test -- --filter 'PaidArticleCheckoutServiceTest|PaidArticleCheckoutControllerTest|StripeWebhookServiceTest|StripeWebhookControllerTest|PaidArticleAccessServiceTest|FrontendAccessHooksTest|PaidArticleCheckoutReturnHooksTest|RestAccessHooksTest|PaidArticlePurchaseAdminNotifierTest'環境によってテストコマンド名が違う場合は PHPUnit実行ガイド を参照する。
管理者メール通知(#2703)の受け入れ条件と自動テスト対応
S14 / S15 の観点は、次の PHPUnit で自動確認済み。手動シナリオは実メール到達の最終確認に限定できる。
| 受け入れ条件 | 確認する PHPUnit |
|---|---|
| 新規 INSERT 成功時のみ管理者へ通知する | StripeWebhookServiceTest::test_handle_webhook_inserts_purchase_and_notifies_admin_on_checkout_completed |
Webhook 再送・重複 INSERT(null)では通知しない | StripeWebhookServiceTest::test_handle_webhook_returns_success_without_notify_on_duplicate_insert |
| メタデータ不正時は通知しない | StripeWebhookServiceTest::test_handle_webhook_returns_success_without_insert_when_metadata_invalid |
| 通知が例外を投げても Webhook は成功(権利付与優先) | StripeWebhookServiceTest::test_handle_webhook_returns_success_when_admin_notify_throws |
| 本文に日時・ユーザー・記事・金額・Session ID(+ PI)が含まれる | PaidArticlePurchaseAdminNotifierTest::test_notify_purchase_sends_mail_with_purchase_details |
| ユーザー・記事が取得できない場合は不明プレースホルダで送る | PaidArticlePurchaseAdminNotifierTest::test_notify_purchase_uses_unknown_placeholders_when_user_and_post_missing |
admin_email 空・不正時は送らず常時ログ | PaidArticlePurchaseAdminNotifierTest::test_notify_purchase_skips_when_admin_email_empty / PaidArticlePurchaseAdminNotifierTest::test_notify_purchase_skips_when_admin_email_invalid |
wp_mail 失敗時は常時ログする | PaidArticlePurchaseAdminNotifierTest::test_notify_purchase_logs_when_wp_mail_returns_false |
実装参照: App\Commerce\Service\stripe_webhook_service\StripeWebhookService / App\Commerce\Service\paid_article_purchase_admin_notifier\PaidArticlePurchaseAdminNotifier。
2. Stripe CLI で Webhook だけ確認する
Checkout UI を通らずに Webhook 経路だけ見る場合:
bash
stripe listen --forward-to https://example.test/wp-json/commerce/v1/stripe-webhook
stripe trigger checkout.session.completedただし、この方法で送られる fixture の metadata が実装の期待する user_id / post_id と合わない場合、購入履歴は作成されない。その場合は Dashboard から実際の Checkout Session イベントを再送するほうが確実。
3. WP-CLI で購入済み判定だけ確認する
Webhook を待たずに購入履歴を直接入れて、閲覧制御だけ確認できる。
bash
wp db query "INSERT INTO <prefix>db_paid_article_purchase (user_id, post_id, price, external_provider, external_transaction_id, purchased_at) VALUES (<user_id>, <post_id>, 500, 'manual_test', 'manual_test_<post_id>', NOW());"確認後は該当レコードを削除する。
4. ブラウザ E2E テスト化する
サイト内表示制御(未ログイン / 未購入 CTA / 購入済み全文 / REST マスク)は、DB fixture による 通常 Playwright E2E で自動化済みです。
Stripe Checkout 外部画面まで含む通しの自動化方針は 有料記事 Stripe Checkout / Webhook E2E 方針 を正とします。
- S5 / S9 / S10(正常購入・キャンセル・失敗カード)— Local 手動実行専用 Playwright(
npm run test:e2e:paid-article-stripe)で自動化済み。手順の正本は 有料記事 Stripe Checkout / Webhook E2E 方針 の「実行手順」 - S6 / S14 / S15(Webhook 再送・管理者メール実到達)— PHPUnit で論理を担保し、実操作は手動の任意確認に残す
- Staging 通し — 手動シナリオを維持する
- 通常の
npm run test:e2eおよび CI Quality Gate には Stripe 通しを載せない
片付け
テスト後は必要に応じて以下を戻す。
bash
wp db query "DELETE FROM <prefix>db_paid_article_purchase WHERE external_provider = 'manual_test' AND user_id = <user_id> AND post_id = <post_id>;"
wp db query "DELETE FROM <prefix>db_paid_article_purchase WHERE external_provider = 'stripe' AND external_transaction_id = '<cs_test_session_id>' AND user_id = <user_id> AND post_id = <post_id>;"
wp db query "UPDATE <prefix>db_member_plan_master SET price = 0 WHERE plan_key = 'paid_article_single';"<cs_test_session_id> は S5 で控えたテストモードの Checkout Session ID に置き換える。実購入履歴を誤って削除しないため、external_provider = 'stripe' だけを条件にした広範な削除は行わない。
価格をステージングで継続利用する場合は price = 0 に戻さず、運用で使うテスト価格を維持する。