How to monitor subscription status upon app launch (swift) - ios

I'm building a simple dating app that allows a user to become premium with an auto-renewing subscription. The user's value for their "isPremium" attribute is false upon creating an account, and becomes true once they purchase a subscription via an in-app purchase. But I have to implement one more thing to finish this premium feature, and this is where I run into problems!
How do I monitor the app's current user's subscription status when the app launches? I want to keep their "isPremium" attribute true if their subscription is still ongoing, but immediately change it to false if it has expired.
I'm aware this has probably already been asked on here, but responses are mixed and mostly in Obj-C (I'm building this app in Swift). Would the best move be a backend server in Python to validate receipts? Or is there a simpler, more direct way of getting an expiration date from a subscription product object or a Cocoapod that handles this backend work? Please help! Thanks

The best move is a backend server like you mentioned. This way you store the receipt file there and periodically refresh it with Apple, so you always know the subscription status for any user.
On the device, you request the subscription status from your server and cache it (~5min). Every time the user resumes or opens your app check if the cache is stale and refresh it. This way you can prevent expired users from retaining access for more than 5mins yet keep it fast and snappy on app launch.
Or is there a simpler, more direct way of getting an expiration date
from a subscription product object or a Cocoapod that handles this
backend work? Please help!
If you're looking for an out of the box solution, RevenueCat is a solid option and handles all of the backend work. They have a free tier as well. https://github.com/revenuecat/purchases-ios

Related

Implementing iOS App Store Subscriptions without a backend?

I'm implementing subscriptions in a React Native app right now and while I was building a function to check if the subscription was still active (I would call getPurchaseHistory to get the past transactions, then use Apple's verifyReceipt endpoint to decrypt the transactionReceipt to compare the current date with its expiry date) but I read not to do that on app's launch.
Then I discovered that the "go-to" way of handling receipts was to have a backend to store them. I thought I'd be able to implement it all without any backend because I was trying to save time but also avoid any backend downtime or latency.
Basically I'm wondering, is it possible to do it all without any backend at all?
I thought of storing the transaction receipt on the device. And use that to check if the subscription is still active. But what if they cancel the subscription?
Any suggestions? I can't believe that's impossible. There's got to be a way I'm not thinking of.

Apple In-App Purchase and Receipt Refresh

I have a side project and I recently worked on my receipt manager to make it stronger and to relies more on the receipt of the app rather than persistently storing a value after a transaction.
However, there are 2 main points which although I read Apple docs and other answers on the web, I'm still confused about:
1. When a user restore their purchase, does the receipt get refreshed?
I made several tests in sandbox, and I have seen that when restoring, the receipt gets refreshed, and when I verify the receipt through the iTunes server verification, it returns a JSON including the latest transactions. This is very helpful because even if I close/open the app, the app receipt is updated and I can always verify it without refreshing it.
However, in production, this didn't work. The app receipt wasn't refreshed after restoring purchases and my users got asked to restore purchases continuously. Can anyone answer on this point?
2. Does the refresh receipt request triggers an alert asking for the Apple ID's password in production?
From the previous point, I thought ok, I will force receipt refresh after a user restores their purchases. However, in development / sandbox, I get asked to insert my sandbox user's pass every time I try to refresh the receipt (although I could restore purchases without a password request before asking for the refresh). I read a lot about this and someone says that might not happen in production. Does any of you have a clarification on this?
Note:
I know that when restoring / purchasing I get back a transaction with the receipt, however, I need to use the App Receipt to verify transactions (and this is also what Apple says to do).
Thank you in advance.
1. Refreshing the receipt
In theory, calling restore purchases should get the latest receipt. In the instances where you are experiencing issues, take a look at SKReceiptRefreshRequest. Typically, I use this in production when a call to restore purchases has encountered errors.
Use it wisely, triggering that API can lead to showing the Sign In prompts for the App Store.
2. When is the user asked to sign in?
Sadly, I have seen this vary so I cannot give a definitive answer. More often than not, a call to restore purchases should not trigger a sign in. Explicitly using SKReceiptRefreshRequest will.
If the user is not signed in to the store, calling any Store API like attempting a purchase or restoring purchases could trigger the sign in flow.
What Apple Says
From the docs
Refreshing a receipt doesn't create new transactions; it requests the latest copy of the receipt from the App Store. Refresh the receipt only once; refreshing multiple times in a row has the same result.
Restoring completed transactions creates a new transaction for every transaction previously completed, essentially replaying history for your transaction queue observer. Your app maintains its own state to keep track of why it’s restoring completed transactions and how to handle them. Restoring multiple times creates multiple restored transactions for each completed transaction.
My Recommendation
Store the hash of the last receipt you used on device. You can use this hash to check against the latest receipt so you know if anything has changed. Whenever your App resumes you can always check if the current receipt hash is different from the last cached value.
Try and submit the receipt as soon as possible. Typically when the App has launched.
If a user tries to manually restore purchases, I would start with a call to restoreCompletedTransactions. This can trigger an App Store sign in but is often less likely. Most of the time this is enough as the receipt on the device is often pretty up to date.
If the user tries another restore purchases, OR if the call failed, then move to SKReceiptRefreshRequest to guarantee a fresh receipt.
When using SKReceiptRefreshRequest, I would recommend wrapping this behind UIAlertController. I normally show something that indicates it has failed and have a "Retry" button that uses the request. This will trigger a new store sign in.
Use restoreCompletedTransactions to get a playback of all completed transactions the device is aware of.
When a user restore their purchase, does the receipt get refreshed?
Yes, it should. But it also sounds like you're doing some server-side validation? If that's the case, you can send any receipt from the user to the /verifyReceipt endpoint to get the latest status. You don't need to send the latest receipt, since /verifyReceipt will also refresh it.
Does the refresh receipt request triggers an alert asking for the Apple ID's password in production?
There's no clear Apple documentation on this, but it definitely will if there's no receipt file present in the app (rare in production). But if you're doing server-side validation (see #1), then you can send up any receipt you have, no need to refresh it. So you're only refreshing the receipt if nothing is present, which will trigger the sign-in. Keep in mind a receipt file is not present on the device after installing in sandbox - only after a purchase. This differs a lot from production where a receipt file is generated after installation.
From what it sounds like you're trying to do, my recommendation would be to check if any receipt file is present at launch, send it to /verifyReceipt to get the latest status for the user and cache the result. You can do this on every app launch.
In a perfect world you're storing the receipt server-side and keeping it up-to-date there, but you mentioned side project so that sounds like overkill. However, an out-of-the box solution that correctly implements all of this and will scale with you - such as RevenueCat - is another alternative (Disclaimer: I work there).
After many tests and after I sent my app in production, I'm now able to answer my questions properly:
1. When a user restores their purchase, does the receipt get refreshed?
YES, this is immediate as for Sandbox, BUT the problem is that the receipt DOESN'T include non-consumable purchases.
This means in other words that the receipt will include the purchased subscriptions, but you won't find purchases of non-consumable products.
However, when the user restores or purchases, you get the transactions in return, and you can extract the non-consumable products, and store this info somewhere like UserDefaults or Keychain, so you can use them when the user opens your app.
For the rest, the best approach is to always validate and check the receipt when the app is opened.
2. Does the refresh receipt request trigger an alert asking for the Apple ID's password in production?
YES. For sure it does the first time.
Thank you to Daniel and enc for their answers that can still be useful.

Linking iOS In-App Subscription to MySQL database

I have followed some tutorials on implementing In-App purchases and subscriptions for iOS (especially Ray Wenderlich) but I have doubts on how to integrate our iOS app with our website so that if someone subscribes to the app they can access the content on the web as well.
Our website and app offer access to videos. All subscribers have access to all videos. Our web works with PHP and MySQL. When someone subscribes on the web, a record for their account is created in our MySQL database and they are flagged as subscribers so that they can access the content.
We want to implement in-app subscription in our app, but we need for a record to be created in our database when the subscription takes place.
The question is, should we do this upon receipt validation (we'll be doing this in PHP)? Also, is there a way to know when a user unsubscribes through iOS so that the database can be updated accordingly?
Our closest example of how we would like it to work is Gaia.
Should we do this upon receipt validation?
Yes, you should wait until you validate the receipt to mark the subscription as active for the user.
Is there a way to know when a user unsubscribes through iOS so that
the database can be updated accordingly?
The correct way to implement this is to store the entire IAP receipt on your server and periodically refresh it with Apple to get the current subscription status. Just because somebody started a subscription doesn't mean it will still be active the next time you check it (e.g. they may have turned off auto-renew or been issued a refund).
This blog posts explains some of the nuances in further detail: iOS Subscriptions Are Hard
Is there a way to know when a user unsubscribes through iOS so that
the database can be updated accordingly?
Use status update notification.
A statusUpdateNotification is a server-to-server notification service
for auto-renewable subscriptions. A notification specifies the status
of a subscription at the time the notification is sent.
As status update notification is not a reliable service, Apple recommend to use this in combination of other method such as polling the verifyreceipt end point. creating a scheduler in server will be best option along with notification.

iOS Handling local SKPaymentTransaction when using backend server

I'm creating an iOS app with an auto-renewal subscription. I've been reading a lot of tutorials and documentation but I'm a bit confused about how to handle certain scenarios.
Here's how my app works:
User installs app
User creates account within signup flow
User is asked to select a plan and pay within signup flow
The payment receipt is uploaded to my server and I activate their account in my database.
My server polls the /verifyReceipt endpoint on regular basis to renew the user's account or deactivate it depending on what the latest info from apple. (or use Apple's new Status Update Notification, both serve the same purpose to get me the latest subscription info on my server)
After a month when the subscription renews I know a transaction will appear on the SKPaymentQueue on the user's device. Because of this a lot of tutorials/documentation recommend having your AppDelegate implement the SKPaymentTransactionObserver protocol so that you can handle a transaction at any time.
But, I didn't use AppDelegate. I used the view controller in signup where the user picks their plans to implement SKPaymentTransactionObserver.
My reasoning is that since I'm getting info on the backend do I need to care about the transactions that will show up in the queue in the client each month when the subscription renews? Can't I just ignore these transactions, or will I need to call queue.finishTransaction on them?
I also read some things about restoring transactions when the user deletes the app and re-installs or gets a new phone. Again, do I need to worry about this? Because I should still know about the subscription on the backend and all the user has to do when they get a new phone is log in to their account for my service and it'll check the backend to see if their subscription is active.
I guess my larger question is: When you have a backend to handle IAP auto-renewal subscriptions, can you ignore some of the stuff happening on the client with the payment queue because that feature was built for apps that don't have a backend.
It's best practice to implement the observer immediately in the AppDelegate in case something goes wrong between the user being charged by Apple and you upgrading their account - if they close the app or it crashes you may lose that transaction.
Also, I think I've had cases where I forgot to call finishTransaction and that annoying iTunes login prompt kept popping up, not sure if that was a Sandbox only event though.
Like #Paulw11 said. Don't rely on the Status Notifications. At time of writing, they don't provide enough info to update a user's status, namely any sort of user identifier. Refreshing receipts from the backend is the way to go. If a new receipt is posted to the SKPaymentQueue (say on a renewal), you can handle it like any other receipt refresh for the user on your server.
Here's a good blog post that provides more detail on what should be happening exactly on the server: iOS Subscriptions are Hard
For your restore logic, you don't need to use the StoreKit restore method if you've implemented your own restore functionality through an account based system. If that's the route you want to go, you should definitely listen to SKPaymentQueue in the AppDelegate to avoid as many edge cases as possible where you could lose track of someone's subscription status. The good 'ol "Restore Purchases" button is a great way to fix some slightly flawed in-app purchase code :)
I guess my larger question is: When you have a backend to handle IAP
auto-renewal subscriptions, can you ignore some of the stuff happening
on the client with the payment queue because that feature was built
for apps that don't have a backend.
Don't ignore the payment queue. You can ignore the "Restore Transactions" if you have your own account based restore system.

Accessing StoreKit receipts directly from a server

I'm building an iOS app, which provides a service which our website already provides. It's a subscription based service, where all features are enabled when you're subscribed. To get people to sign up for regular payments on the app, it seems like I'll have to go through Apple's in app purchasing API, with auto-renewing subscriptions. That's fine, but the problem is the service users will purchase through the app must also be available on the site. But reading through walkthroughs and the developer guide, it looks like the only way to process transactions is through the app itself, which then can be set up to pass the info to the server. But then I'm imagining this scenario:
User purchases a subscription on the iOS app, goes back and forth between using the site and the iOS app. On the day the user is to be charged, and the days after, they're using just the website, for whatever reason. The server doesn't know they've been billed though, and so features are disabled. In order for the server to find out the user has been billed by Apple, the user has to open the app on their iphone or ipad and sync it with both Apple and my server.
Is there any way for my server to ping Apple directly and get information about the purchases made for my app for a given user? I haven't been able to find anything; the two suggested layouts, with or without server, both use the iOS app as the sole communicator with Apple, aside from having the server verify that information is valid. If it's not possible for the server to get this information first hand, what other possible solution could there be?
Try to not use Apple's built in payment system, and risk getting rejected (with the app duplicating a service that's been available for years online, do we no longer have to use their in app purchasing system anyway?)
If a user is paying through Apple, have the server give them a generous buffer between expected payment dates and when features are disabled (makes the problem happen less frequently but doesn't solve it)
When their account seems expired to the server but it doesn't expect that, email the user a message saying they have to open the app or else their account will not be credited for time purchased (seems like a strange and not really great thing to have to ask a user to do in order to use the service)
This is about all I've been able to think up so far. Anyone have any advice on these solutions, or know of others, or know who I could talk to to try and figure this out?
Yes, you can check on the status of a user's account from your server. There are a few caveats:
First and foremost, Auto-Renewing Subscriptions are reserved for periodicals such as magazines and newspapers. If your app doesn't resemble those, Apple may reject it (as they did mine) and request that you use Non-Renewing Subscriptions (read: Manually-Renewing Subscriptions).
Second: This scenario would require you to store all receipts that you receive on the app, on your server.
Finally: I don't know how your username/password system works, but the user would have to login with the same credentials on your app as they do on your website.
Here's how you check the status of a user's account: Store at least one receipt per user on your server. When you want to check the status, follow Apple's procedure for Verifying App Store Receipts. Send them that one receipt and they'll respond with the latest receipt and the expiration date. Now you'll know, at any given moment, if a person's account is current or not.

Resources