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!
Related
I have implemented auto-renewal subscription in iOS by swift language. The plan is for 1 year and has 3 days trial, the purchase is working perfectly. For checking if user has already buy a subscription or not, for that I am restoring purchases every time when app will open, and that also works. but problem is even subscription is expired or i have canceled from Settings > App Store > Sandbox account > Manage > Subscription then also restored the purchase successfully. I think should be not work right?
I am using SwiftyStoreKit package for handle in-app purchase.
How should I restore purchases?
SwiftyStoreKit.restorePurchases(atomically: true) { results in
if results.restoreFailedPurchases.count > 0 {
print("Restore Failed: \(results.restoreFailedPurchases)")
}
else if results.restoredPurchases.count > 0 {
print("Restore Success: \(results.restoredPurchases)")
}
else {
print("Nothing to Restore")
}
}
I know this is a common question but since there are lots of things changing with each ios update , curious to ask this again.
I have a requirement in iOS app (implemented using ionic framework) , where there must be an option to go to Bluetooth settings of the iphone from within the app so that the user can turn it on/off.
I have read several articles saying that Apple may reject apps trying to access phone settings and it is not advisable to access phone settings through app. Can someone clarify if this still holds true with latest iOS versions and should I never try to do this in future?
You cannot open the Bluetooth settings by using
App-Prefs:root=Bluetooth
The issue is that Apple does not allow us to use non-public APIs anymore and you maybe at risk of getting your developer program cancelled if you try to do so.
All you can do is open the iPhone settings in general by using this code:
extension UIApplication {
static func openAppSettings(completion: #escaping (_ isSuccess: Bool) -> ()) {
guard let url = URL(string: UIApplication.openSettingsURLString) else {
completion(false)
return
}
UIApplication.shared.open(url) { isSuccess in
completion(isSuccess)
}
}
}
Usage:
UIApplication.openAppSettings { isSuccess in
if isSuccess == false {
//Display error
}
}
I have one non-consumable IAP in iOS app (premium update) currently purchase ad restore are performed without receipt verification , i come along some articles that say that an IAP can be hacked in a jailbroken devices ,and i need to avoid this issue so
1- Should I call this verify method every time the user opens the app SwiftyStoreKit
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "xxxxxxxxxxxx")
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: true) { result in
switch result {
case .success(let receipt):
print("success: \(receipt)")
case .error(let error):
print("Verify receipt failed: \(error)")
}
}
To make sure app is truly paid and IAP not hacked ?
2- Also inside the success block i should open the app premium features , while inside failure i should lock them ?
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.
I am implementing in-app purchases in a Swift 3.0 app so I need to grab the app receipt to verify it against the iTunes store. Here is how I am getting the receipt:
func getReceipt() -> Data? {
if Bundle.main.appStoreReceiptURL != nil {
print("app receipt: \(Bundle.main.appStoreReceiptURL)")
do {
let receiptData = try Data(contentsOf: Bundle.main.appStoreReceiptURL!)
print(receiptData)
return receiptData
} catch {
print("error converting receipt to Data: \(error.localizedDescription)")
}
}
return nil
}
my console output for the receipt URL is:
app receipt: Optional(file:///Users/dustinspengler/Library/Developer/XCPGDevices/433E8E8F-B781-4ADC-A92D-5CABC28E94D6/data/Containers/Data/Application/C25BE9B6-FB64-4D49-9CF2-9DA371060A7B/StoreKit/receipt)
It then failed to convert the receipt to Data and the catch statement prints:
error converting receipt to Data: The file “receipt” couldn’t be opened because there is no such file.
I get the exact same output when running this in a playground, simulator, and real devices so does this mean that no receipt exists for the app considering the fact that the user has not made an in-app purchase yet? When reading through Apple's documentation I got the impression that they are always created created regardless of prior purchases.
This answer is based off the context that the app is being run on your local machine. If the app is live in the app store, a receipt will be in place the moment you download the app even if its free.
Answered by #Paulw11:
There is no receipt until the user makes a purchase. For an app downloaded from the App Store (even a free one). This is a purchase, so there will be a receipt. For a debug build from Xcode there is no receipt until an in-app purchase is made.