Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apple always rejects app because of in app purchase #397

Open
Tracked by #550
Turacbey opened this issue Aug 18, 2018 · 33 comments
Open
Tracked by #550

Apple always rejects app because of in app purchase #397

Turacbey opened this issue Aug 18, 2018 · 33 comments
Labels
answered Questions which have accepted answers. area: receipt-validation validating receipts for customer or purchase verification type: bug

Comments

@Turacbey
Copy link

Turacbey commented Aug 18, 2018

Bug Report

Apple says,

We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 11.4.1 on Wi-Fi connected to an IPv6 network.

Specifically, we are still unable to purchase IAP in your app. Error dialog appears.

Expected Results
I didn't expect to have a problem when Apple checked in-app purchases.

Actual Results
A problem happened. Error dialog always appears when apple checks the app. errorAlertDialog()

Additional Context

  • Platform: iOS
  • Purchase type: non-consumable
  • Environment: production
  • SwiftyStoreKit Version: 0.13.3

Related Issues

Code

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "2030202")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
    switch result {
     case .success(let receipt):
          let productId = "xxxxxxxxxxxxxxx"
          let purchaseResult = SwiftyStoreKit.verifyPurchase(
               productId: productId,
               inReceipt: receipt)
          switch purchaseResult {
               case .purchased(let receiptItem):
                // Success
                case .notPurchased:
                    SwiftyStoreKit.restorePurchases(atomically: true) { results in
                         if results.restoreFailedPurchases.count > 0 {
                              self.errorAlertDialog()
                        } else if results.restoredPurchases.count > 0 {
                              print("success")
                        } else {
                              print("Nothing to Restore")
                              //purchase      
                              SwiftyStoreKit.purchaseProduct("xxxxxxxxxxxxxxx", quantity: 1, atomically: true) { result in
                              switch result {
                              case .success(let purchase):
                                   SwiftyStoreKit.finishTransaction(purchase.transaction)
                              case .error(let error):
                                   self.errorAlertDialog()
                            }
                        }     
                    }
                }
            }
      case .error(let error):
          self.errorAlertDialog()
       }
}
@mouness2020
Copy link

same problem

@mouness2020
Copy link

mouness2020 commented Aug 22, 2018

Response from Apple Review:

Hello,

Thank you for your response. Upon review, we found that your app is not in compliance with guideline 2.1.

We discovered one or more bugs in your app when reviewed on iPad running iOS 11.4.1 on Wi-Fi connected to an IPv6 network, the app asks users to subscribe before reviewing their options, not all users returning users.

Please run your app on a device to identify any issues, then revise and resubmit your app for review.

Thank you, we look forward to reviewing your revised app.

Best Regards,
Apple

@mouness2020
Copy link

mouness2020 commented Aug 22, 2018

We discovered one or more bugs in your app when reviewed on iPad running iOS 11.4.1 on Wi-Fi connected to an IPv6 network.

  • The app asks users to subscribe before reviewing their options.

Next Steps

To resolve this issue, please run your app on a device to identify any issues, then revise and resubmit your app for review.

If we misunderstood the intended behavior of your app, please reply to this message in Resolution Center to provide information on how these features were intended to work.

For new apps, uninstall all previous versions of your app from a device, then install and follow the steps to reproduce the issue. For updates, install the new version as an update to the previous version, then follow the steps to reproduce the issue.

Resources

For information about testing your app and preparing it for review, please see Technical Note TN2431: App Testing Guide.

For a networking overview, please review About Networking. For a more specific overview of App Review’s IPv6 requirements, please review the IPv6 and App Review discussion on the Apple Developer Forum.

@koraycayiroglu
Copy link

Hello,

I have same problem with my application, is there any quick solution for this issue?

Best regards

@edwinps
Copy link

edwinps commented Aug 29, 2018

I have similar problem, someone have any solutions ?

best regards

@JustinGanzer
Copy link

Hope I'm not too late for some of you. I've had a similar problem and used the apple technical support to give me some insight into the issue.

What is the cause of this problem? -> Somewhere in the app, I would try to verify the receipt before the user ever made purchases in the app. Why? Well I wanted to check if the user might have bought something already.

The issue with this is that on the devices that the apple testers use, a call to "verifyReceipt", "verifySubscription" etc... from within SwiftyStoreKit/ regular StoreKit, will automatically return an "Error could not connect to iTunes". Even if ´forceRefresh´ is set to false for those functions. Since their devices are always reset to factory settings and they initially do not have iTunes Accounts on them before testing, their devices do not have a receipt. Not even locally. For normal users, this is impossible, since you download your apps through the app store, however the testers at apple install the app via an appLoader and have not signed in to itunes ever. Because of this the app will try to fetch a receipt even when the forceRefresh value is set to false, as it wants to get the initial receipt.

Solution: Don't verify anything before the user has made a purchase. Apart from initialisation and such, the first call to Storekit/SwiftyStorekit should be ´SwiftyStoreKit.purchaseProduct´ or similar.

If your app depends on verifying the user receipt before allowing purchases, because of some reason -> You can't! The app will be rejected for reasons stated above.

@FlashTang
Copy link

@JustinGanzer

Thank you, do you mean we should store a Bool to e.g. Userdefault , and set it to true once user made a purchase, so verifyReceipt func can be called in the next bootup ?

@JustinGanzer
Copy link

@FlashTang

Yes that is a possible solution, in fact that is how I do it too. Once the user has bought something OR restored his/her purchases, I will allow the app to verify purchases upon every startup after that. I store this variable in the user and gave it the name "bIsSubscriber".

Upon login/installation/signin I set this value to false. Therefore a returning user with previous purchases must go into the settings menu and click a button called "Restore Purchases" to be a subscriber again.
Making the same purchase that the user already owns will just restore it automatically. There is nothing you need to implement. He/she will not be billed.

This is the way Apple intends apps to behave and the review team has not given me an issue since.

@FlashTang
Copy link

@FlashTang

Yes that is a possible solution, in fact that is how I do it too. Once the user has bought something OR restored his/her purchases, I will allow the app to verify purchases upon every startup after that. I store this variable in the user and gave it the name "bIsSubscriber".

Upon login/installation/signin I set this value to false. Therefore a returning user with previous purchases must go into the settings menu and click a button called "Restore Purchases" to be a subscriber again.
Making the same purchase that the user already owns will just restore it automatically. There is nothing you need to implement. He/she will not be billed.

This is the way Apple intends apps to behave and the review team has not given me an issue since.

Thanks for the confirmation JustinGanzer

@Joshandrews43
Copy link

Currently getting the 'Code=0 "Cannot connect to iTunes Store"' Error.

I placed print statements in every verifyReciept or verifySubscription function in SwiftyStoreKit, and none are being called, yet I am still getting this error on my personal device and my simulators, using both tester accounts and my real Apple ID (with my real apple id trying to make a live purchase). However, when I use a real device that has never had the app installed, it works fine.

I should also note that it was working fine up until about a few weeks ago, at which point it just stopped working. Any ideas?

@dashvlas
Copy link

Still receiving this error from the Review team. I'm gonna try to roll back to 0.14.1 and see what happens, the previous version was fine.

@dashvlas
Copy link

Yep, 0.14.1 works for me, the app has been released

@mrtksn
Copy link

mrtksn commented May 3, 2019

I had a rejection with the same feedback from Apple. They attached a screenshot too, which indicates that the app was not able to fetch pricing info either.

Now I made sure that I'm verifying against prod and if it fails, then against sandbox servers, displaying a message that an iTunes account is required to get pricing. The App is in review for more than 24 hours now, previously it was rejected after 4 hours.

Does anybody have a more concrete explanation one the issue?

@SohaibSiddique
Copy link

I had a rejection with the same feedback from Apple. They attached a screenshot too, which indicates that the app was not able to fetch pricing info either.

Now I made sure that I'm verifying against prod and if it fails, then against sandbox servers, displaying a message that an iTunes account is required to get pricing. The App is in review for more than 24 hours now, previously it was rejected after 4 hours.

Does anybody have a more concrete explanation one the issue?

your problem solved or not?

as @JustinGanzer explain, did you try not to fetch any receipts and subscription before the user made a purchase?

I'm getting purchase detail without signing to the iTunes store and its working fine.

@mrtksn
Copy link

mrtksn commented Jun 14, 2019

I had a rejection with the same feedback from Apple. They attached a screenshot too, which indicates that the app was not able to fetch pricing info either.
Now I made sure that I'm verifying against prod and if it fails, then against sandbox servers, displaying a message that an iTunes account is required to get pricing. The App is in review for more than 24 hours now, previously it was rejected after 4 hours.
Does anybody have a more concrete explanation one the issue?

your problem solved or not?

as @JustinGanzer explain, did you try not to fetch any receipts and subscription before the user made a purchase?

I'm getting purchase detail without signing to the iTunes store and its working fine.

Not fetching pricing info would degrade the user experience for all of the users. Instead, I followed Apple's recommendation to check if the user can make purchases before fetching anything from the AppStore servers.

You do that by calling SKPaymentQueue.canMakePayments(). If returns true then you can go ahead and make calls to Apple Servers. Also, it is important to call Production servers first and if fails the Sandbox servers. This is because during the testing the app would be signed with a production certificate however the testers would still use sandbox account, therefore even tough your app thinks that it's in prod you should fall back the sandbox to accommodate the testers.

You would still want to follow @JustinGanzer s advice to not verify receipts before a purchase is made because it can show annoying "enter your Apple ID and Password" alerts when your starts. If the user is not claiming that a purchase is made, do not bother to verify purchases :)

After making these changes my App was approved, you can see it here: https://apps.apple.com/us/app/smoky-in-rehab/id1459979131

@nuthinking
Copy link

nuthinking commented Aug 9, 2019

@mrtksn can you please expand on your solution? When do you check if the user can make payment? Before pulling the pricing? In which call do you call production first and sandbox later? It looks like in my app the failure happens on purchase, before any validation is triggered.

@mrtksn
Copy link

mrtksn commented Aug 9, 2019

@mrtksn can you please expand on your solution? When do you check if the user can make payment? Before pulling the pricing? In which call do you call production first and sandbox later? It looks like in my app the failure happens on purchase, before any validation is triggered.

You should check if the user's payment ability before attempting anything else. You can do it at app launch, that's where I'm doing it.

The production/sandbox servers are different when validating a purchase. You first should validate against production and if it fails, against sandbox.

The idea is that if the Apple's testers are testing your app, the app would be signed as in production but the testers will still be using sandbox accounts, so you want to accommodate this use case.

Here is my code that works just fine:

func checkSubscription(_ callback : ((Bool)->Void)? = nil, service: AppleReceiptValidator.VerifyReceiptURLType = .production){
 
        let appleValidator = AppleReceiptValidator(service: service, sharedSecret: self.appSecret)
        SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
            switch result {
            case .success(let receipt):
                print("Receipt verified")
                let productId = self.productId
                // Verify the purchase of a Subscription
                let purchaseResult = SwiftyStoreKit.verifySubscription(
                    ofType: .autoRenewable, // or .nonRenewing (see below)
                    productId: productId,
                    inReceipt: receipt)
                
                // Then call the callback if available
                if callback != nil {
                    callback!(true)
                }
                
                switch purchaseResult {
                case .purchased(let expiryDate, let items):
                    print("\(productId) is valid until \(expiryDate)\n\(items)\n")
                    self.activateSubscription()
                case .expired(let expiryDate, let items):
                    print("\(productId) is expired since \(expiryDate)\n\(items)\n")
                    self.deactivateSubscription()
                case .notPurchased:
                    print("The user has never purchased \(productId)")
                    self.deactivateSubscription()
                    
                }
                
            case .error(let error):
                print("Receipt verification failed: \(error)")
                if service == .production {
                    self.checkSubscription(callback, service: .sandbox)
                }else{
                    print("Trying again with sandbox")
                    if callback != nil {
                        callback!(false)
                    }
                }
            }
        }
       
    }

@nuthinking
Copy link

nuthinking commented Aug 9, 2019

The second part is clear, despite I thought that SwiftyStoreKit was already taking care of the sandbox error. Check AppleReceiptValidator line 94.
But the first part, SKPaymentQueue.canMakePayments(), what's the benefit during the app review process? Why should we handle this since SwiftyStoreKit handles it already?

@nuthinking
Copy link

nuthinking commented Aug 9, 2019

In my case the reviewer got the error "The purchase identifier was invalid" and looks like they failed to pull the product info. So, it seems the error happened before any validation despite what they tell in the report. Would this still be the same issue?

@mrtksn
Copy link

mrtksn commented Aug 9, 2019

I don't think that you will fail the review if you don't make a check but if you don't check you can have broken user interface where you are making the user to try something that he/she technically cannot. Maybe this can cause trouble in the review? Apple's own documentation recommends validating it, so.

I haven't troubleshot the issue deeply but in my case, my app was also not able to fetch the IAP product info.

Just don't try to validate purchases right away, it seems to be causing issues later on. I'm still not sure why on the first review the app failed to fetch the price but I didn't want to spend too much time since it worked later. The main change that I made was not to validate purchases if the app doesn't seem to claim any and if I do verification and it fails against the production I try against the sandbox. After that change product info fetching issue vanished.

in essence, first thing first you check if the user has the ability to make payments and keep that in mind to prevent situations where the user keeps tapping on the purchase but a cryptic error says that the purchase failed without explaining why it failed.

Then you check if the user has made any purchases and validate those purchases. If the app doesn't claim purchases, don't do the validation.

Then you can go ahead and fetch pricing info, items and so on.

@nuthinking
Copy link

Maybe it was a random Sandbox error, what a luck!

@njovy
Copy link

njovy commented Dec 13, 2019

I am having the same problem as well.
Guideline 2.1 - Performance - App Completeness

We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 13.3 on Wi-Fi.

Specifically, when we tapped on the purchase button at your store, no purchasing activity was carried out.

Next Steps

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.

Resources

You can learn more about testing in-app purchase products in your development sandbox environment in App Store Connect Developer Help.

For more information on receipt validation, please see What url should I use to verify my receipt? in the In-App Purchase FAQ.

Learn how to generate a receipt validation code in App Store Connect Developer Help.

Please see attached screenshot for details.

@dashvlas
Copy link

@njovy try to use
pod 'SwiftyStoreKit', '0.14.1'

and when some error occurs on their network you can reply them something like that:

Good morning!
Our developers have verified this problem and found that this error occurs when ”unknown” error is received from Apple request for buying a subscription. It's sometimes happening on your wifi. Please, we are highly asking for requesting "subscription buy" once again.

@dashvlas
Copy link

It's worked all the times when something not expected occurs with subscription on Apple's side. Just be sure that everything implemented correctly

@louiskabo
Copy link

louiskabo commented Jan 31, 2020

Following this post seems to have solved my similar issue as reported in #515

As mentioned by others in this thread, this process solved my issue. Thank you!

I set a UserDefault to "disallow" checking of receipts until a purchase has been initiated or the user presses "restore purchases". This skips the checking for receipts on first run, which it will find none, as described in this post, or in my case, probably completely overwhelmed by the number of receipts it does find and allows sandbox purchasing to be successful every time.

@Sam-Spencer Sam-Spencer added answered Questions which have accepted answers. area: receipt-validation validating receipts for customer or purchase verification type: bug labels Feb 24, 2020
@patelmm
Copy link

patelmm commented Mar 9, 2020

Getting same issue, sandbox working fine but apple reject, apple didn't get product when they test any one get solution ?

@louiskabo
Copy link

louiskabo commented Mar 9, 2020 via email

@louiskabo
Copy link

To followup my last comment, update sent to review, and passed all checks with the setup above. IAP's worked fine on the reviewers end, saw the sale go thru my server side. - approved.

@sabiland
Copy link

sabiland commented Dec 13, 2021

EDIT: I do not verify any receipts in my app - because I do not need to.

I got yesterday this reviewer app rejected message (I did not have this kind of problem before).

"Guideline 2.1 - Performance - App Completeness
We found that your in-app purchase products
exhibited one or more bugs when reviewed on iPad
running iOS 15.1 on Wi-Fi.

Specifically, your app displayed an error message
when we tried to purchase the in-app purchase item."

I got into this enum case and I do not know what could be wrong?

Screenshot 2021-12-13 at 08 38 24

@louiskabo
Copy link

@sabiland can you start a new thread to look into your issue? Also, please post the full rejection message and any screenshots apple provided in your post.

@sabiland
Copy link

sabiland commented Dec 15, 2021

@louiskabo it seems everything is ok.

  • I got an .unknown case (but that means error on Apple's side) when reviewer made IAP
  • And then I changed to-show-to-user message from "Unknown error" to some nicer error message and my app was approved
  • I changed user message to: "Sorry, the purchase (or restore) is unavailable for an unknown reason - Please try again later"

@louiskabo
Copy link

Ok. That’s interesting. I wonder why it was failing. And purchases work ok in production?

@aalokhyperlink
Copy link

aalokhyperlink commented May 10, 2023

@mrtksn can you please expand on your solution? When do you check if the user can make payment? Before pulling the pricing? In which call do you call production first and sandbox later? It looks like in my app the failure happens on purchase, before any validation is triggered.

You should check if the user's payment ability before attempting anything else. You can do it at app launch, that's where I'm doing it.

The production/sandbox servers are different when validating a purchase. You first should validate against production and if it fails, against sandbox.

The idea is that if the Apple's testers are testing your app, the app would be signed as in production but the testers will still be using sandbox accounts, so you want to accommodate this use case.

Here is my code that works just fine:

func checkSubscription(_ callback : ((Bool)->Void)? = nil, service: AppleReceiptValidator.VerifyReceiptURLType = .production){
 
        let appleValidator = AppleReceiptValidator(service: service, sharedSecret: self.appSecret)
        SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
            switch result {
            case .success(let receipt):
                print("Receipt verified")
                let productId = self.productId
                // Verify the purchase of a Subscription
                let purchaseResult = SwiftyStoreKit.verifySubscription(
                    ofType: .autoRenewable, // or .nonRenewing (see below)
                    productId: productId,
                    inReceipt: receipt)
                
                // Then call the callback if available
                if callback != nil {
                    callback!(true)
                }
                
                switch purchaseResult {
                case .purchased(let expiryDate, let items):
                    print("\(productId) is valid until \(expiryDate)\n\(items)\n")
                    self.activateSubscription()
                case .expired(let expiryDate, let items):
                    print("\(productId) is expired since \(expiryDate)\n\(items)\n")
                    self.deactivateSubscription()
                case .notPurchased:
                    print("The user has never purchased \(productId)")
                    self.deactivateSubscription()
                    
                }
                
            case .error(let error):
                print("Receipt verification failed: \(error)")
                if service == .production {
                    self.checkSubscription(callback, service: .sandbox)
                }else{
                    print("Trying again with sandbox")
                    if callback != nil {
                        callback!(false)
                    }
                }
            }
        }
       
    }

The user has never purchased
I am getting this error always if I have purchased item already

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
answered Questions which have accepted answers. area: receipt-validation validating receipts for customer or purchase verification type: bug
Projects
None yet
Development

No branches or pull requests