UWP 如何判断 Microsoft Store 订阅是否开启 Recurring Billing
结论
不能。Windows.Services.Store API 只能告诉 UWP 应用:用户当前是否拥有有效许可,以及许可何时到期。它不会公开 Microsoft Store 账户是否开启了”定期计费(Recurring Billing)”。
因此,只要 ExpirationDate 尚未到期,下面两种情况在应用中可能表现得完全一样:
- 定期计费已开启,订阅预计会自动续订;
- 定期计费已关闭,但当前已付款周期仍然有效。
仅凭 StoreLicense.IsActive、StoreLicense.ExpirationDate、StoreSku.SubscriptionInfo 等客户端属性,无法可靠区分这两种状态。
为什么 IsActive 不能用于判断自动续订
StoreLicense.IsActive 表示用户当前是否拥有该 SKU 的有效使用权,不表示下一个计费周期是否会扣款。
用户关闭定期计费后,Microsoft Store 通常不会立即撤销已经支付的订阅权益。当前周期结束前,下面两个条件仍然成立:
license.IsActive == true
license.ExpirationDate > DateTimeOffset.Now
只有当前权益到期,且续订没有带来新的有效周期,许可状态才可能变为非活动状态。也就是说:
IsActive = 当前能否使用订阅功能
而不是:
IsActive = 是否启用了自动续订
StoreSku.SubscriptionInfo.BillingPeriod 和 BillingPeriodUnit 也不能用来判断自动续订。它们描述的是订阅商品的计费周期,例如一个月或一年,并不是用户当前的续订设置。
应用中应如何展示
应用无法确定自动续订状态,因此不应根据 ExpirationDate 显示”将在该日期自动续订”。界面只需陈述许可数据能够确认的事实,例如:
您的订阅当前有效,有效期至 2026 年 9 月 30 日。是否自动续订取决于 Microsoft Store 账户中的定期计费设置。
同时提供”管理订阅”入口,让用户前往 Microsoft 账户的服务与订阅页面,查看或修改定期计费状态。
客户端可以这样保存订阅状态:
public sealed class SubscriptionStatus
{
public bool IsEntitled { get; set; }
public DateTimeOffset? EntitlementEndDate { get; set; }
// Windows.Services.Store 无法提供该值,因此不要把它定义为 bool。
public bool? IsRecurringBillingEnabled { get; set; }
}
查询许可时,将续订状态明确保留为未知:
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Services.Store;
public async Task<SubscriptionStatus> GetSubscriptionStatusAsync()
{
StoreContext context = StoreContext.GetDefault();
StoreAppLicense appLicense = await context.GetAppLicenseAsync();
StoreLicense subscriptionLicense = appLicense.AddOnLicenses
.Select(item => item.Value)
.Where(license => license.IsActive)
.OrderByDescending(license => license.ExpirationDate)
.FirstOrDefault();
if (subscriptionLicense == null)
{
return new SubscriptionStatus
{
IsEntitled = false,
EntitlementEndDate = null,
IsRecurringBillingEnabled = null
};
}
return new SubscriptionStatus
{
IsEntitled = true,
EntitlementEndDate = subscriptionLicense.ExpirationDate,
IsRecurringBillingEnabled = null
};
}
展示文本可以写成:
public static string FormatSubscriptionMessage(
SubscriptionStatus status)
{
if (!status.IsEntitled)
{
return "当前没有有效订阅。";
}
string endDate = status.EntitlementEndDate?
.ToLocalTime()
.ToString("yyyy-MM-dd") ?? "未知日期";
return $"订阅当前有效,有效期至 {endDate}。" +
"请在 Microsoft Store 账户中确认是否已开启定期计费。";
}
用户点击按钮后,可以打开 Microsoft 账户的订阅管理页面:
using System;
using System.Threading.Tasks;
using Windows.System;
public static async Task OpenSubscriptionManagementAsync()
{
await Launcher.LaunchUriAsync(
new Uri("https://account.microsoft.com/services"));
}
这是账户级页面。用户可能需要登录购买订阅时使用的 Microsoft 账户。
代码中的其他注意事项
不要把 ExpirationDate.DateTime 当成本地时间
ExpirationDate 的类型是 DateTimeOffset,包含时区偏移信息。过早调用 .DateTime 会丢失偏移量。建议在业务模型中继续使用 DateTimeOffset:
pobj_SubscriptionData.SubscriptionEndDate = license.ExpirationDate;
等到需要显示时再转换:
string displayDate = license.ExpirationDate
.ToLocalTime()
.ToString("yyyy-MM-dd");
如果现有属性只能保存 DateTime,应明确它表示 UTC 还是本地时间,以免在订阅到期边界出现一天的显示误差。
不要把所有活动附加项都当作目标订阅
当前循环会让最后一个活动的 add-on 覆盖之前保存的数据:
foreach (var addOnLicense in appLicense.AddOnLicenses)
{
StoreLicense license = addOnLicense.Value;
if (license.IsActive)
{
// 每次都会覆盖
}
}
如果应用包含多个 durable add-on 或多个订阅 SKU,应使用已知的 SkuStoreId 或其他稳定标识筛选目标订阅,例如:
StoreLicense license = appLicense.AddOnLicenses
.Select(item => item.Value)
.FirstOrDefault(item =>
item.IsActive &&
item.SkuStoreId == expectedSubscriptionSkuStoreId);
不要自行推断续订状态
下面这些条件都不能可靠判断续订状态:
ExpirationDate接近当前日期;- 用户以前成功购买过订阅;
- SKU 是订阅商品;
- 许可仍为
IsActive; - 到期日期比一个计费周期更远;
- 本地保存的上一次到期日期与当前日期不同。
到期日期延后,只能证明用户获得了新的权益周期,不能证明定期计费目前仍处于开启状态。用户可能在续订后立即关闭定期计费。
是否存在其他可靠方法
如果问题限定在 Windows.Services.Store 客户端 API,答案仍然是没有。应用只能读取当前权益状态,不能读取用户的自动续订开关。
除非 Microsoft 另行提供服务端接口,且明确记录该接口及相应字段适用于这类订阅产品,否则也不应根据订单历史、日期变化或本地缓存推断定期计费状态。无法取得权威状态时,应用应当:
- 使用
IsActive决定当前是否开放订阅功能; - 使用
ExpirationDate显示当前权益的有效期; - 将自动续订状态显示为”请在 Microsoft Store 中确认”;
- 提供 Microsoft 账户订阅管理入口;
- 不承诺”将在某日自动续订”,也不要把
ExpirationDate直接描述为确定的取消日期。
备注:内容仅供参考。
