Change Plan API
Plan Change Preview
Integration Guide
What is a subscription upgrade or downgrade?
Changing plans lets you move a customer between subscription tiers or quantities. Use it to:- Align pricing with usage or features
- Move from monthly to annual (or vice versa)
- Adjust quantity for seat-based products
When to use plan changes
- Upgrade when a customer needs more features, usage, or seats
- Downgrade when usage decreases
- Migrate users to a new product or price without cancelling their subscription
Plan Change Flow
Prerequisites
Before implementing subscription plan changes, ensure you have:- A Dodo Payments merchant account with active subscription products
- API credentials (API key and webhook secret key) from the dashboard
- An existing active subscription to modify
- Webhook endpoint configured to handle subscription events
Step-by-Step Implementation Guide
Follow this comprehensive guide to implement subscription plan changes in your application:Understand Plan Change Requirements
- Which subscription products can be changed to which others
- What proration mode fits your business model
- How to handle failed plan changes gracefully
- Which webhook events to track for state management
Choose Your Proration Strategy
- prorated_immediately
- difference_immediately
- full_immediately
- do_not_bill
- Calculates exact prorated amount based on remaining cycle time
- Charges a prorated amount based on unused time remaining in the cycle
- Provides transparent billing to customers
Implement the Change Plan API
prorated_immediately, full_immediately, difference_immediately, or do_not_bill.prevent_change: Keep subscription on current plan until payment succeedsapply_change(default): Apply plan change immediately regardless of payment outcome
- 未提供 /
null— 如果适用于新产品,现有的折扣与preserve_on_plan_change=true会被保留。 [](空数组) — 从订阅中移除所有现有折扣。["CODE_A", "CODE_B", ...]— 用该堆叠集替换任何现有的折扣。
discount_codes。此字段仍然保留后向兼容性,但不能在同一请求中与 discount_codes 结合使用。immediately(默认):立即应用计划更改next_billing_date:为下一个账单日期安排更改。客户在账单周期结束前保持当前计划。
next_billing_date,以便客户在账单周期结束前保留当前计划的好处。Handle Webhook Events
subscription.active:计划更改成功,订阅已更新subscription.plan_changed:订阅计划已更改(升级/降级/插件更新)subscription.on_hold:计划更改费用失败,订阅暂停payment.succeeded:计划更改的即时费用已成功payment.failed:即时费用失败
Update Your Application State
- 根据新计划授予/撤销功能
- 使用新计划详细信息更新客户仪表板
- 发送有关计划更改的确认电子邮件
- 记录账单更改以供审核
预览计划更改
在确认计划更改之前,使用预览 API 向客户展示他们将被收取的确切费用:- Node.js SDK
- Python SDK
更改计划 API
使用更改计划 API 修改活动订阅的产品、数量和比例行为。快速启动示例
- Node.js SDK
- Python SDK
- Go SDK
- HTTP
invoice_id和payment_id才会返回。总是依赖 webhook 事件(例如payment.succeeded,subscription.plan_changed)来确认结果。管理插件
在更改订阅计划时,您还可以修改插件:应用折扣代码
在更改订阅计划时,您可以应用一个或多个 stacked 折扣代码(最多20个,按数组顺序应用)。这对于在升级或迁移时提供促销价格很有用。- Node.js SDK
- Python SDK
- HTTP
计划更改时的折扣行为
discount_codes 值 | 行为 |
|---|---|
未提供 / null | 自动保留适用于新产品的现有折扣与 preserve_on_plan_change=true。 |
[](空数组) | 从订阅中移除所有现有折扣。 |
["CODE_A", "CODE_B", ...] | 用该堆叠集替换任何现有折扣,按数组顺序验证和应用。 |
discount_code 字段 已弃用 但仍然可以用于后向兼容性 — 现有集成不需要立即更改。它不能与 discount_codes 在同一请求中结合使用。方便时迁移到数组形式。比例模式
选择在更改计划时如何向客户收费:prorated_immediately
- 对当前周期的部分差异收费
- 如果在试用期内,立即收费并转换为新计划
- 降级:可能生成预付抵用金,用于未来续订
full_immediately
- 立即全额收费
- 忽略旧计划的剩余时间
difference_immediately进行的降级所创建的抵用金是订阅范围内的,与基于抵用的结算权利相区别。它们会自动应用于同一订阅的未来续订,不能在订阅之间转移。difference_immediately
- 升级:立即收费旧计划和新计划之间的价格差异
- 降级:将剩余价值作为内部抵用金添加到订阅,并在续订时自动应用
do_not_bill
- 不计算任何费用或抵用金
- 客户立即切换到新计划,没有任何账单调整
- 账单周期保持不变
- 最适合礼遇迁移、免费计划切换或吸收成本差异
| 功能 | prorated_immediately | difference_immediately | full_immediately | do_not_bill |
|---|---|---|---|---|
| 升级费用 | 剩余天数的比例差异 | 计划之间的全额价格差异 | 新计划的全额价格 | 无费用 |
| 降级抵用 | 剩余天数的比例抵用 | 全额价格差异作为抵用 | 无抵用 | 无抵用 |
| 账单周期 | 未变 | 未变 | 重置为今天 | 未变 |
| 试用行为 | 结束试用,立即收费 | 结束试用,立即收费 | 结束试用,全额收费 | 结束试用,无费用 |
| 最适合 | 公平的基于时间的账单 | 简单的升级/降级计算 | 重置账单周期 | 免费迁移或礼遇切换 |
| 复杂性 | 中等(天数计算) | 低(简单减法) | 低(全额收费) | 无 |
示例场景
使用这些规范数字保持一致:- 当前计划:Basic 每月 $30
- 升级目标:Pro 每月 $80
- 降级目标(从 Pro):Starter 每月 $20
- 账单周期:30天,从 1月1日 开始
- 计划更改发生在 1月16日(剩余 15 天,已使用 15 天)
Upgrade: Basic ($30) → Pro ($80) with prorated_immediately
Upgrade: Basic ($30) → Pro ($80) with prorated_immediately
Downgrade: Pro ($80) → Starter ($20) with prorated_immediately
Downgrade: Pro ($80) → Starter ($20) with prorated_immediately
Upgrade: Basic ($30) → Pro ($80) with difference_immediately
Upgrade: Basic ($30) → Pro ($80) with difference_immediately
Downgrade: Pro ($80) → Starter ($20) with difference_immediately
Downgrade: Pro ($80) → Starter ($20) with difference_immediately
Upgrade: Basic ($30) → Pro ($80) with full_immediately
Upgrade: Basic ($30) → Pro ($80) with full_immediately
Mid-cycle upgrade with add-ons using prorated_immediately
Mid-cycle upgrade with add-ons using prorated_immediately
每种模式如何处理账单
处理支付失败
控制计划更改支付失败时的行为,使用on_payment_failure 参数。
支付失败模式
- prevent_change (Recommended for critical upgrades)
- apply_change (Default)
- 计划更改标记为“待定”
- 客户继续访问现有计划
- 只有在支付成功后订阅才会移至
active状态 - 当您想确保在授予升级功能前支付成功时很有用
on_payment_failure 参数使用您的仪表板中配置的业务级别默认设置。何时使用每种模式
| 场景 | 推荐模式 | 理由 |
|---|---|---|
| 升级到高级功能 | prevent_change | 确保在授予访问权限之前支付成功 |
| 数量增加(更多席位) | prevent_change | 防止未支付情况下使用 |
| 降级计划 | apply_change | 客户在减少支出 |
| 可信企业客户 | apply_change | 非支付风险较低 |
| 从试用到付费转换 | prevent_change | 关键支付时刻 |
处理 webhooks
通过 webhooks 跟踪订阅状态以确认计划更改和支付。处理的事件类型
subscription.active:订阅已激活subscription.plan_changed:订阅计划已更改(升级/降级/插件更改)subscription.on_hold:费用失败,订阅暂停subscription.renewed:续订成功payment.succeeded:计划更改或续订的支付成功payment.failed:支付失败
验证签名并处理意图
- Next.js Route Handler
- Express.js
最佳实践
请遵循以下建议,以实现可靠的订阅计划更改:计划更改策略
- 彻底测试:在生产之前始终在测试模式下测试计划更改
- 仔细选择比例方式:选择符合您业务模型的比例方式
- 优雅地处理失败:实现适当的错误处理和重试逻辑
- 监控成功率:跟踪计划更改成功/失败率并调查问题
Webhook 实施
- 验证签名:始终验证 webhook 签名以确保真实性
- 实现幂等性:优雅地处理重复的 webhook 事件
- 异步处理:不要用繁重的操作阻塞 webhook 响应
- 记录一切:保持详细日志以进行调试和审计
用户体验
- 清晰沟通:告知客户账单变化和时间
- 提供确认:为成功的计划更改发送电子邮件确认
- 处理边缘案例:考虑试用期、比例调整和支付失败
- 立即更新 UI:在您的应用程序界面中反映计划更改
常见问题及解决方案
解决订阅计划更改中遇到的典型问题:Charge created but subscription not updated
Charge created but subscription not updated
- Webhook 处理失败或延迟
- 在接收到 webhooks 后应用程序状态未更新
- 在状态更新期间出现数据库事务问题
- 实现强大的 webhook 处理和重试逻辑
- 对状态更新使用幂等操作
- 添加监控以检测和提醒遗漏的 webhook 事件
- 验证 webhook 终端是否可访问并正确响应
Credits not applied after downgrade
Credits not applied after downgrade
- 比例方式期望:降级使用
difference_immediately抵用全额计划价格差异,而prorated_immediately根据周期内的剩余时间创建比例抵用金 - 抵用金是订阅特定的,不会在订阅之间转移
- 客户仪表板上未显示抵用余额
- 对降级使用
difference_immediately,当您希望自动抵用时 - 向客户解释抵用适用于同一订阅的未来续订
- 实现客户门户显示抵用余额
- 检查下一个发票预览以查看应用的抵用金
Webhook signature verification fails
Webhook signature verification fails
- 错误的 webhook 秘钥
- 在签名验证前修改了原始请求主体
- 错误的签名验证算法
- 验证您使用的
DODO_WEBHOOK_SECRET是否正确,并来自仪表板 - 在任何 JSON 解析中间件之前阅读原始请求主体
- 使用适用于您的平台的标准 webhook 验证库
- 在开发环境中测试 webhook 签名验证
Plan change fails with 422 error
Plan change fails with 422 error
- 无效的订阅 ID 或产品 ID
- 订阅未处于活动状态
- 缺少必需参数
- 产品不可用于计划更改
- 验证订阅是否存在且处于活动状态
- 检查产品 ID 是否有效且可用
- 确保提供所有必需的参数
- 查看 API 文档以获取参数要求
Immediate charge fails during plan change
Immediate charge fails during plan change
- 客户的支付方式上的资金不足
- 支付方式过期或无效
- 银行拒绝交易
- 欺诈检测阻止了收费
- 适当地处理
payment.failedwebhooks 事件 - 通知客户更新支付方式
- 为临时故障实现重试逻辑
- 考虑允许在即时费用失败的情况下进行计划更改
Subscription on hold after plan change
Subscription on hold after plan change
on_hold 状态发生了什么:
当计划更改费用失败时,订阅会自动进入 on_hold 状态。直到更新支付方式,订阅才会自动续订。解决方案:更新支付方式以重新激活订阅要在计划更改失败后从 on_hold 状态重新激活订阅:- 使用更新支付方式 API 更新支付方式
- 自动收费创建:API 自动为剩余款项创建费用
- 生成发票:为费用生成发票
- 支付处理:使用新支付方式处理支付
- 重新激活:在支付成功后,将订阅重新激活到
active状态
subscription.on_hold:订阅已暂停(在计划更改费用失败时接收)payment.succeeded:剩余费用支付成功(在更新支付方式后)subscription.active:支付成功后订阅重新激活
- 在计划更改费用失败时立即通知客户
- 提供有关如何更新支付方式的明确说明
- 监控 webhook 事件以跟踪重新激活状态
- 考虑为临时支付故障实施自动重试逻辑
Update Payment Method API Reference
测试您的实现
按照以下步骤彻底测试您的订阅计划更改实现:Test different proration modes
- 使用各种账单周期位置测试
prorated_immediately - 为升级和降级测试
difference_immediately - 测试
full_immediately以重置账单周期 - 测试
do_not_bill以进行无费用/无抵用的计划切换 - 验证抵用计算是否正确
Test webhook handling
- 验证是否收到了所有相关的 webhook 事件
- 测试 webhook 签名验证
- 优雅地处理重复的 webhook 事件
- 测试 webhook 处理失败情况
错误处理
在您的实现中优雅地处理常见的 API 错误:HTTP 状态代码
200 OK
200 OK
400 Bad Request
400 Bad Request
401 Unauthorized
401 Unauthorized
404 Not Found
404 Not Found
422 Unprocessable Entity
422 Unprocessable Entity
500 Internal Server Error
500 Internal Server Error
错误响应格式
下一步
- 查看 更改计划 API
- 浏览基于抵用的结算
- 实现
subscription.on_hold警报 - 查看我们的Webhook 集成指南
200 OK
200 OK
400 Bad Request
400 Bad Request
401 Unauthorized
401 Unauthorized
404 Not Found
404 Not Found
422 Unprocessable Entity
422 Unprocessable Entity
500 Internal Server Error
500 Internal Server Error
错误响应格式
后续步骤
- 查看 更改计划 API
- 探索 基于信用的计费
- 为
subscription.on_hold实现警报 - 查看我们的 Webhook 集成指南