Skip to content

[in_app_purchase_storekit] Use purchase(confirmIn:) for StoreKit 2 purchases - #13410

Merged
auto-submit[bot] merged 2 commits into
flutter:mainfrom
LouiseHsu:fix-sk2-purchase-confirm-in
Nov 4, 2026
Merged

auto-submit[bot] merged 2 commits into
flutter:mainfrom
LouiseHsu:fix-sk2-purchase-confirm-in

Conversation

@LouiseHsu

@LouiseHsu LouiseHsu commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes flutter/flutter#194373 Fixes flutter/flutter#195494

This PR switches StoreKit 2 purchases from the scene-less Product.purchase(options:) to Product.purchase(confirmIn:options:) whenever the plugin has a view controller attached to a window scene (iOS 17+) or an NSWindow (macOS 15.2+).

purchase(confirmIn:) is Apple's recommended purchase API for UIKit/AppKit apps. The
scene-less variant relies on StoreKit locating a UI anchor itself; on iOS 27 / iPadOS 27
that lookup fails (Could not find a UI anchor for … purchase) and the await never
returns. In the field this surfaced as App Review rejecting iPad subscription purchases
(#194373); on the iOS 27 simulator it surfaced as every StoreKit 2 purchase test timing
out (#195494). The scene-less call remains the fallback for iOS < 17 / macOS < 15.2 and
for headless engines whose registrar has no view in a window.

Test changes

The test setUp now hands the plugin a registrar stub backed by the test host's root
view controller, mirroring how the plugin runs inside a real Flutter app, so the
existing purchase tests exercise the confirmIn: path.

Two further adjustments were needed for the iOS 27 ipad simulator:

  • testGetTransactionJsonRepresentation makes a purchase and then asks the plugin to look
    that transaction up by its ID. It took the ID from the test framework's
    SKTestTransaction.originalTransactionIdentifier, which only worked because, on earlier
    OS versions, that value happened to equal the StoreKit 2 Transaction.id the plugin
    matches on (both are 0 for the first purchase). On iOS 27 StoreKitTest numbers
    transactions differently (identifier is a non-zero counter, originalTransactionIdentifier
    is reported as 0), so the lookup found nothing. The test now uses identifier, which is the
    field that corresponds to Transaction.id on every OS version.
  • testFailedProductUnavilablePurchase and testPurchaseWithIntroductoryOfferEligibilityJWS
    are skipped on iOS 27+. Both make a purchase that fails by design, and on iOS 27
    StoreKitTest shows an error dialog for failed purchases even with disableDialogs
    (iOS 27 release notes, known issue 184255116), so the purchase never completes. Since StoreKit serializes purchases per process, each of those also blocked every later purchase test, which is the cascade you see in the CI log.

Pre-Review Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools.
  • I read the [Tree Hygiene] page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran [the auto-formatter].
  • I signed the [CLA].
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I [linked to at least one issue that this PR fixes] in the description above.
  • I followed [the version and CHANGELOG instructions], using [semantic versioning] and the [repository CHANGELOG style], or I have commented below to indicate which documented exception this PR falls under[^1].
  • I updated/added any relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or I have commented below to indicate which [test exemption] this PR falls under[^1].
  • All existing and new tests are passing.

@flutter-dashboard

Copy link
Copy Markdown

This pull request is not mergeable in its current state, likely because of a merge conflict. Pre-submit CI jobs were not triggered. Pushing a new commit to this branch that resolves the issue will result in pre-submit jobs being scheduled.

@LouiseHsu
LouiseHsu force-pushed the fix-sk2-purchase-confirm-in branch from ec9ad44 to 606db97 Compare October 29, 2026 20:08
@LouiseHsu LouiseHsu changed the title init [in_app_purchase_storekit] Use purchase(confirmIn:) for StoreKit 2 purchases Oct 29, 2026
@LouiseHsu

LouiseHsu commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

@vashworth Could you try this branch? I ran locally with ios 27 on ipad simulator and it passed

…rchases

- Route StoreKit 2 purchases through Product.purchase(confirmIn:options:) when a view controller in a window scene (iOS 17+) or NSWindow (macOS 15.2+) is available, falling back to Product.purchase(options:).
- Wire FlutterPluginRegistrarStub into InAppPurchase2PluginTests.setUp so tests exercise the confirmIn: path.
- Update testGetTransactionJsonRepresentation to use SKTestTransaction.identifier instead of originalTransactionIdentifier, and skip failed-purchase tests on iOS 27+ due to StoreKitTest dialog hang (FB/184255116).
@LouiseHsu
LouiseHsu force-pushed the fix-sk2-purchase-confirm-in branch from 606db97 to bc23db6 Compare October 30, 2026 20:45
@LouiseHsu
LouiseHsu marked this pull request as ready for review November 2, 2026 22:57

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the StoreKit 2 purchase implementation to use Product.purchase(confirmIn:options:) when a view controller, window scene, or window is available, fallbacking to purchase(options:) otherwise. It also updates the test suite to anchor purchases to the host app's window scene and introduces a helper to skip failing simulator tests. The review feedback identifies a systematic typo where 17 is written as 27 across the changelog, code comments, and test skip conditions, which would cause the test skip logic to be bypassed on iOS 17 and 18.

) async throws -> Product.PurchaseResult {
#if os(iOS)
if let viewController = self.registrar?.viewController,
let windowScene = viewController.view.window?.windowScene

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uber nit: move this to if #available (iOS 17.0, *)? Just in case accessing windowScene has side effects.

return nil
}

/// Purchases `product`, anchoring the confirmation UI to the Flutter view when possible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in public API docs?

@vashworth

Copy link
Copy Markdown
Contributor

@LouiseHsu Is this ready to merge?

@LouiseHsu LouiseHsu added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 4, 2026
@auto-submit
auto-submit Bot merged commit d2b9d20 into flutter:main Nov 4, 2026
13 checks passed
bkonyi pushed a commit to bkonyi/flutter that referenced this pull request Nov 5, 2026
flutter/packages@670b30e...0478162

2026-11-05 [email protected] [material_ui] Document RefreshIndicator mouse
behavior on desktop and web (flutter/packages#13286)
2026-11-05 [email protected] [material_ui] Make
SearchAnchor.bar respect SearchBarThemeData padding
(flutter/packages#13360)
2026-11-04 [email protected] [pointer_interceptor] Use
dart.library.js_interop in example export (flutter/packages#13558)
2026-11-04 [email protected] [in_app_purchase_storekit] Use
purchase(confirmIn:) for StoreKit 2 purchases (flutter/packages#13410)
2026-11-04 [email protected] [material_ui] Fix
dismissal near minimum extent when shouldCloseOnMinExtent
(flutter/packages#12545)
2026-11-04 [email protected] [material_ui]
Add Material 3 Expressive `ElevatedButton` (flutter/packages#13084)
2026-11-04 [email protected] [go_router]
Stop inactive StatefulShellRoute branches from vetoing pops
(flutter/packages#12972)
2026-11-04 [email protected] Manual roll Flutter (stable)
from abaf9c5 to c197ca5 (14 revisions)
(flutter/packages#13542)
2026-11-04 [email protected] [in_app_purchase] Convert example app
to Swift (flutter/packages#13556)
2026-11-04 [email protected] Sync release-go_router-18.1.3 to
main (flutter/packages#13559)
2026-11-04 [email protected] Sync release-material_ui-1.10.0 to
main (flutter/packages#13546)
2026-11-04 [email protected]
[camera_android_camerax] Add magic number guideline to `TESTING.md`
(flutter/packages#13081)
2026-11-04 [email protected] Sync release-cupertino_ui-1.1.6 to
main (flutter/packages#13547)
2026-11-04 [email protected] [file_selector] Convert unit tests to
Kotlin (flutter/packages#13102)
2026-11-04 [email protected] Roll Flutter from
9347202 to 3623d2d (25 revisions) (flutter/packages#13555)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected] on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD p: in_app_purchase platform-ios platform-macos

Projects

None yet

4 participants