diff --git a/CHANGELOG.md b/CHANGELOG.md index d82cd45164..28bd424737 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup - Fixes issue with compiling on Xcode 26.4 beta. - Fixes dashboard display of multiple active entitlements. +- Makes `device.isSandbox` more reliable. ## 4.13.0 diff --git a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift index 41812f290f..f5512bc875 100644 --- a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift +++ b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift @@ -192,6 +192,13 @@ class DeviceHelper { return "true" #else + // Prefer AppTransaction.environment (iOS 16+) when available, + // as it reliably detects sandbox for all purchase types including + // Stripe and non-StoreKit flows. + if let isSandbox = ReceiptManager.isSandboxEnvironment { + return "\(isSandbox)" + } + guard let url = Bundle.main.appStoreReceiptURL else { return "false" } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/Receipt Manager/ReceiptManager.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/Receipt Manager/ReceiptManager.swift index 60672ac564..ef82f876e6 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/Receipt Manager/ReceiptManager.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/Receipt Manager/ReceiptManager.swift @@ -32,6 +32,9 @@ actor ReceiptManager { private unowned let storage: Storage static var appTransactionId: String? static var appId: UInt64? + /// Set from `AppTransaction.shared` when available (iOS 16+). + /// `true` means sandbox, `false` means production, `nil` means not yet determined. + static var isSandboxEnvironment: Bool? init( storeKitVersion: SuperwallOptions.StoreKitVersion, @@ -91,6 +94,7 @@ actor ReceiptManager { .unverified(let transaction, _): Self.appTransactionId = transaction.appTransactionID Self.appId = transaction.appID + Self.isSandboxEnvironment = transaction.environment == .sandbox || transaction.environment == .xcode if Superwall.isInitialized { registerAppTransactionIdIfNeeded() Superwall.shared.dequeueIntegrationAttributes() diff --git a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift index e25409f156..1568e0346e 100644 --- a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift @@ -79,7 +79,27 @@ class ReceiptManagerTests: XCTestCase { } func test_isFreeTrialAvailable() { - + + } + + func test_isSandboxEnvironment_defaultsToNil() { + // Reset to default state + ReceiptManager.isSandboxEnvironment = nil + XCTAssertNil(ReceiptManager.isSandboxEnvironment) + } + + func test_isSandboxEnvironment_canBeSetToTrue() { + ReceiptManager.isSandboxEnvironment = true + XCTAssertEqual(ReceiptManager.isSandboxEnvironment, true) + // Clean up + ReceiptManager.isSandboxEnvironment = nil + } + + func test_isSandboxEnvironment_canBeSetToFalse() { + ReceiptManager.isSandboxEnvironment = false + XCTAssertEqual(ReceiptManager.isSandboxEnvironment, false) + // Clean up + ReceiptManager.isSandboxEnvironment = nil } /* // MARK: - Test processing of receipt data