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

SDK fetching Receipt breaks first-time App Review #2120

Closed
5 tasks done
natelowry opened this issue Dec 7, 2022 · 11 comments
Closed
5 tasks done

SDK fetching Receipt breaks first-time App Review #2120

natelowry opened this issue Dec 7, 2022 · 11 comments
Labels

Comments

@natelowry
Copy link
Contributor

Describe the bug
Note: This may be related to #2116, but it seems a bit different.

We've been attempting the "submit & pray" technique for our first App Store Review with new subscription products. I have read allllll the forum and blog posts on the issues in Sandbox and tried our submission at least a dozen times.

We finally opened a TSI with Apple and got some information about how we should be handling those missing receipts. Basically, we should ignore any missing receipts as they are guaranteed to be there in Prod and kinda guaranteed to be broken in App Review Sandbox. It seems like maybe the SDK should either ignore that specific error OR could have a firstTimeAppReview config flag that folks could set for their first review that ignores that error.

I'm also not 100% sure how to do the ignoring except for forking the SDK and adding some custom code. I'm happy to do that, but based on the forum posts, we're not the only ones hitting this issue :) Any direction there would be helpful.

It sorta seems to me like the RevenueCat SDK is calling SKReceiptRefreshRequest instead of restoreCompletedTransactions even though that code is in the SK1 branch that we opted out of. I'm a bit confused why it's fetching the receipt at all...is there something I'm doing wrong in the setup to cause it to fetch that receipt early?

Here's the response from the Apple tech:

I asked my App Review contact to review your app a second time. He replicated the failure and captured a sysdiagnose for my review. From the system log, I see the following

default	2022-12-06 17:49:21.559689 -0800	storekitd	StoreKitServiceConnection(2044): Returning 3 products, 0 invalid identifiers for com.our.app.id

This tells me that the SKProductsRequest or the  StoreKit.Product.products(for: identifiers) was successful.

However, I then see that the app calls SKReceiptRefreshRequest
as evidenced by the log statement

default	2022-12-06 17:49:21.847469 -0800	storekitd	[812F55CB_SK1] [com.our.app.id] Fetching app receipt
default	2022-12-06 17:49:21.854010 -0800	storekitd	AMSURLRequestEncoder: [0D3B9158_SK2] Encoding request for URL: https://mzstorekit-sb.itunes.apple.com/inApps/v1/receipts/createAppReceipt { 
	account = <ACAccount: 0xdeadbeef type = iTunesStore.sandbox | backingID = 227FF557-1111-2222-3333-58C2CA6A229B | username = H:19deadbeef | altDSID = H:02deadbeef | DSID = H:12deadbeef | active = true | storefront = H:62deadbeef> 
	mediaType = com.apple.AppleMediaServices.accountmediatype.appstore.sandbox 
}

In app review, this call will always fail as evidenced by 

error	2022-12-06 17:49:22.143853 -0800	MyApp	<SKReceiptRefreshRequest: 0x2803868a0>: Finished refreshing receipt with error: Error Domain=ASDServerErrorDomain Code=500317 "Unhandled exception" UserInfo={NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception, AMSServerErrorCode=0}

If you are using SK2, you should make use of currentEntitlements instead. 
If you want to continue to use SK1, you can make use of SKReceiptRefreshRequest, but if it fails (as it always will in AR), then when the didFailWithError delegate is called assume that the app has no access to premium content, nor is there anything to restore, nor access to first time offers. 

If you are making use of SKReceiptRefreshRequest as a means to restore previous purchases, the preferred method is to use restoreCompletedTransactions - which will work in AR. 
Once the app is in production - a production app installed by the App Store will always have an appStoreReceipt and the use of SKReceiptRefreshRequest is no longer necessary. 
There is no App Review requirement that the appStoreReceipt be present until after an in-app purchase has been made - which will always happen.
  1. Environment
    1. Platform: iOS
    2. SDK version: 4.15.0
    3. StoreKit 2 (disabled with useStoreKit2IfEnabled(false)) (Y/N): Y (tried w/ N as well)
    4. OS version: 16.1.2 (have seen on all 15+)
    5. Xcode version: 14.1 (14B47b)
    6. How widespread is the issue. Percentage of devices affected. 100% of devices in App Review, 0% in simulator or TestFlight.
  2. Debug logs that reproduce the issue (newest to oldest)
🍎‼️ The receipt is missing.
ℹ️ Unable to load receipt, ensure you are logged in to a valid Apple account.
ℹ️ Force refreshing the receipt to get latest transactions from Apple.
ℹ️ Found 0 unsynced attributes for App User ID: $RCAnonymousID:b2ad3a7006af48839e0df9333933beef
💰 Finishing transaction '2000000220835431' for product 'myapp.monthly.2'
ℹ️ Unable to load receipt, ensure you are logged in to a valid Apple account.
ℹ️ Force refreshing the receipt to get latest transactions from Apple.
ℹ️ Found 0 unsynced attributes for App User ID: $RCAnonymousID:b2ad3a7006af48839e0df9333933beef
ℹ️ Store products request finished
😻 Store products request request received response
💰 Finishing transaction '2000000220824047' for product 'myapp.monthly.2'
ℹ️ No existing products cached, starting store products request for: ["myapp.monthly.2", "myapp.quarterly.2", "myapp.annual.2"]
ℹ️ Store products request finished
😻 Store products request request received response
ℹ️ Unable to load receipt, ensure you are logged in to a valid Apple account.
ℹ️ Force refreshing the receipt to get latest transactions from Apple.
ℹ️ Found 0 unsynced attributes for App User ID: $RCAnonymousID:b2ad3a7006af48839e0df9333933beef
💰 Finishing transaction '2000000220815446' for product 'myapp.monthly.2'
ℹ️ Unable to load receipt, ensure you are logged in to a valid Apple account.
ℹ️ Force refreshing the receipt to get latest transactions from Apple.
ℹ️ Found 0 unsynced attributes for App User ID: $RCAnonymousID:b2ad3a7006af48839e0df9333933beef
ℹ️ Serial request done: GET subscribers/$RCAnonymousID%3Ab2ad3a7006af48839e0df9333933beef/offerings, 0 requests left in the queue
ℹ️ GetOfferingsOperation: Finished
ℹ️ Found an existing request for products: ["myapp.annual.2", "myapp.quarterly.2", "myapp.monthly.2"], appending to completion
ℹ️ Found an existing request for products: ["myapp.quarterly.2", "myapp.annual.2", "myapp.monthly.2"], appending to completion
ℹ️ Found an existing request for products: ["myapp.quarterly.2", "myapp.annual.2", "myapp.monthly.2"], appending to completion
ℹ️ No existing products cached, starting store products request for: ["myapp.monthly.2", "myapp.annual.2", "myapp.quarterly.2"]
ℹ️ API request completed: GET /v1/subscribers/$RCAnonymousID:b2ad3a7006af48839e0df9333933beef/offerings (200)
  1. Steps to reproduce, with a description of expected vs. actual behavior
    Try to purchase any product in our app
  2. Other information (e.g. stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, etc.)

Additional context
Here's our setup code:
in the App init:

Purchases.configure(with: Configuration.Builder(withAPIKey: "our-key").with(usesStoreKit2IfAvailable: true).build())
Purchases.shared.delegate = PurchasesDelegateHandler.shared

as a .Task on our main Scene, we bootstrap things with these two calls:

UserViewModel.shared.customerInfo = try await Purchases.shared.customerInfo()
UserViewModel.shared.offerings = try await Purchases.shared.offerings()

our PurchasesDelegateHandler is identical to the example app PurchasesDelegateHandler.swift

here's the code we use to make the Purchase (and where the receipt error pops up):
let result = try await Purchases.shared.purchase(package: package)
(packages are read from userModel.offerings?.current?.availablePackages)

Products are in the Ready To Submit, Approved, or Waiting for Review status and the Subscription Group is attached to the build.
Paid Application Agreement is signed and up to date.
We have a synced StoreKit Configuration file set on our Target, but sounds like that shouldn't matter in Sandbox testing, only Xcode.
Reference Names have not been changed recently.
We tried recreating all of our App Store Subscription Products, but it didn't help.

Looking forward to getting through this. Glad Apple is being helpful on the technical side.

Suggestions for us going forward?

-nate

@natelowry natelowry added the bug label Dec 7, 2022
@natelowry natelowry changed the title SDK fetching Receipt breaks App Review SDK fetching Receipt breaks first-time App Review Dec 7, 2022
@aboedo
Copy link
Member

aboedo commented Dec 7, 2022

Hey @natelowry, that sounds like a really frustrating experience.

I'll add some context here:

re: the first fetch will fail
This is fine, like the App Review team pointed out, the receipt won't be there until there's a purchase in Sandbox, but as soon as there is one we will force-refresh the receipt, which should ensure that a receipt is found. At this point the receipt is sent to the backend.

re: why the SDK uses receipt refresh even when StoreKit 2 is enabled
Currently when enabling SK2, the SDK uses SK2 APIs under the hood for the purchasing process, but posting receipts is still done with StoreKit 1. This may change in the future as we move into SK2 APIs server side as well.
However SK1 should still work for purchases, even in App Review.

Could you clarify what exactly is the rejection reason? Was there a user-facing error popping up? All of this stuff should be under the hood, so that's why I'm asking.

@natelowry
Copy link
Contributor Author

@aboedo it feels like a sick right of passage to get through the review process lol 🙃

Your notes all make sense and it has me questioning some things. We were seeing an error pop up when they tapped the button that calls purchase. Basically this call would throw.
let result = try await Purchases.shared.purchase(package: package)

The reviewer said Specifically, an error was returned after we purchased a subscription. and posted a screenshot of the error modal from that call.

I thought it was the missing receipt error in that, but now I'm not so sure. I'm going to have them trigger that one more time just to make sure I have the logs correlated correctly.

Standby for more info. Thanks!

-nate

@NachoSoto
Copy link
Contributor

This looks similar to https://community.revenuecat.com/sdks-51/missing-receipt-leading-to-many-app-store-rejections-2321. There's some good guidance there as well for how to help Apple reviewers when they encounter these issues.

@natelowry
Copy link
Contributor Author

Hi @NachoSoto thanks for that link, had seen that before and tried to pass that info to the reviewer.

I finally got a clean error log from review. The receipt error shows up when trying to purchase the product.

💰 Product purchase for 'my-app.monthly.2' failed with error: Error Domain=RevenueCat.ErrorCode Code=9 "The receipt is missing." UserInfo={NSLocalizedDescription=The receipt is missing., source_file=RevenueCat/PurchasesOrchestrator.swift:710, readable_error_code=MISSING_RECEIPT_FILE, source_function=handlePurchasedTransaction(_:storefront:)}

here are the events preceding that:

Purchasing: $rc_monthly
💰 Purchasing Product 'my-app.monthly.2' from package in Offering 'default'
ℹ️ Force refreshing the receipt to get latest transactions from Apple.
ℹ️ Unable to load receipt, ensure you are logged in to a valid Apple account.
💰 Product purchase for 'my-app.monthly.2' failed with error: Error Domain=RevenueCat.ErrorCode Code=9 "The receipt is missing." UserInfo={NSLocalizedDescription=The receipt is missing., source_file=RevenueCat/PurchasesOrchestrator.swift:710, readable_error_code=MISSING_RECEIPT_FILE, source_function=handlePurchasedTransaction(_:storefront:)}
🍎‼️ The receipt is missing.

we also get occasional ℹ️ applicationDidBecomeActive logs in the middle of those.

It seems the receipt validation is still happening and erroring somewhere in that purchasing call chain. Is there any way to remove that?

Another error we get that seems to be before the receipt issue (possibly when they first hit purchase?):

💰 Product purchase for 'my-app.monthly.2' failed with error: PurchasesError(error: There was a problem with the App Store., userInfo: ["readable_error_code": "STORE_PROBLEM", "NSUnderlyingError": StoreKit.StoreKitError.unknown, "source_function": "asPurchasesError", "source_file": "RevenueCat/StoreKitError+Extensions.swift:43", "NSLocalizedDescription": "There was a problem with the App Store."])

I think I may be able to repro this in XCode here by disabling our storekit config file and logging in w/ a Sandbox account on the simulator. Standby.

Thanks.

-nate

@NachoSoto
Copy link
Contributor

Like the error says, that's an App Store bug. StoreKit is throwing StoreKit.StoreKitError.unknown, so there's not much we can do.

https://www.revenuecat.com/docs/app-store-rejections has some good guidance for how to deal with these rejections.

I recommend attaching a video showing that you can purchase the subscription yourself through TestFlight.

I completely understand this is frustrating, having had an App Store rejection myself for this same issue.
The main solution currently is to resubmit the app and hope that the reviewer doesn't run into this issue.

@natelowry
Copy link
Contributor Author

@NachoSoto yep, understood it's an App Store bug, but the actual purchase is failing because that bug and/or the missing receipt error are killing the Purchase per this error:

💰 Product purchase for 'my-app.monthly.2' failed with error: Error Domain=RevenueCat.ErrorCode Code=9 "The receipt is missing." UserInfo={NSLocalizedDescription=The receipt is missing., source_file=RevenueCat/PurchasesOrchestrator.swift:710, readable_error_code=MISSING_RECEIPT_FILE, source_function=handlePurchasedTransaction(_:storefront:)}

So it seems like there's still a spot where I need to have RC swallow that missing receipt error and not blow up the Purchase. Or am I seeing something wrong? (end of the week and I'm seeing double lol)

@natelowry
Copy link
Contributor Author

The reviewer provided some additional info that really helped. The Purchase is definitely going through on the Store side because you see the "You're all set" dialog pop up. But the RC purchase method still throws the error.

I was able to sorta reproduce it by putting a throw ErrorCode.networkError at PurchasesOrchestrator.swift:391. This is after the purchase on the StoreKit Product which I'm not 100% sure was successful, but this simulates what we're seeing pretty well.

I don't see where RC could throw that error other than from the StoreKit purchase call itself, but there is quite a bit of completion handling logic in there that I'm not super familiar with. Will keep investigating, but wanted to share that info.

@NachoSoto
Copy link
Contributor

The reviewer provided some additional info that really helped. The Purchase is definitely going through on the Store side because you see the "You're all set" dialog pop up. But the RC purchase method still throws the error.

Do you have logs for that? In the logs you've attached, the purchase is always showing as failed:

💰 Product purchase for 'my-app.monthly.2' failed with error: Error Domain=RevenueCat.ErrorCode Code=9 "The receipt is missing." UserInfo={NSLocalizedDescription=The receipt is missing., source_file=RevenueCat/PurchasesOrchestrator.swift:710, readable_error_code=MISSING_RECEIPT_FILE, source_function=handlePurchasedTransaction(_:storefront:)}

@NachoSoto
Copy link
Contributor

FYI I just filed a Feedback about this: FB11873941

@natelowry
Copy link
Contributor Author

@NachoSoto thanks! Also, thanks for troubleshooting it here too (I know you're busy with other features/fixes).

We are actually going to do our first release without any subscriptions just to get the app out there before the holidays. We'll be adding them back in later, so hoping that might be a better/easier process 🤞

@github-actions
Copy link

This issue has been automatically locked due to no recent activity after it was closed. Please open a new issue for related reports.

@github-actions github-actions bot locked and limited conversation to collaborators Dec 22, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants