On an app that was using the old API for In-App Purchases (StoreKit 1). The app is already published on the App Store. The purchase is non-consumable.
While trying to migrate to StoreKit 2, I'm unable to restore purchases.
Specifically displaying and purchasing products works as expected, but when deleting and reinstalling the app, and then trying to restore purchases I can't do it.
I'm trying to restore them using the new APIs but it doesn't seem to be working.
What I have tried so far:
I'm listening for transaction updates during the whole lifetime of the app, with:
Task.detached {
for await result in Transaction.updates {
if case let .verified(safe) = result {
}
}
}
I have a button that calls this method, but other than prompting to log in again with the Apple ID it doesn't seem to have any effect at all:
try? await AppStore.sync()
This doesn't return any item
for await result in Transaction.currentEntitlements {
if case let .verified(transaction) = result {
}
}
This doesn't return any item
for await result in Transaction.all {
if case let .verified(transaction) = result {
}
}
As mentioned before I'm trying this after purchasing the item and deleting the app. So I'm sure it should be able to restore the purchase.
Am trying this both with a Configuration.storekit file on the simulator, and without it on a real device, in the Sandbox Environment.
Has anyone being able to restore purchases using StoreKit 2?
PD: I already filed a feedback report on Feedback Assistant, but so far the only thing that they have replied is:
Because StoreKit Testing in Xcode is a local environment, and the data is tied to the app, when you delete the app you're also deleting all the transaction data for that app in the Xcode environment. The code snippets provided are correct usage of the API.
So yes, using a Configuration.storekit file won't work on restoring purchases, but if I can't restore them on the Sandbox Environment I'm afraid that this won't work once released, leaving my users totally unable to restore what they have already purchased.
After releasing to the App Store and finally trying the app directly in production I can confirm that it works, but I have to say that it is not ideal to be unable to test this on the sandbox environment.
Also I feel the documentation was not clear enough, at least not for me.
Probably it is clear for other folks, but I was expecting the purchases to be restored automatically and get them on for await result in Transaction.updates, but this didn't work.
What did work was to check Transaction.currentEntitlements, and if the entitlement is not there, then do a sync() and check again.
This is the code that worked for me:
try? await AppStore.sync()
for await result in Transaction.currentEntitlements {
if case let .verified(transaction) = result {
// ...
}
}
Caveats
As mentioned before, this only works on release mode and doesn't work on debug mode without StoreKit Testing, that is without a Configuration.storekit.
If you want to use StoreKit Testing (using a Configuration.storekit) restoring purchases works (with this same approach), but only if you don't delete de app and reinstall again. By deleting the app you loose StoreKit Testing history.
As mentioned by #loremipsum, this can be tested on TestFlight too (for it being release mode).
Verified both iOS and macOS.
Related
On running purchase UITests with XCUitest, sometimes after tapping on the purchase button in the IOS native pop-up
Sign-in Required
doesn't display for a long time (more than 2 min).
The tests are running on a real device(iPhone 6 ios 12).
I've tried to reproduce it manually with no success.
Every time I tried it manually it works fine and most of the time it works fine with the automatic test.
This is the code that waits for the alerts and handles with the alerts with "addUIInterruptionMonitor"
func PurchaseTest1(elementName: String) -> Bool {
if TestUtilities.wait(forMax: 120.0, condition: { return app.buttons[elementName].exists }, doPerIteration: { self.app.swipeDown() } ) == false {
return false
}
return true
}
It's waiting for an element to appear, after every iteration, it swipes down to call to "addUIInterruptionMonitor" to check if there is an iOS pop-up to handle.
Most of the time it works and the pop-up appear but sometimes pop-up doesn't appear at all (I can see in the screenshots that I have for every test).
I had a similar problem. I saw network error, and some time didn't see any errors. I would do the following steps, I hope, It will fix your issue.
reset your device; When I was debugging an issue, I realize there is some certificate related issue that causing this problem.
I think you are making apple purchase, Sandbox allows for a certain time for one user. You can't buy a continuous basis like running over and over again. May be 30 min before you can start purchase again using the same users
Under subscription, Make sure you don't have an active subscription, It can be already purchased and didn't show that button
I think the test will pass if you change device and use new apple sandbox users.
I integrated in app purchase in my app and i tested it with created test account. It was working fine but when i submitted it to app store for live in app review my app got rejected due to:
Guideline 2.1 - Performance - App Completeness
When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code “Sandbox receipt used in production,” you should validate against the test environment instead.
From WWDC 2013 Session 308 at 44:00:
"The app reviewers are actually testing your production signed, ready-to-go-into-the-store binary, but against the test environment. So your production signed app will see test environment receipts".
What this means is that if you're using a server to validate your receipt, make sure to validate the receipt against the production server first, and if that validation fails, then validate the receipt against the sandbox server.
This is what is instructed to do in the rejection reason you received:
"The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code “Sandbox receipt used in production,” you should validate against the test environment instead."
Please also see the following answers to similar questions:
https://stackoverflow.com/a/49398571/5948155
https://stackoverflow.com/a/21515982/5948155
Actually I am using this function to purchase:
func purchase(_ purchase: RegisteredPurchase) {
self.view.showLoad()
// NetworkActivityIndicatorManager.NetworkOperationStarted()
SwiftyStoreKit.purchaseProduct(bundleId + "." + purchase.rawValue, atomically: true) { result in
// NetworkActivityIndicatorManager.NetworkOperationFinished()
self.view.hideLoad()
print(result)
if case .success(let purchase) = result {
self.refrenceNumber = purchase.transaction.transactionIdentifier!
// print(purchase.transaction.transactionDate)
self.sendRefrence()
if purchase.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
}else{
if let alert = self.alertForPurchaseResult(result) {
self.showAlert(alert)
}
}
}
}
It working fine in testing environment but in review its response is :
paymentInvalid
How can i change environment for purchase.
Actually I understand the issue, but when I am purchasing in app purchase product. I am not validating receipt. I am direct purchasing and after calling purchase function as i updated above it gives error .paymentinvalid. where i have to validate or change or where i have to give sandbox or production url to purchase or check. Because my function is not checking any thing directly gives error.
In a previous version of my app I had in app purchases(IAPs) working fine. In my latest release I am getting some strange inconsistencies reported to me by users.
The IAP simply grants access to the "Pro" version of the app that removes ads and allows users to play audio files while the app is backgrounded.
The Issue:
Users can successfully purchases the IAP within the app. They receive the message stating that the purchase was successful. However when they restart the app (as instructed to do by the successful purchase UIAlert) the Pro features are not unlocked.
When the users then use the "Restore Purchases" button, they receive an error message stating that "Nothing to restore" basically tell them that they have not purchased anything.
The interesting things is if they press the purchase button again, then they are told that they have already purchased the IAP and can re-download it for free. Again this doesn't unlock the Pro features.
Reported Cases
I have had numerous cases of this issue being reported to me. Users on both iOS 10 and 11 have encountered this issue. However, other users on both iOS 11 and 10 have also been able to purchase the upgrade without any problem. So I am a little confused on what could of caused the issue.
Tools
I am using SwiftyStoreKit 0.10.5 to manage my IAPs. I am on Xcode 9.0 using Swift 4.
Below is the code I use to purchase and restore purchases.
I have covered my bundleID with *** for safety reasons as I don't know what can be done with it.
// Purchase Product
func purchase(purchase: RegisteredPurchases) {
NetworkActivityIndicatorManager.NetworkOperationStarted()
SwiftyStoreKit.purchaseProduct(bundleID + "." + purchase.rawValue, completion: {
result in
NetworkActivityIndicatorManager.NetworkOperationFinished()
if case .success(let product) = result {
if product.productId == "com.************.RemoveAds" {
self.defaults.set(true, forKey: "NoAds")
}
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
self.showAlert(alert: self.alertForPurchaseResult(result: result))
}
})
}
// Restore Purchases
func restorePurchases() {
NetworkActivityIndicatorManager.NetworkOperationStarted()
SwiftyStoreKit.restorePurchases(atomically: true, completion: {
result in
NetworkActivityIndicatorManager.NetworkOperationFinished()
for product in result.restoredPurchases {
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
if product.productId == "com.************.RemoveAds" {
self.defaults.set(true, forKey: "NoAds")
}
print(product.productId)
self.defaults.set(true, forKey: "\(product.productId)")
}
self.showAlert(alert: self.alertForRestorePurchases(result: result))
})
}
Any information on what could be the cause of this I would really appreciate it. As I am at a bit of a lose at the moment on what the cause could be.
Thanks!
We are using the latest available version of the Facebook SDK for Unity (v7.2.2)
One of the available APIs is ActivateApp (FB.ActivateApp).
Should this method be called, specifically on iOS ?
Digging a bit into what's under the hood (the SDK is open source), I see that on iOS (in the file FBUnityInterface.mm), there's the following code:
- (void)didBecomeActive:(NSNotification *)notification
{
[FBSDKAppEvents activateApp];
}
It looks like ActivateApp is being called automatically (whether i manually call it or not).
Should we call FB.ActivateApp when our app starts / resumes? won't this cause duplicate app activation reports ?
Answering my own question:
On iOS, ActivateApp seems to be called automatically in iOS native code.
Invoking the FB.ActivateApp method does nothing (see this code from IOSFacebook.cs):
public override void ActivateApp(string appId)
{
IOSFacebook.IOSFBSettingsActivateApp(appId);
}
The called method is simply empty:
private static void IOSFBSettingsActivateApp(string appId)
{
}
So, this seems to be taken care of automatically on iOS.
Just to chime in here. We currently had a bug where when using UnityIAP, if a user bought a consumable IAP and quit the app before marking it Complete, they wouldn't get notified of the purchase on app restart.
In the end it came down to Facebook's automatic logging of IAP events in iOS (SDK >= 3.22). I think because it was adding a listener to the transaction queue before Unity.
I mention this because to turn on FB's auto logging of IAP, you need to set it in:
developers.facebook.com > App > Settings > Basic > iOS > iOS Only: Log In-App Purchase Events Automatically (Recommended)
Then call ActivateApp(). However, this is done automatically for you on iOS, so that only leaves the setting on the website or changing the code.
Long story short, if you're using Unity IAP and you're not getting purchase callbacks for incomplete purchases on iOS startup, it's either because of FB or another plugin adding a transaction queue listener (another user reported the possibility of AdMob). For us it was FB.
http://forum.unity3d.com/threads/iap-restorepurchase-on-ios-not-return-processpurchase-callback.392000/
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.