为应用启用订阅加载项
通用 Windows 平台 (UWP) 应用可以向客户提供订阅加载项的应用内购买。 你可以按照自动定期帐单期间间使用订阅在你的应用中销售数字产品(如应用功能或数字内容)。
注意
若要支持应用内的订阅加载项购买,你的应用必须在 Visual Studio 中面向 Windows 10 周年纪念版(10.0;版本 14393)或更高版本(这对应于 Windows 10 版本 1607),并且必须使用 Windows.Services.Store 命名空间(而不是 Windows.ApplicationModel.Store 命名空间)中的 API 来实现应用内购买体验。 有关这些命名空间之间的差异的详细信息,请参阅应用内购买和试用。
功能特点
UWP 应用的订阅加载项支持以下功能:
- 你可以选择订阅期为 1 个月、3 个月、6 个月、1 年或 2 年。
- 你可以在你的订阅中添加 1 周或 1 个月的免费试用期间。
- Windows SDK 提供了一些 API,可用于在应用中获取有关应用的可用订阅加载项的信息并支持购买订阅加载项。 我们还提供你可以从你的服务调用的 REST API 来管理用户订阅。
- 你可以查看分析报告,其中提供了订阅购置、活动订户以及在指定的时间段内取消的订阅的数量。
- 客户可以在他们的 Microsoft 帐户的 https://account.microsoft.com/services 页面管理他们的订阅。 客户可以使用此页面查看他们购买的所有订阅、取消订阅,以及更改与其订阅关联的付款方式。
为你的应用启用订阅加载项的步骤
若要在你的应用中支持购买订阅加载项,请执行以下步骤。
在合作伙伴中心中为你的订阅创建加载项提交,并发布此提交。 在你执行加载项提交流程时,请密切注意以下属性:
产品类型:确保选择订阅。
订阅期:为你的订阅选择定期帐单期间。 在发布加载项之后,你将不能更改订阅期。
每个订阅加载项都支持单个订阅期和试用期。 你必须在应用中为你想要提供的每种订阅创建不同的订阅加载项。 例如,如果你想要提供按月订阅无试用、按月订阅送一个月试用、按年订阅无试用以及按年订阅送一个月试用,则需要创建四个订阅加载项。
试用期间:考虑为你的订阅选择 1 周或 1 个月的试用期,让用户可以在购买之前先试用。 在发布订阅加载项之后,你将不能更改或删除试用期。
要免费试用你的订阅,用户必须通过标准的应用内购买流程(包括有效的付款方式)购买你的订阅。 在试用期内不收取任何费用。 在试用期结束时,订阅自动将转换为完全订阅,并将按用户的支付方式收取第一期的付费订阅费用。 如果用户在试用期内选择取消订阅,则订阅在试用期结束之前仍然会保持有效。 一些试用期并非对所有订阅期都可用。
注意
每位客户一次只能获取一个订阅加载项的免费试用版。 客户获得某项订阅的免费试用版后,Microsoft Store 将阻止该客户再次获取相同的免费试用订阅。
可见性:如果要创建测试加载项并且仅用它来测试订阅的应用内购买体验,建议你选择一个在 Microsoft Store 中隐藏选项。 否则,你可以选择最适合你的情形的可见性选项。
定价:在此部分中选择你的订阅的价格。 在发布加载项之后,你将不能提高订阅价格。 不过,可以在以后降价。
重要
默认情况下,当你创建任何加载项时,价格最初都设置为免费。 因为你在完成加载项提交之后不能提高订阅加载项的价格,请确保在此处选择你的订阅的价格。
在你的应用中,使用 Windows.Services.Store 命名空间中的 API 来确定当前用户是否已获取你的订阅加载项,然后以应用内购买的形式将它提供给用户。 请参阅本文中的代码示例以了解更多详细信息。
测试你的应用中订阅的应用内购买实现情况。 你需要将你的应用从应用商店下载到你的开发设备,才能使用其许可证进行测试。 有关详细信息,请参阅我们的应用内购买测试指南。
创建并发布应用提交,并使之包含你已更新的应用包(包括你已测试的代码)。 有关详细信息,请参阅应用提交。
代码示例
此部分中的代码示例介绍如何使用 Windows.Services.Store 命名空间中的 API 来获取有关当前应用的订阅加载项的信息,以及代表当前用户请求购买订阅加载项。
这些示例有以下先决条件:
- 适用于面向 Windows 10 周年纪念版(10.0;版本 14393)或更高版本的通用 Windows 平台 (UWP) 应用的 Visual Studio 项目。
- 你已在合作伙伴中心中创建应用提交,并且此应用已在应用商店中发布。 在测试应用期间,你可以选择将应用配置为在 Microsoft Store 中隐藏。 有关详细信息,请参阅测试指南。
- 你已在合作伙伴中心中为该应用创建了一个订阅加载项。
这些示例中的代码假设:
- 此代码文件包含对 Windows.Services.Store 和 System.Threading.Tasks 命名空间的 using 语句。
- 该应用是单用户应用,仅在启动该应用的用户上下文中运行。 有关详细信息,请参阅应用内购买和试用。
注意
如果你有使用桌面桥的桌面应用程序,可能需要添加未在这些示例中显示的额外代码来配置 StoreContext 对象。 有关更多信息,请参阅在使用桌面桥的桌面应用程序中使用 StoreContext 类。
购买订阅加载项
此示例演示如何代表当前客户请求购买已知订阅加载项。 此示例还显示如何处理订阅有试用期的情况。
- 这段代码首先确定客户是否已经拥有一个活动订阅许可证。 如果客户已经有活动的许可证,则你的代码应根据需要解锁订阅功能(因为它专用于你的应用,这在示例中已用注释指明)。
- 接下来,这段代码获取表示你想要代表客户购买的订阅的 StoreProduct 对象。 代码假定你已知道你想要购买的订阅的 Store ID,并且你已对 subscriptionStoreId 变量指定此值。
- 然后代码确定该订阅是否有试用版。 (可选)你的应用可以使用此信息向客户显示详情,说明存在可用的试用版或完整订阅。
- 最后,代码调用 RequestPurchaseAsync 方法来请求购买订阅。 如果该订阅有试用版,则会向客户提供试用版以便购买。 否则,将提供完整订阅以便其购买。
private StoreContext context = null;
StoreProduct subscriptionStoreProduct;
// Assign this variable to the Store ID of your subscription add-on.
private string subscriptionStoreId = "";
// This is the entry point method for the example.
public async Task SetupSubscriptionInfoAsync()
{
if (context == null)
{
context = StoreContext.GetDefault();
// If your app is a desktop app that uses the Desktop Bridge, you
// may need additional code to configure the StoreContext object.
// For more info, see https://aka.ms/storecontext-for-desktop.
}
bool userOwnsSubscription = await CheckIfUserHasSubscriptionAsync();
if (userOwnsSubscription)
{
// Unlock all the subscription add-on features here.
return;
}
// Get the StoreProduct that represents the subscription add-on.
subscriptionStoreProduct = await GetSubscriptionProductAsync();
if (subscriptionStoreProduct == null)
{
return;
}
// Check if the first SKU is a trial and notify the customer that a trial is available.
// If a trial is available, the Skus array will always have 2 purchasable SKUs and the
// first one is the trial. Otherwise, this array will only have one SKU.
StoreSku sku = subscriptionStoreProduct.Skus[0];
if (sku.SubscriptionInfo.HasTrialPeriod)
{
// You can display the subscription trial info to the customer here. You can use
// sku.SubscriptionInfo.TrialPeriod and sku.SubscriptionInfo.TrialPeriodUnit
// to get the trial details.
}
else
{
// You can display the subscription purchase info to the customer here. You can use
// sku.SubscriptionInfo.BillingPeriod and sku.SubscriptionInfo.BillingPeriodUnit
// to provide the renewal details.
}
// Prompt the customer to purchase the subscription.
await PromptUserToPurchaseAsync();
}
private async Task<bool> CheckIfUserHasSubscriptionAsync()
{
StoreAppLicense appLicense = await context.GetAppLicenseAsync();
// Check if the customer has the rights to the subscription.
foreach (var addOnLicense in appLicense.AddOnLicenses)
{
StoreLicense license = addOnLicense.Value;
if (license.SkuStoreId.StartsWith(subscriptionStoreId))
{
if (license.IsActive)
{
// The expiration date is available in the license.ExpirationDate property.
return true;
}
}
}
// The customer does not have a license to the subscription.
return false;
}
private async Task<StoreProduct> GetSubscriptionProductAsync()
{
// Load the sellable add-ons for this app and check if the trial is still
// available for this customer. If they previously acquired a trial they won't
// be able to get a trial again, and the StoreProduct.Skus property will
// only contain one SKU.
StoreProductQueryResult result =
await context.GetAssociatedStoreProductsAsync(new string[] { "Durable" });
if (result.ExtendedError != null)
{
System.Diagnostics.Debug.WriteLine("Something went wrong while getting the add-ons. " +
"ExtendedError:" + result.ExtendedError);
return null;
}
// Look for the product that represents the subscription.
foreach (var item in result.Products)
{
StoreProduct product = item.Value;
if (product.StoreId == subscriptionStoreId)
{
return product;
}
}
System.Diagnostics.Debug.WriteLine("The subscription was not found.");
return null;
}
private async Task PromptUserToPurchaseAsync()
{
// Request a purchase of the subscription product. If a trial is available it will be offered
// to the customer. Otherwise, the non-trial SKU will be offered.
StorePurchaseResult result = await subscriptionStoreProduct.RequestPurchaseAsync();
// Capture the error message for the operation, if any.
string extendedError = string.Empty;
if (result.ExtendedError != null)
{
extendedError = result.ExtendedError.Message;
}
switch (result.Status)
{
case StorePurchaseStatus.Succeeded:
// Show a UI to acknowledge that the customer has purchased your subscription
// and unlock the features of the subscription.
break;
case StorePurchaseStatus.NotPurchased:
System.Diagnostics.Debug.WriteLine("The purchase did not complete. " +
"The customer may have cancelled the purchase. ExtendedError: " + extendedError);
break;
case StorePurchaseStatus.ServerError:
case StorePurchaseStatus.NetworkError:
System.Diagnostics.Debug.WriteLine("The purchase was unsuccessful due to a server or network error. " +
"ExtendedError: " + extendedError);
break;
case StorePurchaseStatus.AlreadyPurchased:
System.Diagnostics.Debug.WriteLine("The customer already owns this subscription." +
"ExtendedError: " + extendedError);
break;
}
}
获取有关当前应用的订阅加载项的信息
此代码示例介绍如何获取有关可用于你的应用的所有订阅加载项的信息。 要获取此信息,请先使用 GetAssociatedStoreProductsAsync 方法来获取表示应用提供的每个可用加载项的 StoreProduct 对象的集合。 然后,获取每个产品的 StoreSku,并使用 IsSubscription 和 SubscriptionInfo 属性访问订阅信息。
private StoreContext context = null;
public async Task GetSubscriptionsInfo()
{
if (context == null)
{
context = StoreContext.GetDefault();
// If your app is a desktop app that uses the Desktop Bridge, you
// may need additional code to configure the StoreContext object.
// For more info, see https://aka.ms/storecontext-for-desktop.
}
// Subscription add-ons are Durable products.
string[] productKinds = { "Durable" };
List<String> filterList = new List<string>(productKinds);
StoreProductQueryResult queryResult =
await context.GetAssociatedStoreProductsAsync(productKinds);
if (queryResult.ExtendedError != null)
{
// The user may be offline or there might be some other server failure.
System.Diagnostics.Debug.WriteLine($"ExtendedError: {queryResult.ExtendedError.Message}");
return;
}
foreach (KeyValuePair<string, StoreProduct> item in queryResult.Products)
{
// Access the Store product info for the add-on.
StoreProduct product = item.Value;
// For each add-on, the subscription info is available in the SKU objects in the add-on.
foreach (StoreSku sku in product.Skus)
{
if (sku.IsSubscription)
{
// Use the sku.SubscriptionInfo property to get info about the subscription.
// For example, the following code gets the units and duration of the
// subscription billing period.
StoreDurationUnit billingPeriodUnit = sku.SubscriptionInfo.BillingPeriodUnit;
uint billingPeriod = sku.SubscriptionInfo.BillingPeriod;
}
}
}
}
从你的服务管理订阅
在你更新过的应用在应用商店中发布且客户可以购买你的订阅加载项之后,你可能会遇到需要管理某个客户的订阅的情况。 我们提供了你可以从你的服务中调用的 REST API 来执行以下订阅管理任务:
获取用户有权使用的订阅。 如果你的订阅是跨平台服务的一部分,则可以调用此 API 来确定指定的用户对于你的订阅是否拥有授权以及他们的订阅在你的 UWP 应用的上下文中的状态。 然后,你可以使用此信息来更新你的服务支持的其他平台上的订阅状态。
更改给定用户订阅的计费状态. 使用此 API 可取消、延长或禁用订阅的自动续订。
取消
客户可以使用其 Microsoft 帐户的 https://account.microsoft.com/services 页面查看他们购买的所有订阅、取消订阅,以及更改与其订阅关联的付款方式。 当客户使用此页面取消订阅时,他们在当前帐单期间内将仍然拥有对该订阅的访问权限。 他们不能获得当前帐单周期任何部分的退款。 在当前帐单周期结束时,他们的订阅会停止。
你还可以使用我们的 REST API 更改给定用户的订阅的帐单状态,以代表用户取消订阅。
订阅续订和宽限期
在每个计费周期内的某个时间点,我们将尝试向客户的信用卡收取下一个计费周期的费用。 如果收费失败,客户的订阅将进入催缴状态。 这意味着他们的订阅在当前计费周期剩余时间内仍然保持活动状态,但是我们将定期尝试向其信用卡收费以便自动续订订阅。 这种状态最多可以持续两周,直到当前计费周期结束和下一个帐单期间的续订日期。
对于订阅帐单,我们不提供宽限期。 如果在当前计费周期结束时我们都无法对客户的信用卡收费,订阅将取消,在当前计费周期结束后,客户将不再能够访问订阅。
不支持的方案
目前订阅加载项不支持以下情形。
- 目前不支持直接通过应用商店向客户销售订阅。 订阅只能通过数字产品的应用内购买提供。
- 客户不能使用他们的 Microsoft 帐户的 https://account.microsoft.com/services 页面切换到不同订阅期。 若要切换到不同订阅期,客户必须取消其当前的订阅,然后从你的应用中购买具有订阅期不同的订阅。
- 订阅加载项目前不支持分级订阅(例如,将客户从基本订阅切换到包含更多功能的高级订阅)。
- 目前订阅加载项不支持销售和促销代码。
- 将订阅加载项的可见性设置为停止获取后续订 现有订阅。 有关更多详细信息,请参阅设置加载项定价和可用性。