testing-and-go-live

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing and Go-Live

测试与上线

This skill covers test mode, test payment methods, webhook testing, and the production launch checklist. Use it when building payment flows that need verification before going live, or when preparing to switch from test to production.
本技能涵盖测试模式、测试支付方式、Webhook测试以及生产上线检查清单。当你构建需要在上线前验证的支付流程,或准备从测试环境切换到生产环境时,可以使用本技能。

When to use this skill

适用场景

  • You need to test card success/decline scenarios, subscription renewals, or regional payment methods.
  • You're setting up webhook testing locally or in a staging environment.
  • You're preparing to launch to production and need a go-live checklist.
  • You want to verify that test and live environments are properly isolated.
  • You're copying products from test to production.
  • 你需要测试卡片支付成功/失败场景、订阅续费或区域支付方式。
  • 你正在本地或预发布环境中设置Webhook测试。
  • 你准备上线到生产环境,需要一份上线检查清单。
  • 你想要验证测试环境与正式环境是否已正确隔离。
  • 你正在将产品从测试环境复制到生产环境。

Core concepts

核心概念

Test vs. live mode: Dodo Payments maintains two completely separate environments. Each has its own base URL, API keys, webhooks, products, customers, and transactions. Data does not carry over between them.
  • Test mode:
    https://test.dodopayments.com
  • Live mode:
    https://live.dodopayments.com
Each environment requires separate API keys issued from the dashboard. The SDK defaults to
live_mode
if you don't specify
environment
, which is a real footgun in development.
Test payment methods: Dodo documents specific card numbers, VPAs, and payment method scenarios for testing. These only work in test mode.
Webhook testing: The CLI provides commands to listen for webhooks locally and trigger test events without making real payments.

测试模式 vs 正式模式:Dodo Payments 维护两个完全独立的环境。每个环境都有自己的基础URL、API密钥、Webhook、产品、客户和交易记录。数据不会在两个环境之间互通。
  • 测试模式:
    https://test.dodopayments.com
  • 正式模式:
    https://live.dodopayments.com
每个环境都需要从控制台获取单独的API密钥。如果你未指定
environment
,SDK默认使用
live_mode
,这在开发过程中是一个极易引发问题的设置。
测试支付方式:Dodo 提供了用于测试的特定卡号、VPA和支付场景。这些仅在测试模式下有效。
Webhook测试:CLI提供了命令,可在本地监听Webhook并触发测试事件,无需进行真实支付。

Setting up test mode

设置测试模式

Always explicitly set
environment: 'test_mode'
during development. The default is
live_mode
.
typescript
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode', // REQUIRED — defaults to 'live_mode'
});
python
from dodopayments import DodoPayments

client = DodoPayments(
    bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
    environment="test_mode",  # defaults to "live_mode"
)
go
client := dodopayments.NewClient(
    option.WithBearerToken("dodo_test_..."),
    option.WithEnvironmentTestMode(), // defaults to WithEnvironmentLiveMode()
)
Get your test API key from the Dodo dashboard. Test keys are prefixed
dodo_test_
.

开发过程中请始终显式设置
environment: 'test_mode'
。默认值为
live_mode
。
typescript
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode', // 必填项——默认值为'live_mode'
});
python
from dodopayments import DodoPayments

client = DodoPayments(
    bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
    environment="test_mode",  # 默认值为"live_mode"
)
go
client := dodopayments.NewClient(
    option.WithBearerToken("dodo_test_..."),
    option.WithEnvironmentTestMode(), // 默认值为WithEnvironmentLiveMode()
)
从Dodo控制台获取你的测试API密钥。测试密钥前缀为
dodo_test_
。

Test payment methods

测试支付方式

Card success and decline scenarios

卡片支付成功与失败场景

Use these card numbers in test mode. The expiry can be any future date; the CVC can be any three digits.
ScenarioCard NumberResult
Success
4242 4242 4242 4242
Payment succeeds
Decline
4000 0000 0000 0002
Payment declined
Decline (insufficient funds)
4000 0000 0000 9995
Insufficient funds error
Decline (lost card)
4000 0000 0000 9987
Lost card error
在测试模式下使用以下卡号。有效期可以是任何未来日期;CVC可以是任意三位数字。
场景卡号结果
支付成功
4242 4242 4242 4242
支付成功
支付失败
4000 0000 0000 0002
支付被拒绝
支付失败(余额不足)
4000 0000 0000 9995
余额不足错误
支付失败(卡片丢失)
4000 0000 0000 9987
卡片丢失错误

Subscription renewal failure

订阅续费失败

To test subscription renewal failures, use this card:
Card NumberBehavior
4000 0000 0000 0069
Renewal fails on next billing date
要测试订阅续费失败,请使用以下卡片:
卡号行为
4000 0000 0000 0069
下一个账单日期续费失败

UPI (India)

UPI(印度)

Test UPI success and failure with these VPAs:
VPAResult
success@okhdfcbank
Payment succeeds
failure@okhdfcbank
Payment fails
使用以下VPA测试UPI支付的成功与失败:
VPA结果
success@okhdfcbank
支付成功
failure@okhdfcbank
支付失败

BNPL, wallets, and regional methods

BNPL、钱包及区域支付方式

BNPL, Apple Pay, Google Pay, and European payment methods (Giropay, iDEAL, Bancontact) are available in test mode. Exact test scenarios vary by method. Refer to the Testing Process documentation for the current test matrix.

测试模式支持BNPL、Apple Pay、Google Pay以及欧洲支付方式(Giropay、iDEAL、Bancontact)。具体测试场景因支付方式而异。请参考测试流程文档获取当前测试矩阵。

Testing subscription renewals

测试订阅续费

To force a subscription to renew on a specific date without waiting, update the
next_billing_date
:
typescript
await client.subscriptions.update('sub_123', {
  next_billing_date: '2026-05-03T00:00:00Z',
});
python
client.subscriptions.update(
    'sub_123',
    next_billing_date='2026-05-03T00:00:00Z'
)
go
// NextBillingDate is param.Field[time.Time], not a string. Passing a string
// literal compiles to param.Field[string] and will not type-check.
client.Subscriptions.Update(ctx, "sub_123", dodopayments.SubscriptionUpdateParams{
    NextBillingDate: dodopayments.F(time.Date(2026, time.May, 3, 0, 0, 0, 0, time.UTC)),
})
This immediately schedules the renewal. If you used the subscription-renewal-failure card, the failed charge emits
payment.failed
and the affected subscription enters
on_hold
, which emits
subscription.on_hold
.

要强制订阅在特定日期续费而无需等待,请更新
next_billing_date
:
typescript
await client.subscriptions.update('sub_123', {
  next_billing_date: '2026-05-03T00:00:00Z',
});
python
client.subscriptions.update(
    'sub_123',
    next_billing_date='2026-05-03T00:00:00Z'
)
go
// NextBillingDate 是 param.Field[time.Time],而非字符串。传递字符串字面量
// 会编译为param.Field[string],无法通过类型检查。
client.Subscriptions.Update(ctx, "sub_123", dodopayments.SubscriptionUpdateParams{
    NextBillingDate: dodopayments.F(time.Date(2026, time.May, 3, 0, 0, 0, 0, time.UTC)),
})
这会立即安排续费。如果你使用了订阅续费失败的卡片,失败的扣款会触发
payment.failed
事件,受影响的订阅会进入
on_hold
状态,并触发
subscription.on_hold
事件。

Webhook testing

Webhook测试

The CLI ships separately from the SDKs:
bash
npm install -g dodopayments-cli
dodo login
dodo login
opens the API Keys page, accepts a pasted key, and asks whether it is Test Mode or Live Mode. One key of each can stay authenticated at the same time.
CLI与SDK分开发布:
bash
npm install -g dodopayments-cli
dodo login
dodo login
会打开API密钥页面,接受粘贴的密钥,并询问该密钥属于测试模式还是正式模式。可以同时保持一个测试模式密钥和一个正式模式密钥处于认证状态。

Listen for webhooks locally

本地监听Webhook

bash
dodo wh listen http://localhost:3000/webhook
This forwards real test-mode events to your local URL over an outbound relay. It is not a tunnel: nothing listens publicly and no URL is printed for you to paste into the dashboard. If you need a registerable HTTPS URL, use ngrok instead — that flow is described in the
webhook-integration
skill.
Two constraints to know before running it:
  • A Test Mode key is required. Live Mode keys are not supported by the listen flow.
  • The URL argument is required in direct mode. Bare
    dodo wh listen
    only works inside the interactive TUI as
    /wh listen
    , which opens a wizard.
Events arriving this way carry valid signatures, so verify them normally with
webhooks.unwrap()
.
bash
dodo wh listen http://localhost:3000/webhook
此命令会通过出站中继将真实的测试模式事件转发到你的本地URL。它不是隧道:不会公开监听,也不会打印可粘贴到控制台的URL。如果你需要可注册的HTTPSURL,请使用ngrok替代——该流程在
webhook-integration
技能中有描述。
运行前需要了解两个限制:
  • 需要测试模式密钥:监听流程不支持正式模式密钥。
  • 直接模式下必须提供URL参数:仅输入
    dodo wh listen
    仅在交互式TUI中作为
    /wh listen
    时有效,会打开一个向导。
通过这种方式到达的事件带有有效的签名,因此可以正常使用
webhooks.unwrap()
进行验证。

Trigger mock webhook events

触发模拟Webhook事件

bash
dodo wh trigger payment.success http://localhost:3000/webhook
Sends a realistic mock payload for a chosen event. It runs offline and works even while logged out — which is the tell for the part that matters: these payloads are unsigned.
unwrap()
rejects them because there is no valid signature to verify.
Use
unsafeUnwrap()
for triggered events only, and never on an endpoint that also receives real traffic. Event names are
<category>.<event>
, for example
payment.success
,
subscription.active
, or
dispute.opened
.
As with
listen
, both arguments are required in direct mode;
/wh trigger
inside the TUI opens a wizard instead.
bash
dodo wh trigger payment.success http://localhost:3000/webhook
发送所选事件的真实模拟负载。它离线运行,即使未登录也能工作——这意味着关键信息:这些负载是未签名的。
unwrap()
会拒绝它们,因为没有有效的签名可供验证。
仅对触发的事件使用
unsafeUnwrap()
,且绝不要在同时接收真实流量的端点上使用。事件名称格式为
<category>.<event>
,例如
payment.success
、
subscription.active
或
dispute.opened
。
与
listen
一样,直接模式下必须提供两个参数;TUI中的
/wh trigger
会打开一个向导。

Webhook signature verification

Webhook签名验证

Covered in the
webhook-integration
skill. Verify every webhook that arrives over the network, in test mode as well as live. The single exception is the unsigned payloads produced by
dodo wh trigger
above.

在
webhook-integration
技能中有详细说明。验证所有通过网络到达的Webhook,无论是测试模式还是正式模式。唯一的例外是上述
dodo wh trigger
生成的未签名负载。

Copying products from test to live

将产品从测试环境复制到正式环境

Products are environment-specific. You cannot reuse a test product ID in production. Instead, recreate the product in live mode or use the dashboard to export and reimport.
Manual approach:
  1. Note the product details (name, price, tax category, etc.) from test mode.
  2. Create a new product in live mode with the same configuration.
  3. Update your code to use the new live product ID.
Programmatic approach:
Fetch the product from test mode, extract its configuration, and create it in live mode:
typescript
// In test mode
const testClient = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode',
});
const testProduct = await testClient.products.retrieve('pdt_test_123');

// In live mode
const liveClient = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'live_mode',
});
const liveProduct = await liveClient.products.create({
  name: testProduct.name,
  price: testProduct.price,
  tax_category: testProduct.tax_category,
  // ... other fields
});

产品是环境特定的。你无法在生产环境中重用测试产品ID。请在正式模式下重新创建产品,或使用控制台导出并重新导入。
手动方法:
  1. 记录测试模式下的产品详情(名称、价格、税目等)。
  2. 在正式模式下创建具有相同配置的新产品。
  3. 更新代码以使用新的正式产品ID。
程序化方法:
从测试模式获取产品,提取其配置,然后在正式模式下创建:
typescript
// 测试模式下
const testClient = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode',
});
const testProduct = await testClient.products.retrieve('pdt_test_123');

// 正式模式下
const liveClient = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'live_mode',
});
const liveProduct = await liveClient.products.create({
  name: testProduct.name,
  price: testProduct.price,
  tax_category: testProduct.tax_category,
  // ... 其他字段
});

Go-live checklist

上线检查清单

Before switching to production, verify each item:
切换到生产环境前,请验证以下每一项:

Business and compliance

业务与合规

  • KYC (Know Your Customer) verification is complete in the Dodo dashboard.
  • Business verification is approved.
  • Your business details are correct in the dashboard.
  • Dodo控制台中的KYC(了解你的客户)验证已完成。
  • 企业验证已通过。
  • 控制台中的企业信息正确无误。

API and keys

API与密钥

  • You have live API keys from the dashboard (prefixed
    dodo_live_
    ).
  • You've rotated or revoked any test keys that were accidentally committed.
  • Your code uses
    environment: 'live_mode'
    (or omits it, since live is the default).
  • 你已从控制台获取正式API密钥(前缀为
    dodo_live_
    )。
  • 你已轮换或撤销任何意外提交的测试密钥。
  • 你的代码使用
    environment: 'live_mode'
    (或省略该配置,因为正式模式是默认值)。

Webhooks

Webhook

  • Your webhook endpoint is publicly accessible over HTTPS.
  • You've updated the webhook URL in the dashboard to your production domain.
  • You've updated the webhook secret in your environment variables.
  • Your webhook handler verifies signatures using
    client.webhooks.unwrap()
    or the
    standardwebhooks
    package.
  • You've tested the webhook flow end-to-end in production (or a staging environment with live keys).
  • 你的Webhook端点可通过HTTPS公开访问。
  • 你已将控制台中的Webhook URL更新为你的生产域名。
  • 你已更新环境变量中的Webhook密钥。
  • 你的Webhook处理程序使用
    client.webhooks.unwrap()
    或
    standardwebhooks
    包验证签名。
  • 你已在生产环境(或使用正式密钥的预发布环境)中端到端测试了Webhook流程。

Products and checkout

产品与结账

  • All products are created in live mode (not copied from test).
  • Your checkout code uses live product IDs.
  • Return URLs point to your production domain.
  • Tax categories are correct for your products.
  • 所有产品均已在正式模式下创建(而非从测试环境复制)。
  • 你的结账代码使用正式产品ID。
  • 返回URL指向你的生产域名。
  • 产品的税目设置正确。

Monitoring and support

监控与支持

  • You have monitoring in place for payment failures, webhook delivery, and API errors.
  • You know how to access the Dodo dashboard to view transactions and disputes.
  • You have a plan for handling refunds and chargebacks.

  • 你已设置支付失败、Webhook交付和API错误的监控机制。
  • 你知道如何访问Dodo控制台查看交易记录和争议。
  • 你有处理退款和拒付的计划。

Common mistakes

常见错误

Leaving
environment
unset

未设置
environment

The SDK defaults to
live_mode
. If you forget to set
environment: 'test_mode'
during development, your test code will hit production and create real charges.
typescript
// WRONG — this hits live mode
const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});

// CORRECT
const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode',
});
SDK默认使用
live_mode
。如果你在开发过程中忘记设置
environment: 'test_mode'
,你的测试代码会访问生产环境并产生真实扣款。
typescript
// 错误——访问正式模式
const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});

// 正确
const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode',
});

Reusing test product IDs in production

在生产环境中重用测试产品ID

Test and live environments are completely separate. A product ID from test mode does not exist in live mode. Always create new products in live mode or use the dashboard to migrate them.
typescript
// WRONG — pdt_test_123 does not exist in live mode
const session = await liveClient.checkoutSessions.create({
  product_cart: [{ product_id: 'pdt_test_123', quantity: 1 }],
});

// CORRECT — use a live product ID
const session = await liveClient.checkoutSessions.create({
  product_cart: [{ product_id: 'pdt_live_456', quantity: 1 }],
});
测试环境与正式环境完全独立。测试模式的产品ID在正式模式下不存在。请始终在正式模式下创建新产品,或使用控制台迁移它们。
typescript
// 错误——pdt_test_123在正式模式下不存在
const session = await liveClient.checkoutSessions.create({
  product_cart: [{ product_id: 'pdt_test_123', quantity: 1 }],
});

// 正确——使用正式产品ID
const session = await liveClient.checkoutSessions.create({
  product_cart: [{ product_id: 'pdt_live_456', quantity: 1 }],
});

Forgetting to reconfigure webhooks for production

忘记为生产环境重新配置Webhook

Test and live modes have separate webhook endpoints and secrets. If you copy your test webhook configuration to production without updating the URL and secret, webhooks will fail or go to the wrong place.
  • Update
    DODO_PAYMENTS_WEBHOOK_KEY
    in production environment variables.
  • Update the webhook URL in the dashboard to your production domain.
  • Test webhook delivery in production.
测试模式与正式模式有独立的Webhook端点和密钥。如果你将测试Webhook配置复制到生产环境而未更新URL和密钥,Webhook会失败或发送到错误的位置。
  • 更新生产环境变量中的
    DODO_PAYMENTS_WEBHOOK_KEY
    。
  • 将控制台中的Webhook URL更新为你的生产域名。
  • 在生产环境中测试Webhook交付。

Not verifying webhook signatures in production

生产环境中未验证Webhook签名

Always verify webhook signatures using
client.webhooks.unwrap()
or the
standardwebhooks
package. Never trust a webhook without verification, even if it came from Dodo.
typescript
// WRONG — no verification
app.post('/webhook', (req, res) => {
  const event = req.body;
  handleEvent(event);
  res.json({ received: true });
});

// CORRECT
app.post('/webhook', async (req, res) => {
  try {
    const unwrapped = client.webhooks.unwrap(req.body.toString(), {
      headers: {
        'webhook-id': req.headers['webhook-id'] as string,
        'webhook-signature': req.headers['webhook-signature'] as string,
        'webhook-timestamp': req.headers['webhook-timestamp'] as string,
      },
    });
    handleEvent(unwrapped);
    res.json({ received: true });
  } catch (error) {
    res.status(401).json({ error: 'Invalid signature' });
  }
});
请始终使用
client.webhooks.unwrap()
或
standardwebhooks
包验证Webhook签名。即使来自Dodo的Webhook,也绝不要在未验证的情况下信任它。
typescript
// 错误——未验证
app.post('/webhook', (req, res) => {
  const event = req.body;
  handleEvent(event);
  res.json({ received: true });
});

// 正确
app.post('/webhook', async (req, res) => {
  try {
    const unwrapped = client.webhooks.unwrap(req.body.toString(), {
      headers: {
        'webhook-id': req.headers['webhook-id'] as string,
        'webhook-signature': req.headers['webhook-signature'] as string,
        'webhook-timestamp': req.headers['webhook-timestamp'] as string,
      },
    });
    handleEvent(unwrapped);
    res.json({ received: true });
  } catch (error) {
    res.status(401).json({ error: '无效签名' });
  }
});

Mixing test and live keys

混合使用测试与正式密钥

Test keys (prefixed
dodo_test_
) only work with
https://test.dodopayments.com
. Live keys are issued per mode from the dashboard. Using a test key with live mode will fail with an authentication error.

测试密钥(前缀
dodo_test_
)仅适用于
https://test.dodopayments.com
。正式密钥从控制台按模式颁发。将测试密钥用于正式模式会因认证错误而失败。

Resources

资源