In-App Purchases in iOS. Part 2: Initialization and processing of purchases
Hi, I am continuing a series of articles about IAP. In the previous article, I talked about how to create products in App Store Connect, IAP rules, and how to add purchases to a project. In this article, we will do a simple paywall as well as the initialization and processing of purchases.
Links to all my articles about IAP:
- In-App Purchases in iOS. Part 1: Creating purchases and adding to the project.
- In-App Purchases in iOS. Part 2: Initialization and processing of purchases.
- In-App Purchases in iOS. Part 3: Testing purchases in TestFlight, Sandbox and locally in Xcode.
- In-App Purchases in iOS. Part 4: Receipt validation.
Creating paywall
Let’s make a simple screen with subscriptions. In any application that has IAP, there should be a screen with subscriptions. There are guidelines from Apple that it must comply with. We will make a simplified version.
So, what elements have I added to our screen:
- Premium title with a brief description of what he will receive. It is good practice to provide a complete list of features or content a user will receive if they subscribe.
- Buttons that activate the purchase, indicate the local price and currency.
- Restore the purchases button. This button is required for all applications that use subscriptions or non-consumable purchases.
Purchase information displays
I used Interface Builder to lay out our subscription screen and connected all UI elements to the ViewController class. Also, I added UIActivityIndicatorView to display the purchase process. Let’s see what we have in the ViewController class at the moment. It doesn’t have any logic, so I’ll fill it in later.
Let’s take a look at what we have inside:
- Variables that we tied to the UI.
- We initialize the IAPManager singleton class, which starts loading products on initialization. It is good practice to do this at the start of the application.
- The method in which we update the UI after the products are loaded.
- For methods associated with the purchase buttons, later, we will add logic to them that will initiate the purchase process.
Notice how I’m updating the purchase data, I’m not using the SKProduct fields directly. I made an extension for a more convenient extraction of the information I need:
IAPManager module
In the previous article, I told how to create a request to download products from Apple servers. Let’s finalize our IAPManager class. I added an initialize() method to create a request for loading products. After the products are successfully loaded, the productsRequest(_ request:SKProductsRequest, didReceive response:SKProductsResponse) method is called, I added the fetchAvailableProductsBlock block to it, which will return an array of our products. I also added saving an array of products for later use.
Adding purchase logic
Firstly, I added a purchaseStatusBlock block to return the status of the purchase process. The status will be returned from the IAPManagerAlertType enum. In IAPManagerAlertType, I immediately added the text of the message that the user will see in the pop-up, which will appear to him on the screen with subscriptions.
Secondly, I added methods that will launch the purchase process. In the purchaseMyProduct method, you need to put the product ID to launch the purchase process. In this method, I check if the product array(iapProducts) is empty or not. If it is empty, I make a new request to download products. After that, I check whether the ability to make purchases on the device is enabled or not in the canMakePayments method. Some devices and accounts may not permit an in-app purchase. It can happen, for example, if parental controls are set. Apple requires this situation to be handled gracefully. Not doing so will likely result in an app rejection.
If all the above checks are passed successfully, I add the product(SKPayment) to the SKPaymentQueue and subscribe to the SKPaymentTransactionObserver.
The second method is to restore the purchase (restorePurchase). If the user deletes and re-installs the app or installs it on another device, they need the ability to access previously purchased items. In fact, Apple may reject an app if it cannot restore non-consumable purchases. I will add completed transactions for the current user back to the queue to be re-completed. The user will be asked to authenticate. Observers will receive 0 or more -paymentQueue:updatedTransactions:, followed by either -paymentQueueRestoreCompletedTransactionsFinished: on success or -paymentQueue:restoreCompletedTransactionsFailedWithError: on failure. In the case of partial success, some transactions may still be delivered. If you are worried about multiple products being added to the queue, this will not happen because I block the UI after the purchase process has begun.
SKPaymentTransactionObserver
After the purchase process is launched, all results and statuses will be sent to the SKPaymentTransactionObserver methods. In this section, we will look at the methods we will work with.
paymentQueueRestoreCompletedTransactionsFinished: — Sent when all transactions from the user’s purchase history have successfully been added back to the queue.
paymentQueue:restoreCompletedTransactionsFailedWithError: — Sent when an error is encountered while adding transactions from the user’s purchase history back to the queue.
paymentQueue:shouldAddStorePayment: — Sent when a user initiates an IAP buy from the App Store.
paymentQueue:updatedTransactions: — Sent when the transaction array has changed (additions or state changes). The client should check the state of transactions and finish as appropriate. To finish a transaction, you need to call the SKPaymentQueue.default().finishTransaction(transaction) method.
In each of these methods, I pass the state to the UI using the purchaseStatusBlock block. More on this is in the section below.
UI
Let’s go back to our subscriptions screen and add calls to start the purchase process. In the methods bound to the buttons, I add a call to the purchaseMyProduct method, displaying the spinner and disabling the buttons, so that the user does not click on the purchase and the product is not added to the queue again.
We will receive the status of the result of the purchase in the purchaseStatusBlock, in this block, I added the display of the pop-up, the hiding of the spinner, and the enabling of buttons.
You can display IAP products on both the iOS simulator, as well as physical iOS devices. But if you want to test buying or restoring purchases, you can only do this on physical devices.
Note: If the run was unsuccessful and you didn’t see any products, check these points:
- Does the project’s Bundle ID match the App ID from the iOS Development Center?
- Is the full product ID being used when making an
SKProductRequest
? (Check the productIdentifiers property of fetchAvailableProducts method) - Is the Paid Applications Contract in effect on iTunes Connect? It can take hours to days for them to go from pending to accepted.
- Have you waited several hours since adding your product to App Store Connect? Product additions may be active immediately or may take some time.
- Check Apple Developer System Status. Alternatively, try this link. If it doesn’t respond with a status value, then the iTunes sandbox may be down. The status codes are explained in Apple’s Validating Receipts With the App Store documentation.
- Have IAPs been enabled for the App ID? (Did you select Cleared for Sale earlier?)
- Have you tried deleting the app from your device and reinstalling it?
Still stuck? As you can see, there’s a lot of setting up to do for IAP. Try this tutorial’s comments for a discussion with other readers.
In the next article, I will cover how to test IAP locally in Xcode and through TestFlight.
Contact me: My Twitter
Full source code: GitHub