I have a Xamarin Forms UWP app. I am trying to provide more specific information to users of my app regarding their subscription expiration / renewal. I would like to be able to tell if they have turned off recurring billing for the subscription. Below are the details I get back from the store regarding the current subscription. I assume the isActive will turn to false after they turn off recurring billing AND cancel the subscription after Expiration date. However, in my testing I have found that if they turn off recurring billing and are still within the the subscription period of their last recurring purchase (i.e. the expiration date is still in the future), everything seems to look exactly as if they have recurring billing turned on and the subscription will auto-renew. I am trying find a way to let them know that their current subscription will expire on a specific date versus will auto-renew on a specific date. Am I missing something?
Here is the code -- Everything works fine I just don't see anything in the object returned from the Microsoft Store API that indicates if recurring billing is on for the subscription.
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
}
}
Currently, there is no such API from StoreContext
that could get the information about whether the user disables auto-renew for the subscription.
When users have purchased a subscription add-on, it will renew automatically by default. Detailed messages are listed here: Subscription renewals and grace periods. If users need to check subscription mode, he still need to check it from the account service site.