AppTransaction.originalAppVersion returns "1.0" in App Review environment — not the actual build number

Hi,

I'm using AppTransaction.originalAppVersion to detect whether a user originally purchased the app under the old paid model, so I can automatically unlock the app for them as a courtesy when migrating to freemium.

Background

On iOS, originalAppVersion returns CFBundleVersion (the build number). When I transitioned the app from paid (v1.x) to freemium (v2.0), I defined a numeric threshold for CFBundleVersion to distinguish legacy purchasers from new users:

Build number below the threshold → v1.x purchase → auto-unlock Build number at or above the threshold → v2.0+ install → requires IAP In Production, originalAppVersion correctly returns the actual build number, and the comparison works as intended.

Detection logic (simplified)

// Determine environment via receipt URL
func detectStoreEnvironment() -> String {
    if let url = Bundle.main.appStoreReceiptURL,
       url.lastPathComponent == "sandboxReceipt" {
        return "Sandbox"
    }
    return "Production"
}

// Legacy check using numeric comparison
static func isLegacyPaidUser(version: String, threshold: String) -> Bool {
    guard !version.isEmpty else { return false }
    return version.compare(threshold, options: .numeric) == .orderedAscending
}

// In checkLegacyPurchase():
let version = appTransaction.originalAppVersion
let isLegacy = isLegacyPaidUser(version: version, threshold: legacyBuildNumberThreshold)
let env = detectStoreEnvironment()
let shouldAutoUnlock = isLegacy && env != "Sandbox"

The problem

I know that in the Sandbox environment, originalAppVersion always returns "1.0" — this is mentioned in the AppTransaction documentation. My code already suppresses the auto-unlock for Sandbox (env != "Sandbox").

However, it appears that the App Review environment also returns "1.0" for originalAppVersion. Because the receipt URL path component is "receipt" (not "sandboxReceipt"), my environment detection classifies it as "Production" — so the Sandbox suppression doesn't apply. The reviewer is incorrectly identified as a legacy paid user and the app is unlocked without a purchase.

This caused our v2.0 submission to be rejected under Guideline 2.1a.

Questions

Is it documented that the App Review environment returns "1.0" for AppTransaction.originalAppVersion, similar to Sandbox?

Is there a reliable way to detect the App Review environment specifically — separate from both Sandbox and Production? For example, does the receipt URL differ, or is there another API?

Is using originalAppVersion for legacy paid-user detection a supported pattern? If so, what is the recommended approach to handle the App Review case?

Any guidance would be greatly appreciated. Thank you.

AppTransaction.originalAppVersion returns "1.0" in App Review environment — not the actual build number
 
 
Q