Last Updated: August 2019
Note: This article is a work in progress. This article is meant to be a all in one place to find everything you need to get started and work with IAP. But it will most likely not get into detail in terms of code till it is tested and confirmed. If there’s code, that means I have tested it and it works. Feel free to comment and let me know what I need to add or update as time passes as I want to make sure this article is actually useful as I haven’t found a go to place for IAP that works nicely.
So with Apple IAP, there are multiple types of products three types fall under one bucket while the other falls in the other. That is Consumables, Non Consumables, and non renewing subscription. Those fall in one bucket for implementation while Auto Renewing Subscription is in a different bucket.
The first thing we need to do id make sure that you are in a PAID developer program. IAP will not work on a free program as you need to be able to go into iTunes Connect to create an app and user it to great an IAP product.
To start with the first bucket these types of purchases do not require any web experience or experience in creating servers which is why they are in one buck. Pretty much after you implement IAP into your app and have it talk to your server which speaks to your database, you are pretty much good to go considering that you probably want to store whether or not your user purchased your in-app purchase product.
Mutual Grounds; the basic.
To start there is class call SKStore. You will need to import this to the class where the purchase will be taking place. I would also recommend creating a ‘Store’ class that can handle the process for in-app purchase.
With the Store class, you will need to import SKStore, and subclass SKProductsRequestDelegate, SKPaymentTransactionObserver.
We will need to implement a few of its functions.
Above is a simple gist with all the delegate methods that we need to add. I didn’t add code for writing to the database but I would put it wherever finishTransaction(:) is.
Then at the view controllers where the Store is going to be accessed, I would fetch the products you need. This will be your productID from iTunes Connect.
var productsRequest = SKProductsRequest()
var productIdentifiers = []//Array of product IDs.
//Recommended: For Unique Product ID, using your Bundle ID then product name as productID. i.e. com.mediumArticle.andyIAP.product1productsRequest.cancel()
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)productsRequest.delegate = selfproductsRequest.start()
We are going to cancel any product request if there are any and start a new one because we do not want to have more than one occurring at a time.
So now that we have everything setup, the last thing to setup is what to do when the user clicks the buy button.
When the user presses the buy button we want to make sure that they are able to make a purchase. Sometimes they are disabled by the app store due to bad payment methods.
Above is the code necessary when the user clicks the buy button for a product. First, we have to know the index of the product. This is the rather tricky part because it is honestly somewhat hardcoded if we need the index, but thats okay because we have control over that. So the array indexes of the productIDs is also the array index for the product we will be using so we are okay, I would recommend store the data on the array of products so if you have three buttons, each button should hold the ID so it is easier to know what product the user is trying to buy.
Lastly, we to remove the paymentQueue when the user leaves the ViewController. We do this by:
SKPaymentQueue.default().remove(self)
That was the basic, now comes the Auto Renewable Subscriptions.
Also an advanced version of above, it is a continuation implementation while not necessary, is a nice to add if you want to have the most thought through app.
So the hardest thing about Auto Renewable subscription is knowing when a user renews or cancels a subscription. Which means our app has to handle it. This means that we need to validate purchases. So the first section was a bunch of client-side work to allow the user to press the buy button. But you want to make sure the user actually paid for it and didn’t just cancel it after. So to do that, we need to be able to handle that using Apple’s implementation method of server to server notifications. We can do this as well for the first section, I don’t find it necessary when I was working on it considering that a user has to got a long way to make an fake purchase for the app I was building at the time. Regardless, this is something that should be added to be sure that the user paid.
Apple gives us two URLS for verifying Receipts. These url will essentially return a receipt that can include all the purchases a user has made on your app.
Sandbox is basically Apple’s development
Sandbox URL: https://sandbox.itunes.apple.com/verifyReceipt
Production URL: https:// buy.itunes.apple.com/verifyReceipt
While it is tempting to think that we can use this URL on the Client side, Apple does not recommend this since it does not prevent Man in the middle attacks. Also a benefit of receipt validation is that we make sure that the user actually made a purchase rather than fake it if they have a jailbroken phone.
Before getting started on the server side of things, on the client side we have a couple of things we need to do. We need to get the receipt on the client side, send it to our servers when then sends it to Apple, Apple returns a JSON receipt, then we can do what we want with that receipt.
Above is the code for getting the receipt data. We are essentially trying to grab the receipt from the user’s device. This should exist as every time a user downloads our app from the app store, the receipt is grabbed. But if say for some reason that doesn’t happen, we will want to try and grab it.
let receiptRefreshRequest = SKReceiptRefreshRequest()
receiptRefreshRequest.delegate = self
let receiptUrl = Bundle.main.appStoreReceiptURLdo {if let receiptFound = try receiptUrl?.checkResourceIsReachable() { if !receiptFound { receiptRefreshRequest.start() }}
Here we are creating a SkReceiptRefreshRequest
, setting the delegate, then try to grab the receipt, if it cannot be grabbed we want to start the receiptRefreshRequest. Make sure that wherever you place this code, you want to also subclass a delegate method called SkRequestDelegate
We want to get it’s finished delegateMethod to try to validate the receipt again. Remember, the steps are the following:
- Try to get receipt on device using
Bundle.main.appStoreReceiptURL
- We then try to make sure it exists using either FileManager,
.checkResourceIsReachable()
. - Then we have a method that sends the receipt to our servers. In the code for this article, it is double checked. Which isn’t necessary so I would do the check in either method only.
- After the receipt is sent to the server, it is your servers responsibility from there.
Finally the server stuff
Okay so final stretch… somewhat. We need to create a REST API that talks to our servers. The API only needs two endpoints one for receipt validation, and the other for Apple’s end to end notifications. For first, we just need an endpoint that gets the receipt from our client, take it, send it to apple, and when it returns the JSON, we want to save it to our servers. Now, you can do what you want from there. If it is not validated, maybe just kick the user off the app by signing them out. Also, on your client, I would not use UserDefaults to set the app functionalities. You can, but make sure to always try to rely on your servers to get the most up to date info. So I would talk to the server whenever the app is run at launch to make sure the user is still a paying user.
I recommend using Python and Flask to build your REST API
Option 1 — Mix of Apple Server to Server Notification + CRON :
The server to server notification is the harder part. When we get the info, we we will get a notification in JSON on what happen to the user’s IAP. and the `original_transaction_id`. We will use this to pinpoint which user canceled or stopped renewing their subscription. Also your server needs to be able to run a cron job or some job everyday perhaps for users that have an expiration date that is today, we should stop the user from access to the app after today.
The CRON JOB will essentially give us the flag so we know when to stop the user. We can run if every minute if we’d like so the user will lose its access at the right time. This also means that we get more accurate info since if Apple does not send us the notification we need to know if user stop the subscription, we are giving access to a user that has stopped paying so by running a CRON job, we are taking an extra step to ensure the latest info.
Apple server provides these notifications:
- ‘INITIAL_BUY’- When the user subscribed to the app the first time.
- ‘CANCEL’ — Contacts Apple to cancel subscription, or user upgrade such as going from a weekly subscription to a monthly subscription.
- ‘RENEWAL’- Successful renew of the subscription, change expiration date on your server. ‘
- INTERACTIVE RENEWAL’ — User tapped to renew on their own, change expiration date and give user access immediately.
- ‘DID_CHANGE_RENEWAL_PREF’ — Change from monthly to weekly subscription, up to you what you want to do with this info.
- ‘DID_CHANGE_RENEWAL_STATUS’ — User taps stop subscription. This is important, this is not cancelling the subscription, it is merely stopping the subscription it is different.
While it seems that it has all the information we need running a CRON JOB is more accurate and gives us a bit more control over the information rather than rely on Apple to always notify us when something changes for the user.
Option 2 — Trust Apple:
Apple has gone pretty far with their server-to-server notifications. They pretty much have most of the information that you need to know immediately if it is time to stop a user from using the app, so you can use just the apple server to tell you when to give user the access to the app and when it is time to stop. There is:
- ‘INITIAL_BUY’- When the user subscribed to the app the first time.
- ‘CANCEL’ — Contacts Apple to cancel subscription, or user upgrade such as going from a weekly subscription to a monthly subscription.
- ‘RENEWAL’- Successful renew of the subscription, change expiration date on your server. ‘
- INTERACTIVE RENEWAL’ — User tapped to renew on their own, change expiration date and give user access immediately.
- ‘DID_CHANGE_RENEWAL_PREF’ — Change from monthly to weekly subscription, up to you what you want to do with this info.
- ‘DID_CHANGE_RENEWAL_STATUS’ — User taps stop subscription. This is important, this is not cancelling the subscription, it is merely stopping the subscription it is different.
Using this method relies heavily on Apple to provide the correct information so your server can work correctly and give users the access when they need it. You app will probably need to check the expiration date every time when app loads to see if user is going from ‘FREE’ to ‘PAID’ using this method via the expiration date while option one it is taken care of by the server so you only need to check for a BOOL flag.
Please comment and let me know if there is any information that is missing or is not clear enough for you. I want to make an article that gives all the information needed for IAP. Side note: This is primarily an iOS article so I did not add code for the server implementation.