Introducing Offline Entitlements: Uninterrupted in-app purchases
When 99.9999% uptime isn’t enough.
We’re excited to announce a major addition to the RevenueCat SDKs for both iOS and Android: support for Offline Entitlements.
This new feature paves the way for users to make purchases from the app stores even in scenarios where the RevenueCat servers are temporarily unreachable. Let’s dig into what this means for developers, why it’s important, and how we accomplished this.
A necessity in an unpredictable world
In an ideal world, our servers would never go down, but reality is different. Even the most robust systems can have occasional downtimes, and these can be disruptive for applications relying on server-based functionalities. This is precisely the challenge our new Offline Entitlements feature is designed to address.
Our servers are used to validate in-app purchases and renewals prior to granting access in apps. This is necessary to verify the status of subscriptions as well as determining which entitlements are unlocked.
Therefore any server downtime could temporarily interrupt the purchasing process, affecting the user experience and sales.
With this new feature, even if our main and backup servers are down, the SDKs can continue to process purchases. This is enabled transparently to the user on the rare occasion that our servers are down, and when they come back online, the SDK automatically syncs the information so it can be visible in the dashboard.
We’re still striving to avoid any server downtime, but this adds yet another layer to our Swiss cheese model, thereby improving the reliability of RevenueCat as a whole.
Note that prior versions of the SDK were already resilient to our servers being down thanks to well-tested caching layers. The main missing piece was being able to handle purchases and renewals during an outage.
How we achieved this
The process of adding Offline Entitlements was complex and required careful planning and implementation. We had to ensure that, while allowing Offline Entitlements, we also maintained robust security measures to prevent fraudulent activities.
The first piece of the puzzle was allowing the SDK to have Offerings cached if the server was down.
Due to the dynamic nature of Offerings, which allows apps to change them at any point or perform experiments, the SDK has a low time-to-live on the Offerings cache. However, with the latest versions of our SDKs, apps will now be able to display paywalls even during an outage.
The second piece was allowing the SDK to determine which products unlocked which Entitlements.
Traditionally these two responsibilities were delegated to the backend server:
- Figure out which products a user has purchased: this is done by our servers communicating with Apple and Google’s servers directly
- Figure out which Entitlements the user is entitled to
Now, the latest SDK versions have a full mapping of products and entitlements, allowing them to determine what users have unlocked.
To ensure we had the right safeguards, we leveraged Apple’s and Google’s secure systems. On Apple’s side, we use only StoreKit 2 to determine the current subscriptions, which ensures that users aren’t able to fake this data (as they might have been able with StoreKit 1 receipts).
On Android, we use Google Play BillingClient (or the Amazon Appstore SDK) to determine the current subscriptions securely and calculate the current entitlements.
How we tested this
This inevitably brought additional complexity to the SDK. That’s why it was of utmost importance to ensure proper test coverage now and moving forward.
We followed all stages of rigorous testing: including unit tests, integration tests, and manual testing. If you’d like to find out more about how we test, we wrote a detailed post about our approach to SDK testing at RevenueCat.
We expanded our end-to-end testing infrastructure to allow us to verify the behavior of the SDK with varying combinations of state changes: simulating our API being down, up, or changing throughout the lifecycle of the app.
1func testPurchaseWhileServerIsDownPostsReceiptWhenForegroundingApp() async throws {
2 let logger = TestLogHandler()
3
4 // 1. Purchase while server is down
5 self.serverDown()
6 try await self.purchaseMonthlyProduct()
7
8 logger.verifyMessageWasNotLogged("Finishing transaction")
9
10 // 2. Server is back
11 self.serverUp()
12
13 // 3. Request current CustomerInfo
14 let info1 = try await Purchases.shared.customerInfo()
15 try await self.verifyEntitlementWentThrough(info1)
16
17 // 4. Ensure transaction is finished
18 logger.verifyMessageWasLogged("Finishing transaction", level: .info)
19
20 // 5. Restart app
21 Purchases.shared.invalidateCustomerInfoCache()
22 await self.resetSingleton()
23
24 // 6. To ensure (with a clean cache) that the receipt was posted
25 let info2 = try await Purchases.shared.customerInfo()
26 try await self.verifyEntitlementWentThrough(info2)
27}
28
29
Limitations
Availability
On iOS this requires iOS 15.0+, since it relies on StoreKit 2 to determine current entitlements in a secure way.
Cross-platform purchases
During a server outage, purchases made on a different platform won’t carry over. In other words, if a user purchased a subscription on Android, it won’t be active on iOS, etc.
Fresh installs
This feature requires the user to launch the app at least once with our servers online so it can download the necessary information. But stay tuned on further reliability improvements that will mitigate that.
Consumables
Guaranteeing uniqueness of consumable purchases isn’t possible without a server. Therefore this feature isn’t compatible with them.
Why this is a huge win for developers
This new feature ensures a more reliable experience for users when making in-app purchases and subscriptions. This is a big win because users no longer need to rely on RevenueCat’s uptime or that of our cloud providers in order to validate their purchases. This leads to a better experience and makes apps better equipped to handle potential technical issues with RevenueCat servers, resulting in a more resilient business.
This is just one of the many steps we are taking to ensure that RevenueCat continues to serve you better, helping you to make more money.
We’re thrilled to introduce this new feature that delivers automatic benefits to your app with no code changes needed. We look forward to hearing your feedback!
You might also like
- Blog post
The #RCGrowthChallenge: Win a $15k budget & 3 months of hands-on support from Steve P. Young
Announcing, the #RCGrowthChallenge 2024
- Blog post
We now handle Apple refund requests automatically: How Dipsea’s refund rate dropped 36%
A smarter way to manage refunds and protect your bottom line.
- Blog post
Introducing the Official RevenueCat iOS App
Stay on Top of Your Subscription Business Anytime, Anywhere