mcpField
P
paddle-sandbox-testing

paddle-sandbox-testing

github.com/paddlehq测试与质量9116 次安装6 天前更新
代码仓库

关于 paddle-sandbox-testing

paddle-sandbox-testing 是供开发者和 Agent 在上线前验证 Paddle 集成的技能,涵盖沙盒配置、测试支付、Webhook 模拟及环境差异,无需收取真实款项。

主要功能

  1. 01沙盒与生产环境配置指南
  2. 02测试卡支付场景
  3. 03单事件 Webhook 模拟
  4. 04订阅生命周期场景模拟
  5. 05通过公网隧道进行本地测试

适用场景

  1. 01上线前验证结账流程
  2. 02单独测试 Webhook 处理程序
  3. 03演练订阅续费与取消
  4. 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 提供了两个完全独立的环境:

沙盒生产环境
用途开发和测试真实客户、真实资金
API 基础 URLsandbox-api.paddle.comapi.paddle.com
控制面板sandbox-vendors.paddle.comvendors.paddle.com
API 密钥单独一套单独一套
客户端令牌单独一套单独一套
通知目标单独一套单独一套
结账和账户设置单独一套单独一套
产品 / 价格 / 客户相互独立(不会同步)—
接受的卡片仅限测试卡(拒绝真实卡片)真实卡片
退款自动批准(约 10 分钟)大多数由 Paddle 审核
税费计算但不收取按实际税费处理
域名审批无需审批(任何域名均可)每个域名都需手动审批

两者之间不会共享任何内容。 沙盒中的 pri_01h... 在生产环境中并不存在。通知目标彼此独立,客户也彼此独立。这是有意为之——这样一来,“先在沙盒中测试,再切换到线上”就是默认的安全做法。

步骤 1:配置沙盒

在 sandbox-login.paddle.com/signup 注册沙盒账号(与生产环境的注册相互独立)。然后:

  1. 设置至少一个产品和一个价格——如果尚未完成,请参阅 catalog-setup。
  2. 前往 Paddle > My account > Settings > Authentication 并创建:
    • 一个客户端令牌(在 Client-side tokens 标签页),用于 Paddle.js。
    • 一个服务端 API 密钥(在 API keys 标签页),用于 Node SDK。为其授予本技能的 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 …。
  3. 检查是否已设置沙盒默认支付链接——否则结账页面无法打开。如果有可用的 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 下添加该域名;沙盒会立即批准,然后重试。请先向用户询问 URL,再通过 client.settings.checkoutUrl.update({ default_url: "https://example.com/checkout" }) 设置。使用 Paddle CLI 时,请运行 paddle --environment sandbox settings checkout-url get 和 … settings checkout-url update --json '{"default_url": "…"}'。也可以让用户在 Paddle > Checkout > Checkout configuration 下设置。
  4. 记下 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 模拟器

模拟器会向其中一个通知目标发送伪造但签名有效的 webhook 事件,无需经过真实流程。模拟事件不会更改账号中的任何真实数据。提供两种模式:

单个事件。 选择一种事件类型(例如 subscription.canceled)——模拟的 type 就是事件名称。编辑 JSON 负载,或让 Paddle 填入示例数据,然后运行。Paddle 会使用目标的密钥对其签名,并将其 POST 到目标 URL。可用此功能隔离测试特定处理逻辑——例如:“收到 subscription.paused 时,我的 UI 会怎样显示?” 场景。 一系列预定义事件,模拟真实流程:

场景 type事件(默认选项)
subscription_creationtransaction.created、customer.created、address.created、transaction.ready、transaction.paid、subscription.created、subscription.activated(或 subscription.trialing)、transaction.completed、payment_method.saved — 各步骤之间会有 transaction.updated
subscription_renewalsubscription.updated、transaction.created、transaction.billed、transaction.updated、transaction.paid、transaction.updated、transaction.completed
subscription_pausesubscription.updated、subscription.paused
subscription_resumesubscription.updated、subscription.resumed、transaction.created、transaction.billed,然后是与续订相同的付款事件
subscription_cancellationsubscription.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 服务,则可以在一次 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 为每个服务提供三个工具(search、execute、report_missing_tool)。工作流:调用 search 确认准确的方法名称和参数结构,然后调用 execute,传入一个会调用 client.<resource>.<operation>(...) 的异步函数。方法路径使用 camelCase(client.clientTokens.create、client.pricingPreview.preview)。请求体参数和响应字段使用 snake_case(tax_category、product_id、unit_price、currency_code)。列表方法返回 { pagination: { hasMore, estimatedTotal }, <resource>: [...] },而不是 data。数组的键是资源的 API 路径片段,因此方法路径使用 camelCase 时,该键使用 kebab-case:(await client.products.list()).products、(await client.clientTokens.list())["client-tokens"]。当 pagination.hasMore 为 true 时,使用 { after: "<last_id>" } 翻页,不要使用 .next()。未分页的列表(例如 client.currencies.list())会返回普通数组。单实体方法会返回该实体。在一次 execute 中串联多步工作流;变量不会在不同调用之间保留。硬性限制:每次 execute 最多 50 次 API 调用、超时 30 秒,代码最多 32,000 个字符。

Paddle CLI(paddle,v0.2.0 或更高版本)可使用用户保存的登录凭据,从终端执行 Paddle API 操作。资源命令与 API 参考一致:paddle products create 即为“创建产品”。运行 paddle <resource> --help 查找命令;在 create 或 update 命令中添加 --schema 可查看请求体;对于没有对应命令的端点,可使用 paddle api <path>。它默认连接实时账户,因此除非用户正在使用实时环境,否则请传入 --environment sandbox。Agent 运行该命令时,它会返回 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 …),绝不要在聊天中索要密钥。使用 OAuth 登录的实时账户可以执行用户 Paddle 角色允许的任何更改,因此请逐项说明每项实时更改,并在执行前等待用户确认。对于 CI/CD,请将仅具备所需权限的实时 API 密钥设置为 PADDLE_API_KEY。安装方法请参阅 Paddle CLI。

使用 Paddle CLI 时,请将相同步骤作为独立命令运行。请求体使用相同的 snake_case 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_...

使用模拟器:

  1. 在控制面板中,前往 Paddle > Events > Simulations。
  2. 选择一个场景或单个事件。
  3. 选择要发送到的通知目标。你需要选择已有目标,而不是输入 URL,因此请先将该目标指向通过隧道访问的本地 URL 或已部署的预览 URL。
  4. (可选)对于单个事件,编辑负载 JSON,使用特定 ID(例如你已在数据库中创建的 customer_id)。对于场景,请添加真实的 sandbox 实体。
  5. (可选)配置模拟(例如,将付款结果设为 failed,以模拟付款失败)。
  6. 运行模拟,并查看本地服务日志。

目标的 Usage type 必须为“Simulation”或“Both”(traffic_source: "simulation" 或 "all")——不能使用“Platform”目标。此外,目标必须处于启用状态,并已订阅模拟中的每种事件类型;任何无法接收的事件都会标记为 aborted,且不会发送。“Simulation”目标不会收到真实事件,因此,对于也用于真实结账的本地目标,请使用“Both”。 每个模拟事件只会尝试投递一次,不会重试。在 Paddle > Events > Simulations 中查看该运行的结果,或使用 client.simulations.runsEvents.list(simulationId, runId)——每个事件都有一个 status(success、failed、aborted、pending),以及请求和响应。使用 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 的目标,然后在每次构建中启动一次新运行,并在有任何事件未能投递时使构建失败。将具有模拟器所需权限的沙盒 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:典型的端到端测试

以下是验证全新集成的流程:

  1. 建立隧道 localhost:hookdeck listen 3000 paddle --path /api/webhook。
  2. 将沙盒目标指向隧道的源 URL(只需设置一次——重启后不会改变)。
  3. 打开定价页面 → 点击 Subscribe。
  4. 付款时使用 4242 4242 4242 4242。
  5. 确认:
    • 浏览器跳转到你的 successUrl。
    • 服务器日志显示所有事件
    • 数据库中有新的客户和订阅记录(如果你已接入 subscription-sync)。
    • 控制面板在 Transactions 下显示该交易。
  6. 取消订阅,以下两种方式任选其一:
    • **真实取消:**使用 API 或客户门户取消刚创建的沙盒订阅(见 subscription-cancel)。
    • **模拟取消:**运行 subscription_cancellation 模拟,在 entities.subscription_id 中填写该订阅的 ID,并设置 effective_from: "next_billing_period"。这只会发送载荷使用该订阅详情的事件——真实订阅仍会保持有效,因此之后你的数据库与 Paddle 中的状态会不一致。
  7. 确认:
    • 真实取消,effective_from: "next_billing_period":收到一个 subscription.updated 事件,其中 scheduled_change.action: "cancel",且状态仍为 active。只有在计费周期结束时才会收到 subscription.canceled。
    • 真实取消,effective_from: "immediately":收到一个状态为 canceled 的 subscription.canceled 事件;你的记录应更新为 canceled。
    • 模拟取消:同一次运行中,先收到一个带有 scheduled_change 的 subscription.updated,然后收到 subscription.updated 和 subscription.canceled,两者状态均为 canceled。

沙盒与实时环境:可能让你措手不及的差异

这些行为在沙盒环境和实时环境中有所不同。

行为沙盒正式环境重要性
调整(退款/抵扣金)退款会创建为 pending_approval,并在约 10 分钟内自动批准。抵扣金会立即变为 approved。大多数退款会保持 pending_approval,直到 Paddle 审核。抵扣金会立即变为 approved。不要在 adjustment.created 时就将退款视为完成——应处理 adjustment.updated,其中 status 会从 pending_approval 变为 approved(或 rejected)。
Webhook 重试总共尝试 3 次,均在约两分钟内完成总共最多尝试 60 次,持续约两天沙盒处理程序失败后,约两分钟后就不会再有重试——修复后重放该事件。不要编写依赖精确时序的测试。
付款无付款向你的银行账户转入真实资金仅针对模拟付款测试付款 Webhook 处理。
域名审批Checkout 可在任何页面上打开,包括 localhost,且添加域名后会立即获批。默认付款链接不需要已获批的域名,但请使用自有域名下的页面,而非 localhost在 Paddle > My account > Settings > Website approval 中审批每个域名。你的默认付款链接(Paddle > Checkout > Checkout configuration)必须位于已获批的域名上。忘记审批域名会导致正式环境的 Checkout 无法加载。使用 GET /checkout-domains 检查每个域名的 status。
Checkout“测试模式”标记在结账页面上显示不显示快速直观地检查结账页面使用的是哪个环境。
催缴(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")。
  • 在代码中为两个环境使用相同的通知目标密钥。 两者并不相同。每个环境中的每个目标都有自己的密钥。
  • 只测试成功路径。 真实用户可能遇到各种异常状态,例如银行卡被拒、试用期到期、立即取消。使用模拟器触发每种边缘情况,并观察代码的响应。

相关文档

评论

支持 Markdown