paddle-sandbox-testing について
paddle-sandbox-testingは、開発者やエージェントが公開前にPaddle連携を検証するためのスキルです。実際の課金なしで、サンドボックス設定、テスト決済、Webhookシミュレーション、環境差を扱います。
主な機能
- 01サンドボックスと本番環境の設定ガイド
- 02テストカードの決済シナリオ
- 03単一イベントのWebhookシミュレーション
- 04サブスクリプションの各段階のシナリオ
- 05パブリックトンネル経由のローカルテスト
活用シーン
- 01公開前のチェックアウト動作確認
- 02Webhookハンドラーの個別テスト
- 03更新やキャンセルのシミュレーション
- 04決済失敗や督促処理の確認
MCP 対応
Paddle MCPサーバーが利用できる場合、searchとexecuteツールでシミュレーションを作成し、プログラムから実行する方法を説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ paddlehq/ paddle-agent-skills --skill paddle-sandbox-testingインストール後、Claude Code で/paddle-sandbox-testingを入力して読み込まれたか確認します。
詳細
サンドボックスで Paddle インテグレーションをテストする
このスキルを使用するタイミング
Paddle インテグレーションを本番公開する前に検証するときに、このスキルを使用します。実際のようなチェックアウトの実行、Webhook イベントのトリガー、実際の決済を行わずにサブスクリプション状態の変更を試すことができます。サンドボックスの設定、テスト用カード番号、Webhook シミュレーター(単一イベントとシナリオ)、パブリックトンネルを使ったローカル開発、本番環境で問題になりやすいサンドボックスと本番の動作の違いを扱います。
このスキルは、ほかのすべてを支える安全網です。リリース時には checkout-web、webhooks、subscription-sync と併せて適用してください。サンドボックスでのテストは、インテグレーションのバグを見つける最も低コストな方法です。
サンドボックスと本番環境の概要
Paddle には、完全に分離された環境が 2 つあります。
| サンドボックス | 本番環境 | |
|---|---|---|
| 用途 | 開発とテスト | 実際の顧客、実際の決済 |
| API ベース URL | sandbox-api.paddle.com | api.paddle.com |
| ダッシュボード | sandbox-vendors.paddle.com | vendors.paddle.com |
| API キー | 別々のセット | 別々のセット |
| クライアントトークン | 別々のセット | 別々のセット |
| 通知先 | 別々のセット | 別々のセット |
| チェックアウトとアカウント設定 | 別々の設定 | 別々の設定 |
| 商品/価格/顧客 | 別々(同期されません) | — |
| 利用可能なカード | テスト用カードのみ(実際のカードは拒否) | 実際のカード |
| 返金 | 自動承認(約 10 分) | 大半は Paddle が審査します |
| 税金 | 計算されますが、徴収されません | 実際の税務処理 |
| ドメイン承認 | 承認不要(すべてのドメインが利用可能) | ドメインごとに手動で承認 |
両者の間で共有されるものはありません。 サンドボックスの pri_01h... は本番環境には存在しません。通知先は別々です。顧客も別々です。これは意図的な設計です。「サンドボックスでテストしてから本番に移行する」ことを、安全な標準手順にするためです。
ステップ 1:サンドボックスを設定する
sandbox-login.paddle.com/signup でサンドボックスに登録します(本番環境とは別に登録が必要です)。次に、以下を行います。
- 商品を少なくとも 1 つ、価格を 1 つ設定します。まだ設定していない場合は、
catalog-setupを参照してください。 - Paddle > My account > Settings > Authentication に移動し、以下を作成します。
Paddle.jsで使用するクライアントサイドトークン(Client-side tokens タブ)。- Node SDK 用のサーバーサイド API キー(API keys タブ)。このスキルの API 呼び出しと MCP 呼び出しで使用する以下の権限を付与します。
notification_simulation.writeとnotification_simulation.read(シミュレーター)、notification.readとnotification.write(通知ログと再送)、API でクライアントサイドトークンを作成する場合はclient_token.write、デフォルトの決済リンクを確認・設定する場合はcheckout_settings.write。このキーで Paddle CLI を使うには、ユーザーにサンドボックスへログインしてもらいます。paddle login --environment sandbox --api-key …
- サンドボックスのデフォルトの決済リンクが設定されていることを確認します。設定されていないとチェックアウトが開きません。Paddle MCP サーバーを利用できる場合は、
paddle-sandboxでclient.settings.checkoutUrl.get()(GET /settings/checkout-url)を呼び出します。default_urlがnullの場合、未設定です。localhostではなく、ユーザー自身のドメイン上のページを使用してください。サンドボックスではドメインの承認は不要です。更新時にcheckout_url_domain_not_approvedが返された場合は、Paddle > My account > Settings > Website approval でドメインを追加してから、再度試してください。サンドボックスではドメインがすぐに承認されます。client.settings.checkoutUrl.update({ default_url: "https://example.com/checkout" })で設定する前に、ユーザーに URL を尋ねてください。Paddle CLI では、paddle --environment sandbox settings checkout-url getと… settings checkout-url update --json '{"default_url": "…"}'を使います。または、ユーザー自身に Paddle > Checkout > Checkout configuration で設定してもらいます。 - ID を控えて、ローカル環境変数に設定します。
# .env.local — sandbox values
NEXT_PUBLIC_PADDLE_ENV=sandbox
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=test_...
PADDLE_API_KEY=pdl_sdbx_apikey_...
PADDLE_NOTIFICATION_WEBHOOK_SECRET=pdl_ntfset_...
サンドボックスの PADDLE_API_KEY には pdl_sdbx_apikey_ というプレフィックスが付きます。本番環境のキーには pdl_live_apikey_ というプレフィックスが付きます。どの環境に接続しているか不明な場合は、プレフィックスを確認してください。
ステップ 2:テストカードを使用する
サンドボックスでは、テストカードのみ使用できます。実際のカードは拒否されます。標準のテストカードは次のとおりです。
Card number: 4242 4242 4242 4242
Expiry: any future month/year
CVC: any 3 digits
ZIP/Postal: any value
Name: any value
その他のテストカードでは、特定のシナリオが発生します。
| カード番号 | 結果 |
|---|---|
4242 4242 4242 4242 | 3DS 認証なしで有効なカード |
4000 0038 0000 0446 | 3DS 認証ありで有効なカード |
4000 0566 5566 5556 | 有効な Visa デビットカード |
4000 0000 0000 0002 | 拒否されるカード |
4000 0027 6000 3184 | 初回は成功し、その後の更新は拒否される(督促) |
全一覧は developer.paddle.com/sdks/sandbox#test-cards にあります。失敗用カードを使って、checkout.payment.failed の処理(支払いの試行が失敗するたびに発生)、督促フロー、および subscription.past_due の処理がエンドツーエンドで動作することを確認してください。checkout.payment.error はこれらとは異なり、利用可能な決済方法がないことを意味します。
ステップ 3:Webhook シミュレーター
シミュレーターは、実際のフローを経由せずに、偽のイベントを正しい署名付きで通知先の 1 つに送信します。シミュレートされたイベントによって、アカウント内の実データが変更されることはありません。モードは 2 種類あります。
単一イベント。 イベントタイプを 1 つ選びます(例:subscription.canceled)。シミュレーションの type はイベント名になります。JSON ペイロードを編集するか、Paddle にデモ用の例を入力させてから実行します。Paddle は通知先のシークレットで署名し、その通知先の URL に POST します。これを使うと、特定のハンドラーを個別にテストできます。たとえば、「subscription.paused が届いたとき、UI はどう動作するか」を確認できます。
シナリオ。 実際のフローを再現する、事前に定義された一連のイベントです。
シナリオのtype | イベント(デフォルトのオプション) |
|---|---|
subscription_creation | transaction.created、customer.created、address.created、transaction.ready、transaction.paid、subscription.created、subscription.activated(またはsubscription.trialing)、transaction.completed、payment_method.saved — 各ステップの間にtransaction.updatedが発生します |
subscription_renewal | subscription.updated、transaction.created、transaction.billed、transaction.updated、transaction.paid、transaction.updated、transaction.completed |
subscription_pause | subscription.updated、subscription.paused |
subscription_resume | subscription.updated、subscription.resumed、transaction.created、transaction.billed、続いて更新時と同じ決済イベントが発生します |
subscription_cancellation | subscription.updated、subscription.canceled |
オプションを変更すると、一連のイベントも変わります。一時停止とキャンセルのデフォルトはeffective_from: "immediately"です。"next_billing_period"を指定すると、最初にscheduled_changeを含むsubscription.updatedが追加されます。更新と再開ではpayment_outcomeを指定します。"failed"の場合、決済イベントはtransaction.payment_failed、transaction.past_due、subscription.past_dueに置き換わります。実際のサンドボックスエンティティ(例:subscription_id)を追加すると、その詳細がペイロードに反映されます。エンティティ自体は変更されませんが、それらを読み取るには API キーに読み取り権限(例:subscription.read)が必要です。
シミュレーターはPaddle > Events > Simulationsにあります。Paddle MCP サーバーを利用できる場合は、ダッシュボードを使わず、1 回のexecute呼び出しでシミュレーターをプログラムから操作できます。シミュレーションを作成してから、そのシミュレーションに対する実行を作成します。
async (client) => {
const sim = await client.simulations.create({
name: "Subscription created flow",
type: "subscription_creation",
// Required: the destination that receives the simulated events.
notification_setting_id: "ntfset_...",
});
const run = await client.simulations.runs.create(sim.id);
return { simulation_id: sim.id, run_id: run.id };
}
client.simulations.runs.createはsimulationsの下にネストされており(トップレベルのclient.simulationRunsではありません)、シミュレーション ID は位置引数のパスパラメーターです。シナリオオプションは、シナリオの種類をキーとしてconfigに指定します。例:config: { subscription_cancellation: { entities: { subscription_id: "sub_..." }, options: { effective_from: "next_billing_period" } } }。
Paddle MCP は、サーバーごとに 3 つのツール(
search、execute、report_missing_tool)を公開します。ワークフロー:まずsearchを呼び出して、正確なメソッド名とパラメーターの形式を確認し、次にclient.<resource>.<operation>(...)を呼び出す非同期関数を指定してexecuteを呼び出します。メソッドパスはキャメルケースです(client.clientTokens.create、client.pricingPreview.preview)。本文パラメーターとレスポンスフィールドはスネークケースです(tax_category、product_id、unit_price、currency_code)。リストメソッドはdataではなく、{ pagination: { hasMore, estimatedTotal }, <resource>: [...] }を返します。配列のキーはリソースの API パスのセグメントであり、メソッドパスがキャメルケースの場合はケバブケースになります。例:(await client.products.list()).products、(await client.clientTokens.list())["client-tokens"]。pagination.hasMoreが true の間は.next()ではなく、{ after: "<last_id>" }を指定してページを取得します。client.currencies.list()のようにページネーションされないリストは、通常の配列を返します。単一エンティティを返すメソッドは、そのエンティティを返します。複数ステップのワークフローは 1 回のexecute内で連結してください。呼び出し間で変数は保持されません。上限は、1 回のexecuteあたり API 呼び出し 50 回、タイムアウト 30 秒、コード 32,000 文字です。
Paddle CLI(v0.2.0 以降)を使うと、保存済みのユーザーログインでターミナルから Paddle API の操作を実行できます。リソースコマンドは API リファレンスに対応しています。
paddle products createは「製品を作成する」という意味です。paddle <resource> --helpを実行してコマンドを確認し、作成または更新コマンドに--schemaを追加してリクエスト本文を表示します。コマンドがないエンドポイントにはpaddle api <path>を使用します。デフォルトではライブアカウントで実行されるため、ユーザーがライブ環境で作業している場合を除き、--environment sandboxを指定してください。エージェントが実行すると、API の JSON(data、meta)が返され、meta.environmentに使用したアカウントが示されます。キャンセルコマンドと削除コマンドには--confirm <id>が必要です。ユーザーがそのキャンセルまたは削除に同意した後にのみ追加し、拒否を回避するために決して使用しないでください。paddle apiによる書き込みはまったく確認されないため、同様に扱ってください。終了コード5は403です。権限が不足しているか、別のアカウントにログインしています。終了コード7は検証エラーです。paddle whoamiでログインしていないことが示された場合は、ユーザー自身でpaddle loginを実行するよう依頼してください(sandbox では API キーを指定します:paddle login --environment sandbox --api-key …)。チャットで API キーを尋ねてはいけません。OAuth によるライブログインでは、ユーザーの Paddle ロールで許可されているすべての操作が可能です。そのため、ライブ環境での変更を実行する前に、変更内容を個別に提示して承諾を待ってください。CI/CD では、必要な権限だけを付与したライブ API キーをPADDLE_API_KEYとして設定してください。インストール方法については、Paddle CLI を参照してください。
Paddle CLI では、同じ手順を個別のコマンドとして実行します。本文には同じスネークケースの JSON を使用します。
paddle --environment sandbox simulations create --jq '.data.id' --json '{
"name": "Subscription created flow",
"type": "subscription_creation",
"notification_setting_id": "ntfset_..."
}'
paddle --environment sandbox simulation-runs create ntfsim_...
シミュレーターを使用するには:
- ダッシュボードで Paddle > Events > Simulations に移動します。
- シナリオまたは単一のイベントを選択します。
- 送信先の通知先を選択します。URL を入力するのではなく、既存の通知先を選ぶため、事前にその通知先がトンネル経由のローカル URL またはデプロイ済みのプレビュー URL を指すように設定してください。
- (任意)単一のイベントの場合は、ペイロード JSON を編集して特定の ID(たとえば、DB に作成済みの
customer_id)を使用します。シナリオの場合は、実際の sandbox エンティティを追加します。 - (任意)シミュレーションを設定します(たとえば、支払い失敗をシミュレートするには、支払い結果を
failedに設定します)。 - 実行します。ローカルサーバーのログを確認してください。
通知先の Usage type は「Simulation」または「Both」(traffic_source: "simulation" または "all")である必要があります。「Platform」の通知先は使用できません。また、シミュレーション内の各イベントタイプを受信できるよう、通知先が有効で、そのイベントタイプを購読している必要があります。受信できないイベントは aborted とマークされ、送信されません。「Simulation」の通知先には実際のイベントが届かないため、実際のチェックアウトにも使用するローカル通知先には「Both」を選択してください。
シミュレートされた各イベントは、再試行なしで1回だけ配信が試みられます。実行結果は Paddle > Events > Simulations で確認するか、client.simulations.runsEvents.list(simulationId, runId) で確認できます。各イベントには status(success、failed、aborted、pending)とリクエスト、レスポンスが含まれます。イベントを1つ再送するには、client.simulations.runsEvents.replay(simulationId, runId, eventId) を使います。CLI では、paddle simulation-run-events list <simulation_id> <run_id> と paddle simulation-run-events replay <simulation_id> <run_id> <event_id> を使います。
シミュレートされたイベントの署名には、送信先のシークレットが使われるため、通常の検証コードを変更せずに使えます。検証に失敗する場合は、PADDLE_NOTIFICATION_WEBHOOK_SECRET に設定したシークレットが、シミュレーション対象の送信先と一致していません。
CI でシミュレーションを実行する
変更のたびにデプロイ済みのプレビューを確認するには、Paddle CLI を使って CI からシミュレーションを実行します。プレビュー URL の送信先を指定してシミュレーションを一度作成し、ビルドごとに新しい実行を開始して、イベントが1つでも配信されなかった場合は失敗させます。シミュレーターの権限を持つサンドボックス API キーを CI のシークレットとして保存し、決してコミットしないでください:
# CI secrets and variables: PADDLE_API_KEY (sandbox key), PADDLE_SIMULATION_ID (ntfsim_...)
export PADDLE_ENVIRONMENT=sandbox
RUN_ID=$(paddle simulation-runs create "$PADDLE_SIMULATION_ID" --jq '.data.id')
while [ "$(paddle simulation-runs get "$PADDLE_SIMULATION_ID" "$RUN_ID" --jq '.data.status')" = pending ]; do
sleep 5
done
FAILED=$(paddle simulation-run-events list "$PADDLE_SIMULATION_ID" "$RUN_ID" \
--jq '[.data[] | select(.status != "success")] | length')
[ "$FAILED" = 0 ] || { echo "$FAILED simulated events weren't delivered"; exit 1; }
ステップ 4:実際のイベント用に localhost をトンネリングする
シミュレーターでほとんどのテストを実施できます。実際のフロー(例:サンドボックスで実際にチェックアウトを完了し、全体の流れを検証する場合)では、Paddle がローカルマシンにアクセスできる必要があります。トンネルを使います:
Hookdeck(推奨):
# Install: brew install hookdeck
hookdeck listen 3000 paddle --path /api/webhook
# Copy the source URL it prints ("Requests to → https://...")
paddle は Hookdeck のソース名で、--path は Webhook のルートです。サンドボックスの通知先をソース URL に設定し、Usage type を「Both」にすると、実際のイベントとシミュレートされたイベントの両方を受信できます。CLI を再起動してもソース URL は変わらないため、設定は一度だけで済みます。Hookdeck はセッション間でイベント履歴を保持するため、CLI の停止中にイベントが届いていた場合は、Hookdeck または Paddle から再送できます。
配信をデバッグする。 実際のイベントの場合は、Paddle > Events > Notifications で通知を開くか、client.notifications.logs.list("ntf_...") を使って各配信試行を確認し、client.notifications.replay("ntf_...") で再送します。シミュレートされたイベントの場合は、シミュレーション実行のイベントを確認します(ステップ 3 を参照)。
ステップ 5:一般的なエンドツーエンドテスト
新しい連携を検証する手順は次のとおりです:
- localhost をトンネリングします:
hookdeck listen 3000 paddle --path /api/webhook。 - サンドボックスの送信先をトンネルのソース URL に設定します(再起動しても変わらないため、一度だけ設定します)。
- 価格ページを開き → Subscribe をクリックします。
4242 4242 4242 4242で支払います。- 次の項目を確認します:
- ブラウザーが
successUrlに移動します。 - サーバーログにすべてのイベントが表示されます。
- DB に新しい顧客とサブスクリプションの行が作成されています(
subscription-syncを連携している場合)。 - ダッシュボードの Transactions にトランザクションが表示されます。
- ブラウザーが
- 次の2つの方法のいずれかで、サブスクリプションをキャンセルします:
- 実際にキャンセル: API または顧客ポータルを使って、作成したばかりのサンドボックスのサブスクリプションをキャンセルします(
subscription-cancelを参照)。 - シミュレーション: そのサブスクリプションの ID を
entities.subscription_idに指定し、effective_from: "next_billing_period"を設定して、subscription_cancellationシミュレーションを実行します。これにより、ペイロードにサブスクリプションの詳細が使われるイベントだけが送信されます。実際のサブスクリプションは有効なままなので、その後、DB と Paddle の情報が一致しなくなります。
- 実際にキャンセル: API または顧客ポータルを使って、作成したばかりのサンドボックスのサブスクリプションをキャンセルします(
- 次の項目を確認します:
- 実際のキャンセルで、
effective_from: "next_billing_period"の場合:scheduled_change.action: "cancel"を含み、ステータスが引き続きactiveのsubscription.updatedイベントが届きます。subscription.canceledは請求期間の終了時にのみ届きます。 - 実際のキャンセルで、
effective_from: "immediately"の場合:ステータスがcanceledのsubscription.canceledイベントが届きます。該当する行のステータスもcanceledに切り替わるはずです。 - シミュレーションの場合:同じ実行内で、
scheduled_changeを含むsubscription.updatedの後に、ステータスがcanceledのsubscription.updatedとsubscription.canceledが届きます。
- 実際のキャンセルで、
サンドボックスとライブ:注意が必要な違い
サンドボックスとライブでは、次の挙動が異なります。
| 動作 | サンドボックス | 本番環境 | 重要な理由 |
|---|---|---|---|
| 調整(返金/クレジット) | 返金は pending_approval として作成され、約 10 分以内に自動承認されます。クレジットはすぐに approved になります。 | ほとんどの返金は、Paddle の審査が終わるまで pending_approval のままです。クレジットはすぐに approved になります。 | adjustment.created で返金が完了したと見なさないでください。status が pending_approval から approved(または rejected)に変わる adjustment.updated を処理してください。 |
| Webhook の再試行 | 合計 3 回、すべて約 2 分以内に実行されます | 約 2 日間で合計最大 60 回実行されます | サンドボックスのハンドラーが失敗すると、約 2 分後に再試行されなくなります。修正してから、イベントを再送してください。正確なタイミングに依存するテストは作成しないでください。 |
| 支払い | 支払いはありません | 銀行口座への実際の送金 | 支払いに関する Webhook の処理は、シミュレーションされた支払いのみを対象にテストしてください。 |
| ドメインの承認 | localhost を含むどのページでもチェックアウトを開け、ドメインは追加するとすぐに承認されます。デフォルトの支払いリンクには承認済みドメインは不要ですが、localhost ではなく自分のドメイン上のページを使用してください | 各ドメインを Paddle > My account > Settings > Website approval で承認してください。デフォルトの支払いリンク(Paddle > Checkout > Checkout configuration)は、承認済みドメイン上にある必要があります。 | ドメインの承認を忘れると、本番環境のチェックアウトを読み込めません。GET /checkout-domains で各ドメインの status を確認してください。 |
| チェックアウトの「テストモード」フラグ | チェックアウト画面に表示されます | 表示されません | チェックアウトがどの環境を使用しているかをすばやく目視で確認できます。 |
| 督促(Retain) | 利用できません | 利用できます | Retain は、支払い期限を過ぎたサブスクリプションのキャンセルや一時停止を処理します。 |
| メールの配信 | アカウントのメールアドレスのドメイン宛てにのみ送信されます。それ以外のアドレス宛てのメールは、代わりにアカウントのメールアドレスに届きます。 | 顧客レコードに登録されたメールアドレスに送信されます(実際の配信)。 | すべてのテスト顧客宛てのメールが届くため、自分の受信トレイを確認してください。 |
よくある落とし穴
- サンドボックスと本番環境のキーを混在させること。
PADDLE_API_KEYとNEXT_PUBLIC_PADDLE_ENVが正しい環境用であることを必ず確認してください。 - 本番環境でサンドボックス環境パラメーターを使用すること。 本番環境で Paddle.js を初期化する際は、環境パラメーターが
productionに設定されていることを必ず確認してください。 - 再起動後にトンネルの URL が変わること。 起動のたびに新しい URL が割り当てられるトンネル(無料の ngrok URL など)では、再起動のたびに宛先 URL を更新する必要があります。症状:Paddle 側ではイベントが発生しているのに、コードには届きません。Hookdeck のソース URL は変わりません。
- シミュレーションイベントが
abortedとしてマークされること。 宛先の Usage タイプが「Simulation」または「Both」ではない、無効になっている、またはそのイベントタイプを購読していません。 - 「念のため」実際のカードでテストすること。 サンドボックスでは拒否されます。本番環境では利用でき、課金されます。
- 失敗するケースをテストしないこと。
4000 0000 0000 0002を使ってcheckout.payment.failedの処理を確認し、その後シミュレーターを使ってサブスクリプションの更新失敗(payment_outcome: "failed")をテストしてください。 - 両方の環境で同じ通知先シークレットをコード内で使うこと。 シークレットは同じではありません。環境ごと、宛先ごとに、それぞれ固有のシークレットがあります。
- 問題なく完了するケースだけをテストすること。 実際のユーザーは、カードの拒否、トライアル期間の終了、即時解約など、想定外の状態に遭遇することがあります。シミュレーターで各エッジケースを発生させ、コードの応答を確認してください。