At CocoaConf Dallas this weekend I attended a great presentation by Manton Reece on In-App purchases and subscriptions. In it, he cited a few key points:
- Ideally you would load the list of product IDs from a cloud service rather than baking them into your application
- You should verify your receipts on the server and not trust the client (it's extremely easy to cheat if you do).
- Ideally, the enforcement of your subscription should also occur on the server, because we never trust the client.
Since Mobile Services is…
- Good at storing and fetching data in the cloud
- Capable of executing server code and making HTTP requests from the server to other services
- Understands users and allows developers to enforce an authorization policy
... I thought it would be fun to explore how easy it would be to implement this with Windows Azure Mobile Services.
Note - this is not a tutorial on In-App Purchase. Manton mentioned that he would blog the content from his presentation in which case I'll link to this from here. In the meantime - here are the apple docs which are pretty thorough.
To demonstrate the idea, we're going to take the Mobile Services quickstart and make it subscription based. That is, in order to use the app you must pay a weekly fee otherwise the server will deny access. This will be validated and enforced on the server and associated with a particular user. So if I login to another iOS device and have already paid my subscription - the server will automatically let me use the service based on who I am.
Since we're handling users, we need to add login to our application - to save me from boring you with this code here, check out our easy to follow authentication tutorial or watch Brent's 10 minute video: Authenticating users with iOS and Mobile Services.
Now for the in-app purchase stuff. Assumptions:
- you've configured an app in the iOS provisioning portal for in-app purchase
- you've created some auto-renewable subscriptions for you to play with (I called mine com.foo.weekly, com.foo.monthly)
- you've created some test users in itunes connect to 'buy' subscriptions
Phase I - retrieving the Product IDs
This is straightforward since reading data from Mobile Services is extremely easy. I created a new table 'products' and set all permissions except READ to 'Only Script and Admins' (private). READ I set to Only Authenticated Users because they shouldn't be buying stuff without logging in, that's a requirement of my example (but not all, others may choose to make this data completely public).
First, we need to add some new code to the QSTodoService class, including these properties (not public, so just in the .m file)
// add an MSTable instance to hold the products
@property (strong, nonatomic) MSTable *productsTable; // add an MSTable instance to hold the receipts
@property (strong, nonatomic) MSTable *receiptsTable; // add an array property to store the product data loaded from your mobile service
@property (strong, nonatomic) NSArray *productData; // add an array property to the topo of QSTodoService.m
@property (strong, nonatomic) NSArray *products; // add a completion block for the loadproducts callback
@property (nonatomic, copy) QSCompletionBlock loadProductsComplete;
And don't forget to initialize those tables in the init method:
self.productsTable = [self.client tableWithName:@"products"];
self.receiptsTable = [self.client tableWithName:@"receipts"];
We now need to adjust the code that runs after a successful login to load the products data into an array. Add the following method to your QSTodoService.m and the appropriate signature to your .h file. Note you'll need to link to StoreKit.framework
QSTodoService will also need to implement two protocols to complete the walkthrough. I added these in QSTodoService.m as follows:
Notice I added UIAlertViewDelegate too, to save time later. We'll need to implement two methods to satisfy these protocols
Notice how we store the products in our products array property. Finally, we add a call to loadProducts to the login callback in QSTodoListViewController
Phase II - enforce access based on receipts stored in the server
We'll need to prevent people from reading their todo lists if they haven't paid up. We can do this with read script on the todoItem table as follows:
Phase III - add purchasing to the client
Now, we'll add code to the client that listens for the status code 403 and if it has received, prompts the user to subscribe to the service. We'll do this inside refreshDataOnSuccess in QSTodoService.m by adding this code:
To handle the response, you'll need to implement UIAlertViewDelegate's didDismissWithButtonIndex on the QSTodoService.m class (this should probably be on the controller, but this blog post is focused on the concepts - the correct pattern for your app is left as an exercise for the reader). We'll also need to complete our implementation of SKPaymentTransactionObserver above
If this is ever to fire, you'll need to add the QSTodoService class as an observer in the init method
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
In this case, we'll have the client pass the transaction receipt to the receipts table. But then we'll fire this to the App Store to check if it's valid. If it is, we'll receive a detailed response from Apple with contents of the receipt. This is the data we'll insert into the receipts table, along with the current user's userId.
Phase IV - verifying the receipt with apple and storing it in Mobile Services
This really isn't intended to be a full tutorial on In-App purchase and we don't cover a number of topics including restoration of purchases and more, however, hopefully this post has shown how you can leverage Mobile Services to provide a comprehensive In-App Purchase Server and protect your services from customers that haven't paid.
Here's a snap of some of the data stored in my receipt's table, returned from Apple (including our own userId)
And here’s an animated gif showing the flow: