Repository navigation
[in_app_purchase] Support StoreKit 2 introductory offer eligibility JWS - #12584
auto-submit[bot] merged 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request adds support for setting introductory offer eligibility from a server-signed compact JWS via Sk2PurchaseParam.introductoryOfferEligibilityCompactJWS in the StoreKit 2 implementation of the in_app_purchase_storekit package. The value is forwarded verbatim to StoreKit 2 as Product.PurchaseOption.introductoryOfferEligibility(compactJWS:). Corresponding Pigeon messages, platform wrappers, Swift implementation, unit tests, and integration tests have been updated to support this new parameter. I have no feedback to provide as there are no review comments.
|
@googlebot I signed it! |
1 similar comment
|
@googlebot I signed it! |
|
@joelbg-cloud Thanks for contributing! Looks like there are some merge conflicts, could you resolve them? |
Adds an optional StoreKit 2-specific field to Sk2PurchaseParam, `introductoryOfferEligibilityCompactJWS`, which is forwarded unchanged to `Product.PurchaseOption.introductoryOfferEligibility(compactJWS:)`. This lets an app's own server decide, per purchase, whether an introductory offer applies, by signing a compact JWS with an App Store Connect In-App Purchase key. The plugin treats the JWS as opaque: it never generates, signs, parses, validates or inspects it. This is distinct from the existing `isIntroductoryOfferEligible(productId)`, which reads the App Store's default eligibility and cannot express a server-signed per-purchase decision. The option is optional and null by default; when null no purchase option is added and the App Store applies its own default eligibility, so existing behavior is unchanged. StoreKit 1 is untouched. No availability gate is needed: the StoreKit API is back deployed and is available on the same iOS 15.0 / macOS 12.0 floor as the surrounding StoreKit 2 extension. Fixes flutter/flutter#191668
2d7646e to
6375840
Compare
|
autosubmit label was removed for flutter/packages/12584, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label. |
…er#192485) flutter/packages@9af9c60...36e088a 2026-09-09 daniel.leon@cloudsufi.com [quick_actions] Adopt code-excerpts for README (flutter/packages#12643) 2026-09-08 65155920+0xharkirat@users.noreply.github.com [camera_web] Fix TypeError when reading the torch capability (flutter/packages#12647) 2026-09-08 gibbonsj97@gmail.com [google_maps_flutter_web] Avoid replacing advanced marker content on move (flutter/packages#11952) 2026-09-08 mit@google.com [material_ui][cupertino_ui] Change issue tracker label in pubspec.yaml (flutter/packages#12792) 2026-09-08 saurabhmirajkar000@gmail.com [material_ui] Fix FilledButton Material 3 default style docs (flutter/packages#12620) 2026-09-08 21270878+elliette@users.noreply.github.com [infra] Use a modified no-response workflow in flutter/packages (flutter/packages#12745) 2026-09-08 21270878+elliette@users.noreply.github.com [material_ui] Migrate M3 Banner template to use new gen_defaults (flutter/packages#12734) 2026-09-08 engine-flutter-autoroll@skia.org Roll Flutter from 63170e9 to b444e78 (13 revisions) (flutter/packages#12791) 2026-09-08 fluttergithubbot@gmail.com Sync release-go_router-18.0.1 to main (flutter/packages#12725) 2026-09-08 fluttergithubbot@gmail.com Sync release-material_ui-1.1.1 to main (flutter/packages#12726) 2026-09-08 stuartmorgan@google.com [tool] Adopt `platform` 3.2.0 (flutter/packages#12789) 2026-09-07 50643541+Mairramer@users.noreply.github.com [material_ui] Fix SliverGeometry maxPaintExtent assertion in CarouselView.weighted (flutter/packages#12563) 2026-09-05 engine-flutter-autoroll@skia.org Roll Flutter from 5a6cfa7 to 63170e9 (15 revisions) (flutter/packages#12767) 2026-09-04 engine-flutter-autoroll@skia.org Manual roll Flutter from 70797e1 to 5a6cfa7 (52 revisions) (flutter/packages#12760) 2026-09-04 brackenavaron@gmail.com [material_ui] port drawer tests over from flutter/widgets (flutter/packages#12711) 2026-09-04 tarrinneal@gmail.com add cooldown (flutter/packages#12708) 2026-09-04 joeldumasbg@gmail.com [in_app_purchase] Support StoreKit 2 introductory offer eligibility JWS (flutter/packages#12584) 2026-09-04 21270878+elliette@users.noreply.github.com [material_ui] Migrate M3 Badge template to use new gen_defaults (flutter/packages#12733) 2026-09-04 stuartmorgan@google.com [google_maps_flutter] Convert heatmap controller to Swift (flutter/packages#12713) 2026-09-04 97480502+b-luk@users.noreply.github.com [material_ui] Remove unused `maintainState` constructor parameter in `scaffold_test.dart` (flutter/packages#12754) 2026-09-04 a1rwulf@users.noreply.github.com [video_player_avfoundation] Route video over AirPlay (flutter/packages#12490) 2026-09-04 jerome.dellamaria@proton.me [google_fonts] Add google_fonts_lite file to allow tree-shaking of the other huge files (flutter/packages#11433) 2026-09-04 engine-flutter-autoroll@skia.org Roll Flutter from 0cbd1a4 to 70797e1 (27 revisions) (flutter/packages#12727) 2026-09-04 stuartmorgan@google.com Update Chrome for stable tests (flutter/packages#12747) 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 flutter-ecosystem@google.com 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
…WS (flutter#12584) Adds an optional StoreKit 2-specific field to `Sk2PurchaseParam` that forwards a server-signed compact JWS to [`Product.PurchaseOption.introductoryOfferEligibility(compactJWS:)`](https://developer.apple.com/documentation/storekit/product/purchaseoption/introductoryoffereligibility(compactjws:)). Fixes flutter/flutter#191668 ## Problem StoreKit 2 lets an app's own server decide, per purchase, whether an introductory offer applies. The app receives a compact JWS signed with an App Store Connect In-App Purchase key ([Generating JWS to sign App Store requests](https://developer.apple.com/documentation/storekit/generating-jws-to-sign-app-store-requests)) whose `allowIntroductoryOffer` claim Apple documents as "a Boolean value, `true` or `false`, that determines whether the customer is eligible for an introductory offer". The plugin did not expose this purchase option, so apps whose eligibility rules live on their own server could not use the plugin's purchase path. This is distinct from the existing `isIntroductoryOfferEligible(productId)`, which wraps `Product.SubscriptionInfo.isEligibleForIntroOffer` and **reads** the App Store's *default* eligibility. That read cannot express a server-signed per-purchase decision. ## Implementation ```dart Sk2PurchaseParam( productDetails: productDetails, introductoryOfferEligibilityCompactJWS: compactJWSFromMyServer, ); ``` The value flows through `SK2ProductPurchaseOptions` and the Pigeon message to the Swift purchase-option builder: ```swift if let compactJWS = options?.introductoryOfferEligibilityCompactJWS { purchaseOptions.insert(.introductoryOfferEligibility(compactJWS: compactJWS)) } ``` The JWS is opaque to the plugin: it is never generated, signed, parsed, validated or inspected. Generated Pigeon files were regenerated with the repository's canonical `dart run pigeon --input pigeons/sk2_pigeon.dart`. ## Availability No `#available` gate is required. `introductoryOfferEligibility(compactJWS:)` is declared `@backDeployed(before: iOS 18.4, macOS 15.4, tvOS 18.4, watchOS 11.4, visionOS 2.4)` with availability iOS 15.0+ / macOS 12.0+, which is the same floor as the existing `@available(iOS 15.0, macOS 12.0, *)` StoreKit 2 extension it is added to. ## Backwards compatibility Not breaking: - the field is optional and null by default - when null, no purchase option is added and the App Store applies its own default eligibility, so existing apps behave identically - the new Pigeon field is appended, so existing encoded payloads decode unchanged - StoreKit 1 is untouched - `isIntroductoryOfferEligible`, promotional offers, win-back offers, `appAccountToken` and `quantity` are unchanged and continue to work alongside the new option ## Tests Dart (`in_app_purchase_storekit_2_platform_test.dart`), all passing: - null JWS -> no option is sent - non-null JWS -> forwarded unchanged - JWS coexists with `appAccountToken`, `quantity`, `promotionalOffer` and `winBackOfferId` - a generic `PurchaseParam` sends no JWS Native (`InAppPurchaseStoreKit2PluginTests.swift`): - `testPurchaseWithIntroductoryOfferEligibilityJWS` drives a purchase with the option set and passes. ## Known limitation in local native verification I could not run the full `RunnerTests` suite locally. It crashes in `StoreKit2TranslatorTests.setUp()` (`StoreKit2TranslatorTests.swift:69`) with `EXC_BREAKPOINT`/`SIGTRAP`, and the existing purchase-success tests fail with `PigeonError` plus an asynchronous timeout. This is independent of this change: I reproduced the identical crash signature and the identical purchase-test failure on the unmodified upstream base (9f595f3) under the same Xcode 26.6 / iOS 26.5 simulator. It matches flutter/flutter#184678, including the same file and line and the same `SKTestSession` `SKInternalErrorDomain Code=3` symptom. To be precise about what was verified locally: the patch-specific native test passes, and `dart format`, `flutter analyze`, the 106 Dart unit tests and `swift-format lint --strict` are all clean. I am **not** claiming the full native suite passes. Because product lookup fails in that environment, the native test exercises the call path but cannot by itself assert the option reached StoreKit; the deterministic assertions for that are the Dart tests above, and the Swift call site was additionally verified with `swiftc -typecheck` against the iOS 15 target. No unrelated upstream test was modified or worked around. ## Pre-Review Checklist [^1]: See "Known limitation in local native verification" above regarding the pre-existing native suite failure tracked in flutter/flutter#184678.
…WS (flutter#12584) Adds an optional StoreKit 2-specific field to `Sk2PurchaseParam` that forwards a server-signed compact JWS to [`Product.PurchaseOption.introductoryOfferEligibility(compactJWS:)`](https://developer.apple.com/documentation/storekit/product/purchaseoption/introductoryoffereligibility(compactjws:)). Fixes flutter/flutter#191668 ## Problem StoreKit 2 lets an app's own server decide, per purchase, whether an introductory offer applies. The app receives a compact JWS signed with an App Store Connect In-App Purchase key ([Generating JWS to sign App Store requests](https://developer.apple.com/documentation/storekit/generating-jws-to-sign-app-store-requests)) whose `allowIntroductoryOffer` claim Apple documents as "a Boolean value, `true` or `false`, that determines whether the customer is eligible for an introductory offer". The plugin did not expose this purchase option, so apps whose eligibility rules live on their own server could not use the plugin's purchase path. This is distinct from the existing `isIntroductoryOfferEligible(productId)`, which wraps `Product.SubscriptionInfo.isEligibleForIntroOffer` and **reads** the App Store's *default* eligibility. That read cannot express a server-signed per-purchase decision. ## Implementation ```dart Sk2PurchaseParam( productDetails: productDetails, introductoryOfferEligibilityCompactJWS: compactJWSFromMyServer, ); ``` The value flows through `SK2ProductPurchaseOptions` and the Pigeon message to the Swift purchase-option builder: ```swift if let compactJWS = options?.introductoryOfferEligibilityCompactJWS { purchaseOptions.insert(.introductoryOfferEligibility(compactJWS: compactJWS)) } ``` The JWS is opaque to the plugin: it is never generated, signed, parsed, validated or inspected. Generated Pigeon files were regenerated with the repository's canonical `dart run pigeon --input pigeons/sk2_pigeon.dart`. ## Availability No `#available` gate is required. `introductoryOfferEligibility(compactJWS:)` is declared `@backDeployed(before: iOS 18.4, macOS 15.4, tvOS 18.4, watchOS 11.4, visionOS 2.4)` with availability iOS 15.0+ / macOS 12.0+, which is the same floor as the existing `@available(iOS 15.0, macOS 12.0, *)` StoreKit 2 extension it is added to. ## Backwards compatibility Not breaking: - the field is optional and null by default - when null, no purchase option is added and the App Store applies its own default eligibility, so existing apps behave identically - the new Pigeon field is appended, so existing encoded payloads decode unchanged - StoreKit 1 is untouched - `isIntroductoryOfferEligible`, promotional offers, win-back offers, `appAccountToken` and `quantity` are unchanged and continue to work alongside the new option ## Tests Dart (`in_app_purchase_storekit_2_platform_test.dart`), all passing: - null JWS -> no option is sent - non-null JWS -> forwarded unchanged - JWS coexists with `appAccountToken`, `quantity`, `promotionalOffer` and `winBackOfferId` - a generic `PurchaseParam` sends no JWS Native (`InAppPurchaseStoreKit2PluginTests.swift`): - `testPurchaseWithIntroductoryOfferEligibilityJWS` drives a purchase with the option set and passes. ## Known limitation in local native verification I could not run the full `RunnerTests` suite locally. It crashes in `StoreKit2TranslatorTests.setUp()` (`StoreKit2TranslatorTests.swift:69`) with `EXC_BREAKPOINT`/`SIGTRAP`, and the existing purchase-success tests fail with `PigeonError` plus an asynchronous timeout. This is independent of this change: I reproduced the identical crash signature and the identical purchase-test failure on the unmodified upstream base (9f595f3) under the same Xcode 26.6 / iOS 26.5 simulator. It matches flutter/flutter#184678, including the same file and line and the same `SKTestSession` `SKInternalErrorDomain Code=3` symptom. To be precise about what was verified locally: the patch-specific native test passes, and `dart format`, `flutter analyze`, the 106 Dart unit tests and `swift-format lint --strict` are all clean. I am **not** claiming the full native suite passes. Because product lookup fails in that environment, the native test exercises the call path but cannot by itself assert the option reached StoreKit; the deterministic assertions for that are the Dart tests above, and the Swift call site was additionally verified with `swiftc -typecheck` against the iOS 15 target. No unrelated upstream test was modified or worked around. ## Pre-Review Checklist [^1]: See "Known limitation in local native verification" above regarding the pre-existing native suite failure tracked in flutter/flutter#184678.
Adds an optional StoreKit 2-specific field to
Sk2PurchaseParamthat forwards a server-signed compact JWS toProduct.PurchaseOption.introductoryOfferEligibility(compactJWS:).Fixes flutter/flutter#191668
Problem
StoreKit 2 lets an app's own server decide, per purchase, whether an introductory offer applies. The app receives a compact JWS signed with an App Store Connect In-App Purchase key (Generating JWS to sign App Store requests) whose
allowIntroductoryOfferclaim Apple documents as "a Boolean value,trueorfalse, that determines whether the customer is eligible for an introductory offer".The plugin did not expose this purchase option, so apps whose eligibility rules live on their own server could not use the plugin's purchase path.
This is distinct from the existing
isIntroductoryOfferEligible(productId), which wrapsProduct.SubscriptionInfo.isEligibleForIntroOfferand reads the App Store's default eligibility. That read cannot express a server-signed per-purchase decision.Implementation
The value flows through
SK2ProductPurchaseOptionsand the Pigeon message to the Swift purchase-option builder:The JWS is opaque to the plugin: it is never generated, signed, parsed, validated or inspected.
Generated Pigeon files were regenerated with the repository's canonical
dart run pigeon --input pigeons/sk2_pigeon.dart.Availability
No
#availablegate is required.introductoryOfferEligibility(compactJWS:)is declared@backDeployed(before: iOS 18.4, macOS 15.4, tvOS 18.4, watchOS 11.4, visionOS 2.4)with availability iOS 15.0+ / macOS 12.0+, which is the same floor as the existing@available(iOS 15.0, macOS 12.0, *)StoreKit 2 extension it is added to.Backwards compatibility
Not breaking:
isIntroductoryOfferEligible, promotional offers, win-back offers,appAccountTokenandquantityare unchanged and continue to work alongside the new optionTests
Dart (
in_app_purchase_storekit_2_platform_test.dart), all passing:appAccountToken,quantity,promotionalOfferandwinBackOfferIdPurchaseParamsends no JWSNative (
InAppPurchaseStoreKit2PluginTests.swift):testPurchaseWithIntroductoryOfferEligibilityJWSdrives a purchase with the option set and passes.Known limitation in local native verification
I could not run the full
RunnerTestssuite locally. It crashes inStoreKit2TranslatorTests.setUp()(StoreKit2TranslatorTests.swift:69) withEXC_BREAKPOINT/SIGTRAP, and the existing purchase-success tests fail withPigeonErrorplus an asynchronous timeout.This is independent of this change: I reproduced the identical crash signature and the identical purchase-test failure on the unmodified upstream base (9f595f3) under the same Xcode 26.6 / iOS 26.5 simulator. It matches flutter/flutter#184678, including the same file and line and the same
SKTestSessionSKInternalErrorDomain Code=3symptom.To be precise about what was verified locally: the patch-specific native test passes, and
dart format,flutter analyze, the 106 Dart unit tests andswift-format lint --strictare all clean. I am not claiming the full native suite passes. Because product lookup fails in that environment, the native test exercises the call path but cannot by itself assert the option reached StoreKit; the deterministic assertions for that are the Dart tests above, and the Swift call site was additionally verified withswiftc -typecheckagainst the iOS 15 target.No unrelated upstream test was modified or worked around.
Pre-Review Checklist
cla/googlecheck is currently failing and I will re-run it once signed.[shared_preferences]///).Footnotes
See "Known limitation in local native verification" above regarding the pre-existing native suite failure tracked in [Only on 26.3/26.4][in_app_purchase_storekit] iOS native tests failing with timeouts, SKInternalErrorDomain, and crashes flutter#184678. ↩ ↩2