If you've ever added in-app purchases to your app and track each purchase with analytics, you will quickly see the reports from iTunes (real revenue) don't match up with purchases recorded from your analytics tracking.
Sadly, it is pretty easy for jailbroken/rooted devices to circumvent in-app purchases on both iOS and Android. To make things worse, any teenager is skilled enough to set this up with a little research on the internet.
First of all, keep in mind any protection we add is effectively an arms race between the attacker and the developer. Given enough free time, an attacker will always be able to find a way to get free IAPs in your app. The goal for a developer should be to make it difficult enough to where the attacker see their first few attempts fail. Hopefully this will cause them to move on to easier apps to exploit, and trust me, there are plenty out there.
So how can you protect your IAP revenue?
It turns out a little work will cut out almost all piracy against your in-app purchases. Apple recommends quite a bit more protection than this (video here): bundling a custom version of OpenSSL in your app, writing code to parse their receipt certificate format, and then imagine trying to call into that C library from Xamarin, ugh. The act of calling your own server over SSL is going to be more protection for the effort than jumping through all of these hoops.
Maybe this is a good tip for life, in general. If you are just wanting to get in-app purchases implemented as fast as possible I would recommend trying James Montemagno's plugin (which even has an option for adding server validation). However, if your goal is to prevent piracy, you really need to understand what is going on under the hood. Using someone elses library for IAPs is ineffective against piracy because A) the library developer might have a vulnerability you don't know about, and B) an attacker may attack the library directly. Imagine all the games out there using Unity3D's in-app purchasing plugin. If an attacker found an exploit there, they could get a huge amount of free purchases across many games on the mobile market. Rolling your own in-app purchases is definitely the way to go for preventing piracy, regardless of what technologies your app is built with.
Before we get into the weeds, first let's write a base class that can be used on both platforms:
For now, let's just assume these purchases are consumable like purchasing coins, etc. The Purchase and Receipt classes are just DTOs, you can view my full sample project on Github. We only need to implement two simple functions: get the prices (in the user's currency) and actually buying the product.
For our iOS implementation, we will use StoreKit directly. First let's implement GetPrices:
The general pattern here is to Start() an SKProductsRequest, where iOS will post results to an SKProductsRequestDelegate. A good rule to follow for something like this is to use TaskCompletionSource to effectively flatten an ugly callback into a Task and match our nice async API from the base PurchaseService class. I also have a bit of code here to localize the price using native iOS APIs.
Next is the Buy method, a bit more complicated:
What we did here:
So the C# implementation is one thing, but testing this stuff is also a complete mess on iOS.
The minimum setup for testing GetPrices is:
So I would verify GetPrices works in your app before moving on, take a look at my unit test as an example of what I used to test. Note that this will still work in the iOS simulator, however Buy will not.
So the remaining setup for testing Buy?
Another way to test this, is to submit builds to TestFlight. From there you can actually use real iTunes accounts for testing IAPs. If the app is from TestFlight your credit card will not be charged, but you can test the actual App Store build and verify its correctness.
Note that sandbox purchases act really weird in general: popping up multiple login prompts, prompts appear randomly on your device later, etc. I don't really know why Apple hasn't looked into fixing this, as I've seen this stuff since 2012 at least--and I don't think it's my implementation. I've even seen it in Unity3D apps. I would disregard these issues for the most part, but make sure to test your final build from the App Store and spend real money.
If your app does not have a backend, then Azure Functions is a perfect opportunity to get your feet wet with a serverless approach and a good fit for validating IAPs.
First let's look at an Azure Function to validate an IAP with Apple:
What we did here:
You can see the full Azure function on Github here under the api folder. I use file-linking for sharing the Receipt and AppleReceipt classes between my iOS app and my Azure function.
NOTE: in addition to what I have here, you should also keep a record of past purchases somewhere such as an Azure Storage Table. You don't want an attacker to be able to reuse a valid receipt, so I recommend running a query to see if it has been used before. Keeping a log of each Azure Function call and the results should be kept there as well for record-keeping.
So on our client, it is not much code to call our Azure function with HttpClient:
Pretty straightforward, calling an Azure function is simple with HttpClient: no third party dependency required.
Implementing IAPs like this is going to stop a huge percentage of piracy in your app, but it is not completely bullet proof.
Some other things to consider:
I think that wraps it up. I will cover Android and Google Play in a future post, as this one seems quite long enough.