testing-and-go-live
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting 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 if you don't specify , which is a real footgun in development.
live_modeenvironmentTest 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密钥。如果你未指定,SDK默认使用,这在开发过程中是一个极易引发问题的设置。
environmentlive_mode测试支付方式:Dodo 提供了用于测试的特定卡号、VPA和支付场景。这些仅在测试模式下有效。
Webhook测试:CLI提供了命令,可在本地监听Webhook并触发测试事件,无需进行真实支付。
Setting up test mode
设置测试模式
Always explicitly set during development. The default is .
environment: 'test_mode'live_modetypescript
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_modetypescript
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.
| Scenario | Card Number | Result |
|---|---|---|
| Success | | Payment succeeds |
| Decline | | Payment declined |
| Decline (insufficient funds) | | Insufficient funds error |
| Decline (lost card) | | Lost card error |
在测试模式下使用以下卡号。有效期可以是任何未来日期;CVC可以是任意三位数字。
| 场景 | 卡号 | 结果 |
|---|---|---|
| 支付成功 | | 支付成功 |
| 支付失败 | | 支付被拒绝 |
| 支付失败(余额不足) | | 余额不足错误 |
| 支付失败(卡片丢失) | | 卡片丢失错误 |
Subscription renewal failure
订阅续费失败
To test subscription renewal failures, use this card:
| Card Number | Behavior |
|---|---|
| Renewal fails on next billing date |
要测试订阅续费失败,请使用以下卡片:
| 卡号 | 行为 |
|---|---|
| 下一个账单日期续费失败 |
UPI (India)
UPI(印度)
Test UPI success and failure with these VPAs:
| VPA | Result |
|---|---|
| Payment succeeds |
| Payment fails |
使用以下VPA测试UPI支付的成功与失败:
| VPA | 结果 |
|---|---|
| 支付成功 |
| 支付失败 |
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_datetypescript
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 and the affected subscription enters , which emits .
payment.failedon_holdsubscription.on_hold要强制订阅在特定日期续费而无需等待,请更新:
next_billing_datetypescript
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.failedon_holdsubscription.on_holdWebhook testing
Webhook测试
The CLI ships separately from the SDKs:
bash
npm install -g dodopayments-cli
dodo logindodo loginCLI与SDK分开发布:
bash
npm install -g dodopayments-cli
dodo logindodo loginListen for webhooks locally
本地监听Webhook
bash
dodo wh listen http://localhost:3000/webhookThis 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 skill.
webhook-integrationTwo 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 only works inside the interactive TUI as
dodo wh listen, which opens a wizard./wh listen
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参数:仅输入仅在交互式TUI中作为
dodo wh listen时有效,会打开一个向导。/wh listen
通过这种方式到达的事件带有有效的签名,因此可以正常使用进行验证。
webhooks.unwrap()Trigger mock webhook events
触发模拟Webhook事件
bash
dodo wh trigger payment.success http://localhost:3000/webhookSends 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. rejects them because there is no valid signature to verify.
unwrap()Use for triggered events only, and never on an endpoint that also receives real traffic. Event names are , for example , , or .
unsafeUnwrap()<category>.<event>payment.successsubscription.activedispute.openedAs with , both arguments are required in direct mode; inside the TUI opens a wizard instead.
listen/wh triggerbash
dodo wh trigger payment.success http://localhost:3000/webhook发送所选事件的真实模拟负载。它离线运行,即使未登录也能工作——这意味着关键信息:这些负载是未签名的。会拒绝它们,因为没有有效的签名可供验证。
unwrap()仅对触发的事件使用,且绝不要在同时接收真实流量的端点上使用。事件名称格式为,例如、或。
unsafeUnwrap()<category>.<event>payment.successsubscription.activedispute.opened与一样,直接模式下必须提供两个参数;TUI中的会打开一个向导。
listen/wh triggerWebhook signature verification
Webhook签名验证
Covered in the 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 above.
webhook-integrationdodo wh trigger在技能中有详细说明。验证所有通过网络到达的Webhook,无论是测试模式还是正式模式。唯一的例外是上述生成的未签名负载。
webhook-integrationdodo wh triggerCopying 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:
- Note the product details (name, price, tax category, etc.) from test mode.
- Create a new product in live mode with the same configuration.
- 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。请在正式模式下重新创建产品,或使用控制台导出并重新导入。
手动方法:
- 记录测试模式下的产品详情(名称、价格、税目等)。
- 在正式模式下创建具有相同配置的新产品。
- 更新代码以使用新的正式产品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 (or omits it, since live is the default).
environment: 'live_mode'
- 你已从控制台获取正式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 or the
client.webhooks.unwrap()package.standardwebhooks - 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未设置environment
environmentThe SDK defaults to . If you forget to set during development, your test code will hit production and create real charges.
live_modeenvironment: 'test_mode'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_modeenvironment: '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 in production environment variables.
DODO_PAYMENTS_WEBHOOK_KEY - 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 or the package. Never trust a webhook without verification, even if it came from Dodo.
client.webhooks.unwrap()standardwebhookstypescript
// 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' });
}
});请始终使用或包验证Webhook签名。即使来自Dodo的Webhook,也绝不要在未验证的情况下信任它。
client.webhooks.unwrap()standardwebhookstypescript
// 错误——未验证
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 ) only work with . 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测试密钥(前缀)仅适用于。正式密钥从控制台按模式颁发。将测试密钥用于正式模式会因认证错误而失败。
dodo_test_https://test.dodopayments.com