Claude Media
Claude CodeでStripeのテストクロックを回す — トライアル終了・更新・決済失敗の再現

Claude CodeでStripeのテストクロックを回す — トライアル終了・更新・決済失敗の再現

StripeのテストクロックでトライアルやサブスクをClaude Codeに再現させる手順です。時計を進めるスクリプトを固定し、各ステップ後のDBを期待値と照合させるところまで示します。

サブスクの課金は「待たないと起きないこと」だらけです。7日間のトライアルが終わる日、翌月の更新日、更新時のカード拒否。どれも実装のバグが出る場面なのに、普通に開発していると本番で初めて通ります。

Stripeのテストクロックは、サンドボックスの中でこの時間経過を進める仕組みです。Claude Codeに任せる範囲は、時計を進めるスクリプトを書くこと、進めるたびにDBの状態を期待値と照合すること、食い違いの原因を読むことの3つです。時計を進める操作そのものは、人間が固定したスクリプトに閉じ込めます。

状態遷移表をコードに落とす側の話はStripeのサブスク同期の記事にあります。この記事は、その実装が時間の経過に耐えるかを確かめる側です。

stripe triggerでは足りず、テストクロックが要る理由

Webhookの動作確認には、Stripe CLIのstripe triggerやダッシュボードからのイベント送信が使えます。ただしStripeの説明では、この方法で届くイベントには、サブスクと関連のない架空のデータが入ります。Webhook通知を確かめる最も信頼できる方法は、実際のテストサブスクを作り、対応するイベントを処理することだとされています。

状態の同期は、イベントの本文と自社DBの行が噛み合って初めて成立します。架空のデータでは、その噛み合いを確かめられません。テストクロックなら、実在のサブスクと請求書が時間とともに変わり、本物と同じ形のイベントが届きます。

もう一つの利点は、年次更新のような長い周期も待たずに試せることです。Stripeの説明にも、1年待たずに更新時の決済失敗を確かめられるとあります。

時計の操作を1本のスクリプトに固定する

Claude Codeにcurlを自由に撃たせると、毎回違う形でAPIを呼びます。再現性が要るテストなので、時計の操作は先に1本のスクリプトにまとめます。次の形は、Stripeのテストクロック用APIをそのまま呼ぶ例です。

#!/usr/bin/env bash
# scripts/clock.sh — テストクロックの作成・顧客・サブスク・時刻を進める操作
set -euo pipefail
: "${STRIPE_TEST_KEY:?テスト用キーを環境変数に置く}"
[[ "$STRIPE_TEST_KEY" == sk_test_* ]] || { echo "テスト用キーではない"; exit 1; }
api=https://api.stripe.com/v1
post() { curl -sS -u "$STRIPE_TEST_KEY:" "$api/$1" "${@:2}"; }
 
case "$1" in
  create)    # clock.sh create <名前> <凍結時刻(epoch秒)>
    post test_helpers/test_clocks -d name="$2" -d frozen_time="$3" ;;
  customer)  # clock.sh customer <clock_id> <payment_method>
    post customers -d test_clock="$2" -d payment_method="$3" \
      -d "invoice_settings[default_payment_method]=$3" ;;
  subscribe) # clock.sh subscribe <customer_id> <price_id> <トライアル日数>
    post subscriptions -d customer="$2" -d "items[0][price]=$3" \
      -d trial_period_days="$4" ;;
  advance)   # clock.sh advance <clock_id> <epoch秒>
    post "test_helpers/test_clocks/$2/advance" -d frozen_time="$3" >/dev/null
    until [[ "$(post "test_helpers/test_clocks/$2" | jq -r .status)" == ready ]]; do
      sleep 2
    done
    echo "clock $2 ready" ;;
  delete)    # clock.sh delete <clock_id>
    curl -sS -X DELETE -u "$STRIPE_TEST_KEY:" "$api/test_helpers/test_clocks/$2" ;;
esac

要点は4つあります。

  • 先頭でキーの接頭辞を検査する。テスト用でなければ即終了します。接頭辞の規則が自分の環境と違うなら、この条件を書き換えます
  • 顧客を作るときにtest_clockを渡す。サブスクはその顧客を通じてクロックに結びつくので、サブスク側にクロックIDは要りません
  • 支払い方法はpm_card_visaのようなPaymentMethodを使う。Stripeの説明でも、テストコードではカード番号を直接使わず、PaymentMethodを使うよう勧められています
  • advanceは完了まで待つ。時刻を進める要求が通ってから指定時刻に届くまで数秒かかるため、クロックのstatusがreadyになるまで見ています

進行の完了は、Webhookのtest_helpers.test_clock.readyでも知れます。ここでは、アプリ側の受信口を増やさないよう、取得のポーリングにしてあります。

3つのシナリオと、進行ごとの期待値

次に、確かめたい場面を表にします。この表をClaude Codeに作らせ、人間が直してからscenarios.jsonなどに落とします。数字や状態は、Stripeの説明にある7日トライアルの例をそのまま写します。

凍結時刻は2026年1月1日0時(UTC)、epoch秒で1767225600です。顧客の支払い方法はpm_card_visa、月次の価格を1つ用意します。

進める先epoch秒サブスクの状態届くはずのイベント
1月1日(作成直後)epoch秒1767225600サブスクの状態trialing届くはずのイベントcustomer.subscription.created
1月5日epoch秒1767571200サブスクの状態trialingのまま届くはずのイベントcustomer.subscription.trial_will_end
1月8日epoch秒1767830400サブスクの状態active届くはずのイベントinvoice.paid
2月8日epoch秒1770508800サブスクの状態active(期間が更新)届くはずのイベントinvoice.paid

7日のトライアルを設定すると、Stripeは終了の3日前にtrial_will_endを送ります。起点が1月1日なら1月5日です。1月8日にはトライアルが終わって最初の請求書が支払われ、サブスクがactiveになります。2月8日まで1周期進めれば、更新が通るかを確かめられます。

決済失敗は別のシナリオにします。

進める先epoch秒サブスクの状態届くはずのイベント
1月1日(作成直後)epoch秒1767225600サブスクの状態trialing届くはずのイベントcustomer.subscription.created
1月3日(1日トライアルの後)epoch秒1767398400サブスクの状態past_due届くはずのイベントinvoice.payment_failed

失敗用の支払い方法にはpm_card_chargeCustomerFailを使います。顧客に関連付けることはできますが、請求は失敗するカードです。支払い方法を関連付ける時点で拒否されるカードでは、サブスクの更新時の失敗を再現できません。Stripeの説明も、関連付けた後に失敗するカードをトライアルと組み合わせ、トライアル終了後の請求で失敗させる方法を示しています。

past_dueの行は、Stripeの説明にある状態の意味と整合しています。直近の確定済みの請求書の決済が失敗したか、試行されなかったときの状態です。ただし、再試行が尽きた後にcanceled、unpaid、past_dueのどれに移るかは、ダッシュボードの設定で決まります。この表は、再試行が続いている間の状態を想定しています。

もう一つ、失敗の表には待ち時間が絡みます。Stripeの説明では、トライアルが終わると下書きの請求書ができ、ステータスがopenに変わるまでにおよそ1時間かかります。決済の回収が試みられて失敗するのは、その後です。advanceの直後にinvoice.payment_failedが届かなくても、すぐ実装のバグと決めないでください。past_dueになっているかを確かめる前に、時計をさらに少し進めて再度取得する手順を、期待値の側に入れておきます。

claude -pで1ステップずつ回して照合させる

スクリプトと期待値が固まったら、Claude Codeを非対話モードで呼びます。claude -pは、プロンプトを渡すと結果を出して終了します。終了コードで成否を分岐でき、--output-format jsonならresultにテキストが入ります。

claude -p "scenarios.jsonのtrial-to-paidを実行して。
手順: scripts/clock.shでクロックと顧客とサブスクを作り、
表の各時刻へadvanceする。進めるたびにnpm run billing:stateで
usersテーブルの行を取得し、期待値と比べる。
食い違いは、どのイベントのハンドラが何を更新しなかったかまで書く。
最後にclock.sh deleteで後始末する。コードはまだ直さない。" \
  --allowedTools "Bash(./scripts/clock.sh *),Bash(npm run billing:state *),Read" \
  --permission-mode dontAsk \
  --output-format json | jq -r '.result'

--allowedToolsは、確認なしで使わせるツールの一覧です。ここにはスクリプトの呼び出しとDB状態の取得、読み取りだけを入れます。--permission-mode dontAskは、許可の対象外で確認が必要になった呼び出しをすべて拒否するモードで、人のいない実行に向いています。

注意点が2つあります。

  • Claude Code v2.1.302以降のclaude -pは、権限モードを指定しないと通常の確認ありのモードで始まります。確認に答える人がいなければ、許可していない呼び出しは拒否されます
  • --permission-prompts noneを足すと、確認が必要な呼び出しは拒否されたままClaudeに「承認する人がいない」と伝わり、再試行を避けます。このフラグはv2.1.259以降が必要です

「コードはまだ直さない」と明示するのが肝心です。このステップの役割は観測で、修正は別のプロンプトにします。観測と修正を同じ実行に混ぜると、食い違いの原因を確かめる前に直す方向へ進み、どこがずれていたかの記録が残りにくくなります。

権限の全体像はStripeのサブスク同期の記事のpermissionsの節に置いてあります。--liveと--api-keyのdenyもそちらに合わせておきます。headlessモードの使い方全般はclaude -pの記事にあります。

食い違いが出たとき、どこから疑うか

照合で食い違ったとき、原因は実装と、テストの前提のどちらにもあります。切り分けの順に並べます。

  1. DBの更新が追いついていない。時計がreadyになっても、Webhookの到着とハンドラの処理は少し遅れます。DB取得側に数十秒までのリトライを入れ、それでも違うときに不一致とします
  2. 進めすぎ。時計を進めると、途中の期間のイベントも順に発生します。1回のadvanceで複数の周期を跨いだ場合、期待値の表は途中経過を含めて書き直します
  3. イベントの順序。Stripeはイベントを生成順に届ける保証をしていません。trial_will_endとinvoice.paidの到着順が入れ替わっても、最終状態が同じになる実装かを見ます。冪等性の側はWebhookの冪等受信の記事にまとめています
  4. 設定の差。past_dueの先の行き先や、ステータス判定に使う請求書の範囲は、ダッシュボードの設定です。サンドボックスと本番で設定がずれていると、テストが通っても本番で違う動きをします

3の確認をClaude Codeに頼むときは、イベントの到着順を入れ替えて同じ最終状態になるかを調べるテストを書かせます。この種のテストは、先に失敗を見せてから直す進め方と相性が良いです。進め方は再現テストを先に書く記事にあります。

制限と後始末

テストクロックには、運用上の上限があります。

  • 時刻は進めることしかできない。凍結時刻の設定後に戻せないので、やり直しは新しいクロックで行います
  • 一度に進められるのは、サブスクの2周期分まで。月次なら最大2か月です。サブスクを置いていないクロックは、最初の凍結時刻から最大2年進められます
  • ダッシュボードでは、1つのシミュレーションに顧客を3人まで、顧客ごとにサブスクを3件まで追加できます。サブスクが4件以上あるなど複雑な顧客では、「シミュレーションを実行」のボタンが無効になることもあります
  • シミュレーションは作成から30日で自動的に削除されます

終わったらclock.sh deleteでクロックを消します。クロックに結びついたテスト用の顧客も一緒に消え、そのサブスクはキャンセルされます。サンドボックスだけの機能なので、本番のオブジェクトは消えません。

自動テストに組み込むなら、クーポンやプロモーションコードなどのリソースを、テスト間で使い回さないようにします。サンドボックス内のサブスク関連データには、データ保持のポリシーがかかる点も押さえておきます。

まとめ

テストクロックの価値は、時間そのものより、実在のサブスクと請求書でイベントを出せる点にあります。Claude Codeに任せる範囲は、固定したスクリプトの呼び出し、期待値との照合、食い違いの読み解きまでです。時計を進める手段を自由にさせず、期待値の表は人間が持ちます。

最初の1本は、7日トライアルがactiveになる流れで十分です。それが通ってから、更新と決済失敗を足します。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.stripe.com/billing/testing
  2. docs.stripe.com/billing/testing/test-clocks/api-advanced-usage
  3. docs.stripe.com/billing/subscriptions/overview
  4. docs.stripe.com/testing
  5. code.claude.com/docs/en/headless
  6. code.claude.com/docs/en/permission-modes
この記事を共有:XはてブLinkedIn