Re purchasing on auto renewable subscription Error in ios - in-app-purchase

I am re purchasing the subscription it's giving me this popup "You are currently subscribed" and then in UpdateTransaction observer I am getting, transaction state is Failed.
http://prntscr.com/n7tiq1
https://prnt.sc/n7ti45

That is the expected behavior - you can't repurchase a subscription that you already own. You'll get a failure reason of SKErrorCode = 2 which is payment cancelled: https://developer.apple.com/documentation/storekit/skerrorcode
Hopefully Apple can improve this in the future to just automatically restore the subscription or something and send a success response.

Related

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 to detect if user cancel auto-renewable subscriptions during the free trial period?

Apple's document does not seem to mention this. So if user cancels an auto-renewable subscription purchased during the free trial period, how do we detect?
In appstore receipt JSON there is this field: is_trial_period. But I think this is for indication of whether the free trial period is over.
The only thing I can think of is this NSBundle.mainBundle().appStoreReceiptURL?.path and if this is nil than that will indicate the user has not subscribed or cancel within the free trial period. But for sandbox testing, there is no way to do a cancel during free trial period to test this scenario.
Does anyone have a solid knowledge of this?
In order to support auto-renewing subscriptions, your app needs to periodically submit the app receipt obtained from NSBundle.mainBundle().appStoreReceiptURL?.path to Apple's receipt validation service.
Contained in the JSON response from this service is the latest_receipt_info array.
By examining this array you will be able to determine the currently active subscription(s).
If a user turns off auto-renewal before the expiration of the free trial then latest_receipt_info won't contain a purchase with an expires_date after the free trial end date
This means, that strictly speaking, you can't "detect a cancellation" as there is no "cancellation"; there just isn't a renewal at the end of the free trial period.
This is possible with the web hook feature in iTunes Connect.
When you set a path for the Subscription Status URL of your app the App Store server will call that URL when the subscription status changes.
Currently the following key events will trigger the call:
INITIAL_BUY Initial purchase of the subscription.
CANCEL Subscription was canceled by Apple customer support.
RENEWAL Automatic renewal was successful for an expired subscription.
INTERACTIVE_RENEWAL Customer renewed a subscription interactively after it lapsed, either by using your app’s interface or on the App
Store in account settings.
DID_CHANGE_RENEWAL_PREFERENCE Customer changed the plan that takes affect at the next subscription renewal.
DID_CHANGE_RENEWAL_STATUS Subscription has expired and customer resubscribed to the same or another plan.
More can be found here and here.
The correct way to do this is to check the auto-renew preference on the receipt. If you want to get notified of this even if the user doesn't open your app (or deletes it) you'll need to store and refresh the receipt on your server. There are 3 fields that you should be concerned with to detect a cancellation.
Expiration Date (lets you know if subscription is still active)
Auto-renew status (lets you know if the user "cancelled")
Cancellation Date (tells you why subscription cancelled by support)
You should check for receipts that are not expired, not cancelled and have an auto-renew status of "0". These will be users that are in a free trial, but have auto-renew turned off. Unfortunately, the App Store Connect Subscription Status Notifications don't report this to you.
Here's a good blog post that goes over a little more of the the details: iOS Subscriptions are Hard
Here is how I got the json data about receipt:
let sharedSecret = "..." //you can find this string on AppStoreConnect-IAP
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
AF.request("https://sandbox.itunes.apple.com/verifyReceipt", method: .post, parameters: ["receipt-data":receiptString, "password": sharedSecret], encoding: JSONEncoding.default, headers: nil)
.responseJSON(completionHandler: { (response) -> Void in
print(response)
})
}
catch {
print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
If you want a production URL, not the Sandbox, change the url to https://buy.itunes.apple.com/verifyReceipt
In the response you will find all data you need.
Note that you need to include this in the Podfile : pod 'Alamofire', '~> 5.2'

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.

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?

StoreKit's SKErrorPaymentCancelled still charge user's itunes account

According to https://developer.apple.com/library/ios/documentation/StoreKit/Reference/StoreKitTypes/Reference/reference.html, SKErrorPaymentCancelled means that user cancelled a payment request. However, in our production app, we are getting a failed transaction with error code = SKErrorPaymentCancelled but there was a charge to the user's account (user later sent it a receipt as a proof). Looking at the error, it said "cannot connect to itunes store". Are we doing something wrong? Why is the user getting charged even though the error is SKErrorPaymentCancelled?
Note that we also listen to transaction when the app starts to capture all missed transaction that comes our way.

Resources