I have an iOS app (Objective C) with a subscription (non renewing).
How can I check that it is still active when the user restarts the app?
I have read a lot about this, but does answer not seem clear how to do this correctly.
What I have currently is when the app starts I register the TransactionObserver,
IAPManager* iapManager = [[IAPManager alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver: iapManager];
Then when the user makes a purchase I have,
- (void) paymentQueue: (SKPaymentQueue *)queue updatedTransactions: (NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
[self showTransactionAsInProgress:transaction deferred:NO];
break;
case SKPaymentTransactionStateDeferred:
[self showTransactionAsInProgress:transaction deferred:YES];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
[queue finishTransaction: transaction];
break;
case SKPaymentTransactionStatePurchased:
[self persistPurchase: transaction];
[queue finishTransaction: transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
[queue finishTransaction: transaction];stopBusy];
break;
default:
break;
}
}
}
So this works fine when the user first subscribes.
But I am confused on how you should store/track this purchase. I store in a static variable that the user subscribed to enable app functionality, but when the user restarts the app, how should I check that they have an active subscription?
The code I was using was storing the subscription in either iCloud or NSUserDefaults.
Storing in iCloud did not work at all, and when they restarted the app they lost their subscription. Storing in NSUserDefaults works, but the subscription will eventually expired, and could be refunded or canceled. How to check if it is active? I could store the subscription date and assume the duration and try to check myself, but this seems very wrong.
Also what if the user uninstalls the app and reinstalls, or gets a new phone/etc.
- (void) persistPurchase: (SKPaymentTransaction*) transaction {
#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif
if ([transaction.payment.productIdentifier isEqualToString: SUBSCRIPTION]) {
[storage setBool: true forKey: SUBSCRIPTION];
[IAPManager upgrade];
}
[storage synchronize];
[self unlockPurchase: transaction];
}
For this I think I need to call restoreCompletedTransactions. I think it would make sense to not store the subscription in NSUserDefaults, but instead call restoreCompletedTransactions every time the app starts.
But from what I read Apple seems to want you to have a "Restore Purchases" button that does this? Does it make sense to just call it every time the app starts?
This will call the callback with every payment you ever processed (I think??) for each of these payments how to know if they are still active?
I can get the transaction date but this does not tell me if the payment is expired or not, unless I assume it was not canceled and assume the duration and check the date myself? or does Apple only give you active subscriptions/payments from restoreCompletedTransactions?
Also do I need to call finishTransaction for the restore again?
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
How can I check that it is still active when the user restarts the
app?
https://developer.apple.com/documentation/storekit/in-app_purchase/persisting_a_purchase
For non-renewing subscriptions, use iCloud or your own server to keep
a persistent record.
I think it is better to use a server (so you can receive refund notifications), rather than iCloud (you will not receive a notification).
I'm guessing you mean that you wish to check that:
A non-renewable subscription has not been refunded
The subscription has not expired (period has not completed).
I am assuming you use receipts. This is important, as you will need it to compute the duration of the subscription in case the user chooses to renew a non-renewing subscription before it expires.
So this works fine when the user first subscribes. But I am confused
on how you should store/track this purchase. I store in a static
variable that the user subscribed to enable app functionality, but
when the user restarts the app, how should I check that they have an
active subscription?
In my opinion, the correct way is to check records stored on your server. This is because, refund notifications will be sent by Apple to the URL you configure on the server.
This link explains how your server will be notified of refunds.
https://developer.apple.com/documentation/storekit/in-app_purchase/handling_refund_notifications
As your app will not directly be notified by apple of a refund, you must check with your server if a refund has been issued or not. How you communicate with your server is up to you (polling periodically, push notifications etc). The point is, you should store a record on your server, and communicate that a refund has been issued to your app, so you can decide whether to revoke the feature or not.
Since you need to store a record on your server to check for refunds, you may as well ask your own server if a subscription has expired or not. To do this, you will simply have to store an identifier in your App (NSUserDefaults etc) so that you can ask your server if the period has expired.
This is better than simply storing the end date of the subscription in your app, because if you store the end date in an insecure manner, a user can simply edit the file and keep extending the end date. Apple states in the following link :
Storing a receipt requires more application logic but prevents the
persistent record from being tampered with.
As you need to implement the server to check for refunds, it is trivial to use the server to check if the subscription has expired.
Storing in NSUserDefaults works, but the subscription will eventually
expired, and could be refunded or canceled. How to check if it is
active? I could store the subscription date and assume the duration
and try to check myself, but this seems very wrong.
.....
Yes, in my opinion storing the subscription date on your device is not the best way for several reasons:
The user can change the time on his device to trick your app, and continue using your subscription. When he is done he can reset the time to the correct time.
If you store the subscription expiry date in an insecure manner and his phone is jailbroken, he can just edit the date himself.
The correct way is to query your server. That way he won't be able to edit the time on his device to trick your app into extending his subscription. Neither will he be able to "edit" the subscription date if you've stored it insecurely. The app should simply send an identifier to your server and receive a yes or no response as to whether the subscription is active or not. The following link describes how to do so:
Send a copy of the receipt to your server along with credentials or an
identifier so you can keep track of which receipts belong to a
particular user. For example, let users identify themselves to your
server with a username and password. Don't use the identifierForVendor
property of UIDevice. Different devices have different values for this
property, so you can't use it to identify and restore purchases made
by the same user on a different device.
..
For this I think I need to call restoreCompletedTransactions. I think
it would make sense to not store the subscription in NSUserDefaults,
but instead call restoreCompletedTransactions every time the app
starts.
But from what I read Apple seems to want you to have a "Restore
Purchases" button that does this? Does it make sense to just call it
every time the app starts? This will call the callback with every
payment you ever processed (I think??) for each of these payments how
to know if they are still active?
No, it is not a good idea to call restore purchases every time your app starts. Apple explicitly says not to:
https://developer.apple.com/documentation/storekit/in-app_purchase/restoring_purchased_products
Important Don't automatically restore purchases, especially when your
app is launched. Restoring purchases prompts for the user’s App Store
credentials, which interrupts the flow of your app.
It also says:
However, an app might require an alternative approach under the given
circumstances:...
Your app uses non-renewing subscriptions — Your app is responsible for
the restoration process.
In my opinion, it is unnecessary to restore purchases every time your app starts up - restoring purchases is intended for if you reinstall / install on a new device. It also won't tell you if the subscription has been refunded or active anyway, since Apple will notify only your server URL if a refund has been issued. What you should be doing each time your app starts up, is to query your server.
Apple itself says to keep balances for user accounts updated on your server to identify refund abuse.
Reduce refund abuse and identify repeated refunded purchases by
mapping REFUND notifications to the player accounts on your server.
Monitor and analyze your data to identify suspicious refund activity.
If you offer content across multiple platforms, keep the balances for
user accounts updated on your server. Use App Store Server
notifications to get near real-time status updates for the
transactions that affect your customers.
You should implement the button to "restore purchases", to handle the case that a user uninstalls / reinstalls an app / installs on a different device. When the user restores a purchase, you should be able to compute the identifier you need to talk to your server to check if the subscription is still active or not (You can probably just use the original transaction id)
Your receipt will contain both the original purchase date, as well as product id, and will be updated every time a non-renewing subscription is purchased. You can access this data by making a call to refresh the receipt (SKReceiptRefreshRequest) so any device has the receipt, and you can compute the subscription period.
I can get the transaction date but this does not tell me if the
payment is expired or not, unless I assume it was not canceled and
assume the duration and check the date myself? or does Apple only give
you active subscriptions/payments from restoreCompletedTransactions?
When you implement your subscription behavior, the first time the subscription is initiated you should store the expiry date on your server. As mentioned earlier, Apple will send the refund notification to your server, so you should have your app check with your server if the subscription is refunded or expired. Also remember - the user can change his devices time / date to get around you storing the expiry date on the device. But if you're checking with your own server he cannot, since he cannot tamper with the time on your server.
Also do I need to call finishTransaction for the restore again?
I use swift, and in swift you simply call restoreCompletedTransactions(). It is easy to implement.
See: How to restore in-app purchases correctly?
At this point, you may be wondering, if the receipt contains a record of every single non-renewing subscription purchase, then why do I need a server for checking if a subscription is active ?
Because (for non-renewing subscriptions) refund notifications are only sent to your server. They are not mentioned on the receipt.
You can use the receipt for calculating the end date of a subscription, but it won't tell you if a refund was issued.
You still want to check with your server, so that the user can't simply change the time / date on this device to get around your subscription duration.
Remember:
Every time your user purchases a non-renewing subscription, you should ensure the logic on your server computes the correct end date. For example, if he chooses to purchase a non-renewing subscription before his current subscription ends, you should correctly compute the end date by letting the next subscription start only when the current one ends.
There a lightweight iOS library for In-App Purchases called RMStore.
RMStore supports transaction persistence and provides the reference implementations. You can check it in below link for more details.
https://github.com/robotmedia/RMStore
The documentation explicitly states that to persist a purchase of a non-renewing subscription, "use iCloud or your own server to keep a persistent record". So basically Apple does not provide a built-in way to track in app purchases against users / installs, you have to roll your own.
As you figured out, storing on-device (e.g. using NUserDefaults) is not very useful, as it wouldn't survive a re-install by the same user, on the same or a different device.
If you already have an identity provider backend which can accomodate storing and retrieving the users in-app purchases data, you may want to use that. For each successful purchase, associate the SKU, expiry date and receipt against the user (Your server at this point should check that the receipt is valid to avoid fraud). When your app starts or resume, authenticate with your backend and retrieve the SKUs the user should have access to, and deliver the content in-app.
If you do not have an identity provider backend, you may want to use iCloud key-value store. You can then associate in-app purchases to apple ID, not per-install. Note that using iCloud has some side-effect, for example you may not be able to ever transfer your app to another organisation.
I'm trying to work out the best way to get updated receipt expiration when an iOS auto-renewing subscription renews. I know that what's supposed to happen is that it will be delivered via the SKPaymentQueue transaction observers. However,
This only happens when the app launches. My app is vertical market style app that might run for hours or days, so checking at launch isn't enough.
Transaction observer delivery doesn't seem to be reliable. In testing I've had subscriptions that should have renewed but where no new info was delivered to the transaction observer even after relaunching.
But of course it's also possible to get updated info on demand by revalidating the receipt with Apple. Given the above two issues, I'm thinking I'll need to run a timer that just periodically
Checks if the subscription should renew "soon", i.e. within some TBD period in the future (maybe within the next day).
If so, attempts to get updated info from Apple by revalidating the receipt.
How do other people handle this? The timer approach seems ridiculous but I haven't come up with anything better yet.
First off there seem to be many questions on SO regarding this topic but most seem outdated and not considering APIs that are available in iOS 9.
1) How are purchases restored?
When a user taps the Restore Purchase button for auto-renewable subscriptions on >=iOS 9, should a SKReceiptRefreshRequest or SKPaymentQueue.defaultQueue().restoreCompletedTransactions() be called? Currently I verify the receipt file to determine the active subscription and parse for the latest_receipt_info, is a receipt file also created on the device when restoreCompletedTransactions is called?
2) How can an auto-renewable subscription be verified?
To verify that an auto-renewable subscription on >= iOS 9 is still active and not cancelled I would call SKReceiptRefreshRequest every time the user launches the app. But the docs say:
Include some mechanism in your app to let the user restore their
purchases, such as a Restore Purchases button. Restoring purchases
prompts for the user’s App Store credentials, which interrupts the
flow of your app: because of this, don’t automatically restore
purchases, especially not every time your app is launched.
Should I call it only once a day or is there another way to verify the subscription?
Update:
For 2) just found this here:
Perform receipt validation immediately after your app is launched,
before displaying any user interface or spawning any child processes.
Implement this check in the main function, before the
NSApplicationMain function is called. For additional security, you may
repeat this check periodically while your application is running.
Apparently the receipt can be validated without user interaction. It is even recommended to do it on one's own server for security purpose. So as long as the receipt is validated by the App Store server there is no need for a receipt refresh request.
I have an app using the non-renewing subscription model. It works great when using in-app purchase test accounts. However, on Apple's production servers, sometimes the user gets taken away from my app to the app store for one reason or another (sometimes it's because of updated billing info, sometimes it's to answer security questions). This is after the payment has been added to the queue and in Apple's own payment flow. Once the user hits the alert view option to go into the app store, my transaction Observer gets a transaction with the SKPaymentTransactionStateFailed state. That's fine. However, after the user updates their billing info or confirms their security question, they're asked (still outside the app) if they still want to purchase the in-app purchase. When that goes through, they are taken back to my app (which has closed itself), and nothing comes back from the transaction observer. The queue only gets updated with the purchased product when the list of products is retrieved. The observer registers for notifications before
So my questions are:
How do I handle purchases made outside my app (in the app store app)?
If there are purchases made before the app opened (but not completed), at what point does the queue get updated? I know that I should have the observer going at all times, but I want to avoid having the user purchase the item twice, not knowing they had already purchased it.
Through some trial and error, I seemed to have solved my problem
It turns our that I wasn't initializing my transaction observer for SKPaymentQueue soon enough. It needs to be initialized and added as a transaction observer in the application:didFinishLaunchingWithOptions: method, and no later. As for when the transaction comes in, the paymentQueue:updatedTransactions method of your transaction observer will get called with a purchased transaction the next time your app is active. It's important to note that your app may or may not close once for one reason or another when you're taken to the App Store, and if it does end up closing, the method will get called the next time the app opens.
I have a few questions that will help me understand things better if answered:
Is there a way to differentiate between a fresh subscription and the renewal of a previously-purchased one?
Does the subscription go through the renewal process immediately after the expires_date is hit? It seems that sometimes (in the Sandbox, at least) my subscriptions will renew 30-60 seconds before the expires_date.
Does the renewal always happen at a consistent time after the expires_date is hit? For instance, if I launch my app and expires_date has been passed, when will the renewal occur (assuming the user didn't cancel)? Or rather, when will my app know that the renewal has occurred on Apple's end?
Scenario: app is launched and expires_date has passed for one of my subscriptions. Should I send a receipt to Apple to see if that subscription was renewed, or should I wait a couple seconds to see if the renewal process occurs?
Thanks!
I will attempt to answer my own question:
To do this, I store expires_date of the subscription in NSUserDefaults once the initial purchase is completed. When the subscription expires, I remove the object from NSUserDefaults. This allows me to determine if any subsequent subscriptions are a renewal (expires_date exists in NSUserDefaults - update with new expires_date upon renewal completion) or a fresh purchase (expires_date does not exist, either because it was removed upon a previous subscription expiration or the product has never been purchased).
This question is irrelevant - what I do is compare expires_date stored in NSUserDefaults to current_date every time the app enters the foreground. If current_date has passed expires_date, I call my server to verify the receipt with Apple. Apple returns the status of the subscription to my server (0 means the subscription is valid, 21006 means it has expired, and all others are trivial as far as my app is concerned) and the server forwards it to my app. In this manner, it doesn't matter how long it takes my app to be alerted of a renewal (aka the subscription has already technically renewed through Apple but my app doesn't yet know) because I know for certain whether the subscription has expired or not.
In the sandbox, renewals seem to be very untrustworthy. Sometimes my subscriptions will renew five or six times (the standard, according to Apple), sometimes once, and sometimes not at all. For the record, the 7 day subscriptions don't renew a lot more often than the 30 day subscriptions. It would be nice to have the same reliability in the sandbox as we do in the live servers so I can write code accordingly (and stress less), but I digress. The only sure-fire and consistent way I've discovered to make the subscriptions alert my app that they've renewed is when expires_date has passed && I force close/relaunch my app. Sometimes toggling between background and foreground states works, but in my experience this is much less reliable - I have a feeling that this would work [better] in the live servers though.
What I do is check with my server (which in turn checks with Apple) when I notice that expires_date has passed. So it doesn't matter if the renew occurs before/after/never, because my server tells me for certain the status of the subscription.
I hope this information helps others who have the same questions I did!