Don’t lose your customers In-App Purchases, back them up in your App Settings

There’s currently a problem in the Windows Phone Store where customers are losing their IAP history. At the time I’m writing this post Microsoft should be rolling out a fix, and hopefully developer’s won’t be faced with this issue again.



The issue seems to be that Durable In-App Purchase products that have been purchased by the user go missing. The problem can be resolved by going to re-purchase the product, cancelling (to avoid actually spending money to buy the product a 2nd time) then going to re-purchase the product again. On the second time the user should see the ‘install’ options indicating that they’ve already purchased this product and can restore their license. Several users won’t know to go through this though. They instead will probably assume that the developer is scamming them, resulting in angry support emails and 1-star ratings.

I haven’t experienced any of this with my users though because I use my App Settings to store In-App Purchase history. I didn’t have any foresight that this problem would happen someday, I just thought it’d make sense to not have to check the In-App Purchase API every time I wanted to see if the user should have access to certain content. Additionally I was concerned with scenario’s of a user trying to use one of my Apps while the phone was not connected. I wouldn’t want them to lose access to features just because they couldn’t connect to the store service. (I now know that the phone stores a list of IAP on the device, and server interaction shouldn’t be necessary for this API call, of course if that were the case then how did we get into this mess in the first place?)

Below is a code sample that I use to store IAP state to the App Settings.

public static bool HasDurableSKU() { bool hasDurableSKU = Settings.GetValueOrDefault("DurableSKUPurchased", false); if (!hasDurableSKU) { foreach (var licKeyValPair in CurrentApp.LicenseInformation.ProductLicenses) { if (licKeyValPair.Key.Contains("durable-sku-name")) { var product = licKeyValPair.Value; if (product.IsActive) { hasDurableSKU = true; Settings.AddOrUpdateValue("DurableSKUPurchased", true); break; } } } } return hasDurableSKU; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static bool HasDurableSKU ( ) { bool hasDurableSKU = Settings . GetValueOrDefault ( "DurableSKUPurchased" , false ) ; if ( ! hasDurableSKU ) { foreach ( var licKeyValPair in CurrentApp . LicenseInformation . ProductLicenses ) { if ( licKeyValPair . Key . Contains ( "durable-sku-name" ) ) { var product = licKeyValPair . Value ; if ( product . IsActive ) { hasDurableSKU = true ; Settings . AddOrUpdateValue ( "DurableSKUPurchased" , true ) ; break ; } } } } return hasDurableSKU ; }

I can call this HasDurableSKU() method whenever I need to check if a feature should be available for the customer, and once the SKU has been purchased once we should never have to check the ProductLicenses list again.

Sounds Great, What’s the catch?

I think using this pattern will be a good move for most indie developers, but it’s definitely not secure. If your app was hacked and sideloaded onto a phone a knowledgeable user could easily modify your app settings and set the “DurableSKUPurchased” key to true, causing my app to think the user had unlocked the feature.

I’m not relying on my apps for income though, and it’s a very small percentage of users who will go through the effort of sideloading an app. I’m more concerned with making the user experience better for the 99% of users who download apps through the store and are willing to occasionally purchase an IAP.

What do you think?

I’m really interested in what other developers are doing with their In-App Purchases. Are you always checking against the API to see if an IAP has been purchased? Would you consider writing out purchase state to App Settings, or are you concerned with security? Please send me your thoughts in the comments below.

Update

I got some great feedback on this post via the twitters today. First from Oliver Ulm, Nokia Developer Champion:

@robwirving instead of writing a bool you could store install time and a hash of that and compare at runtime. — Oliver Ulm (@oliver_ulm) September 25, 2014

This is a great tip, and I may update my own code with his suggestion. This obfuscation should make it much more difficult for a potential hacker to get your IAP content for free.

Carl Schweitzer, one of my fellow Microsoft Windows Platform Development MVPs, also pointed out to me that this post actually suggests going against Microsoft’s suggested best practices for IAP:

@robwirving look at the first bullet under “Best Practices” http://t.co/ScOAt3FiG5 — Carl Schweitzer (@carlschweitzer) September 25, 2014

It’s good to know that Microsoft has optimized this API so that it can be called during startup/resume scenarios, but obviously things can go wrong. So for now I plan on continuing to write out IAP history to the Application Settings.

This does shine a light on a scenario that my code doesn’t handle though, occasionally users may request a refund for an In-App Purchase. With my code in place that user could continue using the content that they should no longer have. Of course if the user was complaining about your app enough to warrant a refund they’re probably not interested in using it anymore anyway.