Until recently, the Weebly iPhone app only offered our customers the ability to upgrade their site with a non-renewable in-app purchase. After a subscription’s duration elapsed, we needed to notify the user to purchase another upgrade before their service expired.
As of October 2016, we switched to renewable subscriptions which resulted in reduced friction and improved revenue terms for renewals beyond a year.
Weebly’s business model is unique in that a single user may upgrade multiple sites concurrently. At the time of this writing, Apple’s subscription groups only allow for one active subscription per user.
Implementing Apple’s renewable subscription model is more challenging for products that diverge outside of the standard subscriber model. This article provides a high-level overview of our strategy for working within those constraints.
Determining when a user can upgrade
The app must decide at runtime whether a subscription can be purchased by a given iTunes user. If you attempt to initiate the purchase for a user who has an existing subscription, they will receive an alert stating that the item has already been purchased.
Our solution is to examine the user’s receipt locally. This involves extracting the receipt from it’s PKCS #7 envelope and parsing the data from ASN.1 formatting using LibreSSL. We’re able to skip the authenticity validations because the local receipt is only used to determine the appropriate product identifier to display. Secure validation is performed by our server using Apple’s remote verification API.
If the current iTunes user already has an active subscription, we can either disable the upgrade from mobile or offer multiple renewable subscription groups.
Multiple Subscription Groups
The challenge presented by multiple groups is that you’ll need to determine which SKProduct to display/sell based on the user’s purchase history. This can be determined by parsing the local receipt and checking for active subscriptions in each subscription group.
Another caveat comes from attempting to keep all of your iTunes products in sync with their respective screenshots, prices, descriptions, localizations, and other product attributes. Some of this work can be mitigated by using Apple’s Transporter to automate updates.
In order to securely verify the customer’s active subscription, you’ll need to verify their Grand Unified Receipt. Apple offers two ways of securely verifying the receipt, locally and remotely.
Remote App-Store Verification (Recommended)
Apple’s docs do a great job of explaining how to perform a secure remote verification so I won’t repeat all of that here. At a high-level, remote verification initiated by your server performing an API request to Apple with a receipt identifier. Security is more straight-forward because you can trust both sides of the connection.
The interesting component of Weebly’s implementation is our strategy for uniquely identifying transactions and periodically checking them for status updates.
When parsing the receipt, we must verify that the expected transaction exists in an active state and that it has not been applied to any Weebly website. All transactions contain an original_transaction_id property which is used to group related transactions.
The catch is that the original_transaction_id is only unique to the subscription group. If a user cancels their service and later applies it to another website, the original_transaction_id is no longer unique. One solution to this is to store a combination of the original_transaction_id and the purchase_date of the oldest transaction in the chain before any subscription lapses.
It is necessary to periodically re-validate receipts to detect cancellations, refunds, and upgrades. Thus, it’s important to store the receipt identifier and create a job that fetches a fresh copy of the receipt and searches for updates.
As of iOS 10 users may upgrade their active subscriptions through their device settings. If the user doesn’t open the associated app, you won’t receive any notifications that their service needs to be promoted. This is ok if your app only offers mobile service, but you’ll need to proactively check if you offer paid functionality on more than one platform.
The periodic check must loop through all of the related transactions to observe for changes to the product_identifier for each active subscription.
Consider the scenario where a user purchases an active subscription but your server is down. The user is charged but the service has not yet been activated.
We’re able to detect this scenario by the existence of an unfinished SKPaymentTransaction in the payment queue. When this occurs, we send a copy of the receipt identifier to our API which determines if any active transactions in that receipt have not been associated with any Weebly service.
Unfortunately, you can not attach an identifier to the SKPaymentTransaction to remember the initial selection. If unassociated transactions exist, then we must prompt the user to select which site they intended to upgrade.
This process generally happens transparently to the user, but they can also initiate a restoration through the app settings (per Apple requirements).
Apple provides a sandbox environment for creating test purchases that expire at a faster rate. This is ideal for testing the initial purchase, but it requires special handling for periodic checks and quality assurance testing.
Purchases in the sandbox environment auto-renew up to six times. This means that your periodic receipt service needs to check for cancellations and renewals at a faster rate for sandbox testing.
Any purchases from a sandbox account remain in the account’s purchase history. This means that future purchases from the same sandbox account will share the original_transaction_id. Theoretically, this will be handled by your expiration handling from above.