Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.4.9

* Add support for offer codes in StoreKit 2.
* Updates minimum supported SDK version to Flutter 3.38.

## 0.4.8+1

* Fixes StoreKit 2 purchase flow to send cancelled/pending/unverified results to `purchaseStream`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,67 @@ extension InAppPurchasePlugin: InAppPurchase2API {
}
}

func presentOfferCodeRedeemSheet(completion: @escaping (Result<Void, Error>) -> Void) {
#if os(iOS)
if #available(iOS 16.0, *) {
guard let windowScene = self.registrar?.viewController?.view.window?.windowScene else {
let error = PigeonError(
code: "storekit2_missing_key_window_scene",
message: "Failed to fetch key window scene",
details: "registrar.viewController.view.window.windowScene returned nil."
)
completion(.failure(error))
return
}
Task { @MainActor in
do {
try await AppStore.presentOfferCodeRedeemSheet(in: windowScene)
completion(.success(()))
} catch {
completion(.failure(error))
}
}
} else {
completion(
.failure(
PigeonError(
code: "storekit2_unsupported_platform_version",
message: "Offer code redemption requires iOS 16+",
details: nil
)))
}
#elseif os(macOS)
if #available(macOS 15.0, *) {
guard let viewController = self.registrar?.viewController else {
let error = PigeonError(
code: "storekit2_missing_view_controller",
message: "Failed to fetch view controller",
details: "registrar.viewController returned nil."
)
completion(.failure(error))
return
}

Task { @MainActor in
do {
try await AppStore.presentOfferCodeRedeemSheet(from: viewController)
completion(.success(()))
} catch {
completion(.failure(error))
}
}
} else {
completion(
.failure(
PigeonError(
code: "storekit2_unsupported_platform_version",
message: "Offer code redemption requires macOS 15+",
details: nil
)))
}
#endif
}
Comment on lines +324 to +383
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

If the #available check fails, the completion handler is never called. This will cause the calling Dart code to hang indefinitely. You should add else blocks to handle cases where the API is not available on the current OS version and call the completion handler with an appropriate error.

  func presentOfferCodeRedeemSheet(completion: @escaping (Result<Void, Error>) -> Void) {
    #if os(iOS)
      if #available(iOS 16.0, *) {
        guard let windowScene = self.registrar?.viewController?.view.window?.windowScene else {
          let error = PigeonError(
            code: "storekit2_missing_key_window_scene",
            message: "Failed to fetch key window scene",
            details: "registrar.viewController.view.window.windowScene returned nil."
          )
          completion(.failure(error))
          return
        }
        Task { @MainActor in
          do {
            try await AppStore.presentOfferCodeRedeemSheet(in: windowScene)
            completion(.success(()))
          } catch {
            completion(.failure(error))
          }
        }
      } else {
        let error = PigeonError(
          code: "unavailable",
          message: "presentOfferCodeRedeemSheet is only available on iOS 16.0 and newer.",
          details: nil)
        completion(.failure(error))
      }
    #elseif os(macOS)
      if #available(macOS 15.0, *) {
        guard let viewController = self.registrar?.viewController else {
          let error = PigeonError(
            code: "storekit2_missing_view_controller",
            message: "Failed to fetch view controller",
            details: "registrar.viewController returned nil."
          )
          completion(.failure(error))
          return
        }

        Task { @MainActor in
          do {
            try await AppStore.presentOfferCodeRedeemSheet(from: viewController)
            completion(.success(()))
          } catch {
            completion(.failure(error))
          }
        }
      } else {
        let error = PigeonError(
          code: "unavailable",
          message: "presentOfferCodeRedeemSheet is only available on macOS 15.0 and newer.",
          details: nil)
        completion(.failure(error))
      }
    #endif
  }

Comment on lines +324 to +383
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The completion handler is not called if the OS version requirements are not met (iOS < 16.0 or macOS < 15.0), or if the platform is neither iOS nor macOS. This will cause the Dart Future to hang indefinitely. An error should be returned in these cases to ensure the caller receives a response.

  func presentOfferCodeRedeemSheet(completion: @escaping (Result<Void, Error>) -> Void) {
    #if os(iOS)
      if #available(iOS 16.0, *) {
        guard let windowScene = self.registrar?.viewController?.view.window?.windowScene else {
          let error = PigeonError(
            code: "storekit2_missing_key_window_scene",
            message: "Failed to fetch key window scene",
            details: "registrar.viewController.view.window.windowScene returned nil."
          )
          completion(.failure(error))
          return
        }
        Task { @MainActor in
          do {
            try await AppStore.presentOfferCodeRedeemSheet(in: windowScene)
            completion(.success(()))
          } catch {
            completion(.failure(error))
          }
        }
      } else {
        completion(.failure(PigeonError(
          code: "storekit2_unsupported_version",
          message: "Offer code redemption via StoreKit 2 requires iOS 16.0 or later.",
          details: nil
        )))
      }
    #elseif os(macOS)
      if #available(macOS 15.0, *) {
        guard let viewController = self.registrar?.viewController else {
          let error = PigeonError(
            code: "storekit2_missing_view_controller",
            message: "Failed to fetch view controller",
            details: "registrar.viewController returned nil."
          )
          completion(.failure(error))
          return
        }

        Task { @MainActor in
          do {
            try await AppStore.presentOfferCodeRedeemSheet(from: viewController)
            completion(.success(()))
          } catch {
            completion(.failure(error))
          }
        }
      } else {
        completion(.failure(PigeonError(
          code: "storekit2_unsupported_version",
          message: "Offer code redemption via StoreKit 2 requires macOS 15.0 or later.",
          details: nil
        )))
      }
    #else
      completion(.failure(PigeonError(
        code: "storekit2_unsupported_platform",
        message: "Offer code redemption via StoreKit 2 is not supported on this platform.",
        details: nil
      )))
    #endif
  }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is fine bc users should not be on storekit 2 below ios 15

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.

But it looks like the availability check is checking for iOS 16 and macOS 15? The extension availability:


/// Wrapper method around StoreKit2's sync() method
/// https://developer.apple.com/documentation/storekit/appstore/sync()
/// When called, a system prompt will ask users to enter their authentication details
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Autogenerated from Pigeon (v26.1.5), do not edit directly.
// Autogenerated from Pigeon (v26.1.10), do not edit directly.
// See also: https://pub.dev/packages/pigeon

import Foundation
Expand Down Expand Up @@ -759,6 +759,7 @@ protocol InAppPurchase2API {
func restorePurchases(completion: @escaping (Result<Void, Error>) -> Void)
func countryCode(completion: @escaping (Result<String, Error>) -> Void)
func sync(completion: @escaping (Result<Void, Error>) -> Void)
func presentOfferCodeRedeemSheet(completion: @escaping (Result<Void, Error>) -> Void)
}

/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
Expand Down Expand Up @@ -1008,6 +1009,24 @@ class InAppPurchase2APISetup {
} else {
syncChannel.setMessageHandler(nil)
}
let presentOfferCodeRedeemSheetChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.presentOfferCodeRedeemSheet\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
presentOfferCodeRedeemSheetChannel.setMessageHandler { _, reply in
api.presentOfferCodeRedeemSheet { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
presentOfferCodeRedeemSheetChannel.setMessageHandler(nil)
}
}
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = "<group>"; };
78DABEA22ED26510000E7860 /* in_app_purchase_storekit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = in_app_purchase_storekit; path = ../../darwin/in_app_purchase_storekit; sourceTree = "<group>"; };
6458340B2CE3497379F6B389 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -85,6 +84,8 @@
F2D527192C50627500C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = "<group>"; };
F2D527292C583C4A00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = "<group>"; };
F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = "<group>"; };
784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = "<group>"; };
78DABEA22ED26510000E7860 /* in_app_purchase_storekit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = in_app_purchase_storekit; path = ../../darwin/in_app_purchase_storekit; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -289,7 +290,7 @@
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -526,6 +527,8 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
Expand All @@ -544,6 +547,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.inAppPurchaseExample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
Expand All @@ -553,6 +557,8 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
Expand All @@ -571,6 +577,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.inAppPurchaseExample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
Expand All @@ -586,6 +593,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = RunnerTests/Info.plist;
Expand Down Expand Up @@ -616,6 +624,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = RunnerTests/Info.plist;
Expand Down Expand Up @@ -666,7 +675,7 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<MacroExpansion>
Expand Down Expand Up @@ -73,6 +74,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>

@interface AppDelegate : FlutterAppDelegate
@interface AppDelegate : FlutterAppDelegate <FlutterImplicitEngineDelegate>

@end
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ @implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge> *)engineBridge {
[GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
Expand All @@ -22,6 +24,29 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -39,9 +64,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class _MyAppState extends State<_MyApp> {
_buildConnectionCheckTile(),
_buildProductList(),
_buildConsumableBox(),
_buildCodeRedemptionButton(),
_buildRestoreButton(),
],
),
Expand Down Expand Up @@ -429,6 +430,30 @@ class _MyAppState extends State<_MyApp> {
);
}

Widget _buildCodeRedemptionButton() {
if (_loading) {
return Container();
}

return Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
),
onPressed: () =>
_iapStoreKitPlatformAddition.presentCodeRedemptionSheet(),
child: const Text('Show code redemption sheet'),
),
],
),
);
}

Widget _buildRestoreButton() {
if (_loading) {
return Container();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = ephemeral/Packages/.packages/FlutterFramework; sourceTree = "<group>"; };
78DABEA22ED26510000E7860 /* in_app_purchase_storekit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = in_app_purchase_storekit; path = ../../../darwin/in_app_purchase_storekit; sourceTree = "<group>"; };
39C4797E13DFF5FCF1A87568 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
46EFB01DD1BBB34F886C33A0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
Expand Down Expand Up @@ -184,8 +184,6 @@
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
78DABEA22ED26510000E7860 /* in_app_purchase_storekit */,
784666492D4C4C64000A1A5F /* FlutterFramework */,
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ description: Demonstrates how to use the in_app_purchase_storekit plugin.
publish_to: none

environment:
sdk: ^3.9.0
flutter: ">=3.35.0"
sdk: ^3.10.0
flutter: ">=3.38.0"

dependencies:
flutter:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ final class InAppPurchase2PluginTests: XCTestCase {
case .failure(let error):
XCTAssertEqual(
error.localizedDescription,
"The operation couldn’t be completed. (NSURLErrorDomain error -1009.)")
"The operation couldn’t be completed. (in_app_purchase_storekit.PigeonError error 1.)")
expectation.fulfill()
}
}
Expand Down Expand Up @@ -547,4 +547,20 @@ final class InAppPurchase2PluginTests: XCTestCase {
await fulfillment(of: [expectation], timeout: 5)
}

@available(iOS 16.0, macOS 15.0, *)
func testRedeemCodeSheetFailsGracefullyWhenNoWindow() {
let expectation = self.expectation(
description: "Should fail gracefully when without key window")

plugin.registrar = nil

plugin.presentOfferCodeRedeemSheet { result in
if case .failure = result {
expectation.fulfill()
}
}

waitForExpectations(timeout: 1.0)
}

}
Loading
Loading