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. 扣款后关闭自动续订是否会自动退款
关闭自动续订不会撤销已经完成的扣款。
银行卡通知可能对应两种交易状态:
- 预授权或待入账;
- 已完成结算的续订交易。
如果通知对应预授权,款项后续可能自行释放,这并不一定表示新订阅周期已经正式生成。如果续订交易已经结算,关闭自动续订通常只影响下一次计费,不会自动撤销本次续订。
可以按以下顺序处理:
- 调用
Get subscriptions for a user,确认新的订阅周期、expirationDate和recurrenceState。 - 通过订单或交易查询结果,确认续订交易是否已经完成。
- 如果用户只是不想继续续订,执行
Cancel。 - 如果用户还要求退回已经完成的续订费用,并且该交易符合退款条件,执行
Refund,或引导用户使用 Microsoft Store 的官方退款渠道。 - 退款完成后重新查询订阅状态,不要只依赖提交退款请求时记录的本地结果。
不要同时通过开发者服务器和 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、退款结果和权益查询结果。 - 如果当前官方文档对特定市场、退款期限或退款金额有其他规定,应优先遵循相应规则。
备注:内容仅供参考。