How to detect when SKPaymentTransaction is interrupted? - ios

I am following the steps for testing interrupted in-app purchases, as outline in the Apple docs here under the header Test an Interrupted Purchase. My problem starts on step 6., which states:
"In your code, observe that the payment fails. The payment queue receives an updated transaction in the state SKPaymentTransactionState.failed."
This works for me, but the failed state triggers my UI to show an error alert saying that the payment failed, even though it's just gearing up to show the Terms & Conditions page.
Is there a way to detect when a transaction has actually failed, versus when it's just being put on pause to let some other action take place? I would have expected that the SKPaymentTransactionState state here be deferred instead of failed, as that would make it much easier to handle appropriately.

Related

iOS in-App Purchase failure due to "Strong customer authentication transactions in European Economic Area"

In our iOS app, in-App purchase(auto-renewable subscriptions) was working very well, but from January this year due to some unknown reason in-App purchase getting failed for European Customers mainly from country "Denmark".
For "Strong customer authentication transactions in European Economic Area" , Now users are moved to outside of the App for Strong Authentication as per new European law (ref: https://developer.apple.com/support/psd2/) and then need to enter NemID etc. and when the User is moving back to the App, I'm getting this error
A. “Purchase failed“ . B. The action could not be performed. C.
SKErrorDomain error 0.
One thing to mention as I'm using SwiftyStoreKit POD for in-App purchase, and Transaction observer is already added in AppDelegate by calling SwiftyStoreKit.CompleteTransaction(atomically: true) as suggested by Apple(Transaction Observer).
#sca(Strong Customer Authentication) #EuropeanUnionregulation
If anyone faced same issue, any help would be great.
Here is the transaction failed screenshot after moving back to my app from strong authentication process:
Indeed since January in Europe they're is a new regulation regarding to transaction security. You can find more details there : https://developer.apple.com/support/psd2/
Lot of apps undergo the same problem. Indeed, even if the documentation says that the transaction observer may receive a "failed" or "deferred" state we can expect that failed state will be only for iOS version that doesn't support the "deferred" state (so iOS versions <=7). Indeed the deferred state case is available since iOS 8.0.
However in reality even on iOS 14 the app transaction observer receive a failed state which results in an error pop-up displayed by a lot of app when they do the error management. Even if at the end the purchase succeed once user come back to the app (after validated the transaction with his bank app), it would be FAR better if iOS can return a "deferred" state to avoid an error pop-up while keeping the error management. And it will be more consistent with what it's happening.
In fact once the security process is completed your app's transaction observer must immediately receive a new transaction with a state purchased so in finality everything should be okay but with an unexpected error pop-up displayed for no real reason.
The only choice we have is let the error be displayed awaiting the end of process or discard the error management. I think the first choice is better and if an iOS version update the statement received it will be going fine without any app update.

Handling IAP Fullfilment results for Windows 10 Store

When providing consumable In App Purchases on the Windows 10 Store, there are FullfillmentResults when ReportConsumableFullfillmentAsync is called.
The user of my app has had their IAP fullfilled by the time I get this result. This means they have their Coins/Gems/Potatoes.
But if I receive FulfillmentResult.PurchaseReverted, then what happened? How did the user just revert the purchase? Am I meant to withdraw their Coins/Gems/Potatoes?
What are scenarios behind the other error messages?
Note: I'm working with using Windows.ApplicationModel.Store
But if I receive FulfillmentResult.PurchaseReverted, then what
happened? How did the user just revert the purchase? Am I meant to
withdraw their Coins/Gems/Potatoes?
The value PurchaseReverted means the transaction is canceled on the backend and users get their money back. So you should disable the user's access to the cosumable content (withdraw the Coins/Gems/Potatoes) as necessary.
What are scenarios behind the other error messages?
NothingToFulfill : The transaction id has been fulfilled or is otherwise complete
PurchasePending: The purchase is not complete. At this point it is still possible for the transaction to be reversed due to provider failures and/or risk checks. It means the purchase has not yet cleared and could still be revoked.
ServerError: There was an issue receiving fulfillment status. It might be the problem from the Store.
Succeed: The fulfillment is complete and your Coins/Gems/Potatoes can be offered again.
Here is the documentation about FulfillmentResult Enum

How does data in the IOS In App Purchase flow travel exactly?

I am working on a mobile app using Ionic (with Cordova purchase plugin) but this question is more general. We are using In App Purchases (IAP) and are currently getting an error when we try to finish the consumable purchase. Our current flow is like this:
Get list of our Products from Apple and render on our IAP page
User clicks consumable IAP they want and it fire off a message to StoreKit initiating the purchase
We get a response with a consumable IAP object with the state set to approved.
We initiate the verification procedure with a callback to our own server where we hit apple up to verify the purchase and then log it on our database and send the app a 200 response (not sure if we need to send back the IAP object here with receipt from our server or we just work with the one already inside the app?)
We try finish the purchase where we get an error saying (InAppPurchase[objc]: Cannot finish transaction)
My question is assuming this is the correct flow what does the finish method do? Looking in the source code of the Cordova Purchase Plugin wrapper I can see it sets the state of the object to finished but I am assuming (I couldn't find the code where this happens) it also talks to Apple so that Apple marks the purchase as finished on their side? If we manually set the state to finished the IAP error goes away but the consumable can still not be purchased multiple times which means to me that Apple also need to close it. Is this a correct assumption? Any other tips to getting this to work would also be appreciated.
I'm not sure how cordova handles IAP, but on the apple side, the transaction needs to be finished by calling finishTransaction. It would appear thats the step thats not working.
One thing that might happen due to the delay in going to the server to validate is that the original transaction object has expired, and calling finishTransaction with it does nothing. At this point you might be able to search for your transaction in: [[SKPaymentQueue defaultQueue] transactions]
If you can grab it from there then call finishTransaction then it should work. Not sure how you do this with cordova but I hope this helps.

What should I do if IAP validation fails?

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.
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.
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)
}

Cancelled Touch ID returns unknown error, not cancelled

Long time reader, first time writer. Thanks for a great community.
The app I am working on has In-App Purchase implemented and is working well, however I've run into a strange error case that I don't see an obvious solution to.
The scenario is seen on iPhone 5S with Touch ID enabled for purchases. When the user selects an item to purchase, the Touch ID dialog pops up. If user cancels this specific dialog, the SKPayment delegate receives a SKPaymentTransactionStateFailed event with SKErrorUnknown code - not SKErrorPaymentCancelled as one would expect and what is received when I cancel the password input dialog. As the code cannot categorize this as the user canceling the purchase, the user will see a generic error alert view.
I don't see this being a sandbox issue as purchases themselves works well both testing in sandbox and live.
Thoughts on how to detect that unknown error as a cancellation, anyone?

Resources