如何通过 GetSubscriptionsForUser 判断自动续订偏好变更?
结论
第五种场景不能只看 recurrenceState。
用户关闭下一周期的自动续订后,当前已支付的订阅周期仍然有效,所以 recurrenceState 仍可能是 Active。判断时需要同时检查:
recurrenceState == "Active":当前订阅有效。autoRenew == false:当前周期结束后不会自动续订。expirationTime > 当前时间:当前许可证尚未到期。
因此,第五种场景通常对应以下状态:
recurrenceState = Active
autoRenew = false
expirationTime > now
recurrenceState 表示订阅当前的生命周期状态,autoRenew 表示下一周期是否计划自动续订。
建议的状态判断
可以组合以下字段判断订阅状态:
| 场景 | recurrenceState | autoRenew | expirationTime |
|---|---|---|---|
| 当前订阅有效,并已成功续订 | Active | 通常为 true | 晚于当前时间 |
| 因计费失败进入催款期,当前权益仍有效 | InDunning | 通常为 true | 结合返回值及业务宽限期判断 |
| 续订最终失败,订阅已失效 | Failed | 不应单独依赖 | 通常不晚于当前时间 |
| 用户不再续订且订阅已经到期 | Inactive | 通常为 false | 不晚于当前时间 |
| 用户关闭下一周期自动续订,但当前仍有效 | Active | false | 晚于当前时间 |
状态值的拼写必须与 API 的实际返回值一致。例如,文档中的枚举值是 Inactive,代码中不要自行写成 InActive。
判断步骤
续订任务不能在看到 Active 后直接延长一个完整周期。可以按以下流程处理:
- 通过
productId、skuId等字段找到需要处理的订阅。 - 读取
recurrenceState,确定订阅当前的生命周期状态。 - 读取
autoRenew,确认下一周期是否仍计划自动扣款。 - 读取
expirationTime,确定当前权益实际有效到什么时间。 - 将本地许可证有效期同步到 Store 返回的
expirationTime,不要在每次查询到Active时固定增加一个周期。 - 保存订阅的
id、expirationTime或lastModified,保证续订任务重复执行时仍然幂等。
第五种场景可以这样判断:
subscription.RecurrenceState == "Active"
&& subscription.AutoRenew == false
&& subscription.ExpirationTime > DateTimeOffset.UtcNow
C# 示例
以下示例假设 API 响应已经反序列化为本地模型:
public enum SubscriptionDecision
{
ActiveAndAutoRenewing,
ActiveButAutoRenewDisabled,
InDunning,
RenewalFailed,
ExpiredWithoutRenewal,
Unknown
}
public static SubscriptionDecision Classify(
StoreSubscription subscription,
DateTimeOffset now)
{
bool currentlyValid = subscription.ExpirationTime > now;
if (subscription.RecurrenceState == "Active")
{
if (currentlyValid && !subscription.AutoRenew)
{
return SubscriptionDecision.ActiveButAutoRenewDisabled;
}
if (currentlyValid && subscription.AutoRenew)
{
return SubscriptionDecision.ActiveAndAutoRenewing;
}
}
if (subscription.RecurrenceState == "InDunning")
{
return SubscriptionDecision.InDunning;
}
if (subscription.RecurrenceState == "Failed")
{
return SubscriptionDecision.RenewalFailed;
}
if (subscription.RecurrenceState == "Inactive" && !currentlyValid)
{
return SubscriptionDecision.ExpiredWithoutRenewal;
}
return SubscriptionDecision.Unknown;
}
public sealed class StoreSubscription
{
public string Id { get; init; } = "";
public string ProductId { get; init; } = "";
public string SkuId { get; init; } = "";
public string RecurrenceState { get; init; } = "";
public bool AutoRenew { get; init; }
public DateTimeOffset ExpirationTime { get; init; }
public DateTimeOffset LastModified { get; init; }
}
处理许可证时,可以直接将 Store 返回的到期时间作为上限:
if (subscription.ExpirationTime > localLicense.ExpirationTime)
{
localLicense.ExpirationTime = subscription.ExpirationTime;
localLicense.StoreSubscriptionId = subscription.Id;
localLicense.LastStoreModifiedTime = subscription.LastModified;
await licenseRepository.SaveAsync(localLicense);
}
这样即使续订任务重复执行,也不会因为多次看到 Active 而反复增加许可证期限。
注意事项
Active 不等于”一定会再次续订”
Active 只能说明当前订阅有效。即使用户已经关闭自动续订,在当前付费周期结束前,订阅仍可能保持 Active。因此,不能仅凭 Active 推断下一周期一定会扣款。
autoRenew 表示计划,不表示续订已经成功
autoRenew == true 表示订阅当前设置为自动续订,并不代表下一次付款已经完成。判断续订是否成功,需要检查 expirationTime 是否向后推进,并结合 recurrenceState 判断。
许可证应同步到 expirationTime
如果任务是延长本地许可证,应将本地有效期更新到 API 返回的 expirationTime,不要根据订阅周期自行计算。这样可以减少重复任务、周期长度差异、催款状态及时间边界引起的错误。
时间比较统一使用 UTC
解析和比较 expirationTime 时,应使用 DateTimeOffset 或其他能够保留时区信息的类型,并统一按 UTC 比较,以免服务器本地时区导致订阅提前或延后失效。
保留未知状态分支
不要假设所有响应都属于预设的五种场景。如果 recurrenceState、autoRenew 或 expirationTime 未知或缺失,应记录原始响应并暂缓延长许可证,避免错误授予权益。
备注:内容仅供参考。
