Skip to content

[in_app_purchase_storekit] StoreKit 2 purchase uses Product.purchase(options:) without a scene; App Review rejected subscription purchases on iPad (iPadOS 27) #194373

Closed
flutter/packages
#13410

Description

@salihagic

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:

  1. Our app is a universal iOS app (iPhone and iPad) on the UIScene lifecycle (FlutterSceneDelegate), with StoreKit 2 enabled in in_app_purchase_storekit.
  2. On an iPad Air 11-inch (M3) running iPadOS 27.0, App Review opened the purchase screen and bought an auto-renewable subscription (buyNonConsumable with an app account token).
  3. 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 SUBSCRIBED notification 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. […] Use purchase(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?.windowScene for presentOfferCodeRedeemSheet.

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 }
  }
#endif

Screenshots 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

  1. suryaKommana2662 commented on Oct 14, 2026

    @suryaKommana2662
    Contributor

    @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-ecosystem for further investigation

  2. added
    platform-iosiOS applications specifically
    packageflutter/packages repository. See also p: labels.
    and removed
    in triagePresently being triaged by the triage team
    on Oct 14, 2026
  3. added
    fyi-ecosystemFor the attention of Ecosystem team
    team-iosOwned by iOS platform team
    and removed on Oct 20, 2026
  4. added
    P1High-priority issues at the top of the work list
    triaged-iosTriaged by iOS platform team
    on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listp: in_app_purchasePlugin for in-app purchasepackageflutter/packages repository. See also p: labels.platform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions