> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 订阅套餐的升级与降级

> /subscription/change 的端到端流程：remainingAmount 是什么、由谁计算，四种变更状态，原订阅会发生什么、何时发生，如何替换客户从未支付的变更，以及 A0048 / A0049 / A0050 / A0054 的含义。

`/api/v1/subscription/update` 只修改客户已有订阅上的**金额**，且只能调低。其他一切——不同的周期、更高的价格、不同的套餐、不同的币种——都属于通过 `/api/v1/subscription/change` 进行的**套餐变更**。本页是套餐变更的契约说明。仅改金额的变更见
[订阅金额与分期计划](/docs/zh/support/subscription-amount-scheduling-faq)。

## 套餐变更到底做了什么？

它**不会**编辑现有订阅。它会创建一个**新订阅**，让客户授权，然后让旧订阅退役。

1. 你调用 `/api/v1/subscription/change`，传入原订阅的
   `originSubscriptionRequest`，加上新套餐的完整描述和一个新的
   `subscriptionRequest`。
2. Waffo 创建新订阅并返回一个包含 `webUrl` 的 `subscriptionAction`。你把客户引导到该地址去授权新套餐。
3. 当客户完成授权且新套餐的首次扣款成功时，变更提交：新订阅变为 `ACTIVE`，原订阅退役。
4. Waffo 向你在变更请求中传的 `notifyUrl` 推送 `SUBSCRIPTION_CHANGE_NOTIFICATION`。`/api/v1/subscription/change/inquiry` 可按需返回同样的视图。

所以，「能不能直接编辑客户现有的套餐」这个问题的答案是**不能**——没有任何接口能在一个存续中的订阅上重写周期、期数或套餐。客户必须重新授权，因为他们原先给出的授权是针对旧条款的。

**`subscriptionId` 会变。** 从变更提交那一刻起，你自己的记录要以新的 `subscriptionId` 为键，旧的仅作历史保留。

## 我必须给客户一个支付页面吗，还是可以静默完成？

必须给页面。变更是由客户在收银台完成授权的——这正是 `subscriptionAction.webUrl` 的用途。不存在复用现有授权的服务端到服务端套餐变更。

要为客户放弃支付的情况做好准备：在他们授权之前，什么都没有改变，原订阅继续正常扣款。

## 变更状态有哪些？

| `subscriptionChangeStatus` | 含义 |
| - | - |
| `IN_PROGRESS` | 支付请求已发起。 |
| `AUTHORIZATION_REQUIRED` | 等待客户授权。`subscriptionAction` 中带有 URL。 |
| `SUCCESS` | 变更已完成。终态。 |
| `CLOSED` | 变更未完成。终态。 |

`CLOSED` 是有意设计的**所有非成功结局的统一归类**——客户支付失败、授权窗口超时，或你用一个更新的变更替换了这个变更。它不区分「超时」与「被拒绝」，所以不要把它当作可区分的信号去分支处理。

看到 `CLOSED` 该做什么：不做任何自动动作。原订阅仍然存在并继续扣款。如果客户仍想要新套餐，发起一个**新的**变更请求；不要重试旧的，也不要复用它的 `subscriptionRequest`。

## `remainingAmount` 是什么，由谁计算？

`remainingAmount` 在每个变更请求上都是**必填**的，并且是**由你**计算的值。Waffo 不计算按比例分摊，也没有分摊字段——见
[金额页](/docs/zh/support/subscription-amount-scheduling-faq)的分摊章节。

Waffo 会怎么处理它：以变更请求的 `currency` 把该数值作为**变更上的抵扣余额**持有，并在新订阅的扣款中逐步抵扣。当被抵扣的某笔扣款是被关闭而非收款成功，或实际抵扣少于预留金额时，未使用的部分会退回余额而不会丢失。

实操规则：

* 这是你的数字。如果你要表达「客户付了 30 天、用了 12 天」，你自己做这个算术并把结果传过来。
* 以变更请求的 `currency` 传入。不会为你做换算。
* 它是对未来扣款的抵扣，不是退款。如果客户应得的是退钱而不是往后抵扣，请改为退还原始扣款——见
  [退款](/docs/zh/support/refund-faq)。
* `remainingAmount` 也会在 `/api/v1/subscription/change/inquiry` 中返回，所以你可以直接读取剩余值而无需自己跟踪。

## 套餐变更能同时切换币种或支付方式吗？

两者都可以——变更请求是从零开始描述新订阅的，所以这些只是请求上的字段：

| 你想切换什么 | 怎么做 |
| - | - |
| 定价币种 | 变更请求上的 `currency`。它是必填的，所以你总是在重新声明它。 |
| 客户的扣款币种 | 变更请求上的 `userCurrency`。 |
| 支付方式 | 变更请求上的 `paymentInfo.payMethodType` / `payMethodName`；客户在收银台页面重新授权，所以换成完全不同的方式也没问题。 |
| 周期、期数、试用期、分期金额 | `productInfoList` 条目——所有创建时的字段都可用。 |
| 优惠券或促销 | `promotionInfo`，带 `promotionCode` 或 `couponId`（二者互斥）。 |

因为 `remainingAmount` 是以变更请求的 `currency` 表达的，切换币种意味着你必须用**新**币种表达结转金额。Waffo 不会为你换算旧币种的余额。

旧订阅上的任何东西都不会被继承。凡是你没有重新声明的都会消失，包括分期金额计划——如果价格阶梯需要保留，请在变更请求中重新声明 `scheduledAmounts`。

## 一个变更请求能带多个套餐吗？

`productInfoList` 是一个列表，每个条目描述一个套餐。把它当作客户要迁移到的**一个**套餐的结构来用：变更产生的订阅只有一个周期、一个期数和一个金额，所以描述两个不同周期的请求没有连贯的意义。如果客户确实在按不同周期购买两样东西，那应该是两个订阅。

## 旧订阅是立即停止，还是在周期结束时停止？

两种都有，**由你通过 `startTime` 来选择**：

| 变更请求上的 `startTime` | 新套餐开始时间 | 原订阅 |
| - | - | - |
| 省略 | 授权后立即开始 | 变更提交后即刻取消——`MERCHANT_CANCELLED` |
| 未来某个时间 | 该日期 | 继续运行，并**预约在当前周期结束时取消**，客户可继续享有已付费的服务 |

第二行就是「升级在下个周期生效」的实现方式，既不重复扣款也不需要自己写调度器。这也是 Waffo 唯一执行的周期末退役——`/api/v1/subscription/cancel` 始终是立即生效的，相关内容见
[托管管理页](/docs/zh/support/subscription-hosted-management-faq)。

变更的 `startTime` 有两个约束：

* 不得**早于原订阅的下一次预定扣款**。落在客户已付费周期内的开始时间会被拒绝。
* 通用的创建限制仍然适用：必须严格在未来、最多 365 天以内，且在承载新订阅的协议的有效期窗口之内。

当一个带未来开始时间的变更处于待处理状态时，原订阅的续订会被暂缓而不会触发，这样客户不会为即将离开的周期付费。

## 客户一直没付升级链接的钱，现在又想换另一个套餐怎么办？

用他们实际想要的套餐再次调用 `/api/v1/subscription/change`。

**Waffo 会在这次调用中替你结清待处理的变更**：待处理新订阅上未支付的扣款被取消，该新订阅被作为「已替换」关闭，待处理变更转为 `CLOSED`，原订阅被释放以便在其上构建新的变更。你不需要找支持人员清理，也不需要单独的取消调用——之所以没有「取消变更」接口，正是因为下一次变更会自动完成这件事。

重要的注意事项：结清可能失败，失败时新的变更**不会**被创建。你会收到以下之一，而待处理变更仍是当前有效的那个：

| 错误码 | 消息 | 含义 | 该怎么做 |
| - | - | - | - |
| `A0048` | The previous plan change is still in payment | 客户针对待处理变更的支付确实仍在进行中，无法取消。 | 等待该支付的结果，再据此处理。不要循环调用 `change`。 |
| `A0049` | The previous plan change is being cancelled, please retry later | 结清没有干净地完成，结果尚未确认。 | 短暂停顿后重试同一变更请求。这个是真正可重试的。 |
| `A0050` | The previous plan change has been paid successfully | 客户在你替换待处理变更的同时把它付掉了。它现在是客户当前生效的套餐。 | 重新读取订阅，然后基于**新的**生效套餐发起一个**新的**变更。 |
| `A0054` | Another plan change request for this subscription is being processed | 同一订阅的两次变更调用发生了竞争。 | 短暂停顿后重试。 |

`A0048` 和 `A0049` 容易混淆。`A0048` 表示*钱在流转中，停下来等结果*；`A0049` 表示*我们还不知道，再问一次*。在 `A0048` 上重试没有用，而把 `A0049` 当作致命错误会让客户卡住。

## 我能一口气同时改金额和改套餐吗？

不能，而且在任一操作进行中时二者互斥。Waffo 按订阅串行化 `update`、`change` 和 `cancel`：其中一个在处理时，其他的会以 `A0055`（"The subscription is being modified, please retry later"）被拒绝。

`A0055` 可重试。如果你在批量重新定价，把它当作背压而不是失败——但不要对同一订阅并发地发起 `update` 和 `change` 并期望两者都成功。

## 新套餐上的分期金额和试用期会怎样？

什么都不会延续。新订阅的 `amount`、`trialPeriodAmount`、`numberOfTrialPeriod` 和 `scheduledAmounts` **完全来自变更请求**。省略 `scheduledAmounts`，新订阅每期都按新的 `amount` 扣款。

在升级时授予试用期之前要考虑一点：试用期计数会重新开始，因为这是一个新订阅。已经享受过你试用期的客户，如果你配置了试用期，会再得到一次。

## 以哪条记录为准？

`/api/v1/subscription/change/inquiry`，以新的 `subscriptionRequest` 加 `originSubscriptionRequest` 为键。它返回变更状态、授权仍在等待时的 `subscriptionAction`、当前的 `remainingAmount`，以及你发送的完整新套餐描述。

`SUBSCRIPTION_CHANGE_NOTIFICATION` 告诉你*何时*去看；查询接口告诉你*什么是真实的*。从中持久化新的 `subscriptionId`，并只在变更读到 `SUCCESS` 之后才把权益切换到新订阅。

## 接下来看哪里？

* [订阅创建与校验](/docs/zh/support/subscription-creation-validation-faq)
  ——新套餐必须满足的取值范围，以及 `startTime` 规则。
* [订阅金额与分期计划](/docs/zh/support/subscription-amount-scheduling-faq)
  ——`update` 能做什么，以及为什么没有按比例分摊。
* [订阅到底什么时候扣款？](/docs/zh/support/subscription-billing-schedule-faq)
  ——新套餐的日期是如何推导的。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.