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 generally understand receipt validation on In App Purchases. Most libraries make this as simple as calling a method.
I'm not so clear on how subscriptions work though -- specifically, how can I detect if the subscription is cancelled?
Subscriptions are cancelled on Apple's iTunes interface only. How is my server supposed to know that a subscription was cancelled?
From the Apple docs:
The user can also cancel their subscription by disabling auto-renew and intentionally letting their subscription lapse. This action triggers the App Store to send your server a status update notification of type DID_CHANGE_RENEWAL_STATUS. Your server can parse the auto_renew_status and the auto_renew_status_change_date to determine the current renewal status of the subscription.
https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/handling_subscriptions_billing#3221914
The answer from Jacob is correct for the case that the user disables the auto-renewal of his subscription. If the user requested a refund from Apple and they cancel the subscription for him. Your server will receive a CANCEL notification and the receipt will contain a cancellation_date field.
You need to handle both of those cases, because when the user cancels (deactives the auto-renewal) his subscription, it is still valid (till it expires). When Apple customer support cancels the subscription, then it is from this point on invalid.
Note: the CANCEL notification is also triggered when a user up- or downgrades to a different subscription of the same subscription group. See this answer for more details on that.
I would like to implement In-App purchase of consumable products for an iOS app I'm developing. I want to validate the receipt on the server side as indicated in the documentation:
App start transaction asking to the Apple Store
Apple Store return receipt to the app
The app send the receipt to a trusted server
Trusted server sends receipt (in base 64 format) to the Apple Store through an HTTP POST
Apple Store replies to the HTTP POST with the validation
If the validation response is ok, the purchase can be enabled
I'm concerning about the following situation: a user buys a product and the Apple Store sends back the receipt to the App (steps 1,2). Then, when the app tries to send the receipt to the trusted server (step 3) the connection goes down. In this case receipt validation has to be retried in a second moment, however, I have the following questions:
a) in this case, has the user already paid for the product?
b) Since receipts for consumable products compare only at the time of the purchase, how should the validation retry be managed? Should I save the receipt locally in order to retransmit it in the future?
Can I simply does not mark the transaction as "finished"? From what I understand reading the documentation, in this case StoreKit should call the transaction queue observer again (passing again the receipt?) until the transaction is marked as "finished", is it correct?
Thank you in advance
a) in this case, has the user already paid for the product?
The PaymentTransaction observer will tell you what state the user's purchase is .Purchased, .Purchasing, .Failed . e.t.c.
So we can assume from your question that the user's state is .Purchased and You would have received a payment.
Now since they have a receipt and it’s hopefully from a real purchase they will be waiting for you to validate on your server and unlock the purchase because they have received a receipt containing data about their purchase.
b) Since receipts for consumable products compare only at the time of the purchase, how should the validation retry be managed? Should I save the receipt locally in order to retransmit it in the future? Can I simply does not mark the transaction as "finished"? From what I understand reading the documentation, in this case StoreKit should call the transaction queue observer again (passing again the receipt?) until the transaction is marked as "finished", is it correct?
You don’t need to store it locally, with the information stored about the purchase in the receipt. This is kept until your paymentObserver finishes the transaction or the receipt is refreshed/ updated again.
Your app will keep trying to validate with your server until it receives a response about the receipt usually when the user launches the app again,
At that point if we assume it's valid, you enable your product and then after this finish the transaction on the SKPaymentQueue.
In the documentation it says’s
“The in-app purchase receipt for a consumable product is added to the receipt when the purchase is made. It is kept in the receipt until your app finishes that transaction. After that point, it is removed from the receipt the next time the receipt is updated “
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1
Also:
“For example, consider the case of a user buying something in your app right before going into a tunnel. Your app isn’t able to deliver the purchased content because there’s no network connection. The next time your app is launched, StoreKit calls your transaction queue observer again and delivers the purchased content at that time. Similarly, if your app fails to mark a transaction as finished, StoreKit calls the observer every time your app is launched until the transaction is properly finished.”
I hope this helps and I hope it answered your questions.
I'm adding In-App purchase to my app.
I have some doubts in this.
How to handle SKPaymentTransactionStateDeferred?
Do we need to implement ourselves at this state or Apple will handle this?
If we should implement means how to handle here?
How to test with sandbox tester account?
Anybody tell me clearly. Thanks in advance.
According to Apple StoreKit document, deferred state is:
The transaction is in the queue, but its final status is pending external action such as Ask to Buy. Update your UI to show the deferred state, and wait for another callback that indicates the final status.
We get transaction deferred state, if user is part of Apple family sharing & family admin enabled ASK TO BUY.
As child user try to purchase in-app product, a request is sent to parent user for approval. Parent user has 24 hours to approve or cancel their child's purchase after the Ask to Buy process has begun. If the parent fails to respond within the 24 hours, the Ask to Buy request is deleted from iTunes Store servers and your app's observer does not receive any notifications.
You should update your UI to reflect this deferred state. Avoid blocking your UI or gameplay while waiting for the transaction to be updated.
In Sandbox environment, we can test deferred state by using SKMutablePayment like:-
let product = SKMutablePayment(product: productDetails)
product.simulatesAskToBuyInSandbox = true //Enable to test deferred state.
SKPaymentQueue.defaultQueue().addPayment(product)
For Information:
iOS 8 introduces Ask to Buy, which lets parents approve any purchases initiated by children, including apps or in-app purchases on the App Store. When a child requests to make a purchase, Ask to Buy will indicate that the app is awaiting the parent’s approval for this purchase by sending the Deferred state.
I have a couple questions regarding the process of detecting that a subscription has been auto-renewed.
Currently I have my sandbox test environment all set up, I am able to purchase a 1 month subscription and all of that works nice and dandy.
My questions are as follows:
Do I need to verify the initial receipt (or really any subsequent receipts) of the in app purchases? If so, for what reasons?
Does my app get notified via a paymentQueue:updatedTransactions: call when the auto-renew has happened? Both in the sandbox environment and in the production environment.
So far, I have not seen the StoreKit notify me of a renewed subscription (in the sandbox environment) but I have also read that it is finicky and might not.
Any general tips with Auto-Renewable subscriptions that I should take into account?
Thanks for your help and time!
1. Regarding Receipt Validation
Receipt validation is your means of performing extra security checks before you unlock the users content. Think of a receipt as a lotto ticket. A client comes to you with the "Winning Ticket". Depending on how valuable you consider the reward to your Lottery you can either give said individual their winnings, or you can contact Apple and check whether the user actually purchased this ticket through them.
The client could have got this lotto ticket through some malicious means be it a jail broken device etc and so its really up to you to decide whether you want to verify the receipt or not.
2. Regarding Renewal Notification
The official documentation states the following:
After a subscription is successfully renewed, Store Kit adds a
transaction for the renewal to the transaction queue. Your app checks
the transaction queue on launch and handles the renewal the same way
as any other transaction. Note that if your app is already running
when the subscription renews, the transaction observer is not called;
your app finds out about the renewal the next time it’s launched.
So you should definitely be getting receipt renewal notifications in your app. Don't forget that you should always add yourself as the observer of StoreKit's paymentQueue in order for this to work, something like this in your app delegates didFinishLaunching would work:
//where YourObserver is an object that is alive for the apps lifecycle
[[SKPaymentQueue defaultQueue] addTransactionObserver:YourObserver];