UWP 应用程序是否有办法知道是否为 Microsoft 商店中的订阅打开了定期计费
Is there a way for a UWP app to know if Recurring Billing is turned on for a subscription in the Microsoft store
我有一个 Xamarin Forms UWP 应用程序。我正在尝试向我的应用程序用户提供有关订阅到期/续订的更具体信息。我想知道他们是否关闭了订阅的定期计费。以下是我从商店取回的有关当前订阅的详细信息。我假设 isActive 在他们关闭定期计费并在到期日期后取消订阅后将变为 false。然而,在我的测试中,我发现如果他们关闭定期计费并且仍在他们上次定期购买的订阅期内(即到期日仍然在未来),一切看起来就像他们有定期计费已打开,订阅将自动续订。我正在尝试找到一种方法让他们知道他们当前的订阅将在特定日期到期,而不是在特定日期自动续订。我错过了什么吗?
这是代码 -- 一切正常我只是在从 Microsoft Store API 返回的对象中看不到任何指示订阅是否启用定期计费的内容。
using FCISharedAll;
using FCISuite.UWP;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.Services.Store;
using static FCISharedAll.FCIEnums.FlexEnums;
[assembly: Xamarin.Forms.Dependency(typeof(UWPSubscriptionManager))]
namespace FCISuite.UWP
{
public class UWPSubscriptionManager : IFCISubcriptionManager, IDisposable
{
private StoreContext context = null;
private static string[] ia_productKinds = { "Durable", "Consumable", "UnmanagedConsumable" };
private List<String> iobj_filterList = null;
//We need a constructor to load the list
public UWPSubscriptionManager()
{
iobj_filterList = new List<string>(ia_productKinds);
}
/// <summary>
/// This will run at the start of the app to get the active subscription info for the user.
/// </summary>
/// <returns></returns>
public async Task<ActiveSubscriptionDetails> GetActiveSubscriptionInformation(ActiveSubscriptionDetails pobj_SubscriptionData)
{
string ls_Result = string.Empty;
try
{
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.
}
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.IsActive)
{
pobj_SubscriptionData.ActiveSubscriptionID = license.SkuStoreId;
pobj_SubscriptionData.SubscriptionEndDate = license.ExpirationDate.DateTime;
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
//Debug.WriteLine("End of Subscription Info");
return pobj_SubscriptionData;
}
#region get available subscriptions
public async Task<List<SubscriptionDetail>> GetAvailableSubscriptions(string ps_ProductNotFoundMessage,
string ps_StoreAddOnsNotFound, string ps_EnvironmentPrefix)
{
string ls_Result = "";
List<SubscriptionDetail> lobj_AvailableSubscriptions = new List<SubscriptionDetail>();
try
{
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.
}
// Get app store product details. Because this might take several moments,
// display a ProgressRing during the operation.
StoreProductResult product_queryResult = await context.GetStoreProductForCurrentAppAsync();
if (product_queryResult.Product == null)
{
// Show additional error info if it is available.
if (product_queryResult.ExtendedError != null)
{
ls_Result += $"\nExtendedError: {product_queryResult.ExtendedError.Message}";
}
// The Store catalog returned an unexpected result.
throw new Exception(ps_ProductNotFoundMessage + " " + ls_Result);
}
else
{
StoreProductQueryResult addon_queryResult = await context.GetAssociatedStoreProductsAsync(iobj_filterList);
if (addon_queryResult.ExtendedError != null)
{
// The user may be offline or there might be some other server failure.
ls_Result = $"ExtendedError: {addon_queryResult.ExtendedError.Message}";
// The Store catalog returned an unexpected result for the Add-ons.
throw new Exception(ps_StoreAddOnsNotFound + " " + ls_Result);
}
List<KeyValuePair<string, StoreProduct>> lobj_ProductsforEnvironment = new List<KeyValuePair<string, StoreProduct>>();
//We are in test - do not show the production subscriptions
lobj_ProductsforEnvironment = (from tobj_Product in addon_queryResult.Products
where tobj_Product.Value.Title.ToUpper().StartsWith(ps_EnvironmentPrefix.ToUpper())
select tobj_Product).ToList();
//foreach (KeyValuePair<string, StoreProduct> item in addon_queryResult.Products)
foreach (KeyValuePair<string, StoreProduct> item in lobj_ProductsforEnvironment)
{
// Add the store product's skus to the list
foreach (StoreSku lobj_Sku in item.Value.Skus)
{
if (lobj_Sku.IsSubscription)
{
lobj_AvailableSubscriptions.Add(new SubscriptionDetail()
{
SubscriptionID = item.Value.StoreId,
SubscriptionName = lobj_Sku.Title,
SubscriptionFormattedPrice = lobj_Sku.Price.FormattedPrice,
// 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.
SubscriptionPeriodUnit = lobj_Sku.SubscriptionInfo.BillingPeriodUnit.ToString(),
SubscriptionPeriod = lobj_Sku.SubscriptionInfo.BillingPeriod,
});
}
}
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return lobj_AvailableSubscriptions;
}
#endregion
#region Subscription Purchase area
/// <summary>
/// This will get the product for the ID from the store, then request to purchase it for the user.
/// </summary>
/// <param name="ps_SubscriptionStoreId">The ID of the store subscription to be purchased</param>
/// <returns>boolean that indicates if the purchase succeeded or failed.</returns>
public async Task<SubscriptionPurchaseDetail> PurchaseSubscription(string ps_SubscriptionStoreId)
{
SubscriptionPurchaseDetail lobj_SubscriptionPurchaseDetail = new SubscriptionPurchaseDetail();
try
{
lobj_SubscriptionPurchaseDetail.PurchaseStatus = FCIStorePurchaseStatus.Succeeded;
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage = string.Empty;
lobj_SubscriptionPurchaseDetail.ProductToPurchaseWasFound = true;
//This gets the product to purchase
var tobj_StoreProduct = await GetSubscriptionProductAsync(ps_SubscriptionStoreId);
if (tobj_StoreProduct == null)
{
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage = "The add-on to purchase was not found. Product ID: " + ps_SubscriptionStoreId;
lobj_SubscriptionPurchaseDetail.ProductToPurchaseWasFound = false;
}
else
{
//Get the store product to purchase
StorePurchaseResult result = await tobj_StoreProduct.RequestPurchaseAsync();
//Convert the store namespace string to our string.
lobj_SubscriptionPurchaseDetail.PurchaseStatus = (FCIStorePurchaseStatus)Enum.Parse(typeof(FCIStorePurchaseStatus), result.Status.ToString());
// Capture the error message for the operation, if any.
if (result.ExtendedError != null)
{
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage += " " + result.ExtendedError.Message;
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return lobj_SubscriptionPurchaseDetail;
}
/// <summary>
/// This returns the product we will use to call the RequestPurchaseAsync function on
/// </summary>
/// <param name="ps_SubscriptionStoreId">The ID if the subscription to purchase</param>
/// <returns>StoreProduct which represents subscritpion to purchase.</returns>
private async Task<StoreProduct> GetSubscriptionProductAsync(string ps_SubscriptionStoreId)
{
try
{
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.
}
// 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(iobj_filterList);
if (result.ExtendedError != null)
{
throw new Exception("Something went wrong while getting the add-ons from the store. ExtendedError:" + result.ExtendedError);
}
// Look for the product that represents the subscription and return it if found - this is what we
//will purchase
foreach (var item in result.Products)
{
StoreProduct product = item.Value;
if (product.StoreId == ps_SubscriptionStoreId)
{
return product;
}
}
//If we get here the subscription was not found. We will return null and the calling function will know the
//subscription was not found.
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return null;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
context = null;
}
}
#endregion
}
}
目前 StoreContext
中没有这样的 API 可以获取有关用户是否禁用 auto-renew 订阅的信息。
用户购买订阅后add-on,默认会自动续订。此处列出了详细消息:Subscription renewals and grace periods。如果用户需要查看订阅模式,他仍然需要在账户服务网站上查看。
我有一个 Xamarin Forms UWP 应用程序。我正在尝试向我的应用程序用户提供有关订阅到期/续订的更具体信息。我想知道他们是否关闭了订阅的定期计费。以下是我从商店取回的有关当前订阅的详细信息。我假设 isActive 在他们关闭定期计费并在到期日期后取消订阅后将变为 false。然而,在我的测试中,我发现如果他们关闭定期计费并且仍在他们上次定期购买的订阅期内(即到期日仍然在未来),一切看起来就像他们有定期计费已打开,订阅将自动续订。我正在尝试找到一种方法让他们知道他们当前的订阅将在特定日期到期,而不是在特定日期自动续订。我错过了什么吗?
这是代码 -- 一切正常我只是在从 Microsoft Store API 返回的对象中看不到任何指示订阅是否启用定期计费的内容。
using FCISharedAll;
using FCISuite.UWP;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.Services.Store;
using static FCISharedAll.FCIEnums.FlexEnums;
[assembly: Xamarin.Forms.Dependency(typeof(UWPSubscriptionManager))]
namespace FCISuite.UWP
{
public class UWPSubscriptionManager : IFCISubcriptionManager, IDisposable
{
private StoreContext context = null;
private static string[] ia_productKinds = { "Durable", "Consumable", "UnmanagedConsumable" };
private List<String> iobj_filterList = null;
//We need a constructor to load the list
public UWPSubscriptionManager()
{
iobj_filterList = new List<string>(ia_productKinds);
}
/// <summary>
/// This will run at the start of the app to get the active subscription info for the user.
/// </summary>
/// <returns></returns>
public async Task<ActiveSubscriptionDetails> GetActiveSubscriptionInformation(ActiveSubscriptionDetails pobj_SubscriptionData)
{
string ls_Result = string.Empty;
try
{
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.
}
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.IsActive)
{
pobj_SubscriptionData.ActiveSubscriptionID = license.SkuStoreId;
pobj_SubscriptionData.SubscriptionEndDate = license.ExpirationDate.DateTime;
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
//Debug.WriteLine("End of Subscription Info");
return pobj_SubscriptionData;
}
#region get available subscriptions
public async Task<List<SubscriptionDetail>> GetAvailableSubscriptions(string ps_ProductNotFoundMessage,
string ps_StoreAddOnsNotFound, string ps_EnvironmentPrefix)
{
string ls_Result = "";
List<SubscriptionDetail> lobj_AvailableSubscriptions = new List<SubscriptionDetail>();
try
{
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.
}
// Get app store product details. Because this might take several moments,
// display a ProgressRing during the operation.
StoreProductResult product_queryResult = await context.GetStoreProductForCurrentAppAsync();
if (product_queryResult.Product == null)
{
// Show additional error info if it is available.
if (product_queryResult.ExtendedError != null)
{
ls_Result += $"\nExtendedError: {product_queryResult.ExtendedError.Message}";
}
// The Store catalog returned an unexpected result.
throw new Exception(ps_ProductNotFoundMessage + " " + ls_Result);
}
else
{
StoreProductQueryResult addon_queryResult = await context.GetAssociatedStoreProductsAsync(iobj_filterList);
if (addon_queryResult.ExtendedError != null)
{
// The user may be offline or there might be some other server failure.
ls_Result = $"ExtendedError: {addon_queryResult.ExtendedError.Message}";
// The Store catalog returned an unexpected result for the Add-ons.
throw new Exception(ps_StoreAddOnsNotFound + " " + ls_Result);
}
List<KeyValuePair<string, StoreProduct>> lobj_ProductsforEnvironment = new List<KeyValuePair<string, StoreProduct>>();
//We are in test - do not show the production subscriptions
lobj_ProductsforEnvironment = (from tobj_Product in addon_queryResult.Products
where tobj_Product.Value.Title.ToUpper().StartsWith(ps_EnvironmentPrefix.ToUpper())
select tobj_Product).ToList();
//foreach (KeyValuePair<string, StoreProduct> item in addon_queryResult.Products)
foreach (KeyValuePair<string, StoreProduct> item in lobj_ProductsforEnvironment)
{
// Add the store product's skus to the list
foreach (StoreSku lobj_Sku in item.Value.Skus)
{
if (lobj_Sku.IsSubscription)
{
lobj_AvailableSubscriptions.Add(new SubscriptionDetail()
{
SubscriptionID = item.Value.StoreId,
SubscriptionName = lobj_Sku.Title,
SubscriptionFormattedPrice = lobj_Sku.Price.FormattedPrice,
// 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.
SubscriptionPeriodUnit = lobj_Sku.SubscriptionInfo.BillingPeriodUnit.ToString(),
SubscriptionPeriod = lobj_Sku.SubscriptionInfo.BillingPeriod,
});
}
}
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return lobj_AvailableSubscriptions;
}
#endregion
#region Subscription Purchase area
/// <summary>
/// This will get the product for the ID from the store, then request to purchase it for the user.
/// </summary>
/// <param name="ps_SubscriptionStoreId">The ID of the store subscription to be purchased</param>
/// <returns>boolean that indicates if the purchase succeeded or failed.</returns>
public async Task<SubscriptionPurchaseDetail> PurchaseSubscription(string ps_SubscriptionStoreId)
{
SubscriptionPurchaseDetail lobj_SubscriptionPurchaseDetail = new SubscriptionPurchaseDetail();
try
{
lobj_SubscriptionPurchaseDetail.PurchaseStatus = FCIStorePurchaseStatus.Succeeded;
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage = string.Empty;
lobj_SubscriptionPurchaseDetail.ProductToPurchaseWasFound = true;
//This gets the product to purchase
var tobj_StoreProduct = await GetSubscriptionProductAsync(ps_SubscriptionStoreId);
if (tobj_StoreProduct == null)
{
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage = "The add-on to purchase was not found. Product ID: " + ps_SubscriptionStoreId;
lobj_SubscriptionPurchaseDetail.ProductToPurchaseWasFound = false;
}
else
{
//Get the store product to purchase
StorePurchaseResult result = await tobj_StoreProduct.RequestPurchaseAsync();
//Convert the store namespace string to our string.
lobj_SubscriptionPurchaseDetail.PurchaseStatus = (FCIStorePurchaseStatus)Enum.Parse(typeof(FCIStorePurchaseStatus), result.Status.ToString());
// Capture the error message for the operation, if any.
if (result.ExtendedError != null)
{
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage += " " + result.ExtendedError.Message;
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return lobj_SubscriptionPurchaseDetail;
}
/// <summary>
/// This returns the product we will use to call the RequestPurchaseAsync function on
/// </summary>
/// <param name="ps_SubscriptionStoreId">The ID if the subscription to purchase</param>
/// <returns>StoreProduct which represents subscritpion to purchase.</returns>
private async Task<StoreProduct> GetSubscriptionProductAsync(string ps_SubscriptionStoreId)
{
try
{
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.
}
// 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(iobj_filterList);
if (result.ExtendedError != null)
{
throw new Exception("Something went wrong while getting the add-ons from the store. ExtendedError:" + result.ExtendedError);
}
// Look for the product that represents the subscription and return it if found - this is what we
//will purchase
foreach (var item in result.Products)
{
StoreProduct product = item.Value;
if (product.StoreId == ps_SubscriptionStoreId)
{
return product;
}
}
//If we get here the subscription was not found. We will return null and the calling function will know the
//subscription was not found.
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return null;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
context = null;
}
}
#endregion
}
}
目前 StoreContext
中没有这样的 API 可以获取有关用户是否禁用 auto-renew 订阅的信息。
用户购买订阅后add-on,默认会自动续订。此处列出了详细消息:Subscription renewals and grace periods。如果用户需要查看订阅模式,他仍然需要在账户服务网站上查看。