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

Verify Receipt fails for Apple Reviewer and keeps on rejecting app (Error: SwiftyStoreKit.ReceiptError error 2). #545

Open
Tracked by #550
abhirav opened this issue Jun 15, 2020 · 28 comments
Labels
area: purchase flows purchase processes, efficiency and failures difficulty: advanced we really need help with this one help wanted status: needs analysis community analysis is needed type: bug

Comments

@abhirav
Copy link

abhirav commented Jun 15, 2020

Bug Report

After successful purchase, Verify Receipt Works for us, but not for Apple reviewer. They are getting below error and keeps on rejecting app.
(SwiftyStoreKit.ReceiptError error 2).

To Reproduce

SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
                    if case .success(let receipt) = result {
                        ...
                        
                    } else if case .error(let error) = result {
                        // Always comes here and shows error
                    }

Expected behavior
Should come in success block and proceed further. Purchase is successful, verify receipt is failing.

Platform Information

  • OS: iOS 13.5.1
  • Purchase Type: auto-renewable subscription
  • Environment: Sandbox and app review
  • SwiftyStoreKit version: 0.13.3

Screenshots
image

@wicheda
Copy link

wicheda commented Jun 16, 2020

Was there any update to this, I am having a similar issue and can not reproduce what the Apple reviewers are seeing.

@Sam-Spencer Sam-Spencer added area: purchase flows purchase processes, efficiency and failures difficulty: advanced we really need help with this one help wanted status: needs analysis community analysis is needed labels Jun 16, 2020
@Sam-Spencer
Copy link
Collaborator

It appears that a lot of developers are having the same issue with the library. I'm going to start looking into this and will get back to you as soon as I have more information. In the meantime, community investigation and support would be mighty helpful.

@sam961
Copy link

sam961 commented Jun 16, 2020

Iam having the same issue.
Even I didn't make any changes on my code before regarding auto renewable subscription.
It is working on my phone, but they are getting this error from their side.
I hope this is solved the soonest

@wimbledon
Copy link

This is urgent and high priority issue. I'm getting consistent app rejections from Apple because they are unable to complete the receipt verification due to SwiftyStoreKit.ReceiptError error 2 requestBodyEncodeError.

The error occurs right after the following IAP popup during the receipt verification.
attachment-5650459476598387394Screenshot-0616-075457 (1)

My guess is that the error is in the URLs for AppleReceiptValidator:

	public enum VerifyReceiptURLType: String {
		case production = "https://buy.itunes.apple.com/verifyReceipt"
		case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"
	}

Apple does not want the client/app to call the App Store server verifyReceipt endpoint directly.
More information: https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store

What is interesting is that the error does not occur during debug/testing and the AppStore production environment. Only during the app review Sandbox environment.

I even downgrade to SwiftyStoreKit 0.15.0 and I still get the same Apple app rejection.

@wimbledon
Copy link

@Sam-Spencer, @RomanPodymov, do you have any ideas?

@wicheda
Copy link

wicheda commented Jun 17, 2020

This is quite a perplexing case, my only suspicion is that perhaps Apple blocks or has a different internal redirect to the sandbox verify service for its testers?

This was a critical issue for an app that needed an update, so for now I have used local validation with:

https://github.com/tikhop/TPInAppReceipt

which seems to work nicely. I haven't submitted yet but am hopeful it will go through.

@AdAvAn
Copy link

AdAvAn commented Jun 17, 2020

Hello everybody.
I get the following response from Apple when they try to verify subscriptions:

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

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."

Screenshots
image

I tried using SwiftyStoreKit 0.15.0 and 0.16.0, the result is identical - rejected.

@janczakb
Copy link

I have the same problem!

I still get information from apple:

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.

@janczakb
Copy link

Is there a workaround already? I need this urgently

@wicheda
Copy link

wicheda commented Jun 19, 2020

@janczakb , we needed an update approved urgently, I have managed to get Apple approval now by verifying receipts locally. In other words you can use SwiftyStoreKit for everything else except receipt verification.

This is quite a perplexing case, my only suspicion is that perhaps Apple blocks or has a different internal redirect to the sandbox verify service for its testers?

This was a critical issue for an app that needed an update, so for now I have used local validation with:

https://github.com/tikhop/TPInAppReceipt

which seems to work nicely. I haven't submitted yet but am hopeful it will go through.

@janczakb
Copy link

@wicheda Could you send me an example of use? I will be very grateful

@wicheda
Copy link

wicheda commented Jun 19, 2020

Hi @janczakb , it depends exactly what verification logic you want. But you need to add the TPInAppReceipt library to your project as per their instructions. Then instead of calling SwiftyStoreKit.verifyReceipt , you add a new method, something like:

func validate() {
        // get the receipt from SwiftyStoreKit and validate
        if let receiptData = SwiftyStoreKit.localReceiptData {
            do {
                let receipt = try InAppReceipt.receipt(from: receiptData)
                validateReceipt(receipt: receipt)
            }
            catch {
                // error creating receipt from data?
            }
        }
        else {
            //no receipt, hence force a refresh
            SwiftyStoreKit.fetchReceipt(forceRefresh: true) { result in
                switch result {
                case .success(let receiptData):
                    do {
                        let receipt = try InAppReceipt.receipt(from: receiptData)
                        self.validateReceipt(receipt: receipt)
                    }
                    catch {
                        print("Error validating receipt")
                    }
                case .error(let _):
                    print("Error fetching receipt")
                }
            }
        }
    }
    
    func validateReceipt(receipt:InAppReceipt) {
        
        // Verify all at once
        do {
            try receipt.verify()
        } catch IARError.validationFailed(reason: .hashValidation)
        {
            // Do smth
        } catch IARError.validationFailed(reason: .bundleIdentifierVefirication)
        {
            // Do smth
        } catch IARError.validationFailed(reason: .signatureValidation)
        {
            // Do smth
        } catch {
            // Do smth
        }
        
        // Check whether receipt contains any purchases
        let hasPurchases = receipt.hasPurchases

        // All auto renewable `InAppPurchase`s,
        let purchases: [InAppPurchase] = receipt.autoRenewablePurchases

        // all ACTIVE auto renewable `InAppPurchase`s,
        let activePurchases: [InAppPurchase] = receipt.activeAutoRenewableSubscriptionPurchases
        
        // Add your own logic here around exactly what you want to check
    }

So you'll need to call validate when you want to check after a purchase / restore and at the bottom of validateReceipt add whatever checking logic your app needs. I hope that helps.

@janczakb
Copy link

When testing my application by Apple testers, the "SwiftyStoreKit.purchaseProduct" method returns an "unknown" error

@wicheda
Copy link

wicheda commented Jun 19, 2020

Hi @janczakb , it sounds like you more likely have issues with your products set up then, rather than this specific issue around sandbox receipt validation? SwiftyStoreKit.purchaseProduct is working for me for Apple testers (or certainly was yesterday when it was approved).

@sam961
Copy link

sam961 commented Jun 22, 2020

Guys, I had implemented the verified receipt from the server side and apple accepted it now....it seems now they are blocking verifying receipt from mobile

@Sam-Spencer Sam-Spencer mentioned this issue Jun 23, 2020
9 tasks
@Sam-Spencer
Copy link
Collaborator

Please take a look at issue #550. Your help with this project is dearly needed! @abhirav, @sam961, @wicheda, @AdAvAn, @wimbledon

@cclaflin89
Copy link

I have a different case. I'm not verifying the receipt locally, but do it in the server but got the error SwiftyStoreKit.ReceiptError error 1 when the app is reviewed by Apple testers.
I think this error is occurred in fetchReceipt() before the app sends the receipt to the API server for validation check.

@2856571872
Copy link

I want to know if this problem is solved?

I have a different case. I'm not verifying the receipt locally, but do it in the server but got the error SwiftyStoreKit.ReceiptError error 1 when the app is reviewed by Apple testers.
I think this error is occurred in fetchReceipt() before the app sends the receipt to the API server for validation check.

Hello,I have encountered this problem too, is it finally solved?

@2856571872
Copy link

Why is SwiftyStoreKit.localReceiptData empty after successful purchase

I don’t see the problem on iOS, but it often appears on MacOS

@2856571872
Copy link

2856571872 commented Sep 29, 2020

`func buyProduct(product: SKProduct,orderId: String) {

    SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true, applicationUsername: orderId, simulatesAskToBuyInSandbox: false) { [weak self] (result) in
        switch result {
        case .success(let purchase):
            print("Purchase Success: \(purchase.productId)")
            self?.uploadReceipt(receiptData: nil)
        case .error(let error):
            var errorStr = error.localizedDescription
            switch error.code {
            case .unknown: errorStr = "Unknown error. Please contact support".languageSet()
            case .clientInvalid:
                errorStr = "The payment is cancelled".languageSet()
            case .paymentCancelled:
                errorStr = "Not allowed to make the payment".languageSet()
            case .paymentInvalid:
                errorStr = "The purchase identifier was invalid".languageSet()
            case .paymentNotAllowed:
                errorStr = "The device is not allowed to make the payment".languageSet()
            case .storeProductNotAvailable:
                errorStr = "The product is not available in the current storefront".languageSet()
            case .cloudServicePermissionDenied:
                errorStr = "Access to cloud service information is not allowed".languageSet()
            case .cloudServiceNetworkConnectionFailed:
                errorStr = "Could not connect to the network".languageSet()
            case .cloudServiceRevoked:
                errorStr = "User has revoked permission to use this cloud service".languageSet()
            default: print((error as NSError).localizedDescription)
            }
            
            let customerError = NSError(domain: "generateOrder", code: -1000, userInfo: [NSLocalizedDescriptionKey : errorStr])
            self?.failBlock?(customerError)
        }
    }
}


/// upload receipt to the server
func uploadReceipt(receiptData: Data?) {
    let data = (receiptData == nil) ? SwiftyStoreKit.localReceiptData : receiptData
    let receiptString = data?.base64EncodedString(options: []) ?? ""
    if !receiptString.isEmpty {
            // ... upload receipt
    }else {
        print("## receipt is empty")
        SwiftyStoreKit.fetchReceipt(forceRefresh: true) { [weak self] (result) in
            switch result {
                case .success(let receiptData):
                    self?.uploadReceipt(receiptData: receiptData)
                case .error(let error):
                    self?.failBlock?(error as NSError)
            }
        }
    }
    
}

`

This is my code

@exclucive
Copy link

I think the best solution to this is to remove this buggy library and create a wrapper by yourself.

@Zeeshan0075
Copy link

Today mine app was also rejected because of this reason.

attachment Screenshot-0418-183246

@Zulqarnain-tech
Copy link

Anyone so far got the solution? am facing the same issue. What am doing is if recceiptURL is not existing, I send a force fetch receipt to the apple server. But my build got rejected with prompting message "Receipt Error 2". The mentioned in the review message that first I need to hit to the production server and if that fails then I should go for sandbox server. Cannot produce the issue in local build and on testFlight. Anyone got the solution..?

@Zulqarnain-tech
Copy link

Bug Report

After successful purchase, Verify Receipt Works for us, but not for Apple reviewer. They are getting below error and keeps on rejecting app. (SwiftyStoreKit.ReceiptError error 2).

To Reproduce

SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
                    if case .success(let receipt) = result {
                        ...
                        
                    } else if case .error(let error) = result {
                        // Always comes here and shows error
                    }

Expected behavior Should come in success block and proceed further. Purchase is successful, verify receipt is failing.

Platform Information

  • OS: iOS 13.5.1
  • Purchase Type: auto-renewable subscription
  • Environment: Sandbox and app review
  • SwiftyStoreKit version: 0.13.3

Screenshots image

Did you get the solution? am facing the same issue.

@shahzadhussainios
Copy link

Screenshot-1004-104856

I am facing this issue. please help me

@shls77cxl
Copy link

Why is SwiftyStoreKit.localReceiptData empty after successful purchase

I don’t see the problem on iOS, but it often appears on MacOS

Did you get the solution? am facing the same issue.

@codicil
Copy link

codicil commented Jun 9, 2024

@janczakb , we needed an update approved urgently, I have managed to get Apple approval now by verifying receipts locally. In other words you can use SwiftyStoreKit for everything else except receipt verification.

This is quite a perplexing case, my only suspicion is that perhaps Apple blocks or has a different internal redirect to the sandbox verify service for its testers?
This was a critical issue for an app that needed an update, so for now I have used local validation with:
https://github.com/tikhop/TPInAppReceipt
which seems to work nicely. I haven't submitted yet but am hopeful it will go through.

@wicheda did you try the library and is it working for you as I am having receipt validation error for Mac Catalyst my iOS version with Code is approved though

@codicil
Copy link

codicil commented Jun 9, 2024

Hi @janczakb , it depends exactly what verification logic you want. But you need to add the TPInAppReceipt library to your project as per their instructions. Then instead of calling SwiftyStoreKit.verifyReceipt , you add a new method, something like:

func validate() {
        // get the receipt from SwiftyStoreKit and validate
        if let receiptData = SwiftyStoreKit.localReceiptData {
            do {
                let receipt = try InAppReceipt.receipt(from: receiptData)
                validateReceipt(receipt: receipt)
            }
            catch {
                // error creating receipt from data?
            }
        }
        else {
            //no receipt, hence force a refresh
            SwiftyStoreKit.fetchReceipt(forceRefresh: true) { result in
                switch result {
                case .success(let receiptData):
                    do {
                        let receipt = try InAppReceipt.receipt(from: receiptData)
                        self.validateReceipt(receipt: receipt)
                    }
                    catch {
                        print("Error validating receipt")
                    }
                case .error(let _):
                    print("Error fetching receipt")
                }
            }
        }
    }
    
    func validateReceipt(receipt:InAppReceipt) {
        
        // Verify all at once
        do {
            try receipt.verify()
        } catch IARError.validationFailed(reason: .hashValidation)
        {
            // Do smth
        } catch IARError.validationFailed(reason: .bundleIdentifierVefirication)
        {
            // Do smth
        } catch IARError.validationFailed(reason: .signatureValidation)
        {
            // Do smth
        } catch {
            // Do smth
        }
        
        // Check whether receipt contains any purchases
        let hasPurchases = receipt.hasPurchases

        // All auto renewable `InAppPurchase`s,
        let purchases: [InAppPurchase] = receipt.autoRenewablePurchases

        // all ACTIVE auto renewable `InAppPurchase`s,
        let activePurchases: [InAppPurchase] = receipt.activeAutoRenewableSubscriptionPurchases
        
        // Add your own logic here around exactly what you want to check
    }

So you'll need to call validate when you want to check after a purchase / restore and at the bottom of validateReceipt add whatever checking logic your app needs. I hope that helps.

Is this working for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: purchase flows purchase processes, efficiency and failures difficulty: advanced we really need help with this one help wanted status: needs analysis community analysis is needed type: bug
Projects
None yet
Development

No branches or pull requests