Skip to main content

Change Plan API

Full API docs for updating subscriptions.

Plan Change Preview

See charge amounts before changing plans.

Integration Guide

Step-by-step subscription setup.

What Is a Subscription Upgrade or Downgrade?

Change a customer’s subscription plan to move them between tiers, adjust quantity for seat-based products, or migrate to a new product. The API automatically calculates prorations and charges based on your chosen billing mode.
Plan changes can trigger an immediate charge depending on the proration mode you choose.

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
For detailed setup instructions, see the Integration Guide.

Step-by-Step Implementation Guide

Follow this comprehensive guide to implement subscription plan changes in your application:
1

Understand Plan Change Requirements

Before implementing, determine:
  • 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
Test plan changes thoroughly in test mode before implementing in production.
2

Choose Your Proration Strategy

Select the billing approach that aligns with your business needs:
Best for: SaaS applications that want to credit unused time on the old plan.
  • Credits the unused portion of the current cycle, prorated by time remaining
  • Then charges a full cycle at the new plan — the new plan price is never prorated
  • Net charge = full new cycle − (remaining fraction × full old cycle)
3

Implement the Change Plan API

Use the Change Plan API to modify subscription details:
string
必填
The ID of the active subscription to modify.
string
必填
要将订阅更改为的新产品 ID。
integer
必填
新方案的单位数量(适用于按席位计费的产品)。
string
必填
如何处理立即计费:prorated_immediately、full_immediately、difference_immediately 或 do_not_bill。
array
新计划的可选附加项。省略此字段、发送 null 或发送空数组都会移除任何现有附加项,因此请包含当前附加项以保留它们。
string
控制方案变更付款失败时的行为:
  • prevent_change:在付款成功前让订阅保持当前方案
  • apply_change(默认):无论付款结果如何,立即应用方案变更
If not specified, uses the business-level default setting.
通过 payment link 收取方案变更金额,而不是向订阅保存的付款方式收费。客户将在托管结账页面完成付款。Requires the business’s allow_plan_change_via_payment_link capability (Settings → Subscriptions → Collect Plan Change Payments by Payment Link), effective_at: immediately, and on_payment_failure: prevent_change. See Collecting Payment via a Checkout Link. Ignored by the preview route.
string
客户支付 payment link 后接收其跳转的 URL。重定向会添加 subscription_id、payment_id 和 status。需要设置 collect_via_payment_link: true;否则请求会因 422 失败。请参阅付款后重定向客户。
取消待处理方案变更的未付款 payment link,以便本请求替换它。已付款或进行中的付款会返回 409。请参阅替换待处理的 payment link。preview 路由会忽略此字段。
array
要应用于新方案的可选堆叠折扣码(最多 20 个,按数组顺序应用)。行为取决于传入的值:
  • 未提供 / null — 如果适用于新产品,则保留带有 preserve_on_plan_change=true 的现有折扣。
  • [](空数组) — 从订阅中移除所有现有折扣。
  • ["CODE_A", "CODE_B", ...] — 使用此堆叠集合替换所有现有折扣。
string
已弃用
已弃用 — 新集成请优先使用 discount_codes。此字段仍可用于向后兼容,但不能在同一请求中与 discount_codes 组合使用。
string
默认值:"immediately"
何时应用方案变更:
  • immediately(默认):立即应用方案变更
  • next_billing_date:将变更安排在下一计费日期。客户在计费周期结束前继续保留当前方案。降级时应使用此选项,以便客户在计费周期结束前继续享有当前方案权益。
4

Handle Webhook Events

设置 webhook 处理逻辑,以跟踪方案变更结果:
  • subscription.active:方案变更成功,订阅已更新
  • subscription.plan_changed:订阅方案已变更(升级/降级/附加组件更新)
  • subscription.on_hold:方案变更收费失败,续订已停止
  • payment.succeeded:方案变更的立即收费成功
  • payment.failed:立即收费失败
始终验证 webhook 签名,并实现幂等的事件处理。
5

Update Your Application State

根据 webhook 事件更新应用:
  • 根据新方案授予或撤销功能
  • 使用新方案详情更新客户控制面板
  • 发送方案变更确认邮件
  • 记录计费变更以供审计
6

Test and Monitor

全面测试实现:
  • 使用不同场景测试所有 proration 模式
  • 验证 webhook 处理是否正常
  • 监控方案变更成功率
  • 为失败的方案变更设置提醒
您的订阅方案变更实现现已准备好投入生产环境。

预览方案变更

在提交方案变更前,使用 Preview API 向客户准确展示将收取的费用:
使用 preview API 构建确认对话框,在客户确认方案变更前向其展示准确的收费金额。
在 immediate_charge.summary 中,customer_credits 是客户信用余额的净变动,使用 customer_credits_currency 指定的货币表示。这是客户 credit wallet 的货币,也就是订阅货币。它可能与摘要中的 currency 不同,后者适用于 total_amount 和 tax。例如,使用 INR 支付 USD 订阅的客户会看到 USD credits。

Change Plan API

使用 Change Plan API 修改有效订阅的产品、数量和 proration 行为。

快速开始示例

成功的方案变更会立即返回 200 OK,即使任何收费尚未实际结算。响应主体(ChangePlanResponse)的内容取决于收款方式:
此响应确认请求已被接受,并不表示收费已成功。对于普通立即收费,结果会在调用后立即以 off-session 方式确定。对于 collect_via_payment_link 请求,在客户完成付款前,订阅会保持当前方案。通过 webhook(payment.succeeded、payment.failed、subscription.plan_changed)确认结果,或使用 GET /subscriptions/{subscription_id} 重新读取订阅 — 对于 payment-link 情况,请参阅链接未付款期间会发生什么。
如果立即收费失败,订阅可能会进入 subscription.on_hold,直到付款成功。
默认情况下,立即方案变更会直接向订阅保存的付款方式收费。将 collect_via_payment_link: true 设置为将客户发送到托管结账页面,而不是直接收费 — 当没有保存的付款方式,或希望客户主动确认新价格时,此方式非常有用。
这会启用 Settings → Subscriptions 中的 Collect Plan Change Payments by Payment Link 开关,使 Customer Portal 的方案变更流程通过 checkout 完成。

要求

只有在满足以下所有条件时,collect_via_payment_link: true 才会成功 — 否则请求会因 422 失败:
  • 商家已启用 allow_plan_change_via_payment_link 功能(Settings → Subscriptions → Collect Plan Change Payments by Payment Link)。
  • effective_at 为 immediately(默认值)。已安排的变更(next_billing_date)不需要 checkout 页面。
  • 请求设置了 on_payment_failure: prevent_change。显式设置 apply_change 会因 422 失败。
只要满足上述要求,collect_via_payment_link 适用于任何会产生收费的立即变更,包括降级。
如果变更后的净额为零或产生 credit,则不会生成 payment link:payment_link 及其他 checkout 字段会返回 null,并且变更会立即应用。这不是 422。请求 link 前,先调用 Preview Plan Change 检查金额。
成功的请求会返回 checkout 句柄:

链接未付款期间会发生什么

  • 订阅会保持当前方案 — 在链接付款前,product_id、recurring_pre_tax_amount 和 next_billing_date 均不会改变。
  • 链接处于待处理状态时,后续 change-plan 请求会因 409 PendingPlanChangeExists 被拒绝。要替换待处理变更,请在新请求中发送 cancel_older_payment_link: true。请参阅替换待处理的 payment link。
  • 付款被拒后重试时,再次调用 change-plan 以获取新链接。
  • 如果链接始终未付款,则会在 expires_on 后停止工作 — 随后订阅会自动恢复为可以接受新方案变更请求的状态。
  • 如果此前已有已安排的变更,并使用 cancel_scheduled_change_plan: true 替换它,则在链接未付款期间原计划仍会保留;只有链接付款后才会取消原计划 — 新方案会在同一事务中应用。
一旦生成立即 payment-link 变更,在链接完成处理前,该订阅的所有后续方案变更请求(包括无副作用的 preview)都会被阻止。通过设置 cancel_older_payment_link: true,change-plan 请求可以替换链接。preview 会忽略此字段,因此 preview 仍会被阻止。

付款后重定向客户

设置 return_url,使客户付款后返回您的网站:
checkout 完成后,客户会转到带有以下 query 参数的 return_url: 付款失败时,订阅会在当前方案下保持有效。付款 webhook 到达后,新方案可能会在重定向后不久应用,因此显示新方案前请再次读取订阅。 return_url 需要 collect_via_payment_link: true。没有该字段,请求会因 422 失败。 如果客户未付款就离开 checkout,请在下一次 change-plan 请求中设置 cancel_older_payment_link: true。Dodo Payments 会取消未付款链接,并使用新的方案变更替换待处理变更。已取消的链接不再接受付款,即使 checkout 页面已经打开。
链接取消前会先验证请求,因此无效请求会保留客户的链接。只有在客户尚未开始付款时,链接才会被取消: 每个 change-plan 请求都会生成自己的 invoice。被替换的方案变更及其 invoice 会被取消。如果没有待处理的方案变更,cancel_older_payment_link 不会产生任何效果。
验证后执行的检查(例如最低收费金额)仍可能在链接取消后失败。此时订阅会保持当前方案,且没有打开的链接。再次发送请求以生成新链接。

管理附加组件

变更订阅方案时,也可以修改附加组件:
附加组件会计入 proration 计算,并根据所选 proration 模式收费。

应用折扣码

变更订阅方案时应用一个或多个堆叠折扣码(最多 20 个,按数组顺序应用):

方案变更时的折扣行为

此 endpoint 上的单数 discount_code 字段已弃用,但仍可用于向后兼容 — 现有集成无需立即更改。它不能在同一请求中与 discount_codes 组合使用。方便时请迁移到数组形式。
使用带有 discount_codes 的 Preview Plan Change API,在客户确认方案变更前准确展示其节省的金额。

Proration 模式

选择变更方案时向客户收费的方式:

prorated_immediately

  • 按剩余时间对当前周期未使用的部分(基础方案、数量和附加组件)进行 proration,并记为 credit
  • 然后按新方案、数量和附加组件收取一个完整周期的费用。该收费本身不会进行 proration
  • 立即净收费 =(新的完整周期)−(剩余比例 × 旧的完整周期)
  • 如果 credit 超过新周期收费(降级时很常见),差额会作为订阅范围的 credit 保留,用于未来续订
  • 如果处于 trial,则立即收费并立即切换到新方案

full_immediately

  • 立即收取新方案的全额
  • 忽略旧方案的剩余时间 — 当前周期不产生 credit
由 prorated_immediately 创建的 credit,以及使用 difference_immediately 降级时创建的 credit,均属于订阅范围,与 Credit-Based Billing 权益不同。它们会自动应用于同一订阅的未来续订,且不能在订阅之间转移。

difference_immediately

  • 升级:立即收取新旧方案之间的价格差额
  • 降级:将剩余价值作为内部 credit 添加到订阅,并在续订时自动应用

do_not_bill

  • 不计算收费或 credit
  • 客户立即切换到新方案,不进行任何计费调整
  • 计费周期保持不变
  • 适用于礼遇迁移、切换到免费方案或吸收价格差异

示例场景

始终使用以下标准数字:
  • 当前方案:Basic,$30/月
  • 升级目标:Pro,$80/月
  • 降级目标(从 Pro):Starter,$20/月
  • 计费周期:30 天,从 1 月 1 日开始
  • 方案变更发生在 1 月 16 日(剩余 15 天,已使用 15 天)

各模式如何处理计费

选择 prorated_immediately,在收取新方案完整周期费用的同时抵扣旧方案的未使用时间;选择 full_immediately 以重新开始计费;使用 difference_immediately 进行简单升级并在降级时自动生成 credit;或使用 do_not_bill 在不调整计费的情况下切换方案。

处理付款失败

使用 on_payment_failure 参数控制方案变更付款失败时的处理方式。

付款失败模式

如果未指定,on_payment_failure 参数会使用您在 dashboard 中配置的商家级默认设置。

各模式的适用场景

商家与收款默认设置

在 Settings → Subscriptions 下设置商家级别的升级和降级行为。这些默认设置适用于所有 Customer Portal 方案变更,也可以按产品集合覆盖。 升级和降级分别有独立的默认设置: 在 Settings → Subscriptions 下配置商家默认设置,并在每个产品集合上配置集合覆盖值。每个集合字段彼此独立 — 留空表示继承商家默认值,设置值则会覆盖它。

解析顺序

对于任意方案变更,每项设置都按以下顺序解析:
显式传递给 Change Plan API 的值始终优先。只有未提供显式值时,才会使用商家和集合默认设置 — 从 Customer Portal 发起的所有方案变更都属于这种情况。
一种常见配置是:将升级保持为 immediately + difference_immediately,使客户支付差额并立即获得访问权限;将降级保持为 next_billing_date,使客户在周期结束前继续保留当前方案。

处理 Webhook

通过 webhook 跟踪订阅状态,以确认方案变更和付款。

要处理的事件类型

  • subscription.active:订阅已激活
  • subscription.plan_changed:订阅方案已变更(升级/降级/附加组件变更)
  • subscription.on_hold:收费失败,续订已停止
  • subscription.renewed:续订成功
  • payment.succeeded:方案变更或续订的付款成功
  • payment.failed:付款失败
使用订阅事件驱动业务逻辑,并使用付款事件进行确认和对账。

验证签名并处理意图

有关详细的 payload schema,请参阅 Subscription webhook payloads 和 Payment webhook payloads。

最佳实践

方案变更策略

  • 充分测试:生产环境前始终在测试模式下测试方案变更
  • 谨慎选择 proration:选择符合业务模式的 proration 模式
  • 妥善处理失败:实现适当的错误处理和重试逻辑
  • 监控成功率:跟踪方案变更成功/失败率并调查问题

Webhook 实现

  • 验证签名:始终验证 webhook 签名以确保真实性
  • 实现幂等性:妥善处理重复的 webhook 事件
  • 异步处理:不要让繁重操作阻塞 webhook 响应
  • 记录所有内容:维护详细日志,以便调试和审计

用户体验

  • 清晰沟通:告知客户计费变更及时间安排
  • 提供确认:为成功的方案变更发送确认邮件
  • 处理边界情况:考虑 trial 周期、proration 和付款失败
  • 立即更新 UI:在应用界面中反映方案变更

常见问题与解决方案

解决订阅方案变更期间遇到的常见问题:
症状:API 调用成功,但订阅仍保持旧方案常见原因:
  • Webhook 处理失败或延迟
  • 收到 webhook 后未更新应用状态
  • 更新状态时出现数据库事务问题
解决方案:
  • 使用重试逻辑实现 webhook 处理
  • 对状态更新使用幂等操作
  • 添加监控,以检测并提醒遗漏的 webhook 事件
  • 验证 webhook endpoint 可访问且能正确响应
症状:客户降级后看不到 credit balance常见原因:
  • Proration 模式预期不一致:使用 difference_immediately 降级时,credit 为完整方案价格差额;而 prorated_immediately 会抵扣旧周期未使用时间,再收取新方案的完整周期费用 — 因此只有当该 credit 超过新方案价格时,才会保留 credit balance
  • Credit 属于特定订阅,不能在订阅之间转移
  • 客户控制面板中看不到 credit balance
解决方案:
  • 如果希望自动生成 credit,降级时使用 difference_immediately
  • 向客户说明 credit 会应用于同一订阅的未来续订
  • 实现 Customer Portal 以显示 credit balance
  • 检查下一张 invoice 的 preview,查看已应用的 credit
症状:Webhook 事件因签名无效而被拒绝常见原因:
  • Webhook secret key 不正确
  • 签名验证前原始请求正文被修改
  • 签名验证算法错误
解决方案:
  • 确认使用的是 dashboard 中正确的 DODO_PAYMENTS_WEBHOOK_KEY
  • 在任何 JSON 解析 middleware 之前读取原始请求正文
  • 使用适用于您平台的标准 webhook 验证库
  • 在开发环境中测试 webhook 签名验证
症状:API 返回 422 Unprocessable Entity 错误常见原因:
  • Subscription ID 或 product ID 无效
  • 订阅不处于 active 状态
  • 缺少必填参数
  • 产品不支持方案变更
解决方案:
  • 确认订阅存在且处于 active 状态
  • 检查 product ID 有效且可用
  • 确保提供所有必填参数
  • 查阅 API 文档了解参数要求
症状:方案变更已发起,但立即收费失败常见原因:
  • 客户付款方式余额不足
  • 付款方式已过期或无效
  • 银行拒绝交易
  • 欺诈检测阻止收费
解决方案:
  • 适当处理 payment.failed webhook 事件
  • 通知客户更新付款方式
  • 为临时失败实现重试逻辑
  • 考虑允许立即收费失败的方案变更
症状:方案变更收费失败,订阅进入 on_hold 状态发生的情况: 方案变更收费失败时,订阅会自动进入 on_hold 状态。更新付款方式前,订阅不会自动续订。解决方案:更新付款方式以重新激活订阅方案变更失败后,要从 on_hold 状态重新激活订阅:
  1. 更新付款方式:使用 Update Payment Method API
  2. 自动创建收费:API 会自动为剩余欠款创建收费
  3. 生成 invoice:系统会为该收费生成 invoice
  4. 处理付款:使用新的付款方式处理付款
  5. 重新激活:付款成功后,订阅会重新进入 active 状态
要监控的 Webhook 事件:
  • subscription.on_hold:订阅被暂停(方案变更收费失败时接收)
  • payment.succeeded:剩余欠款付款成功(更新付款方式后)
  • subscription.active:付款成功后订阅重新激活
最佳实践:
  • 方案变更收费失败时立即通知客户
  • 清晰说明如何更新付款方式
  • 监控 webhook 事件以跟踪重新激活状态
  • 考虑为临时付款失败实现自动重试逻辑

Update Payment Method API Reference

查看有关更新付款方式和重新激活订阅的完整 API 文档。

测试您的实现

全面测试订阅方案变更实现:
1

Set up test environment

  • 使用 test API keys 和 test products
  • 创建不同方案类型的测试订阅
  • 配置 test webhook endpoint
  • 设置监控和日志
2

Test different proration modes

  • 使用不同的计费周期位置测试 prorated_immediately
  • 测试 difference_immediately 的升级和降级
  • 测试 full_immediately 以重置计费周期
  • 测试 do_not_bill 的无收费/无 credit 方案切换
  • 验证 credit 计算是否正确
3

Test webhook handling

  • 验证是否收到所有相关 webhook 事件
  • 测试 webhook 签名验证
  • 妥善处理重复的 webhook 事件
  • 测试 webhook 处理失败场景
4

Test error scenarios

  • 使用无效 subscription ID 测试
  • 使用过期付款方式测试
  • 测试网络失败和超时
  • 使用余额不足的情况测试
5

Monitor in production

  • 为失败的方案变更设置提醒
  • 监控 webhook 处理时间
  • 跟踪方案变更成功率
  • 查看客户支持工单中的方案变更问题

错误处理

在实现中妥善处理常见 API 错误:

HTTP 状态码

方案变更请求处理成功。响应正文是一个包含 payment_id、payment_link、client_secret 和 expires_on 的 ChangePlanResponse。四个字段均可为 null,因此普通 off-session 变更的正文会序列化为 {};成功的 collect_via_payment_link 请求会填充这些字段并返回 checkout 句柄 — 请参阅通过 Checkout Link 收款。如果为 on_payment_failure=prevent_change,方案变更会保持 pending,直到付款成功。
请求参数无效。请检查所有必填字段是否已提供且格式正确。
API key 无效或缺失。请确认 DODO_PAYMENTS_API_KEY 正确且具有适当权限。
此订阅已有待处理的方案变更(PendingPlanChangeExists)。对于已安排的变更,请先使用 DELETE /subscriptions/{subscription_id}/change-plan/scheduled 取消,再提交新的变更。对于待处理的payment-link变更,请在新请求中发送 cancel_older_payment_link: true 以替换它。请参阅替换待处理的 payment link。使用 cancel_older_payment_link: true 时,409 表示客户正在支付之前的链接(PLAN_CHANGE_PAYMENT_IN_PROGRESS),或已经支付该链接(PLAN_CHANGE_PAYMENT_ALREADY_COMPLETED)。
使用 cancel_older_payment_link: true 时,之前的 payment link 无法取消(PLAN_CHANGE_LINK_CANCEL_FAILED)。没有任何变更,因此可以重试请求。
订阅处于非 active 或 on-demand 状态,或者请求不符合 collect_via_payment_link 的条件 — 商家未启用该功能,effective_at 不是 immediately,或 on_payment_failure 不是 prevent_change。请参阅要求。不存在或不属于您账户的 subscription ID 会返回带有 NOT_FOUND 代码的 404。
发生服务器错误。请稍候片刻后重试请求。

错误响应格式

错误会返回 JSON 正文,其中包含 code 和人类可读的 message:
完整列表请参阅错误代码。

后续步骤

最后修改于 2026年10月8日