Asked 1 month ago by ZenithGuardian471
Why Does My StoreKit 2 Business Model Change Work in Testing But Not in Production?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by ZenithGuardian471
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm updating my business model according to Apple’s guidelines and implemented a task in the main view of my app. The feature works perfectly on the simulator, physical devices, and TestFlight. However, after releasing the new version to production, all users (both new and existing) are asked to subscribe, even though the console shows that transactions are correctly retrieved. The challenge is that I’m not sure how to view the console logs in production to debug what’s being retrieved.
Here is the sandbox receipt I obtained
JSONAppTransaction.shared obtained: { "applicationVersion" : "1", "bundleId" : ***** "deviceVerification" : "6M0Nnw14nSEOBVTPE//EfnWSwLm7LFSlrpFEwxgH74SBHp5dSzBEm896Uvo42mwr", "deviceVerificationNonce" : "8a8238c0-0aee-41e6-bfb0-1cfc52b70fb6", "originalApplicationVersion" : "1.0", "originalPurchaseDate" : 1375340400000, "receiptCreationDate" : 1737577840917, "receiptType" : "Sandbox", "requestDate" : 1737577840917 }
This is the processing log while verifying the receipt
BASHNew business model change: 1.7 Original versionéis components: ["1", "0"] Major version: 1, Minor version: 0 This user is premium. Original version: 1.0
This is my task implementation
SWIFT.task { do { let shared = try await AppTransaction.shared if case .verified(let appTransaction) = shared { let newBusinessModelVersion = (1, 7) // Represented as (major, minor) let versionComponents = appTransaction.originalAppVersion.split(separator: ".") if let majorVersion = versionComponents.first.flatMap({ Int($0) }), let minorVersion = versionComponents.dropFirst().first.flatMap({ Int($0) }) { if (majorVersion, minorVersion) < newBusinessModelVersion { self.premiumStatus.isPremium = true isPremium = true } else { let customerInfo = try await Purchases.shared.customerInfo() self.premiumStatus.isPremium = customerInfo.entitlements["premium"]?.isActive == true isPremium = self.premiumStatus.isPremium } } else { print("Error: obteining version components") } } else { print("Not verified") } } catch { print("Error processing transaction: \(error.localizedDescription)") } }
I’m looking for suggestions on how to capture production logs to better debug what’s happening with receipt retrieval and processing, and any insight as to why the behavior differs from testing environments.
Comments 0
•Answers 3
•Views 64
Answer by TychoAI Agent • 1 month ago
Below are a few tips based on Apple’s guidelines and our documentation for troubleshooting your issue:
In production you won’t see a live “console” like you do during development. To view logs from a production device you can:
Make sure that your receipt contains the expected information. Note the following:
originalApplicationVersion
field in the receipt is being set correctly. For users who have never made a purchase in your app, the receipt might have different characteristics versus a development or TestFlight receipt.Your code sets premium access if the original app version is less than the new business model version. In production:
originalApplicationVersion
value is what you expect. If the users’ receipts (even from users who purchased previously) do not have the correct version format (e.g., “1.0”), then your version comparison might not work in production.Connect a Production Device: Use Xcode’s Devices and Simulators or Console.app to capture live logs while reproducing the issue on a production-installed app.
Add More Detailed Logging: For example, log the entire decoded receipt information (using OSLog without exposing sensitive data), so you can compare what’s actually received in production versus what you see in sandbox/TestFlight.
Review Receipt Validation Flow: Ensure your receipt validation on the device and your server (if any) checks that align with Apple’s guidelines. See Validating receipts on the device for details.
By using these techniques you should be able to capture and review the production logs and better understand why your business model change isn’t recognized as expected.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by NovaExplorer535 • 1 month ago
This seems to be exactly what’s happening, Apple does not consider the original version as the commercial version. I can imagine that it takes the build uploaded to TestFlight (not even the one shown in Xcode)
In the Xcode console, when retrieving the current version, it prints the build number.
The issue with this is that for each version of the app, I have uploaded 3-4 or even 10 builds, and all of these (users who paid for the app) must be able to download it as premium. Because of this, I assume that there are users with an original version between 1 and 10, so this value is not useful for me.
Don't you think OriginalPurchaseDate could be a solution?
SWIFTfunc fetchActiveTransactions() async { let cohortDateComponents = DateComponents(year: 2025, month: 1, day: 24, hour: 0, minute: 0, second: 0) let cohortDate = Calendar.current.date(from: cohortDateComponents)!) let shared = try? await AppTransaction.shared print(shared as Any) if case .verified(let appTransaction) = shared { print("Verified \(appTransaction)") let originalPurchaseDate = appTransaction.originalPurchaseDate print("Original Purchase Date: \(originalPurchaseDate)") if originalPurchaseDate < cohortDate { print("\(originalPurchaseDate) is before to \(cohortDate). Is premium.") await MainActor.run { print("User is premium because \(originalPurchaseDate) is before to \(cohortDate).") self.isPremium = true self.isLegacyPremium = true self.purchaseDate = appTransaction.originalPurchaseDate } return } else { print("\(originalPurchaseDate) is after \(cohortDate). You need to validate it in RevenueCat.") do { print("Checking premium status in RevenueCat...") let customerInfo = try await Purchases.shared.customerInfo() self.isPremium = customerInfo.entitlements["premium"]?.isActive == true print("Premium State: \(self.isPremium)") } catch { print("Error obtaining customer info from RevenueCat: \(error.localizedDescription)") } } } else { print("Cannot fetch transactions.") } }
No comments yet.
Answer by NeptunianTraveler643 • 1 month ago
It sounds like the issue might be related to how the CFBundleVersion
is defined in your app. If you’re using a build number (e.g., an incremental count of commits) for CFBundleVersion
instead of the app’s version number (e.g., major.minor.patch), this could cause problems in production.
Apple’s documentation for the originalAppVersion
states:
The originalAppVersion [...] contains the original value of the CFBundleShortVersionString for apps running on macOS, and the original value of the CFBundleVersion for apps running on all other platforms.
In the sandbox testing environment, the originalAppVersion value is always 1.0.
This behavior explains why everything works correctly in testing environments (simulator, physical devices, and TestFlight) where originalAppVersion
defaults to 1.0. However, in production, the value of CFBundleVersion
is used, and if it doesn’t match the expected format, it could lead to issues with determining whether a user is new or existing.
To address this, ensure that CFBundleVersion
contains the app's version number (major.minor.patch) for production builds, or update your logic to account for your current use of build numbers.
No comments yet.
No comments yet.