Repository navigation
Description
What package does this bug report belong to?
in_app_purchase
What target platforms are you seeing this bug on?
iOS
Have you already upgraded your packages?
Yes
Dependency versions
pubspec.lock
in_app_purchase:
version: "3.3.1"
in_app_purchase_platform_interface:
version: "1.4.1"
in_app_purchase_storekit:
version: "0.4.13+2"
Steps to reproduce
We could not reproduce this ourselves. App Review hit it on two submissions in a row:
- Our app is a universal iOS app (iPhone and iPad) on the UIScene lifecycle (
FlutterSceneDelegate), with StoreKit 2 enabled inin_app_purchase_storekit. - On an iPad Air 11-inch (M3) running iPadOS 27.0, App Review opened the purchase screen and bought an auto-renewable subscription (
buyNonConsumablewith an app account token). - The purchase failed, and Apple rejected the app under Guideline 2.1(b): "we tried to purchase the subscription and the app returned an error".
Expected results
Apple's payment sheet opens and the purchase completes, as it does on iPhone.
Actual results
The purchase fails before anything is charged.
- The sandbox App Store Server Notifications history holds no
SUBSCRIBEDnotification from App Review in any review round, so StoreKit refused before the purchase completed. - The same builds complete sandbox purchases on iPhone.
- On the iPad simulator with iPadOS 26.5 the payment sheet opens. We have no iPadOS 27 device, so we don't know the exact error StoreKit returned.
Analysis
InAppPurchasePlugin+StoreKit2.swift buys with product.purchase(options: purchaseOptions), which passes no UI context. Apple's documentation for Product.purchase(options:) says:
Use
purchase(confirmIn:options:)for apps that use UIKit. […] Usepurchase(options:)for apps that runs on watchOS.
A Flutter iOS app is a UIKit app. Without a scene, StoreKit has to pick the window for the payment sheet itself. On iPad (resizable windows, Stage Manager) that seems to be the step that fails.
There are two more pointers in the same direction:
- In the iOS 18 SDK, the deprecation messages for the StoreKit 1 purchase APIs name
Product.purchase(confirmIn:options:)as the replacement (see [in_app_purchase_storekit] Uses many deprecated APIs #170442). - The plugin already resolves the window scene through
registrar?.viewController?.view.window?.windowSceneforpresentOfferCodeRedeemSheet.
Proposal
On iOS 17+, confirm the purchase in the window scene of the Flutter view. If that view has no window, use the foreground scene. Keep purchase(options:) only when there is no scene or the OS is older. On macOS 15.2+, purchase(confirmIn:) with the NSWindow could be used the same way.
We shipped exactly this as a patched copy of 0.4.13+2, and App Review approved the next build on the first try. That evidence is circumstantial, because Apple doesn't say which device the reviewer used, but the change matches Apple's guidance. If this approach works for you, we're happy to open a PR with tests.
Code sample
The app-side code is the standard InAppPurchase.instance.buyNonConsumable(purchaseParam: ...) with StoreKit 2 enabled; nothing app-specific is involved. The change we shipped in InAppPurchasePlugin+StoreKit2.swift:
// In purchase(id:options:completion:):
let result = try await confirmPurchase(of: product, options: purchaseOptions)
@MainActor
private func confirmPurchase(
of product: Product, options: Set<Product.PurchaseOption>
) async throws -> Product.PurchaseResult {
#if os(iOS)
if #available(iOS 17.0, *), let scene = purchaseScene() {
return try await product.purchase(confirmIn: scene, options: options)
}
#endif
return try await product.purchase(options: options)
}
#if os(iOS)
/// The scene showing the Flutter view, else the app's foreground scene.
@MainActor
private func purchaseScene() -> UIScene? {
if let scene = registrar?.viewController?.view.window?.windowScene {
return scene
}
let scenes = UIApplication.shared.connectedScenes
return scenes.first { $0.activationState == .foregroundActive }
?? scenes.first { $0.activationState == .foregroundInactive }
}
#endifScreenshots or Video
None. App Review sent no screenshot.
Logs
None available from the review device.
Flutter Doctor output
Flutter 3.47.6 (stable). The iOS builds run on Bitrise's latest stable Xcode stack (osx-xcode-latest-stable).
Related
A similar report on the Apple Developer Forums, with no resolution: https://developer.apple.com/forums/thread/821419. It also involves Flutter in_app_purchase, an iPad Air 11-inch (M3) with iPadOS 26.4, and a 2.1(b) rejection.
Activity
suryaKommana2662 commentedon Oct 14, 2026
@salihagic Thanks for the report and for sharing your findings. The issue may be related to how StoreKit handles purchases on iPad. Further testing is needed to confirm the cause and verify the proposal. routing to the
team-ecosystemfor further investigation[in_app_purchase_storekit] Use purchase(confirmIn:) for StoreKit 2 pu…
[in_app_purchase_storekit] Use purchase(confirmIn:) for StoreKit 2 pu…