Managing subscriptions for iOS apps is critical infrastructure to ensure users have access to the correct content, and equally as important, to ensure access to app content is closed off if that subscription expires or is cancelled. This can be achieved in an automated manner in the form of a Node.js runtime service that will automatically update a subscription status, whether renew or cancellation, from your app database upon every subscription period, whether that be on a monthly, bi-yearly or annual basis.
This issue will be solved in the context of JavaScript utilising a Node.js runtime in this article. A Gist containing the full service logic is included at the end of this article, or alternatively can be viewed here on Github.
Although React Native won’t be discussed in detail, this article assumes that the reader is using a package such as [react-native-iap](https://www.npmjs.com/package/react-native-iap)
to trigger in-app purchases that in turn generate transaction receipts. The solution discussed here also does not rely on Subscription Webhooks, or server to server notifications as the Apple docs term them. One can solely rely on transaction receipt verification to determine the state of a subscription.
Regardless of whether you are using native Swift APIs or a React Native based API such as react-native-iap
to offer in-app purchases, you will still be able to adopt the Node.js based solutions discussed in this piece. The key piece of information is the transaction receipt that’ll be used to fetch subscription state and thus determine the current subscription status.
From here the Node.js runtime will be referred to as the Subscription Manager, given that it essentially acts as the middleware between the subscription state on Apple’s servers and the subscription state on your own servers, providing the means to keep your database in sync with Apple’s.
If the reader would like to start from the beginning of the in-app purchase journey and set up subscription plans in a React Native app, I have published a piece on how do so using [react-native-iap](https://www.npmjs.com/search?q=react-native-iap)
. That article can be found here: React Native: Subscriptions with In-App Purchases.
Another recommended piece centres around the process of validating and persisting iOS transaction receipts as in-app purchases are made and securely storing them in your app database. The article can be read here: Validating iOS Subscription Receipts in React Native & Node.js. With transaction receipts readily available, they can be leveraged by the Subscription Manager service discussed in this piece.
Before diving into the code, the following section will discuss the service in more detail by breaking it down into three stages that will effectively automatically renew or cancel a subscription accordingly.
The service is coded as a JavaScript file that should be run with a process manager such as PM2, although one could also simply test with node
. The service goes through the following stages:
The script periodically checks (every hour in the demos to follow) which subscriptions have come to the end of their renewal period. This is determined by the expiry timestamp that is derived from a transaction receipt. This timestamp should already be persisted in a user’s account with other metadata pertaining to the subscription state.
My receipt validation article discusses how to derive the expiry timestamp from the initial in-app purchase of the subscription, that can then be persisted to a user account’s metadata. The article uses MongoDB as the database engine.
The service will then verify the original transaction receipt pertaining to each of these subscriptions to check whether they are still active. When we verify a transaction receipt with a service like [node-apple-receipt-verify](https://www.npmjs.com/package/node-apple-receipt-verify)
, the latest subscription purchase is returned along with every detail related to the subscription, including timestamps, auto renew status, the subscription plan in question, and so on. Subscription Manager can use this data to determine whether a new transaction has been made since the previous expiry timestamp.
If a subscription is still active, the expiry timestamp of the subscription will be updated to the end of the current period, as reflected in the most recent purchase. If not, the subscription will be cancelled and the user will be reverted to the “free tier” status of your app.
If this workflow sounds blurry now, do not worry — we’ll discuss the concept in depth further down as the code is examined.
What is interesting in this solution is that webhooks are not required to get the Subscription Manager working, as the runtime service relies predominantly on validating transaction receipts to derive the subscription status. Even though webhooks can be useful to get the real-time status of a subscription, they are not 100% reliable — your endpoints could break and miss a cancellation for example. From this perspective, it is valuable to have alternative means of verifying a state of a subscription in place.
Apple made some enhancements to webhooks at WWDC 2020 pertaining torefund notifications, suggesting that they are actively attempting to improve webhook functionality.
Let’s firstly explore the structure of the Subscription Manager runtime and how the service can run on an hourly basis. We’ll then dive into the code in detail and break down the three tasks described above.
#javascript #programming #nodejs #ios-app-development #software-engineering