Reputation: 8012
In iOS, if the user attempts to purchase IAP, and my server validation of the receipt fails, what is the proper behavior? When testing in sandbox, I just get a ton of pop ups asking for my password. If I call finishTransaction, it stops asking for my password, but I believe this can lead to the user getting charged without receiving a product.
Upvotes: 4
Views: 2156
Reputation: 23
Pop Up A UIAlertView. See Code Below...
self.alert("Purchase Failed", Message: "Please Make Sure You are connected to the internet and try agian")
func alert(title:String, Message:String){
let actionSheet:UIAlertController = UIAlertController(title: "\(title)", message: "\(Message)", preferredStyle: UIAlertControllerStyle.Alert)
// for alert add .Alert instead of .Action Sheet
// start copy
let firstAlertAction:UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler:
{
(alertAction:UIAlertAction!) in
// action when pressed
})
actionSheet.addAction(firstAlertAction)
// end copy
self.presentViewController(actionSheet, animated: true, completion: nil)
}
Upvotes: 0
Reputation: 7584
I think that error handling when doing in-app purchases is one of the lesser talked about difficulties of doing in-app purchases. There are several questions here. For example: 1) If you are doing validation of the purchase receipt on the device (i.e., the >= iOS7 style decryption style validation), and that validation fails, what should you do? 2) If you are doing validation with a web-based server, and that fails, what should you do? 3) If you are storing receipts to your own web-based server, and that fails, what should you do? I'll put some ideas here from my own app. It would be great to see what others are doing about this kind of error handling.
1) If you are doing validation of the purchase receipt on the device (i.e., >= iOS7 decryption style validation), and that validation fails, what should you do?
Apple recommends that you do a receipt refresh (SKReceiptRefreshRequest) if your initial validation, on the device, fails. But what if the validation after the refresh fails? You could interpret that situation as there being something seriously wrong (e.g., the user has somehow hacked into your app to intentionally give you a bad receipt), tell the user the purchase has failed with a receipt validation failure, and finish the transaction (SKPaymentQueue finishTransaction:) , failing permanently. However, I don’t like to be so final. Perhaps it’s my confidence, but something could have gone wrong with my programming. I like to give the user more chances. So, my solution is to: 1) Tell the user that validation has failed, 2) put the SKPaymentTransaction into a “holding” state, and 3) call finishTransaction. This idea of a holding state is my invention, and nothing suggested by (or supported by) Apple. I have made a mutable subclass of SKPaymentTransaction, and have a queue of those objects (call them SPASMutablePaymentTransaction), which I store (using NSCoding) into NSUserDefaults. Nearly always, this queue is empty, but if I get a validation failure like I’m talking about here, I create a SPASMutablePaymentTransaction object, copy over the SKPaymentTransaction info (including the receipt, e.g., from the bundle), and save that transaction into my holding state queue. I make a (normally hidden) part of my UI visible, which can allow the user to retry holding state transactions.
Complicated? Yes, a little. However, since we’re dealing with the user giving you money, I am trying to be robust. It seems to work well so far in testing. I don’t have any feedback from users on this yet (or analytics), but it is deployed to the app store.
2) If you are doing validation with a web-based server, and that fails, what should you do?
For me, this is a similar case to 1), above. You have tried to do a validation of the receipt and it failed. You could first try to refresh the receipt (see above), and then re-try your server validation. In my app, since I always do on-device receipt validation first, so it doesn’t make sense for me to refresh the receipt again (since I would have done that if the on-device receipt validation failed). So, I again put the receipt into a “holding” state (as above), and allow the user to retry the holding state transactions at their discretion.
3) If you are storing receipts to your own web-based server, and that fails, what should you do?
(a) Presumably this should definitely not be a case where you permanently fail to give the user their purchase. This is a failure of your services. In my case this can happen, for example, if I get a network error in communicating with my server. I do something a little different here. I don’t immediately give the user access to their purchase, and I don’t put the transaction into a “holding” state. Instead, I set up a timer to retry the process of storing the receipt to my server. It retries again in a couple of minutes. I tell the user what I’m doing. I store the receipt data into NSUserDefaults (again using a SPASMutablePaymentTransaction object), so will persist in this retrying even if the app crashes/is terminated. When I do succeed in saving the receipt to my server, I give the user access to the purchase.
(b) In this case, I do call finishTransaction: and I don't yet give the user access to the purchase. I could avoid some these details by not calling finishTransaction:, and just let my transaction observer deal with this process again (e.g., when the app next starts), but the user would probably have to enter their Apple Id again. My figuring is that this is my problem (i.e., I failed to save the receipt info to my server), so I'm trying to handle it under the covers.
Upvotes: 5