Helper class to handle In-App-Products (IAP) licenses on Windows Phone (SL)

Like a lot of other apps, also UniShare got hit by an outage in the licensing system of the Windows Phone Store recently.

There was a workaround to get the licenses back from the store, but even with that, users have left negative votes (even after I posted the workaround and also wpcentral wrote about it a few days later).

The problem with those negative reviews: they tend to remain, even after responding to them (if possible) or having conversation with the users via mail. I totally understand users that are annoyed by such facts – I got hit by it as well with other apps I am using regularly. So I was thinking about a better solution as the recommended way by Microsoft, which says you should check it on every app start.

Rob Irving posted about his solution at the same day wpcentral wrote about it, which is one possible solution. His motive was the same as my solution – improving the user experience with the IAP’s.

However, I am preferring to check the licenses against the Store from time to time to make sure that the licenses are still correct.

Here is my solution (for durable IAP):

First, let’s we need to declare an object for the ListingInformation, which will hold the information that the store returns for our IAP:

ListingInformation IAPListing;

Then, we  need to create these two classes:

        public class IAP
        {
            public string Key { get; set; }

            public string Name { get; set; }

            public string Price { get; set; }

            public ProductType Type { get; set; }

            public string Description { get; set; }

            public string Image { get; set; }

            public bool IsLicenseActive { get; set; }
        }

        public class IAPToSave
        {
            public List<IAP> IAPListToSave { get; set; }

            public DateTime date { get; set; }
        }

The class IAP is the class/model that holds a single IAP item information. The second class is needed for saving the fetched IAP information (we’ll see later how I did it).

Now we have prepared these, we can finally go to the store and fetch the IAP list. I created an async Task that returns a List<IAP> for it:

        public async Task<List<IAP>> GetAllIAP()
        {
            var list = new List<IAP>();

            IAPListing = await CurrentApp.LoadListingInformationAsync();

            foreach (var product in IAPListing.ProductListings)
            {
                list.Add(new IAP()
                {
                    Key = product.Key,
                    Name = product.Value.Name,
                    Description = product.Value.Description,
                    Price = product.Value.FormattedPrice,
                    Type = product.Value.ProductType,
                    Image = product.Value.ImageUri.ToString(),
                    IsLicenseActive = isPackageUnlocked(product.Key)
                });
            }
            return list;
        }

To immediately check if our user has already purchased the item, we are getting a Boolean for it. While we are getting the data from the store, we are using this to fill our IAP class with the desired value (see IsLicenseActive property in the IAP item above).

        public bool isPackageUnlocked(string productKey)
        {
            var licenseInformation = CurrentApp.LicenseInformation;

            if (licenseInformation.ProductLicenses[productKey].IsActive)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

Now we have already all data that we need to display all IAP in our app. The usage is fairly simple:

var iapHelper = new IAPHelper();
var iapList = await iapHelper.GetAllIAP();

You can now bind the iapList to a ListBox (or your proper control/view). Next thing we are creating is our helper that performs the purchase action and returns a result string to display in a message to the user in our IAPHelper class:

        public async Task<string> unlockPackage(string productKey, string productName)
        {
            var licenseInformation = CurrentApp.LicenseInformation;

            string response = string.Empty;

            if (!licenseInformation.ProductLicenses[productKey].IsActive)
            {
                try
                {
                    //opening the store to display the purchase page
                    await CurrentApp.RequestProductPurchaseAsync(productKey);

                    //getting the result after returning into app
                    var isUnlocked = isPackageUnlocked(productKey);

                    if (isUnlocked == true)
                    {
                        response = string.Format("You succesfully unlocked {0}.", productName);  
                    }
                    else
                    {
                        response = string.Format("There was an error while trying to unlock {0}. Please try again.", productName);
                    }
                }
                catch (Exception)
                {
                    response = string.Format("There was an error while trying to unlock {0}. Please try again.", productName);
                }
            }
            else
            {
                response = string.Format( "You already unlocked {0}", productName);
            }

            return response;
        }

You may of course vary the messages that are displayed to the user to your favor.

The usage of this Task is also pretty straight forward:

var iapHelper = new IAPHelper();
string message = await iapHelper.unlockPackage(((IAPHelper.IAP)IAPListBox.SelectedItem).Key, ((IAPHelper.IAP)IAPListBox.SelectedItem).Name);

After that, we need to refresh the LicenseInformation by using GetAllIAP() again to refresh the iapList and of course our ListBox.

My goal was to save the LicenseInformation of my IAP for a limited time so the user is protected for future outages (or situations where no network connection is available). That’s why we need to add another Task to our IAPHelper class:

        public async Task<string> SerializedCurrentIAPList(List<IAP> iaplist, DateTime lastchecked)
        {
            string json = string.Empty;

            IAPToSave listToSave = new IAPToSave() { IAPListToSave = iaplist, date = lastchecked };

            if (iaplist.Count != 0)
            {
                json = await Task.Factory.StartNew(() => JsonConvert.SerializeObject(listToSave));
            }
            return json;
        }

As you can see, now is the point where we need the second class I mentioned in the beginning. It has one property for the List<IAP> and a DateTime property that we are saving. I am serializing the whole class to a JSON string. This way, we are able to save it as a string in our application’s storage.

The usage of this Task is as simple as the former ones:

var savedIAPList = await iapHelper.SerializedCurrentIAPList(iapList, DateTime.Now);

The last thing we need to create is an object that helps us indicating if a refresh of the list is needed or not. Like I said, I want to do this action time based, so here is my way to get this value:

        public bool isReloadNeeded(DateTime lastchecked, TimeSpan desiredtimespan)
        {
            bool reloadNeeded = false;

            var now = DateTime.Now;

            TimeSpan ts_lastchecked = now - lastchecked;

            if (ts_lastchecked > desiredtimespan)
            {
                reloadNeeded = true;
            }

            return reloadNeeded;
        }

This method just checks if the desired TimeSpan has passed already and returns the matching Boolean value. The usage of this is likewise pretty simple:

var savedlist = await Task.Factory.StartNew(() => JsonConvert.DeserializeObject<IAPHelper.IAPToSave>(App.SettingsStore.savedIAPList));

if (iapHelper.isReloadNeeded(savedlist.date, new TimeSpan(96, 0, 0)))
{
   //reload the list and perform your actions
}
else
{
   //use savedlist.IAPListToSave and perform your actions 
}

This way, we are able to make sure that all IAPs are available for a minimum of time and protect our users against store outages.

For your convenience, you can download the whole class right here. Just replace NAMESPACE with yours and you are good to go.

Note: I know that this approach does not follow the recommended way of Microsoft. It is up to us to deal with bad reviews if something on the store part is not working. This is my approach to avoid negative reviews because of store outages (at least for a limited time). However, like always, I hope this post is helpful for some of you.

Happy coding!

Join the discussion right now!