I mean, should my steps be?
1) Get SKPaymentTransactionStatePurchased
2) Remove it from SKPaymentQueue and provide the content by [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
3) Validate the receipt and then, if it's invalid, block the content i've just provided
Or should i change 2nd step to 3rd instead?
1) Get SKPaymentTransactionStatePurchased
2) Validate the receipt and then, if it's invalid, dont't provide content
3) Remove it from SKPaymentQueue anyway [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
In first scenario user can turn the internet off right after the purchase, so i won't be able to validate the receipt. But in the second, there may occure some problems with internet between step 1 and 2, so i won't finish the transaction and won't provide the content, that would be a bad user experience.
So what way did you choose for your app and why?
My Choice
I've choosen the second scenario, since choosing the first one makes my app be easily cracked by iAP Cracker.
Scenario 2.
If the internet blows up, you won't get to -finishTransaction.
But that's cool, cause you can retry (NSTimer) and your app will be given the unfinished transaction on start up.
And that's exactly how StoreKit is designed to work (even though it's not obvious from reading the docs).
StoreKit comes with transactions, for a good reason. The user can simply quit the app right after purchasing, and you still have to recover from that.
And that's why Apple recommends to set your transaction observer as soon as possible in the app lifecycle.
Don't ever finish the transaction before providing the content, you'll have to implement your own transaction system on top of StoreKit, and you don't want to do that, believe me (I've seen it done, it was a disaster).
Edit: in all honesty, the eventually of a user turning the internet off after purchase and before you validate is ridiculously low. The guy was on they internet a second ago, nobody just goes out to cut the internet in the middle of a purchase.
But it's possible that the user gets interrupted at that the moment and send your app to the background.
Your app can then get killed for whatever reason iOS deems appropriate.
And when you're apps starts again, well your app won't remember having a purchase to start with, and store kit won't be of big help, since you've already finished the transaction.
This is what I do:
App sends a request for the downloadable content, with receipt attached as an argument.
The server validates the receipt with iTunes, and if it is valid, it returns the purchased content as the response body to the original request.
This way, even if the app binary is hacked/modified, the content is downloaded only against a valid receipt (because no modifications made on the client app can possibly interfere between my sever and Apple’s).
I'd verify first. It takes 2-3 seconds. You can use ReceiptKit https://github.com/maciekish/ReceiptKit for this purpose.
Related
Simple question: How to ensure that a communication between an iOS application and a back-end succeeded?
Detail: For most of the API call in our mobile application, it can be "OK" if the communication between the application and the server fails (for network reason or other), we can just display a message to the user with a retry button to reload his news or posts feed for instance.
However, on some occasion, we want to be really confident that the communication between the application and the backend will never fail and the data of this communication will potentially be "lost".
For this, let's take as an example, the In-App Purchases (IAP):
In my app, an IAP information looks like this:
struct InAppPurchase{
// The id of the purchase
let transactionId: String
// The id of the user who purchased
let userId: String
// The IAP product involved in the purchase (10 or 30 in-app coins for example)
let productId: String
}
Everytime a user makes a purchase, I want to send this information to the back-end in order to save a history of the purchases made in my application.
I don't want to miss any of those communications between the application and the server (if a user send me a feedback saying that he paid for some item in my app I want to make sure by checking the history that he really made the purchase)
If the purchase succeeds but the call to send the information to the server fail (Network error or else), this information can't be lost and must be sent as soon as possible.
I was thinking of this approach:
Creating a pending purchase array with UserDefault or Keychain like so
var pendingPurchases = [InAppPurchase]()
When the user purchases an item, this item is store in the pendingPurchases array.
The application communicates with the back-end by sending this array of IAP
If I receive the answer code 200 SUCCESS from the back-end, I can purge the pendingPurchases array.
If I receive another code for an ERROR, I try to send this call another time (limited to 3 in a row, if it is because of bad network, it maybe not needed to try sending the request indefinitely at the moment) until receiving 200 SUCCESS
Each time the application is open or switch from background to foreground, I check if the pendingPurchases is empty or not. If not empty, I send the request to the server.
What do you think of this approach? How do you manage this kind of data that you don't want to be "lost"?
What you want to do is wait to "finish the transaction" until you've received a 200 response from your server. If you don't finish the transaction, Apple will save it in a payment queue and keep sending it to you every time the app launches. This will give you an opportunity to handle any retries to your server if needed.
Finishing a transaction tells StoreKit that you’ve completed
everything needed for the purchase. Unfinished transactions remain in
the queue until they’re finished, and the transaction queue observer
is called every time your app is launched so your app can finish the
transactions. Your app needs to finish every transaction, regardless
of whether the transaction succeeded or failed.
Complete all of the following actions before you finish the
transaction:
Persist the purchase.
Download associated content.
Update your app’s UI to let the user access the product.
To finish a transaction, call the finishTransaction: method on the payment queue.
SKPaymentTransaction *transaction = <# The current payment #>;
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
You can read more about finishing transactions in Apple's doc below:
https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html
I know there is a lot of info but I sill can't decide what is the proper way of restoring IAP subscriptions between devices. Obviously, after digging the net, there is no really good documentation about this and people have to deal with it mainly by trials and errors.
I'm on the point to finalyze my first app with IAP subscriptions and have some doubts before I'm ready to upload it for approval. It seems like the behaviour during testing is quite different than the production one and I just don't have the production experience yet to be sure what is going on exactly.
The way I have implemented the process is the following:
The user purchase the item for first time so he/she makes a payment and the transaction is added to the queue.
When the app receives the response it calls finishTransaction and validates the receipt (it can be local or remote but it's a different question)
When the renewal comes the queue notifies the app and the app again calls finishTransaction and makes a new validation.
This works fine on one device and there are no problems.
However when I want to restore those purchases on another device the renewal notification might not be sent as the first device that receives the notification calls finishTransaction and all other devices just miss it.
Then I have two options - either to refresh the receipt by using SKReceiptRefreshRequest or restoring the purchases by using restoreCompletedTransactions. I make one of these at a certain time based on the previous purchase expiration date just to check if the user didn't stop the subscription. (Subscription cancelation is another topic - similar but for now I don't mind it)
The refresh process looks like the one I prefer but, at least in Sandbox, it always asks the user to log in (twice and I don't know if it's normal, plus that in sandbox I couldn't find a way to implement the Touch ID). Then the app receives a new refreshed receipt and goes through the validation process which is great.
The second method requests silently a new receipt (which is a plus over the previous) but as far as I can understand it always doubles the amount of transactions in the new receipt as it make a new transaction for every completed one:
The payment queue will deliver a new transaction for each previously
completed transaction that can be restored. Each transaction includes
a copy of the original transaction.
So... my questions are:
Is the production behaviour the same like the sandbox for the SKReceiptRefreshRequest - should the user sign in every time the app wants to refresh the receipt and does integrating Touch ID requires some other implementation?
If I have, let's say Week Subscriptions, it would be quite annoying to the user.
If I use restoreCompletedTransactions will the receipt at some point becomes full of useless transactions?
If the user had, again let's say one year of Week Subscriptions and restores them on a few other devices, they all will fill up the final receipt quite a lot.
Overall which method is preferable and what are the Pros and Cons for each of them.
Thanks a lot!
I am writing an in-app purchase capability for ma iOS app. I'm selling some simple consumables (no downloads). I based my code on this tutorial/topic
How do you add an in-app purchase to an iOS application?
However I noticed it conflicts with official apple docs.
In this code when a Transaction fails for whatever reason, this code
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
is called. However on apple pages
https://developer.apple.com/library/ios/documentation/StoreKit/Reference/SKPaymentQueue_Class/Reference/Reference.html#//apple_ref/occ/instm/SKPaymentQueue/finishTransaction:
it is written:
"Your application should call finishTransaction: only after it has successfully processed the transaction and unlocked the functionality purchased by the user."
So which approach is correct? Should I call finish on failed transactions?
According to the Apple's In-App Purchase Programming Guide
Your app needs to finish every transaction, regardles of whether the
transaction succeeded or failed.
So regardless of the transaction result, you always finish the transaction to remove it from the payment queue and then handle the state (successful or failed) in your code in order to provide the user with the appropriate information
This is an old thread, but I find the answer incomplete, which can be misleading, and Apple docs are a bit vague on the subject. The problem is how we define a 'failed' transaction.
We can identify many states:
SKPaymentTransactionStateFailed
You get notified Apple noticed something went wrong (i.e. user cancelled, credit card declined, the kid is not authorized by its father to make the purchase, etc) and informs you of the event.
You must call finishTransaction
SKPaymentTransactionStatePurchased
We can split this one in multiples ones:
Receipt validated and content delivered. You must call finishTransaction
Receipt is invalid. There is no agreement here, as Apple docs are vague. It's 95.0% certain this is a hacked/jailbreak purchase. You could call finishTransaction to remove it from the queue and thus lighten the load on your servers (otherwise the queue will keep asking your client to revalidate). However the other 5% is that Apple servers were down and somehow they returned invalid (or you interpreted as invalid) when actually Apple servers just were temporarily down.
This applies specially to consumable IAPs. Because once you call finishTransactions, the consumable IAP is gone from the receipt
For non-consumable IAPs, you can safely always call finishTransaction because worst case scenario the user can tap the button to restore transactions (which you have to implement as per App Store Guidelines)
Receipt is valid, but could not be verified for temporary reasons (e.g. your servers are down, Apple servers down, internet connection went down). You must not call finishTransaction. The payment will remain on the payment queue and it will eventually ask you to try again (note that the device that attempts to try again may be a different iPhone, if the user is logged on from multiple Apple devices). Hopefully this time the servers will be back up.
This is a must for consumable IAPs. You can't call finishTransactions or else you'll get legit angry customers
Once again, for non-consumable IAPs, you can safely always call finishTransaction and rely on Restore Transactions instead.
If you don't perform validation nor have server-side inventory, none of this really matters to you, since there are not many points of failure.
Without validation nor server-side inventory, after receiving SKPaymentTransactionStatePurchased you update your database on the phone's storage with the additional/unlocked content, and after that you just call finishTransactions.
SKPaymentTransactionStateRestored
Same flow as SKPaymentTransactionStatePurchased.
SKPaymentTransactionStatePurchasing
You must not call finishTransaction
SKPaymentTransactionStateDeferred
You must not call finishTransaction
The standard in app purchase flow when there is hosted content is:
do the transaction
when the transaction enters in "purchased" state, retrieve the SKDownload and start downloading
when downloading is done, then we can declare the transaction as "finished"
This is fine.
Imagine we want to restore all purchased contents, this is because we delete and re-installed our app or simply because we installed it in another device. Now, and this is confirmed by WWDC 2012 session on the subject, when you restore all purchases you will get the full list of contents to restore but probably you don't want to download all of them. In such case you should offer the user the choice of which content to download and then queue the SKDownload of the chosen product; once the the download terminates and the content is successfully installed, you can declare the transaction finished.
What the session video didn't say instead is what you should do with the rejected or delayed downloads. As far as I know there is no way to save the SKDownload somewhere and retrieve it later and there is no API to ask iTunes a thing like this "give me the SKDownload of this already purchased product".
So according to me there are two options to walk-around this case:
you will ask to restore the non-purchased stuff again; this is not elegant but it works
you start the procedure and then you pause it; this will be resumed later. But this solution has several drawbacks: you have no control of how the system manages the SKDownload and it could cancel if after a certain amount of time. I never tried this solution.
Did someone else faced this issue and found a better solution? does it make sense to open an enhancement request with Apple?
I always do it is as you suggested, and just "rebuy" the purchased items so it goes through the normal purchase flow. You can't mark the transaction as finished until your done with the download. So if you pause it and the app dies when your app is launched again the queue will go off and try to finish the purchase which would start your download logic again. That seems more annoying as a user constantly having this thing popup when I open the app to finish the purchase.
The SKDownload docs say;
Your app never directly creates a SKDownload object. Instead, after a payment is processed, your app reads the transaction object’s downloads property to retrieve an array of SKDownload objects associated with the transaction.
I use server side receipt verification.
When client's
- (void)paymentQueue:(SKPaymentQueue *)queue
updatedTransactions:(NSArray *)transactions
is called, and transactionState is SKPaymentTransactionStatePurchased,
client sends the receipt to our server,
and our server verifies it.
When the server side receipt verification succeeds,
client obviously calls finishTransaction, no problem.
When the server side receipt verification failed,
because apple temporary returned non json, or client sent invalid receipt, or something,
server returns that information to client.
Next, what should our client do?
Should we call finishTransaction?
This leads to invalid transactions living forever in the queue?
like said in this question: iPhone in-app purchase: receipt verification
But if you find out that a receipt is invalid, you should finish the associated transaction. If not, you may have extra-transactions living forever in the transaction queue. That means that each time your app runs, paymentQueue:updatedTransaction: will be called once per transaction...
But if we do finishTransaction, our precious user is charged by this receipt (which we failed to verify), right?
Or does the verify-failed-transaction expires in some period?
Is this documented somewhere in apple's document?
I couldn't find any in http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html
Yes, you have to finishTransaction.
It's up to you if you give the user what they want then. In some cases it's better to give the user what they want, even if the receipt is invalid or fake (using jailbreak). Especially when it costs you nothing.
I finish the transaction to clear it from the queue, but don't provide the extra content if the validation fails. If it is an invalid receipt then they were not charged by Apple. If it turns out to be something else, such as Apple's verification server being temporarily down, then they will have been charged and when they attempt to restore purchases (or add it again) they won't get charged again, and your app will get another shot at verifying the receipt.
If the verification fails for a technical reason such as Apple's server being down it will be awkward, but there is no other way that I can see to prevent someone from stealing your content. The good news is that you can let the user know in a popup if Apple's server is down and that they should try again later and most importantly that they won't be charged again.