diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 3d4cc8d9837..7dd881cb856 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.20+1 + +* Prevent devices below iOS 15 or macOS 15 from enabling StoreKit2. + ## 0.3.20 * Fixes manual invocation of `finishTransaction` causing a fatal crash. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index 8954f2f46b1..089f1c5ca53 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -420,6 +420,15 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { NSLog("Received an updatedDownloads callback, but downloads are not supported.") } + public func supportsStoreKit2WithError(_ error: AutoreleasingUnsafeMutablePointer) + -> NSNumber? + { + if #available(iOS 15.0, macOS 12.0, *) { + return true + } + return false + } + // MARK: - Methods exposed for testing func getProduct(productID: String) -> SKProduct? { return self.productsCache[productID] as? SKProduct diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h index a6059f269d7..3ec07a94628 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -67,7 +67,8 @@ typedef NS_ENUM(NSUInteger, FIASKProductDiscountTypeMessage) { typedef NS_ENUM(NSUInteger, FIASKProductDiscountPaymentModeMessage) { /// Allows user to pay the discounted price at each payment period. FIASKProductDiscountPaymentModeMessagePayAsYouGo = 0, - /// Allows user to pay the discounted price upfront and receive the product for the rest of time + /// Allows user to pay the discounted price upfront and receive the product + /// for the rest of time /// that was paid for. FIASKProductDiscountPaymentModeMessagePayUpFront = 1, /// User pays nothing during the discounted period. @@ -278,6 +279,8 @@ NSObject *FIAGetMessagesCodec(void); - (void)registerPaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error; - (void)removePaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error; - (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable *_Nonnull)error; +/// @return `nil` only when `error != nil`. +- (nullable NSNumber *)supportsStoreKit2WithError:(FlutterError *_Nullable *_Nonnull)error; @end extern void SetUpFIAInAppPurchaseAPI(id binaryMessenger, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m index 62988eb04fe..50963e67039 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.g.h" @@ -978,4 +978,26 @@ void SetUpFIAInAppPurchaseAPIWithSuffix(id binaryMesseng [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.in_app_purchase_storekit." + @"InAppPurchaseAPI.supportsStoreKit2", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FIAGetMessagesCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(supportsStoreKit2WithError:)], + @"FIAInAppPurchaseAPI api (%@) doesn't respond to @selector(supportsStoreKit2WithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + NSNumber *output = [api supportsStoreKit2WithError:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart index cab8ddf0c2d..06950a977e5 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart @@ -17,6 +17,7 @@ void main() { // When using the Android plugin directly it is mandatory to register // the plugin as default instance as part of initializing the app. InAppPurchaseStoreKitPlatform.registerPlatform(); + InAppPurchaseStoreKitPlatform.enableStoreKit2(); runApp(_MyApp()); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart index e74b46ddfc2..51787d9c642 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart @@ -249,8 +249,10 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { Future getCountryCode() => countryCode(); /// Turns on StoreKit2. You cannot disable this after it is enabled. - void enableStoreKit2() { - _useStoreKit2 = true; + /// This can only be enabled if your device supports StoreKit 2. + static Future enableStoreKit2() async { + _useStoreKit2 = await SKRequestMaker.supportsStoreKit2(); + return _useStoreKit2; } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart index 8679df86f74..f5938cf3c25 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -984,4 +984,33 @@ class InAppPurchaseAPI { return; } } + + Future supportsStoreKit2() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.supportsStoreKit2$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart index 9075db9157a..64d00922d11 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart @@ -55,4 +55,9 @@ class SKRequestMaker { {Map? receiptProperties}) { return _hostApi.refreshReceipt(receiptProperties: receiptProperties); } + + /// Check if current device supports StoreKit 2. + static Future supportsStoreKit2() async { + return _hostApi.supportsStoreKit2(); + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart index cd7b6b70a57..774ab69e220 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart @@ -264,4 +264,6 @@ abstract class InAppPurchaseAPI { void removePaymentQueueDelegate(); void showPriceConsentIfNeeded(); + + bool supportsStoreKit2(); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index aa3c1d7b3e6..dd15dd6062e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.20 +version: 0.3.20+1 environment: sdk: ^3.3.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index 55c490bb62b..15ea45f2a42 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -280,6 +280,11 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { void stopObservingPaymentQueue() { queueIsActive = false; } + + @override + bool supportsStoreKit2() { + return true; + } } class FakeStoreKit2Platform implements TestInAppPurchase2Api { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart index 6fb2ba03070..658932e7ce6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart @@ -12,6 +12,7 @@ import 'package:in_app_purchase_storekit/store_kit_2_wrappers.dart'; import 'fakes/fake_storekit_platform.dart'; import 'sk2_test_api.g.dart'; +import 'test_api.g.dart'; void main() { final SK2Product dummyProductWrapper = SK2Product( @@ -26,17 +27,20 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKit2Platform fakeStoreKit2Platform = FakeStoreKit2Platform(); + final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); + late InAppPurchaseStoreKitPlatform iapStoreKitPlatform; setUpAll(() { TestInAppPurchase2Api.setUp(fakeStoreKit2Platform); + TestInAppPurchaseApi.setUp(fakeStoreKitPlatform); }); setUp(() { InAppPurchaseStoreKitPlatform.registerPlatform(); iapStoreKitPlatform = InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; - iapStoreKitPlatform.enableStoreKit2(); + InAppPurchaseStoreKitPlatform.enableStoreKit2(); fakeStoreKit2Platform.reset(); }); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index b774552f84b..8bd00d7de93 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -304,6 +304,11 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { void showPriceConsentIfNeeded() { showPriceConsent = true; } + + @override + bool supportsStoreKit2() { + return true; + } } class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart index 2092930d208..916b37ef1ba 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports @@ -153,6 +153,8 @@ abstract class TestInAppPurchaseApi { void showPriceConsentIfNeeded(); + bool supportsStoreKit2(); + static void setUp( TestInAppPurchaseApi? api, { BinaryMessenger? binaryMessenger, @@ -581,5 +583,31 @@ abstract class TestInAppPurchaseApi { }); } } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.supportsStoreKit2$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + try { + final bool output = api.supportsStoreKit2(); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } }