diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version index d4ac38647b9..ac067af632f 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -d211f42860350d914a5ad8102f9ec32764dc6d06 +db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 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 82a5de827a9..54e5a383171 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.7 + +* Adds `Future storefront()` in SKPaymentQueueWrapper class. + ## 0.3.6+7 * Updates example code for current versions of Flutter. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h index fdc042655fd..356940cd39b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h @@ -21,6 +21,8 @@ typedef void (^UpdatedDownloads)(NSArray *downloads); @property(NS_NONATOMIC_IOSONLY, weak, nullable) id delegate API_AVAILABLE( ios(13.0), macos(10.15), watchos(6.2)); +@property(nonatomic, readonly, nullable) + SKStorefront *storefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2)); /// Creates a new FIAPaymentQueueHandler initialized with an empty /// FIATransactionCache. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m index d18a09cfa40..60056575089 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m @@ -233,4 +233,8 @@ - (BOOL)paymentQueue:(SKPaymentQueue *)queue return self.queue.transactions; } +- (SKStorefront *)storefront { + return self.queue.storefront; +} + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 1ecb0fc1dc6..7086f611ace 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -89,6 +89,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self canMakePayments:result]; } else if ([@"-[SKPaymentQueue transactions]" isEqualToString:call.method]) { [self getPendingTransactions:result]; + } else if ([@"-[SKPaymentQueue storefront]" isEqualToString:call.method]) { + [self getStorefront:result]; } else if ([@"-[InAppPurchasePlugin startProductRequest:result:]" isEqualToString:call.method]) { [self handleProductRequestMethodCall:call result:result]; } else if ([@"-[InAppPurchasePlugin addPayment:result:]" isEqualToString:call.method]) { @@ -139,6 +141,22 @@ - (void)getPendingTransactions:(FlutterResult)result { result(transactionMaps); } +- (void)getStorefront:(FlutterResult)result { + if (@available(iOS 13.0, macOS 10.15, *)) { + SKStorefront *storefront = self.paymentQueueHandler.storefront; + if (!storefront) { + result(nil); + return; + } + result([FIAObjectTranslator getMapFromSKStorefront:storefront]); + return; + } + + NSLog(@"storefront is not avaialbe in iOS below 13.0 or macOS below 10.15."); + result(nil); + return; +} + - (void)handleProductRequestMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if (![call.arguments isKindOfClass:[NSArray class]]) { result([FlutterError errorWithCode:@"storekit_invalid_argument" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index f7e6dcdaab1..f8aa0e34afd 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -419,6 +419,71 @@ - (void)testGetPendingTransactions { XCTAssertEqualObjects(resultArray, @[ transactionMap ]); } +- (void)testPaymentQueueStorefront { + if (@available(iOS 13, macOS 10.15, *)) { + // storefront is not nil + XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue storefront]" arguments:nil]; + SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); + NSDictionary *storefrontMap = @{ + @"countryCode" : @"USA", + @"identifier" : @"unique_identifier", + }; + OCMStub(mockQueue.storefront).andReturn([[SKStorefrontStub alloc] initWithMap:storefrontMap]); + + __block NSDictionary *resultMap; + self.plugin.paymentQueueHandler = + [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:OCMClassMock(FIATransactionCache.class)]; + [self.plugin handleMethodCall:call + result:^(id r) { + resultMap = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertEqualObjects(resultMap, storefrontMap); + } else { + NSLog(@"Skip testPaymentQueueStorefront for iOS lower than 13.0 or macOS lower than 10.15."); + } +} + +- (void)testPaymentQueueStorefrontReturnsNil { + if (@available(iOS 13, macOS 10.15, *)) { + XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue storefront]" arguments:nil]; + SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); + OCMStub(mockQueue.storefront).andReturn(nil); + + __block NSDictionary *resultMap; + self.plugin.paymentQueueHandler = + [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:OCMClassMock(FIATransactionCache.class)]; + [self.plugin handleMethodCall:call + result:^(id r) { + resultMap = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertNil(resultMap); + } else { + NSLog(@"Skip testPaymentQueueStorefront for iOS lower than 13.0 or macOS lower than 10.15."); + } +} + - (void)testStartObservingPaymentQueue { XCTestExpectation *expectation = [self expectationWithDescription:@"Should return success result"]; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index 361cb3af2d9..7682714ab9c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -41,7 +41,19 @@ class SKPaymentQueueWrapper { SKPaymentQueueDelegateWrapper? _paymentQueueDelegate; SKTransactionObserverWrapper? _observer; - /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc) + /// Calls [`[SKPaymentQueue storefront]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182430-storefront?language=objc). + /// + /// Returns `null` if the user's device is below iOS 13.0 or macOS 10.15. + Future storefront() async { + final Map? storefrontMap = await channel + .invokeMapMethod('-[SKPaymentQueue storefront]'); + if (storefrontMap == null) { + return null; + } + return SKStorefrontWrapper.fromJson(storefrontMap); + } + + /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc). Future> transactions() async { return _getTransactionList((await channel .invokeListMethod('-[SKPaymentQueue transactions]'))!); 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 46ab2f20159..49c6bad4265 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.6+7 +version: 0.3.7 environment: sdk: ">=2.19.0 <4.0.0" 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 0cf01b0bbfd..f04f4b3eb3a 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 @@ -108,6 +108,22 @@ void main() { expect(await SKPaymentQueueWrapper.canMakePayments(), false); }); + test('storefront returns valid SKStoreFrontWrapper object', () async { + final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); + expect( + await queue.storefront(), + SKStorefrontWrapper.fromJson(const { + 'countryCode': 'USA', + 'identifier': 'unique_identifier', + })); + }); + + test('storefront returns null', () async { + fakeStoreKitPlatform.testReturnNull = true; + final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); + expect(await queue.storefront(), isNull); + }); + test('transactions should return a valid list of transactions', () async { expect(await SKPaymentQueueWrapper().transactions(), isNotEmpty); }); @@ -250,6 +266,14 @@ class FakeStoreKitPlatform { case '-[SKPaymentQueue transactions]': return Future>.value( [buildTransactionMap(dummyTransaction)]); + case '-[SKPaymentQueue storefront]': + if (testReturnNull) { + return Future.value(); + } + return Future>.value(const { + 'countryCode': 'USA', + 'identifier': 'unique_identifier', + }); case '-[InAppPurchasePlugin addPayment:result:]': payments.add(SKPaymentWrapper.fromJson(Map.from( call.arguments as Map))); diff --git a/script/tool/lib/src/common/process_runner.dart b/script/tool/lib/src/common/process_runner.dart index 8ac98b8394c..1be648874ee 100644 --- a/script/tool/lib/src/common/process_runner.dart +++ b/script/tool/lib/src/common/process_runner.dart @@ -36,11 +36,9 @@ class ProcessRunner { print( 'Running command: "$executable ${args.join(' ')}" in ${workingDir?.path ?? io.Directory.current.path}'); final io.Process process = await io.Process.start(executable, args, - workingDirectory: workingDir?.path, environment: environment); - await Future.wait(>[ - io.stdout.addStream(process.stdout), - io.stderr.addStream(process.stderr), - ]); + workingDirectory: workingDir?.path, + environment: environment, + mode: io.ProcessStartMode.inheritStdio); if (exitOnError && await process.exitCode != 0) { final String error = _getErrorString(executable, args, workingDir: workingDir);