The Ultimate Guide to In-App Purchase in iOS Application Development: Part 2
October 24, 2019
According to Statista prediction, mobile apps will generate 188.9 billion U.S. dollars in revenues via app stores and in-app advertising in 2020. These numbers are incredible and they stress the fact that users build deeper trust with mobile applications day by day. On the other side, users want to receive more exclusive services and are willing to pay additional money for it.
Since its establishment, Dashdevs has worked with a great number of clients that provide paid services via mobile solutions. Our team has tried different methods of in-app purchase service implementation, and finally has come out with its own solution for iOS applications. We’ve decided to write a series of articles to share our vast experience in custom mobile app development, bring our insights and best practices on how to improve software product and thus take user experience to the next level. In our previous article, we’ve started with the basic overview and definitions, while in this part, we want to concentrate on the architecture and security of in-app purchases.
The Architecture of iOS In-App PurchaseController
In general, the iOS in-app purchase library architecture consists of two layers — the StoreKit layer and the PurchaseController layer. The main advantage of such an architecture is that it allows the creation of separate instances within the main library module for different tasks. In practice, mobile app developers can create several modules responsible for various tasks and related to the purchasing process. So each of them works in its separate instance of PurchaseController.
The connection between multiple instances is implemented using the State Machine pattern.
For our real cases of business iOS apps, we’ve added enumeration of states called PurchaseActionState and a method called update(newState: PurchaseActionState, from state: PurchaseActionState). This method may be implemented by a PurchaseController owner.
Enumeration structure of in-app purchase states
PurchaseActionState consists of:
- finish (PurchaseActionResult);
Ths list of iOS in-app purchase results
When an in-app purchase is in the ‘finish’ state, we pass purchase results as an action made by an iOS app user. We’ve made a list of possible results associated with PurchaseActionResult:
Уvery subscribed module can respond to the changes in the purchase operation state due to the architecture described above.
In-app purchase library modules
Main modules of an iOS in-app purchase library are:
- PurchaseController itself;
- Receipt validators.
Let’s take a closer look at each of them.
PurchaseController consists of an interface and private implementation. Such an approach targets several goals:
- The first is the principle of encapsulation.
- The second, more important, is the ability of the controller to unsubscribe from notifications without using any additional library user code, at the end of the owner’s lifetime (the so-called self-unsubscribing feature).
Since there is only one purchase queue for the entire application, and there may be many entities subscribed to its updates, a self-unsubscribing feature is an essential requirement for the controller implementation.
Purchase Controller structure
You may refer to the detailed structure of these classes below.
Purchase Queue Controller
One of the principal modules of an in-app purchase library is a PaymentQueueController — designed for working with the purchase queue. This module is connected to SKPaymentQueue using a Delegate pattern so that a purchase queue module can respond to changes in the StoreKit order. An iOS application payment queue communicates with the App Store and presents a user interface through which an iOS app user can authorize payment.The queue content stays persistent between app launches and since it is unique for the entire application, a software developer can be sure about data consistency.
Purchase Queue Controller Structure
PaymentQueueController is a composite object that holds an array of upcoming payments and also owns a storage object, which contains already purchased items and products available for purchase.
Also, PaymentQueueController holds an array of observers that can respond to controller changes.
Another important module of the in-app purchase library is ReceiptFetcher. This module allows developers to use a locally encrypted receipt for almost the whole user’s transaction history, and refresh it if needed. Receipt Fetcher Structure:
Local Receipt Validator
Another feature of an iOS in-app purchase library is receipt validation. It can be used in two ways — local and remote. The first type of validation is performed by LocalReceiptValidator and the second one — by AppleReceiptValidator. These modules allow library clients to validate an encrypted receipt and transform it into a readable form, based on codable structs. This approach helps clients send a receipt to a remote server in a readable form, use the receipt to display all transaction history on the UI, validate subscriptions, and for many other use cases. After every validation, a new version of a receipt will also be persisted locally.
Local Receipt Validator Structure
You may obtain detailed info about a local receipt validator from the diagram below.
Security of an iOS in-app purchase library
An in-app purchases receipt is an internal data structure used for the validation of purchases. A receipt is generated by the App Store and stored in an app bundle. The receipt is being refreshed every time when an in-app purchase is made or restored.
Apple encourages iOS developers to perform receipt validation in order to secure their apps from possible fraud. Receipt validation is not a trivial task as it requires a software engineer to be familiar with cryptography principles to ensure a secure application code.
Receipt validation security
There are two approaches to receipt validation, which can be combined if needed:
- Local receipt validation. It forces validation of PKCS#7 signature and parsing of a signed payload.
- App Store receipt validation. It requires a secure connection between an app and a server, as well as a specific server code to communicate with the App Store.
According to Apple, each of these techniques has a set of appliances. To make a long story short, it can be said that:
- For consumable products, local receipt validation is sufficient. Server-side receipt validation can be added if there is a need to track user’s purchases on a server.
- For non-consumable products and both types of subscriptions, server-side receipt validation is more advantageous, though it is inaccessible for local validation.
Local receipt validation
An in-app purchase receipt itself is a single file in PKCS#7 format, which is one of the common public-key cryptography standards published by RSA Security LLC.
A receipt contains the following amount of data:
- Payload — a set of receipt attributes, encoded using a standard format named ASN.1 (Abstract Syntax Notation One). Each attribute contains a type, version, and value, while these attributes are used to validate user’s purchased products.
- Certificate chain.
- Digital signature. It is used along with the certificate chain to validate that the receipt was produced by Apple.
The decryption of ASN.1 data is extremely complicated, but thanks to open-source solution OpenSSL, an iOS app developer may implement receipt decryption with the help of an extended cryptographic toolkit.
Compiling OpenSSL for usage in iOS app development isn’t quite simple. Moreover, OpenSSL is not completely compatible with Swift as it is written in the C language. GRKOpenSSLFramework addresses this issue, as it allows to easily add Swift-adapted OpenSSL with the help of a CocoaPods dependency manager.
App Store receipt validation
This approach to in-app purchase receipts validation is also called a “server-side validation”. It involves communication between a trusted application server and the App Store. It’s not possible to establish a trusted connection between an iOS device and the App Store directly because if it were, it would make payments open to man-in-the-middle attacks (MITM). So using an application server to communicate with the App Store is considered to be a safer approach.
Moreover, all information is instantly synchronized with an application server, so that a user will get up-to-date data about purchases on any Apple device within the same account.
In-app purchase validation flow
The following sequence diagram describes the full validation flow:
When a user successfully purchases a product, StoreKit refreshes a stored receipt. It should be fetched from an application bundle and submitted to a server for validation. Then, a server securely communicates with the App Store and receives a validation response. If it is valid, the purchased content is added to the user’s account on the server, and the corresponding response is sent to the client. Once the validation flow is done, the iOS app unlocks the paid content for the user, and the transaction can be finished.
This scheme adds an extra layer between an iOS app user and paid content — a server. This additional layer may result in cases when a purchase has already been made, but a server hasn’t received any info about it yet. There may be a bundle of reasons for those cases to happen, like a server error, internet connection failure, or device shutdown, etc.
To keep purchases consistent, Apple strongly recommends marking a transaction as finished only after the paid content is fully delivered to the user.
The understanding of in-app purchase architecture helped us a lot in the development of complex mobile applications for businesses worldwide. Knowing how in-app purchasing works and how it can help your company grow facilitates the business planning process and can significantly improve your communication with the iOS development team.
At some period of time we’ve decided to create our own solution and this purchase library is already available at GitHub under MIT license. Use it, enjoy, and tell us whether our custom in-app purchase library was helpful for your iOS development challenges in the feedback and comments section below the article.
The actual process of the development will be described in the next part. If you have any questions or suggestions regarding our library functionality, please don’t hesitate to contact us.