PAYATHON 2026

Microsoft Store UWP apps 的 Change Billing State API 问题

支付老李

结论

Cancel 和 Refund 处理的是两件事:

  • Cancel:停止后续自动续订。一般不会立即收回当前付费周期的使用权,也不会自动退还剩余时间对应的费用。用户通常可以继续使用订阅,直到 expirationDate。
  • Refund:撤销最近一笔符合退款条件的订阅交易并退款,通常也会终止该笔交易对应的订阅权益。它并非只取消下一个订阅周期。
  • 如果续订已经扣款,再关闭自动续订通常只能阻止下一次扣款,不能认为 Microsoft Store 会自动退回本次续订费用。如需退款,应执行 Refund,或引导用户通过 Microsoft Store 的退款渠道申请。
  • recurrenceState 表示订阅所处的续订生命周期,不能单独用来判断用户当前是否还有使用权。服务端还需要检查 expirationDate,必要时重新查询用户权益。

Microsoft Store 的退款资格和到账方式可能因地区及消费者保护规则而异。生产环境应以当前 Microsoft Store API 文档和接口的实际响应为准。

1. Cancel 针对哪个订阅周期

Cancel 主要用于停止订阅的后续循环计费。

假设当前付费周期是 9 月 1 日至 9 月 30 日,用户在 9 月 15 日执行 Cancel,通常会出现以下结果:

  • 9 月 30 日之后不再自动续订;
  • 用户仍可使用已经支付的 9 月订阅权益;
  • 系统不会根据剩余天数自动退款;
  • 续订状态会变为已取消,但 expirationDate 仍可能是 9 月 30 日。

所以,Cancel 更接近于关闭后续续订,不会立即撤销当前权益。

如果业务需要立即停止服务并退回最近一笔费用,应使用 Refund,不能把 Cancel 当作退款接口。业务侧应根据接口返回结果决定何时撤销用户权益。

2. 扣款后关闭自动续订是否会自动退款

关闭自动续订不会撤销已经完成的扣款。

银行卡通知可能对应两种交易状态:

  • 预授权或待入账;
  • 已完成结算的续订交易。

如果通知对应预授权,款项后续可能自行释放,这并不一定表示新订阅周期已经正式生成。如果续订交易已经结算,关闭自动续订通常只影响下一次计费,不会自动撤销本次续订。

可以按以下顺序处理:

  1. 调用 Get subscriptions for a user,确认新的订阅周期、expirationDate 和 recurrenceState。
  2. 通过订单或交易查询结果,确认续订交易是否已经完成。
  3. 如果用户只是不想继续续订,执行 Cancel。
  4. 如果用户还要求退回已经完成的续订费用,并且该交易符合退款条件,执行 Refund,或引导用户使用 Microsoft Store 的官方退款渠道。
  5. 退款完成后重新查询订阅状态,不要只依赖提交退款请求时记录的本地结果。

不要同时通过开发者服务器和 Microsoft 客服渠道重复申请同一笔退款。

3. Cancel 是否立即取消并退还剩余费用

通常不会。

这三个操作的结果有所不同:

操作后续续订当前已付费权益自动退款
关闭自动续订停止通常保留至到期否
Cancel停止通常保留至到期否
Refund停止或撤销相关周期通常会被提前终止是,前提是接口接受退款

Cancel 是服务器端管理订阅续订的能力。它会改变计费生命周期,但通常不会按照剩余天数进行比例退款。

Refund 一般用于处理最近一笔符合条件的收费,而不是任意指定某个未来周期。退款能否成功、具体金额以及权益何时失效,都应以 API 响应和随后查询到的 Store 状态为准。

4. recurrenceState 会如何变化

常见的状态变化如下:

Active
  ├─ Cancel ──> Canceled ──> 到达 expirationDate 后不再具有订阅权益
  ├─ Refund ──> Revoked 或其他终止状态
  └─ 扣款失败 ──> InDunning ──> Failed

实际状态取决于订阅当前所处的阶段,以及 Microsoft Store 后端的处理进度。

执行 Cancel 后

通常可以看到:

  • recurrenceState 从 Active 变为 Canceled;
  • expirationDate 仍然指向当前付费周期的结束时间;
  • 在 expirationDate 之前,用户可能仍有使用权;
  • 当前周期到期后,不再生成新的续订周期。

因此,不能把 recurrenceState == "Canceled" 直接解释为用户当前已经无权使用。

执行 Refund 后

如果退款导致权益提前撤销,订阅通常会进入 Revoked 或相应的终止状态。退款处理可能是异步的,所以短时间内查询到的仍可能是旧状态。

业务侧需要:

  • 检查退款请求的 HTTP 状态和响应内容;
  • 稍后再次调用订阅查询接口;
  • 同时检查 recurrenceState 和 expirationDate;
  • 为退款中的状态设置短暂过渡期,防止重复提交退款。

请求示例

以下示例只说明请求结构。访问令牌、用户身份凭据和具体资源地址应以当前 Microsoft Store API 文档为准。

取消后续续订:

POST https://collections.mp.microsoft.com/v6.0/collections/subscriptions/{subscriptionId}
Authorization: Bearer {accessToken}
Content-Type: application/json

{
  "b2bKey": "{userStoreId}",
  "changeType": "Cancel"
}

退还符合条件的订阅交易:

POST https://collections.mp.microsoft.com/v6.0/collections/subscriptions/{subscriptionId}
Authorization: Bearer {accessToken}
Content-Type: application/json

{
  "b2bKey": "{userStoreId}",
  "changeType": "Refund"
}

服务端可以采用类似的处理逻辑:

Subscription subscription = await GetSubscriptionAsync(userStoreId, subscriptionId);

if (request.RefundLatestCharge)
{
    await ChangeBillingStateAsync(
        subscriptionId,
        userStoreId,
        changeType: "Refund");
}
else if (request.DisableRenewal)
{
    await ChangeBillingStateAsync(
        subscriptionId,
        userStoreId,
        changeType: "Cancel");
}

Subscription updated = await GetSubscriptionAsync(
    userStoreId,
    subscriptionId);

// 不要只根据 recurrenceState 判断当前访问权限。
bool hasAccess =
    updated.ExpirationDate > DateTimeOffset.UtcNow &&
    updated.RecurrenceState != "Revoked" &&
    updated.RecurrenceState != "Failed";

这段权限判断仅用于说明业务逻辑。生产环境还需要处理试用订阅、宽限期、退款处理中、查询延迟和接口新增状态。

注意事项

  • Cancel 不等于 Refund,也不会立即撤销权益。
  • Refund 不能当作普通的关闭自动续订功能使用。
  • 银行卡收到扣款通知,不一定表示交易已经完成结算。
  • Store 状态可能存在同步延迟,需要设置幂等键、重试机制和重复退款保护。
  • 不要只保存本地的已取消标记,应定期与 Microsoft Store 的订阅状态进行核对。
  • 判断用户能否继续使用服务时,需要综合检查 recurrenceState、expirationDate、退款结果和权益查询结果。
  • 如果当前官方文档对特定市场、退款期限或退款金额有其他规定,应优先遵循相应规则。

备注:内容仅供参考。