From e7afd0067782aff37ab285bf481fd2fc6c6ba1a9 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 6 Mar 2019 11:19:03 -0800 Subject: [PATCH 1/7] first draft --- .../sk_methodchannel_apis_test.dart | 87 +++++++++++++++++++ .../sk_payment_queue_wrapper_test.dart | 41 +-------- .../store_kit_wrappers/sk_product_test.dart | 33 +------ .../store_kit_wrappers/sk_request_test.dart | 34 +------- .../sk_test_stub_objects.dart | 79 +++++++++++++++++ 5 files changed, 169 insertions(+), 105 deletions(-) create mode 100644 packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart create mode 100644 packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart new file mode 100644 index 000000000000..78a0a9b87b57 --- /dev/null +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -0,0 +1,87 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:in_app_purchase/src/channel.dart'; +import 'package:in_app_purchase/store_kit_wrappers.dart'; +import 'sk_test_stub_objects.dart'; + +void main() { + final FakeIOSPlatform fakeIOSPlatform = FakeIOSPlatform(); + + setUpAll(() { + SystemChannels.platform + .setMockMethodCallHandler(fakeIOSPlatform.onMethodCall); + }); + + setUp(() {}); + + group('sk_request_maker', () { + test('get products method channel', () async { + SkProductResponseWrapper productResponseWrapper = + await SKRequestMaker().startProductRequest(['xxx']); + expect( + productResponseWrapper.products, + isNotEmpty, + ); + expect( + productResponseWrapper.products.first.priceLocale.currencySymbol, + '\$', + ); + expect( + productResponseWrapper.products.first.priceLocale.currencySymbol, + isNot('A'), + ); + expect( + productResponseWrapper.invalidProductIdentifiers, + isNotEmpty, + ); + + expect(fakeIOSPlatform.startProductRequestParam, ['xxx'],); + }); + + test('refreshed receipt', () async { + int receiptCountBefore = fakeIOSPlatform.refreshReceipt; + await SKRequestMaker().startRefreshReceiptRequest(receiptProperties:{"isExpired":true}); + expect(fakeIOSPlatform.refreshReceipt, receiptCountBefore + 1); + expect(fakeIOSPlatform.refreshReceiptParam, {"isExpired":true}); + }); + }); +} + +class FakeIOSPlatform { + FakeIOSPlatform() { + channel.setMockMethodCallHandler(onMethodCall); + } + // get product request + List startProductRequestParam; + bool getProductRequestFailTest; + + // refresh receipt request + int refreshReceipt = 0; + Map refreshReceiptParam; + + Future onMethodCall(MethodCall call) { + switch (call.method) { + case '-[InAppPurchasePlugin startProductRequest:result:]': + List productIDS = + List.castFrom(call.arguments); + assert(productIDS is List, 'invalid argument type'); + startProductRequestParam = call.arguments; + if (getProductRequestFailTest) { + return Future>.value(null); + } + return Future>.value(productResponseMap); + break; + case '-[InAppPurchasePlugin refreshReceipt:result:]': + refreshReceipt++; + refreshReceiptParam = call.arguments; + return Future.sync(() {}); + } + return Future.sync(() {}); + } +} diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart index fc342dbbc759..6ec16ed81a2a 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart @@ -7,49 +7,10 @@ import 'package:test/test.dart'; import 'package:in_app_purchase/store_kit_wrappers.dart'; import 'package:in_app_purchase/src/channel.dart'; import '../stub_in_app_purchase_platform.dart'; +import 'sk_test_stub_objects.dart'; void main() { final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); - final dummyPayment = SKPaymentWrapper( - productIdentifier: 'prod-id', - applicationUsername: 'app-user-name', - requestData: 'fake-data-utf8', - quantity: 2, - simulatesAskToBuyInSandbox: true); - final SKError dummyError = - SKError(code: 111, domain: 'dummy-domain', userInfo: {'key': 'value'}); - final SKDownloadWrapper dummyDownload = SKDownloadWrapper( - contentIdentifier: 'id', - state: SKDownloadState.failed, - contentLength: 32, - contentURL: 'https://download.com', - contentVersion: '0.0.1', - transactionID: 'tranID', - progress: 0.6, - timeRemaining: 1231231, - downloadTimeUnknown: false, - error: dummyError, - ); - final SKPaymentTransactionWrapper dummyOriginalTransaction = - SKPaymentTransactionWrapper( - transactionState: SKPaymentTransactionStateWrapper.purchased, - payment: dummyPayment, - originalTransaction: null, - transactionTimeStamp: 1231231231.00, - transactionIdentifier: '123123', - downloads: [dummyDownload], - error: dummyError, - ); - final SKPaymentTransactionWrapper dummyTransaction = - SKPaymentTransactionWrapper( - transactionState: SKPaymentTransactionStateWrapper.purchased, - payment: dummyPayment, - originalTransaction: dummyOriginalTransaction, - transactionTimeStamp: 1231231231.00, - transactionIdentifier: '123123', - downloads: [dummyDownload], - error: dummyError, - ); setUpAll(() => channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart index cc806d068b76..e953ae061c0d 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart @@ -5,40 +5,9 @@ import 'package:test/test.dart'; import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart'; import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart'; +import 'sk_test_stub_objects.dart'; void main() { - final Map localeMap = { - 'currencySymbol': '\$' - }; - final Map subMap = { - 'numberOfUnits': 1, - 'unit': 2 - }; - final Map discountMap = { - 'price': '1.0', - 'priceLocale': localeMap, - 'numberOfPeriods': 1, - 'paymentMode': 2, - 'subscriptionPeriod': subMap, - }; - final Map productMap = { - 'productIdentifier': 'id', - 'localizedTitle': 'title', - 'localizedDescription': 'description', - 'priceLocale': localeMap, - 'downloadContentVersion': 'version', - 'subscriptionGroupIdentifier': 'com.group', - 'price': '1.0', - 'downloadable': true, - 'downloadContentLengths': [1, 2], - 'subscriptionPeriod': subMap, - 'introductoryPrice': discountMap, - }; - - final Map> productResponseMap = >{ - 'products': >[productMap], - 'invalidProductIdentifiers': ['123'], - }; group('product request wrapper test', () { void testMatchLocale( diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart index 7bb623860a12..e8063a392e83 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart @@ -7,43 +7,11 @@ import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart'; import 'package:in_app_purchase/src/channel.dart'; import 'package:in_app_purchase/src/store_kit_wrappers/sk_request_maker.dart'; import '../stub_in_app_purchase_platform.dart'; +import './sk_test_stub_objects.dart'; void main() { final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); - final Map localeMap = { - 'currencySymbol': '\$' - }; - final Map subMap = { - 'numberOfUnits': 1, - 'unit': 2 - }; - final Map discountMap = { - 'price': '1.0', - 'priceLocale': localeMap, - 'numberOfPeriods': 1, - 'paymentMode': 2, - 'subscriptionPeriod': subMap, - }; - final Map productMap = { - 'productIdentifier': 'id', - 'localizedTitle': 'title', - 'localizedDescription': 'description', - 'priceLocale': localeMap, - 'downloadContentVersion': 'version', - 'subscriptionGroupIdentifier': 'com.group', - 'price': '1.0', - 'downloadable': true, - 'downloadContentLengths': [1, 2], - 'subscriptionPeriod': subMap, - 'introductoryPrice': discountMap, - }; - - final Map> productResponseMap = >{ - 'products': >[productMap], - 'invalidProductIdentifiers': ['123'], - }; - setUpAll(() => channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart new file mode 100644 index 000000000000..e44bf36b9916 --- /dev/null +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -0,0 +1,79 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase/store_kit_wrappers.dart'; + +final dummyPayment = SKPaymentWrapper( + productIdentifier: 'prod-id', + applicationUsername: 'app-user-name', + requestData: 'fake-data-utf8', + quantity: 2, + simulatesAskToBuyInSandbox: true); +final SKError dummyError = + SKError(code: 111, domain: 'dummy-domain', userInfo: {'key': 'value'}); +final SKDownloadWrapper dummyDownload = SKDownloadWrapper( + contentIdentifier: 'id', + state: SKDownloadState.failed, + contentLength: 32, + contentURL: 'https://download.com', + contentVersion: '0.0.1', + transactionID: 'tranID', + progress: 0.6, + timeRemaining: 1231231, + downloadTimeUnknown: false, + error: dummyError, +); +final SKPaymentTransactionWrapper dummyOriginalTransaction = + SKPaymentTransactionWrapper( + transactionState: SKPaymentTransactionStateWrapper.purchased, + payment: dummyPayment, + originalTransaction: null, + transactionTimeStamp: 1231231231.00, + transactionIdentifier: '123123', + downloads: [dummyDownload], + error: dummyError, +); +final SKPaymentTransactionWrapper dummyTransaction = + SKPaymentTransactionWrapper( + transactionState: SKPaymentTransactionStateWrapper.purchased, + payment: dummyPayment, + originalTransaction: dummyOriginalTransaction, + transactionTimeStamp: 1231231231.00, + transactionIdentifier: '123123', + downloads: [dummyDownload], + error: dummyError, +); + + final Map localeMap = { + 'currencySymbol': '\$' + }; + final Map subMap = { + 'numberOfUnits': 1, + 'unit': 2 + }; + final Map discountMap = { + 'price': '1.0', + 'priceLocale': localeMap, + 'numberOfPeriods': 1, + 'paymentMode': 2, + 'subscriptionPeriod': subMap, + }; + final Map productMap = { + 'productIdentifier': 'id', + 'localizedTitle': 'title', + 'localizedDescription': 'description', + 'priceLocale': localeMap, + 'downloadContentVersion': 'version', + 'subscriptionGroupIdentifier': 'com.group', + 'price': '1.0', + 'downloadable': true, + 'downloadContentLengths': [1, 2], + 'subscriptionPeriod': subMap, + 'introductoryPrice': discountMap, + }; + + final Map> productResponseMap = >{ + 'products': >[productMap], + 'invalidProductIdentifiers': ['123'], + }; \ No newline at end of file From 9c9574ff9cfa2a0d31aadd4409e16269b9f17cc8 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 6 Mar 2019 15:34:07 -0800 Subject: [PATCH 2/7] more tests --- .../sk_payment_queue_wrapper.dart | 72 ++++++++ .../sk_product_wrapper.dart | 79 ++++++++ .../sk_methodchannel_apis_test.dart | 154 ++++++++++++++++ .../sk_payment_queue_wrapper_test.dart | 149 +-------------- .../store_kit_wrappers/sk_product_test.dart | 112 ++---------- .../store_kit_wrappers/sk_request_test.dart | 36 +--- .../sk_test_stub_objects.dart | 173 ++++++++++++++++++ 7 files changed, 497 insertions(+), 278 deletions(-) create mode 100644 packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart create mode 100644 packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index 53a9d0e40eae..d4d2bc546c65 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:in_app_purchase/src/channel.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -302,6 +303,25 @@ class SKPaymentTransactionWrapper { /// The error object, only available if the [transactionState] is [SKPaymentTransactionStateWrapper.failed]. final SKError error; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SKPaymentTransactionWrapper typedOther = other; + return typedOther.payment == payment && + typedOther.transactionState == transactionState && + typedOther.originalTransaction == originalTransaction && + typedOther.transactionTimeStamp == transactionTimeStamp && + typedOther.transactionIdentifier == transactionIdentifier && + DeepCollectionEquality() + .equals(typedOther.downloads, downloads) && + typedOther.error == error; + } } /// Dart wrapper around StoreKit's [SKDownloadState](https://developer.apple.com/documentation/storekit/skdownloadstate?language=objc). @@ -403,6 +423,27 @@ class SKDownloadWrapper { /// The error that prevented the downloading; only available if the [transactionState] is [SKPaymentTransactionStateWrapper.failed]. final SKError error; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SKDownloadWrapper typedOther = other; + return typedOther.contentIdentifier == contentIdentifier && + typedOther.state == state && + typedOther.contentLength == contentLength && + typedOther.contentURL == contentURL && + typedOther.contentVersion == contentVersion && + typedOther.transactionID == transactionID && + typedOther.progress == progress && + typedOther.timeRemaining == timeRemaining && + typedOther.downloadTimeUnknown == downloadTimeUnknown && + typedOther.error == error; + } } /// Dart wrapper around StoreKit's [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc). @@ -430,6 +471,21 @@ class SKError { /// A map that contains more detailed information about the error. Any key of the map must be one of the [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc). final Map userInfo; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SKError typedOther = other; + return typedOther.code == code && + typedOther.domain == domain && + DeepCollectionEquality.unordered() + .equals(typedOther.userInfo, userInfo); + } } /// Dart wrapper around StoreKit's [SKPayment](https://developer.apple.com/documentation/storekit/skpayment?language=objc). @@ -493,4 +549,20 @@ class SKPaymentWrapper { /// /// For how to test in App Store sand box, see https://developer.apple.com/in-app-purchase/. final bool simulatesAskToBuyInSandbox; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SKPaymentWrapper typedOther = other; + return typedOther.productIdentifier == productIdentifier && + typedOther.applicationUsername == applicationUsername && + typedOther.quantity == quantity && + typedOther.simulatesAskToBuyInSandbox == simulatesAskToBuyInSandbox && + typedOther.requestData == requestData; + } } diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart index ddc35bb99c56..14961570e053 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:collection/collection.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart'; @@ -41,6 +42,21 @@ class SkProductResponseWrapper { /// found here https://developer.apple.com/documentation/storekit/skproductsresponse/1505985-invalidproductidentifiers?language=objc. /// Will be empty if all the product identifiers are valid. final List invalidProductIdentifiers; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SkProductResponseWrapper typedOther = other; + return DeepCollectionEquality() + .equals(typedOther.products, products) && + DeepCollectionEquality().equals( + typedOther.invalidProductIdentifiers, invalidProductIdentifiers); + } } /// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc). @@ -84,6 +100,18 @@ class SKProductSubscriptionPeriodWrapper { /// The time unit used to specify the length of this period. final SubscriptionPeriodUnit unit; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SKProductSubscriptionPeriodWrapper typedOther = other; + return typedOther.numberOfUnits == numberOfUnits && typedOther.unit == unit; + } } /// Dart wrapper around StoreKit's [SKProductDiscountPaymentMode](https://developer.apple.com/documentation/storekit/skproductdiscountpaymentmode?language=objc). @@ -145,6 +173,22 @@ class SKProductDiscountWrapper { /// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod], /// and their units and duration do not have to be matched. final SKProductSubscriptionPeriodWrapper subscriptionPeriod; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SKProductDiscountWrapper typedOther = other; + return typedOther.price == price && + typedOther.priceLocale == priceLocale && + typedOther.numberOfPeriods == numberOfPeriods && + typedOther.paymentMode == paymentMode && + typedOther.subscriptionPeriod == subscriptionPeriod; + } } /// Dart wrapper around StoreKit's [SKProduct](https://developer.apple.com/documentation/storekit/skproduct?language=objc). @@ -232,6 +276,29 @@ class SKProductWrapper { /// and their units and duration do not have to be matched. final SKProductDiscountWrapper introductoryPrice; + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final SKProductWrapper typedOther = other; + return typedOther.productIdentifier == productIdentifier && + typedOther.localizedTitle == localizedTitle && + typedOther.localizedDescription == localizedDescription && + typedOther.priceLocale == priceLocale && + typedOther.downloadContentVersion == downloadContentVersion && + typedOther.subscriptionGroupIdentifier == subscriptionGroupIdentifier && + typedOther.price == price && + typedOther.downloadable == downloadable && + DeepCollectionEquality.unordered() + .equals(typedOther.downloadContentLengths, downloadContentLengths) && + typedOther.subscriptionPeriod == subscriptionPeriod && + typedOther.introductoryPrice == introductoryPrice; + } + /// Method to convert to the wrapper to the consolidated [ProductDetails] class. ProductDetails toProductDetails() { return ProductDetails( @@ -264,4 +331,16 @@ class PriceLocaleWrapper { ///The currency symbol for the locale, e.g. $ for US locale. final String currencySymbol; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + final PriceLocaleWrapper typedOther = other; + return typedOther.currencySymbol == currencySymbol; + } } diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart new file mode 100644 index 000000000000..e1a3545f4cd6 --- /dev/null +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -0,0 +1,154 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:in_app_purchase/src/channel.dart'; +import 'package:in_app_purchase/store_kit_wrappers.dart'; +import 'sk_test_stub_objects.dart'; + +void main() { + final FakeIOSPlatform fakeIOSPlatform = FakeIOSPlatform(); + + setUpAll(() { + SystemChannels.platform + .setMockMethodCallHandler(fakeIOSPlatform.onMethodCall); + }); + + setUp(() {}); + + group('sk_request_maker', () { + test('get products method channel', () async { + SkProductResponseWrapper productResponseWrapper = + await SKRequestMaker().startProductRequest(['xxx']); + expect( + productResponseWrapper.products, + isNotEmpty, + ); + expect( + productResponseWrapper.products.first.priceLocale.currencySymbol, + '\$', + ); + expect( + productResponseWrapper.products.first.priceLocale.currencySymbol, + isNot('A'), + ); + expect( + productResponseWrapper.invalidProductIdentifiers, + isNotEmpty, + ); + + expect( + fakeIOSPlatform.startProductRequestParam, + ['xxx'], + ); + }); + + test('get products method channel should throw exception', () async { + fakeIOSPlatform.getProductRequestFailTest = true; + expect( + SKRequestMaker().startProductRequest(['xxx']), + throwsException, + ); + fakeIOSPlatform.getProductRequestFailTest = false; + }); + + test('refreshed receipt', () async { + int receiptCountBefore = fakeIOSPlatform.refreshReceipt; + await SKRequestMaker() + .startRefreshReceiptRequest(receiptProperties: {"isExpired": true}); + expect(fakeIOSPlatform.refreshReceipt, receiptCountBefore + 1); + expect(fakeIOSPlatform.refreshReceiptParam, {"isExpired": true}); + }); + }); + + group('sk_receipt_manager', () { + test('should get receipt (faking it by returning a `receipt data` string)', + () async { + String receiptData = await SKReceiptManager().retrieveReceiptData(); + expect(receiptData, 'receipt data'); + }); + }); + + group('sk_payment_queue', () { + test('canMakePayment should return true', () async { + expect(await SKPaymentQueueWrapper.canMakePayments(), true); + }); + + test('throws if observer is not set for payment queue before adding payment', () async { + expect(SKPaymentQueueWrapper().addPayment(dummyPayment), throwsAssertionError); + }); + + test('should add payment to the payment queue', () async { + SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); + TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); + queue.setTransactionObserver(observer); + await queue.addPayment(dummyPayment); + expect(fakeIOSPlatform.payments.first, equals(dummyPayment)); + }); + }); +} + +class FakeIOSPlatform { + FakeIOSPlatform() { + channel.setMockMethodCallHandler(onMethodCall); + getProductRequestFailTest = false; + } + // get product request + List startProductRequestParam; + bool getProductRequestFailTest; + + // refresh receipt request + int refreshReceipt = 0; + Map refreshReceiptParam; + + // payment queue + List payments = []; + + Future onMethodCall(MethodCall call) { + switch (call.method) { + // request makers + case '-[InAppPurchasePlugin startProductRequest:result:]': + List productIDS = + List.castFrom(call.arguments); + assert(productIDS is List, 'invalid argument type'); + startProductRequestParam = call.arguments; + if (getProductRequestFailTest) { + return Future>.value(null); + } + return Future>.value(buildProductResponseMap(dummyProductResponseWrapper)); + case '-[InAppPurchasePlugin refreshReceipt:result:]': + refreshReceipt++; + refreshReceiptParam = call.arguments; + return Future.sync(() {}); + // receipt manager + case '-[InAppPurchasePlugin retrieveReceiptData:result:]': + return Future.value('receipt data'); + // payment queue + case '-[SKPaymentQueue canMakePayments:]': + return Future.value(true); + case '-[InAppPurchasePlugin addPayment:result:]': + payments.add(SKPaymentWrapper.fromJson(call.arguments)); + return Future.sync((){}); + } + return Future.sync(() {}); + } +} + +class TestPaymentTransactionObserver extends SKTransactionObserverWrapper { + void updatedTransactions({List transactions}){} + + void removedTransactions({List transactions}){} + + void restoreCompletedTransactions({Error error}){} + + void paymentQueueRestoreCompletedTransactionsFinished(){} + + void updatedDownloads({List downloads}){} + + bool shouldAddStorePayment( + {SKPaymentWrapper payment, SKProductWrapper product}){ + return true; + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart index fc342dbbc759..734ae11769d5 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart @@ -7,49 +7,10 @@ import 'package:test/test.dart'; import 'package:in_app_purchase/store_kit_wrappers.dart'; import 'package:in_app_purchase/src/channel.dart'; import '../stub_in_app_purchase_platform.dart'; +import 'sk_test_stub_objects.dart'; void main() { final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); - final dummyPayment = SKPaymentWrapper( - productIdentifier: 'prod-id', - applicationUsername: 'app-user-name', - requestData: 'fake-data-utf8', - quantity: 2, - simulatesAskToBuyInSandbox: true); - final SKError dummyError = - SKError(code: 111, domain: 'dummy-domain', userInfo: {'key': 'value'}); - final SKDownloadWrapper dummyDownload = SKDownloadWrapper( - contentIdentifier: 'id', - state: SKDownloadState.failed, - contentLength: 32, - contentURL: 'https://download.com', - contentVersion: '0.0.1', - transactionID: 'tranID', - progress: 0.6, - timeRemaining: 1231231, - downloadTimeUnknown: false, - error: dummyError, - ); - final SKPaymentTransactionWrapper dummyOriginalTransaction = - SKPaymentTransactionWrapper( - transactionState: SKPaymentTransactionStateWrapper.purchased, - payment: dummyPayment, - originalTransaction: null, - transactionTimeStamp: 1231231231.00, - transactionIdentifier: '123123', - downloads: [dummyDownload], - error: dummyError, - ); - final SKPaymentTransactionWrapper dummyTransaction = - SKPaymentTransactionWrapper( - transactionState: SKPaymentTransactionStateWrapper.purchased, - payment: dummyPayment, - originalTransaction: dummyOriginalTransaction, - transactionTimeStamp: 1231231231.00, - transactionIdentifier: '123123', - downloads: [dummyDownload], - error: dummyError, - ); setUpAll(() => channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); @@ -72,29 +33,25 @@ void main() { test('Should construct correct SKPaymentWrapper from json', () { SKPaymentWrapper payment = SKPaymentWrapper.fromJson(dummyPayment.toMap()); - testPayment(payment, dummyPayment); + expect(payment, equals(dummyPayment)); }); test('Should construct correct SKError from json', () { SKError error = SKError.fromJson(buildErrorMap(dummyError)); - testSKError(error, dummyError); + expect(error, equals(dummyError)); }); test('Should construct correct SKDownloadWrapper from json', () { SKDownloadWrapper download = SKDownloadWrapper.fromJson(buildDownloadMap(dummyDownload)); - testDownload(download, dummyDownload); + expect(download, equals(dummyDownload)); }); test('Should construct correct SKTransactionWrapper from json', () { SKPaymentTransactionWrapper transaction = SKPaymentTransactionWrapper.fromJson( buildTransactionMap(dummyTransaction)); - testTransaction(transaction, dummyTransaction); - if (transaction.originalTransaction != null) { - testTransaction(transaction.originalTransaction, - dummyTransaction.originalTransaction); - } + expect(transaction, equals(dummyTransaction)); }); test('Should generate correct map of the payment object', () { @@ -111,99 +68,3 @@ void main() { }); }); } - -Map buildErrorMap(SKError error) { - return { - 'code': error.code, - 'domain': error.domain, - 'userInfo': error.userInfo, - }; -} - -Map buildDownloadMap(SKDownloadWrapper download) { - return { - 'contentIdentifier': download.contentIdentifier, - 'state': SKDownloadState.values.indexOf(download.state), - 'contentLength': download.contentLength, - 'contentURL': download.contentURL, - 'contentVersion': download.contentVersion, - 'transactionID': download.transactionID, - 'progress': download.progress, - 'timeRemaining': download.timeRemaining, - 'downloadTimeUnknown': download.downloadTimeUnknown, - 'error': buildErrorMap(download.error) - }; -} - -Map buildTransactionMap( - SKPaymentTransactionWrapper transaction) { - if (transaction == null) { - return null; - } - Map map = { - 'transactionState': SKPaymentTransactionStateWrapper.values - .indexOf(SKPaymentTransactionStateWrapper.purchased), - 'payment': transaction.payment.toMap(), - 'originalTransaction': buildTransactionMap(transaction.originalTransaction), - 'transactionTimeStamp': transaction.transactionTimeStamp, - 'transactionIdentifier': transaction.transactionIdentifier, - 'error': buildErrorMap(transaction.error), - }; - List downloadList = transaction.downloads.map((SKDownloadWrapper download) { - return buildDownloadMap(download); - }).toList(); - map['downloads'] = downloadList; - return map; -} - -void testSKError(SKError error, SKError dummyError) { - expect(error.code, dummyError.code); - expect(error.domain, dummyError.domain); - expect(error.userInfo, dummyError.userInfo); -} - -void testDownload(SKDownloadWrapper download, SKDownloadWrapper dummyDownload) { - expect(download.contentIdentifier, dummyDownload.contentIdentifier); - expect(download.state, dummyDownload.state); - expect(download.contentLength, dummyDownload.contentLength); - expect(download.contentURL, dummyDownload.contentURL); - expect(download.contentVersion, dummyDownload.contentVersion); - expect(download.transactionID, dummyDownload.transactionID); - expect(download.progress, dummyDownload.progress); - expect(download.timeRemaining, dummyDownload.timeRemaining); - expect(download.downloadTimeUnknown, dummyDownload.downloadTimeUnknown); - expect(download.error.code, dummyDownload.error.code); - expect(download.error.domain, dummyDownload.error.domain); - expect(download.error.userInfo, dummyDownload.error.userInfo); -} - -void testPayment(SKPaymentWrapper payment, SKPaymentWrapper dummyPayment) { - expect(payment.productIdentifier, dummyPayment.productIdentifier); - expect(payment.applicationUsername, dummyPayment.applicationUsername); - expect(payment.requestData, dummyPayment.requestData); - expect(payment.quantity, dummyPayment.quantity); - expect(payment.simulatesAskToBuyInSandbox, - dummyPayment.simulatesAskToBuyInSandbox); -} - -void testTransaction(SKPaymentTransactionWrapper transaction, - SKPaymentTransactionWrapper dummyTransaction) { - // payment - SKPaymentWrapper payment = transaction.payment; - SKPaymentWrapper dummyPayment = dummyTransaction.payment; - testPayment(payment, dummyPayment); - //download - SKDownloadWrapper download = transaction.downloads.first; - SKDownloadWrapper dummyDownload = dummyTransaction.downloads.first; - testDownload(download, dummyDownload); - //error - SKError error = transaction.error; - SKError dummyError = dummyTransaction.error; - testSKError(error, dummyError); - - expect(transaction.transactionState, dummyTransaction.transactionState); - expect( - transaction.transactionTimeStamp, dummyTransaction.transactionTimeStamp); - expect(transaction.transactionIdentifier, - dummyTransaction.transactionIdentifier); -} diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart index cc806d068b76..ff50f4b74aaf 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart @@ -5,54 +5,18 @@ import 'package:test/test.dart'; import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart'; import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart'; +import 'sk_test_stub_objects.dart'; void main() { - final Map localeMap = { - 'currencySymbol': '\$' - }; - final Map subMap = { - 'numberOfUnits': 1, - 'unit': 2 - }; - final Map discountMap = { - 'price': '1.0', - 'priceLocale': localeMap, - 'numberOfPeriods': 1, - 'paymentMode': 2, - 'subscriptionPeriod': subMap, - }; - final Map productMap = { - 'productIdentifier': 'id', - 'localizedTitle': 'title', - 'localizedDescription': 'description', - 'priceLocale': localeMap, - 'downloadContentVersion': 'version', - 'subscriptionGroupIdentifier': 'com.group', - 'price': '1.0', - 'downloadable': true, - 'downloadContentLengths': [1, 2], - 'subscriptionPeriod': subMap, - 'introductoryPrice': discountMap, - }; - - final Map> productResponseMap = >{ - 'products': >[productMap], - 'invalidProductIdentifiers': ['123'], - }; group('product request wrapper test', () { - void testMatchLocale( - PriceLocaleWrapper wrapper, Map localeMap) { - expect(wrapper.currencySymbol, localeMap['currencySymbol']); - } test( 'SKProductSubscriptionPeriodWrapper should have property values consistent with map', () { final SKProductSubscriptionPeriodWrapper wrapper = - SKProductSubscriptionPeriodWrapper.fromJson(subMap); - expect(wrapper.numberOfUnits, subMap['numberOfUnits']); - expect(wrapper.unit, SubscriptionPeriodUnit.values[subMap['unit']]); + SKProductSubscriptionPeriodWrapper.fromJson(buildSubscriptionPeriodMap(dummySubscription)); + expect(wrapper, equals(dummySubscription)); }); test( @@ -68,18 +32,8 @@ void main() { 'SKProductDiscountWrapper should have property values consistent with map', () { final SKProductDiscountWrapper wrapper = - SKProductDiscountWrapper.fromJson(discountMap); - expect(wrapper.price, discountMap['price']); - testMatchLocale(wrapper.priceLocale, discountMap['priceLocale']); - expect(wrapper.numberOfPeriods, discountMap['numberOfPeriods']); - expect(wrapper.paymentMode, - ProductDiscountPaymentMode.values[discountMap['paymentMode']]); - expect( - wrapper.subscriptionPeriod.unit, - SubscriptionPeriodUnit - .values[discountMap['subscriptionPeriod']['unit']]); - expect(wrapper.subscriptionPeriod.numberOfUnits, - discountMap['subscriptionPeriod']['numberOfUnits']); + SKProductDiscountWrapper.fromJson(buildDiscountMap(dummyDiscount)); + expect(wrapper, equals(dummyDiscount)); }); test( @@ -94,49 +48,10 @@ void main() { expect(wrapper.subscriptionPeriod, null); }); - void testMatchingProductMap( - SKProductWrapper wrapper, Map productMap) { - expect(wrapper.productIdentifier, productMap['productIdentifier']); - expect(wrapper.localizedTitle, productMap['localizedTitle']); - testMatchLocale(wrapper.priceLocale, productMap['priceLocale']); - expect(wrapper.localizedDescription, productMap['localizedDescription']); - expect( - wrapper.downloadContentVersion, productMap['downloadContentVersion']); - expect(wrapper.subscriptionGroupIdentifier, - productMap['subscriptionGroupIdentifier']); - expect(wrapper.price, productMap['price']); - expect(wrapper.downloadable, productMap['downloadable']); - expect( - wrapper.downloadContentLengths, productMap['downloadContentLengths']); - expect(wrapper.introductoryPrice.price, - productMap['introductoryPrice']['price']); - expect(wrapper.introductoryPrice.numberOfPeriods, - productMap['introductoryPrice']['numberOfPeriods']); - expect( - wrapper.introductoryPrice.paymentMode, - ProductDiscountPaymentMode - .values[productMap['introductoryPrice']['paymentMode']]); - expect( - wrapper.introductoryPrice.subscriptionPeriod.unit, - SubscriptionPeriodUnit.values[productMap['introductoryPrice'] - ['subscriptionPeriod']['unit']]); - expect( - wrapper.introductoryPrice.subscriptionPeriod.numberOfUnits, - productMap['introductoryPrice']['subscriptionPeriod'] - ['numberOfUnits']); - expect( - wrapper.subscriptionPeriod.unit, - SubscriptionPeriodUnit - .values[productMap['subscriptionPeriod']['unit']]); - expect(wrapper.subscriptionPeriod.numberOfUnits, - productMap['subscriptionPeriod']['numberOfUnits']); - expect(wrapper.price, discountMap['price']); - } - test('SKProductWrapper should have property values consistent with map', () { - final SKProductWrapper wrapper = SKProductWrapper.fromJson(productMap); - testMatchingProductMap(wrapper, productMap); + final SKProductWrapper wrapper = SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); + expect(wrapper, equals(dummyProductWrapper)); }); test('SKProductWrapper should have properties to be null if map is empty', @@ -155,7 +70,7 @@ void main() { }); test('toProductDetails() should return correct Product object', () { - final SKProductWrapper wrapper = SKProductWrapper.fromJson(productMap); + final SKProductWrapper wrapper = SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); final ProductDetails product = wrapper.toProductDetails(); expect(product.title, wrapper.localizedTitle); expect(product.description, wrapper.localizedDescription); @@ -166,11 +81,8 @@ void main() { test('SKProductResponse wrapper should match', () { final SkProductResponseWrapper wrapper = - SkProductResponseWrapper.fromJson(productResponseMap); - testMatchingProductMap( - wrapper.products[0], productResponseMap['products'][0]); - expect(wrapper.invalidProductIdentifiers, - productResponseMap['invalidProductIdentifiers']); + SkProductResponseWrapper.fromJson(buildProductResponseMap(dummyProductResponseWrapper)); + expect(wrapper, equals(dummyProductResponseWrapper)); }); test('SKProductResponse wrapper should default to empty list', () { final Map> productResponseMapEmptyList = @@ -185,8 +97,8 @@ void main() { }); test('LocaleWrapper should have property values consistent with map', () { - final PriceLocaleWrapper wrapper = PriceLocaleWrapper.fromJson(localeMap); - testMatchLocale(wrapper, localeMap); + final PriceLocaleWrapper wrapper = PriceLocaleWrapper.fromJson(buildLocaleMap(dummyLocale)); + expect(wrapper, equals(dummyLocale)); }); }); } diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart index 7bb623860a12..6460078befd9 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart @@ -7,43 +7,11 @@ import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart'; import 'package:in_app_purchase/src/channel.dart'; import 'package:in_app_purchase/src/store_kit_wrappers/sk_request_maker.dart'; import '../stub_in_app_purchase_platform.dart'; +import 'sk_test_stub_objects.dart'; void main() { final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); - final Map localeMap = { - 'currencySymbol': '\$' - }; - final Map subMap = { - 'numberOfUnits': 1, - 'unit': 2 - }; - final Map discountMap = { - 'price': '1.0', - 'priceLocale': localeMap, - 'numberOfPeriods': 1, - 'paymentMode': 2, - 'subscriptionPeriod': subMap, - }; - final Map productMap = { - 'productIdentifier': 'id', - 'localizedTitle': 'title', - 'localizedDescription': 'description', - 'priceLocale': localeMap, - 'downloadContentVersion': 'version', - 'subscriptionGroupIdentifier': 'com.group', - 'price': '1.0', - 'downloadable': true, - 'downloadContentLengths': [1, 2], - 'subscriptionPeriod': subMap, - 'introductoryPrice': discountMap, - }; - - final Map> productResponseMap = >{ - 'products': >[productMap], - 'invalidProductIdentifiers': ['123'], - }; - setUpAll(() => channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); @@ -51,7 +19,7 @@ void main() { test('platform call should get result', () async { stubPlatform.addResponse( name: '-[InAppPurchasePlugin startProductRequest:result:]', - value: productResponseMap); + value: buildProductResponseMap(dummyProductResponseWrapper)); final SKRequestMaker request = SKRequestMaker(); final SkProductResponseWrapper response = await request.startProductRequest(['123']); diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart new file mode 100644 index 000000000000..e47f49359bbf --- /dev/null +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -0,0 +1,173 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase/store_kit_wrappers.dart'; + +final dummyPayment = SKPaymentWrapper( + productIdentifier: 'prod-id', + applicationUsername: 'app-user-name', + requestData: 'fake-data-utf8', + quantity: 2, + simulatesAskToBuyInSandbox: true); +final SKError dummyError = + SKError(code: 111, domain: 'dummy-domain', userInfo: {'key': 'value'}); + +final SKDownloadWrapper dummyDownload = SKDownloadWrapper( + contentIdentifier: 'id', + state: SKDownloadState.failed, + contentLength: 32, + contentURL: 'https://download.com', + contentVersion: '0.0.1', + transactionID: 'tranID', + progress: 0.6, + timeRemaining: 1231231, + downloadTimeUnknown: false, + error: dummyError, +); +final SKPaymentTransactionWrapper dummyOriginalTransaction = + SKPaymentTransactionWrapper( + transactionState: SKPaymentTransactionStateWrapper.purchased, + payment: dummyPayment, + originalTransaction: null, + transactionTimeStamp: 1231231231.00, + transactionIdentifier: '123123', + downloads: [dummyDownload], + error: dummyError, +); +final SKPaymentTransactionWrapper dummyTransaction = + SKPaymentTransactionWrapper( + transactionState: SKPaymentTransactionStateWrapper.purchased, + payment: dummyPayment, + originalTransaction: dummyOriginalTransaction, + transactionTimeStamp: 1231231231.00, + transactionIdentifier: '123123', + downloads: [dummyDownload], + error: dummyError, +); + +final PriceLocaleWrapper dummyLocale = PriceLocaleWrapper(currencySymbol: '\$'); + +final SKProductSubscriptionPeriodWrapper dummySubscription = + SKProductSubscriptionPeriodWrapper( + numberOfUnits: 1, + unit: SubscriptionPeriodUnit.month, +); + +final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( + price: '1.0', + priceLocale: dummyLocale, + numberOfPeriods: 1, + paymentMode: ProductDiscountPaymentMode.payUpFront, + subscriptionPeriod: dummySubscription, +); + +final SKProductWrapper dummyProductWrapper = SKProductWrapper( + productIdentifier: 'id', + localizedTitle: 'title', + localizedDescription: 'description', + priceLocale: dummyLocale, + downloadContentVersion: 'version', + subscriptionGroupIdentifier: 'com.group', + price: '1.0', + downloadable: true, + downloadContentLengths: [1, 2], + subscriptionPeriod: dummySubscription, + introductoryPrice: dummyDiscount, +); + +final SkProductResponseWrapper dummyProductResponseWrapper = SkProductResponseWrapper( + products: [dummyProductWrapper], + invalidProductIdentifiers: ['123'], +); + +Map buildLocaleMap(PriceLocaleWrapper local) { + return { + 'currencySymbol': local.currencySymbol + }; +} + +Map buildSubscriptionPeriodMap(SKProductSubscriptionPeriodWrapper sub) { + return { + 'numberOfUnits': sub.numberOfUnits, + 'unit':SubscriptionPeriodUnit.values.indexOf(sub.unit), + }; +} + +Map buildDiscountMap(SKProductDiscountWrapper discount) { + return { + 'price':discount.price, + 'priceLocale':buildLocaleMap(discount.priceLocale), + 'numberOfPeriods': discount.numberOfPeriods, + 'paymentMode':ProductDiscountPaymentMode.values.indexOf(discount.paymentMode), + 'subscriptionPeriod':buildSubscriptionPeriodMap(discount.subscriptionPeriod), + }; +} + +Map buildProductMap(SKProductWrapper product) { + return { + 'productIdentifier':product.productIdentifier, + 'localizedTitle':product.localizedTitle, + 'localizedDescription':product.localizedDescription, + 'priceLocale':buildLocaleMap(product.priceLocale), + 'downloadContentVersion':product.downloadContentVersion, + 'subscriptionGroupIdentifier':product.subscriptionGroupIdentifier, + 'price':product.price, + 'downloadable':product.downloadable, + 'downloadContentLengths':product.downloadContentLengths, + 'subscriptionPeriod':buildSubscriptionPeriodMap(product.subscriptionPeriod), + 'introductoryPrice':buildDiscountMap(product.introductoryPrice), + }; +} + +MapbuildProductResponseMap(SkProductResponseWrapper response) { + List productsMap = response.products.map((SKProductWrapper product)=>buildProductMap(product)).toList(); + return { + 'products':productsMap, + 'invalidProductIdentifiers':response.invalidProductIdentifiers + }; +} + +Map buildErrorMap(SKError error) { + return { + 'code': error.code, + 'domain': error.domain, + 'userInfo': error.userInfo, + }; +} + +Map buildDownloadMap(SKDownloadWrapper download) { + return { + 'contentIdentifier': download.contentIdentifier, + 'state': SKDownloadState.values.indexOf(download.state), + 'contentLength': download.contentLength, + 'contentURL': download.contentURL, + 'contentVersion': download.contentVersion, + 'transactionID': download.transactionID, + 'progress': download.progress, + 'timeRemaining': download.timeRemaining, + 'downloadTimeUnknown': download.downloadTimeUnknown, + 'error': buildErrorMap(download.error) + }; +} + +Map buildTransactionMap( + SKPaymentTransactionWrapper transaction) { + if (transaction == null) { + return null; + } + Map map = { + 'transactionState': SKPaymentTransactionStateWrapper.values + .indexOf(SKPaymentTransactionStateWrapper.purchased), + 'payment': transaction.payment.toMap(), + 'originalTransaction': buildTransactionMap(transaction.originalTransaction), + 'transactionTimeStamp': transaction.transactionTimeStamp, + 'transactionIdentifier': transaction.transactionIdentifier, + 'error': buildErrorMap(transaction.error), + }; + List downloadList = transaction.downloads.map((SKDownloadWrapper download) { + return buildDownloadMap(download); + }).toList(); + map['downloads'] = downloadList; + return map; +} From 906c1caa1aa2bb7e7a2b4b6350836c0cb58319d7 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 6 Mar 2019 17:25:28 -0800 Subject: [PATCH 3/7] remove duplicated tests --- .../sk_receipt_manager.dart | 2 +- .../app_store_connection_test.dart | 44 ++---------- .../sk_methodchannel_apis_test.dart | 28 +++++++- .../sk_payment_queue_wrapper_test.dart | 70 ------------------- .../store_kit_wrappers/sk_product_test.dart | 42 ++++++++++- .../sk_receipt_manager_test.dart | 26 ------- .../store_kit_wrappers/sk_request_test.dart | 55 --------------- 7 files changed, 74 insertions(+), 193 deletions(-) delete mode 100644 packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart delete mode 100644 packages/in_app_purchase/test/store_kit_wrappers/sk_receipt_manager_test.dart delete mode 100644 packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart index eed09f1835ef..7608275d8a7f 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_receipt_manager.dart @@ -14,7 +14,7 @@ class SKReceiptManager { /// There are 2 ways to do so. Either validate locally or validate with App Store. /// For more details on how to validate the receipt data, you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1). /// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt. - Future retrieveReceiptData() { + static Future retrieveReceiptData() { return channel .invokeMethod('-[InAppPurchasePlugin retrieveReceiptData:result:]'); } diff --git a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart index 3d2ec3f17376..aba9d89dcaa2 100644 --- a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart +++ b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart @@ -8,6 +8,7 @@ import 'package:in_app_purchase/src/channel.dart'; import 'package:in_app_purchase/src/in_app_purchase_connection/app_store_connection.dart'; import '../stub_in_app_purchase_platform.dart'; import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart'; +import '../store_kit_wrappers/sk_test_stub_objects.dart'; void main() { final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); @@ -29,47 +30,14 @@ void main() { }); }); - final Map localeMap = { - 'currencySymbol': '\$' - }; - final Map subMap = { - 'numberOfUnits': 1, - 'unit': 2 - }; - final Map discountMap = { - 'price': '1.0', - 'priceLocale': localeMap, - 'numberOfPeriods': 1, - 'paymentMode': 2, - 'subscriptionPeriod': subMap, - }; - final Map productMap = { - 'productIdentifier': 'id', - 'localizedTitle': 'splash coin', - 'localizedDescription': 'description', - 'priceLocale': localeMap, - 'downloadContentVersion': 'version', - 'subscriptionGroupIdentifier': 'com.group', - 'price': '1.0', - 'downloadable': true, - 'downloadContentLengths': [1, 2], - 'subscriptionPeriod': subMap, - 'introductoryPrice': discountMap, - }; - - final Map> productResponseMap = >{ - 'products': >[productMap], - 'invalidProductIdentifiers': ['567'], - }; - group('query product list', () { test('should get product list', () async { stubPlatform.addResponse( name: '-[InAppPurchasePlugin startProductRequest:result:]', - value: productResponseMap); + value: buildProductResponseMap(dummyProductResponseWrapper)); final AppStoreConnection connection = AppStoreConnection(); final ProductDetailsResponse response = - await connection.queryProductDetails(['123'].toSet()); + await connection.queryProductDetails(['id'].toSet()); List products = response.productDetails; expect( products, @@ -77,7 +45,7 @@ void main() { ); expect( products.first.title, - 'splash coin', + 'title', ); expect( products.first.title, @@ -88,13 +56,13 @@ void main() { test('should get correct not found identifiers', () async { stubPlatform.addResponse( name: '-[InAppPurchasePlugin startProductRequest:result:]', - value: productResponseMap); + value: buildProductResponseMap(dummyProductResponseWrapper)); final AppStoreConnection connection = AppStoreConnection(); final ProductDetailsResponse response = await connection.queryProductDetails(['123'].toSet()); expect( response.notFoundIDs, - ['567'], + ['123'], ); }); }); diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index ec7ed1c66c80..acb629bd3ca9 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -66,7 +66,7 @@ void main() { group('sk_receipt_manager', () { test('should get receipt (faking it by returning a `receipt data` string)', () async { - String receiptData = await SKReceiptManager().retrieveReceiptData(); + String receiptData = await SKReceiptManager.retrieveReceiptData(); expect(receiptData, 'receipt data'); }); }); @@ -87,6 +87,22 @@ void main() { await queue.addPayment(dummyPayment); expect(fakeIOSPlatform.payments.first, equals(dummyPayment)); }); + + test('should finish transaction', () async { + SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); + TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); + queue.setTransactionObserver(observer); + await queue.finishTransaction(dummyTransaction); + expect(fakeIOSPlatform.transactionsFinished.first, equals(dummyTransaction.transactionIdentifier)); + }); + + test('should restore transaction', () async { + SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); + TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); + queue.setTransactionObserver(observer); + await queue.restoreTransactions(applicationUserName: 'aUserID'); + expect(fakeIOSPlatform.applicationNameHasTransactionRestored, 'aUserID'); + }); }); } @@ -105,6 +121,8 @@ class FakeIOSPlatform { // payment queue List payments = []; + List transactionsFinished = []; + String applicationNameHasTransactionRestored; Future onMethodCall(MethodCall call) { switch (call.method) { @@ -131,6 +149,12 @@ class FakeIOSPlatform { case '-[InAppPurchasePlugin addPayment:result:]': payments.add(SKPaymentWrapper.fromJson(call.arguments)); return Future.sync((){}); + case '-[InAppPurchasePlugin finishTransaction:result:]': + transactionsFinished.add(call.arguments); + return Future.sync((){}); + case '-[InAppPurchasePlugin restoreTransactions:result:]': + applicationNameHasTransactionRestored = call.arguments; + return Future.sync((){}); } return Future.sync(() {}); } @@ -151,4 +175,4 @@ class TestPaymentTransactionObserver extends SKTransactionObserverWrapper { {SKPaymentWrapper payment, SKProductWrapper product}){ return true; } -} +} \ No newline at end of file diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart deleted file mode 100644 index 734ae11769d5..000000000000 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:test/test.dart'; - -import 'package:in_app_purchase/store_kit_wrappers.dart'; -import 'package:in_app_purchase/src/channel.dart'; -import '../stub_in_app_purchase_platform.dart'; -import 'sk_test_stub_objects.dart'; - -void main() { - final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); - - setUpAll(() => - channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); - - group('canMakePayments', () { - test('YES', () async { - stubPlatform.addResponse( - name: '-[SKPaymentQueue canMakePayments:]', value: true); - expect(await SKPaymentQueueWrapper.canMakePayments(), isTrue); - }); - - test('NO', () async { - stubPlatform.addResponse( - name: '-[SKPaymentQueue canMakePayments:]', value: false); - expect(await SKPaymentQueueWrapper.canMakePayments(), isFalse); - }); - }); - - group('Wrapper fromJson tests', () { - test('Should construct correct SKPaymentWrapper from json', () { - SKPaymentWrapper payment = - SKPaymentWrapper.fromJson(dummyPayment.toMap()); - expect(payment, equals(dummyPayment)); - }); - - test('Should construct correct SKError from json', () { - SKError error = SKError.fromJson(buildErrorMap(dummyError)); - expect(error, equals(dummyError)); - }); - - test('Should construct correct SKDownloadWrapper from json', () { - SKDownloadWrapper download = - SKDownloadWrapper.fromJson(buildDownloadMap(dummyDownload)); - expect(download, equals(dummyDownload)); - }); - - test('Should construct correct SKTransactionWrapper from json', () { - SKPaymentTransactionWrapper transaction = - SKPaymentTransactionWrapper.fromJson( - buildTransactionMap(dummyTransaction)); - expect(transaction, equals(dummyTransaction)); - }); - - test('Should generate correct map of the payment object', () { - Map map = dummyPayment.toMap(); - expect(map['productIdentifier'], dummyPayment.productIdentifier); - expect(map['applicationUsername'], dummyPayment.applicationUsername); - - expect(map['requestData'], dummyPayment.requestData); - - expect(map['quantity'], dummyPayment.quantity); - - expect(map['simulatesAskToBuyInSandbox'], - dummyPayment.simulatesAskToBuyInSandbox); - }); - }); -} diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart index ff50f4b74aaf..ee1caba7b1ba 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart @@ -5,11 +5,12 @@ import 'package:test/test.dart'; import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart'; import 'package:in_app_purchase/src/in_app_purchase_connection/product_details.dart'; +import 'package:in_app_purchase/store_kit_wrappers.dart'; import 'sk_test_stub_objects.dart'; void main() { - group('product request wrapper test', () { + group('product related object wrapper test', () { test( 'SKProductSubscriptionPeriodWrapper should have property values consistent with map', @@ -101,4 +102,43 @@ void main() { expect(wrapper, equals(dummyLocale)); }); }); + + group('Payment queue related object tests', () { + test('Should construct correct SKPaymentWrapper from json', () { + SKPaymentWrapper payment = + SKPaymentWrapper.fromJson(dummyPayment.toMap()); + expect(payment, equals(dummyPayment)); + }); + + test('Should construct correct SKError from json', () { + SKError error = SKError.fromJson(buildErrorMap(dummyError)); + expect(error, equals(dummyError)); + }); + + test('Should construct correct SKDownloadWrapper from json', () { + SKDownloadWrapper download = + SKDownloadWrapper.fromJson(buildDownloadMap(dummyDownload)); + expect(download, equals(dummyDownload)); + }); + + test('Should construct correct SKTransactionWrapper from json', () { + SKPaymentTransactionWrapper transaction = + SKPaymentTransactionWrapper.fromJson( + buildTransactionMap(dummyTransaction)); + expect(transaction, equals(dummyTransaction)); + }); + + test('Should generate correct map of the payment object', () { + Map map = dummyPayment.toMap(); + expect(map['productIdentifier'], dummyPayment.productIdentifier); + expect(map['applicationUsername'], dummyPayment.applicationUsername); + + expect(map['requestData'], dummyPayment.requestData); + + expect(map['quantity'], dummyPayment.quantity); + + expect(map['simulatesAskToBuyInSandbox'], + dummyPayment.simulatesAskToBuyInSandbox); + }); + }); } diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_receipt_manager_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_receipt_manager_test.dart deleted file mode 100644 index b35fd903b8ec..000000000000 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_receipt_manager_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:test/test.dart'; -import 'package:in_app_purchase/src/store_kit_wrappers/sk_receipt_manager.dart'; -import 'package:in_app_purchase/src/channel.dart'; - -import '../stub_in_app_purchase_platform.dart'; - -void main() { - final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); - - setUpAll(() => - channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); - - group('retrieveReceiptData', () { - test('Should get result', () async { - stubPlatform.addResponse( - name: '-[InAppPurchasePlugin retrieveReceiptData:result:]', - value: 'dummy data'); - final String result = await SKReceiptManager().retrieveReceiptData(); - expect(result, 'dummy data'); - }); - }); -} diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart deleted file mode 100644 index 6460078befd9..000000000000 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:test/test.dart'; -import 'package:in_app_purchase/src/store_kit_wrappers/sk_product_wrapper.dart'; -import 'package:in_app_purchase/src/channel.dart'; -import 'package:in_app_purchase/src/store_kit_wrappers/sk_request_maker.dart'; -import '../stub_in_app_purchase_platform.dart'; -import 'sk_test_stub_objects.dart'; - -void main() { - final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); - - setUpAll(() => - channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); - - group('startProductRequest api', () { - test('platform call should get result', () async { - stubPlatform.addResponse( - name: '-[InAppPurchasePlugin startProductRequest:result:]', - value: buildProductResponseMap(dummyProductResponseWrapper)); - final SKRequestMaker request = SKRequestMaker(); - final SkProductResponseWrapper response = - await request.startProductRequest(['123']); - expect( - response.products, - isNotEmpty, - ); - expect( - response.products.first.priceLocale.currencySymbol, - '\$', - ); - expect( - response.products.first.priceLocale.currencySymbol, - isNot('A'), - ); - expect( - response.invalidProductIdentifiers, - isNotEmpty, - ); - }); - - test('result is null should throw', () async { - stubPlatform.addResponse( - name: '-[InAppPurchasePlugin startProductRequest:result:]', - value: null); - final SKRequestMaker request = SKRequestMaker(); - expect( - request.startProductRequest(['123']), - throwsException, - ); - }); - }); -} From 89fd7b1b5fe8d7d5cd279695a726e4b830948066 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 6 Mar 2019 17:32:26 -0800 Subject: [PATCH 4/7] formatting and renaming --- .../sk_payment_queue_wrapper.dart | 3 +- .../sk_product_wrapper.dart | 27 +++++---- .../sk_methodchannel_apis_test.dart | 46 ++++++++------- .../store_kit_wrappers/sk_product_test.dart | 19 ++++--- .../sk_test_stub_objects.dart | 56 ++++++++++--------- 5 files changed, 83 insertions(+), 68 deletions(-) diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index d4d2bc546c65..456071fefb6b 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -318,8 +318,7 @@ class SKPaymentTransactionWrapper { typedOther.originalTransaction == originalTransaction && typedOther.transactionTimeStamp == transactionTimeStamp && typedOther.transactionIdentifier == transactionIdentifier && - DeepCollectionEquality() - .equals(typedOther.downloads, downloads) && + DeepCollectionEquality().equals(typedOther.downloads, downloads) && typedOther.error == error; } } diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart index 14961570e053..3dc61e9ae61d 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart @@ -52,8 +52,7 @@ class SkProductResponseWrapper { return false; } final SkProductResponseWrapper typedOther = other; - return DeepCollectionEquality() - .equals(typedOther.products, products) && + return DeepCollectionEquality().equals(typedOther.products, products) && DeepCollectionEquality().equals( typedOther.invalidProductIdentifiers, invalidProductIdentifiers); } @@ -64,7 +63,7 @@ class SkProductResponseWrapper { /// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minium is a day and maxium is a year. // The values of the enum options are matching the [SKProductPeriodUnit]'s values. Should there be an update or addition // in the [SKProductPeriodUnit], this need to be updated to match. -enum SubscriptionPeriodUnit { +enum SKSubscriptionPeriodUnit { @JsonValue(0) day, @JsonValue(1) @@ -99,7 +98,7 @@ class SKProductSubscriptionPeriodWrapper { final int numberOfUnits; /// The time unit used to specify the length of this period. - final SubscriptionPeriodUnit unit; + final SKSubscriptionPeriodUnit unit; @override bool operator ==(Object other) { @@ -119,7 +118,7 @@ class SKProductSubscriptionPeriodWrapper { /// This is used as a property in the [SKProductDiscountWrapper]. // The values of the enum options are matching the [SKProductDiscountPaymentMode]'s values. Should there be an update or addition // in the [SKProductDiscountPaymentMode], this need to be updated to match. -enum ProductDiscountPaymentMode { +enum SKProductDiscountPaymentMode { /// Allows user to pay the discounted price at each payment period. @JsonValue(0) payAsYouGo, @@ -158,7 +157,7 @@ class SKProductDiscountWrapper { final String price; /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale. - final PriceLocaleWrapper priceLocale; + final SKPriceLocaleWrapper priceLocale; /// The object represent the discount period length. /// @@ -166,7 +165,7 @@ class SKProductDiscountWrapper { final int numberOfPeriods; /// The object indicates how the discount price is charged. - final ProductDiscountPaymentMode paymentMode; + final SKProductDiscountPaymentMode paymentMode; /// The object represents the duration of single subscription period for the discount. /// @@ -234,7 +233,7 @@ class SKProductWrapper { final String localizedDescription; /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale. - final PriceLocaleWrapper priceLocale; + final SKPriceLocaleWrapper priceLocale; /// The version of the downloadable content. /// @@ -293,8 +292,8 @@ class SKProductWrapper { typedOther.subscriptionGroupIdentifier == subscriptionGroupIdentifier && typedOther.price == price && typedOther.downloadable == downloadable && - DeepCollectionEquality.unordered() - .equals(typedOther.downloadContentLengths, downloadContentLengths) && + DeepCollectionEquality.unordered().equals( + typedOther.downloadContentLengths, downloadContentLengths) && typedOther.subscriptionPeriod == subscriptionPeriod && typedOther.introductoryPrice == introductoryPrice; } @@ -317,14 +316,14 @@ class SKProductWrapper { // Matching android to only get the currencySymbol for now. // https://github.com/flutter/flutter/issues/26610 @JsonSerializable() -class PriceLocaleWrapper { - PriceLocaleWrapper({@required this.currencySymbol}); +class SKPriceLocaleWrapper { + SKPriceLocaleWrapper({@required this.currencySymbol}); /// Constructing an instance from a map from the Objective-C layer. /// /// This method should only be used with `map` values returned by [SKProductWrapper.fromJson] and [SKProductDiscountWrapper.fromJson]. /// The `map` parameter must not be null. - factory PriceLocaleWrapper.fromJson(Map map) { + factory SKPriceLocaleWrapper.fromJson(Map map) { assert(map != null, 'Map must not be null.'); return _$PriceLocaleWrapperFromJson(map); } @@ -340,7 +339,7 @@ class PriceLocaleWrapper { if (other.runtimeType != runtimeType) { return false; } - final PriceLocaleWrapper typedOther = other; + final SKPriceLocaleWrapper typedOther = other; return typedOther.currencySymbol == currencySymbol; } } diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index acb629bd3ca9..75873dd8d438 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -76,13 +76,17 @@ void main() { expect(await SKPaymentQueueWrapper.canMakePayments(), true); }); - test('throws if observer is not set for payment queue before adding payment', () async { - expect(SKPaymentQueueWrapper().addPayment(dummyPayment), throwsAssertionError); + test( + 'throws if observer is not set for payment queue before adding payment', + () async { + expect(SKPaymentQueueWrapper().addPayment(dummyPayment), + throwsAssertionError); }); test('should add payment to the payment queue', () async { SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); - TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); + TestPaymentTransactionObserver observer = + TestPaymentTransactionObserver(); queue.setTransactionObserver(observer); await queue.addPayment(dummyPayment); expect(fakeIOSPlatform.payments.first, equals(dummyPayment)); @@ -90,15 +94,18 @@ void main() { test('should finish transaction', () async { SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); - TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); + TestPaymentTransactionObserver observer = + TestPaymentTransactionObserver(); queue.setTransactionObserver(observer); await queue.finishTransaction(dummyTransaction); - expect(fakeIOSPlatform.transactionsFinished.first, equals(dummyTransaction.transactionIdentifier)); + expect(fakeIOSPlatform.transactionsFinished.first, + equals(dummyTransaction.transactionIdentifier)); }); test('should restore transaction', () async { SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); - TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); + TestPaymentTransactionObserver observer = + TestPaymentTransactionObserver(); queue.setTransactionObserver(observer); await queue.restoreTransactions(applicationUserName: 'aUserID'); expect(fakeIOSPlatform.applicationNameHasTransactionRestored, 'aUserID'); @@ -135,7 +142,8 @@ class FakeIOSPlatform { if (getProductRequestFailTest) { return Future>.value(null); } - return Future>.value(buildProductResponseMap(dummyProductResponseWrapper)); + return Future>.value( + buildProductResponseMap(dummyProductResponseWrapper)); case '-[InAppPurchasePlugin refreshReceipt:result:]': refreshReceipt++; refreshReceiptParam = call.arguments; @@ -148,31 +156,31 @@ class FakeIOSPlatform { return Future.value(true); case '-[InAppPurchasePlugin addPayment:result:]': payments.add(SKPaymentWrapper.fromJson(call.arguments)); - return Future.sync((){}); + return Future.sync(() {}); case '-[InAppPurchasePlugin finishTransaction:result:]': transactionsFinished.add(call.arguments); - return Future.sync((){}); + return Future.sync(() {}); case '-[InAppPurchasePlugin restoreTransactions:result:]': applicationNameHasTransactionRestored = call.arguments; - return Future.sync((){}); + return Future.sync(() {}); } return Future.sync(() {}); } } class TestPaymentTransactionObserver extends SKTransactionObserverWrapper { - void updatedTransactions({List transactions}){} + void updatedTransactions({List transactions}) {} - void removedTransactions({List transactions}){} + void removedTransactions({List transactions}) {} - void restoreCompletedTransactions({Error error}){} + void restoreCompletedTransactions({Error error}) {} - void paymentQueueRestoreCompletedTransactionsFinished(){} + void paymentQueueRestoreCompletedTransactionsFinished() {} - void updatedDownloads({List downloads}){} + void updatedDownloads({List downloads}) {} bool shouldAddStorePayment( - {SKPaymentWrapper payment, SKProductWrapper product}){ - return true; - } -} \ No newline at end of file + {SKPaymentWrapper payment, SKProductWrapper product}) { + return true; + } +} diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart index ee1caba7b1ba..0b3a9ac7d008 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart @@ -9,14 +9,13 @@ import 'package:in_app_purchase/store_kit_wrappers.dart'; import 'sk_test_stub_objects.dart'; void main() { - group('product related object wrapper test', () { - test( 'SKProductSubscriptionPeriodWrapper should have property values consistent with map', () { final SKProductSubscriptionPeriodWrapper wrapper = - SKProductSubscriptionPeriodWrapper.fromJson(buildSubscriptionPeriodMap(dummySubscription)); + SKProductSubscriptionPeriodWrapper.fromJson( + buildSubscriptionPeriodMap(dummySubscription)); expect(wrapper, equals(dummySubscription)); }); @@ -51,7 +50,8 @@ void main() { test('SKProductWrapper should have property values consistent with map', () { - final SKProductWrapper wrapper = SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); + final SKProductWrapper wrapper = + SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); expect(wrapper, equals(dummyProductWrapper)); }); @@ -71,7 +71,8 @@ void main() { }); test('toProductDetails() should return correct Product object', () { - final SKProductWrapper wrapper = SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); + final SKProductWrapper wrapper = + SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); final ProductDetails product = wrapper.toProductDetails(); expect(product.title, wrapper.localizedTitle); expect(product.description, wrapper.localizedDescription); @@ -82,7 +83,8 @@ void main() { test('SKProductResponse wrapper should match', () { final SkProductResponseWrapper wrapper = - SkProductResponseWrapper.fromJson(buildProductResponseMap(dummyProductResponseWrapper)); + SkProductResponseWrapper.fromJson( + buildProductResponseMap(dummyProductResponseWrapper)); expect(wrapper, equals(dummyProductResponseWrapper)); }); test('SKProductResponse wrapper should default to empty list', () { @@ -98,12 +100,13 @@ void main() { }); test('LocaleWrapper should have property values consistent with map', () { - final PriceLocaleWrapper wrapper = PriceLocaleWrapper.fromJson(buildLocaleMap(dummyLocale)); + final PriceLocaleWrapper wrapper = + PriceLocaleWrapper.fromJson(buildLocaleMap(dummyLocale)); expect(wrapper, equals(dummyLocale)); }); }); - group('Payment queue related object tests', () { + group('Payment queue related object tests', () { test('Should construct correct SKPaymentWrapper from json', () { SKPaymentWrapper payment = SKPaymentWrapper.fromJson(dummyPayment.toMap()); diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart index 05b7c8a54d46..bc6d1f67978a 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -75,55 +75,61 @@ final SKProductWrapper dummyProductWrapper = SKProductWrapper( introductoryPrice: dummyDiscount, ); -final SkProductResponseWrapper dummyProductResponseWrapper = SkProductResponseWrapper( +final SkProductResponseWrapper dummyProductResponseWrapper = + SkProductResponseWrapper( products: [dummyProductWrapper], invalidProductIdentifiers: ['123'], ); Map buildLocaleMap(PriceLocaleWrapper local) { - return { - 'currencySymbol': local.currencySymbol - }; + return {'currencySymbol': local.currencySymbol}; } -Map buildSubscriptionPeriodMap(SKProductSubscriptionPeriodWrapper sub) { +Map buildSubscriptionPeriodMap( + SKProductSubscriptionPeriodWrapper sub) { return { 'numberOfUnits': sub.numberOfUnits, - 'unit':SubscriptionPeriodUnit.values.indexOf(sub.unit), + 'unit': SubscriptionPeriodUnit.values.indexOf(sub.unit), }; } Map buildDiscountMap(SKProductDiscountWrapper discount) { return { - 'price':discount.price, - 'priceLocale':buildLocaleMap(discount.priceLocale), + 'price': discount.price, + 'priceLocale': buildLocaleMap(discount.priceLocale), 'numberOfPeriods': discount.numberOfPeriods, - 'paymentMode':ProductDiscountPaymentMode.values.indexOf(discount.paymentMode), - 'subscriptionPeriod':buildSubscriptionPeriodMap(discount.subscriptionPeriod), + 'paymentMode': + ProductDiscountPaymentMode.values.indexOf(discount.paymentMode), + 'subscriptionPeriod': + buildSubscriptionPeriodMap(discount.subscriptionPeriod), }; } Map buildProductMap(SKProductWrapper product) { return { - 'productIdentifier':product.productIdentifier, - 'localizedTitle':product.localizedTitle, - 'localizedDescription':product.localizedDescription, - 'priceLocale':buildLocaleMap(product.priceLocale), - 'downloadContentVersion':product.downloadContentVersion, - 'subscriptionGroupIdentifier':product.subscriptionGroupIdentifier, - 'price':product.price, - 'downloadable':product.downloadable, - 'downloadContentLengths':product.downloadContentLengths, - 'subscriptionPeriod':buildSubscriptionPeriodMap(product.subscriptionPeriod), - 'introductoryPrice':buildDiscountMap(product.introductoryPrice), + 'productIdentifier': product.productIdentifier, + 'localizedTitle': product.localizedTitle, + 'localizedDescription': product.localizedDescription, + 'priceLocale': buildLocaleMap(product.priceLocale), + 'downloadContentVersion': product.downloadContentVersion, + 'subscriptionGroupIdentifier': product.subscriptionGroupIdentifier, + 'price': product.price, + 'downloadable': product.downloadable, + 'downloadContentLengths': product.downloadContentLengths, + 'subscriptionPeriod': + buildSubscriptionPeriodMap(product.subscriptionPeriod), + 'introductoryPrice': buildDiscountMap(product.introductoryPrice), }; } -MapbuildProductResponseMap(SkProductResponseWrapper response) { - List productsMap = response.products.map((SKProductWrapper product)=>buildProductMap(product)).toList(); +Map buildProductResponseMap( + SkProductResponseWrapper response) { + List productsMap = response.products + .map((SKProductWrapper product) => buildProductMap(product)) + .toList(); return { - 'products':productsMap, - 'invalidProductIdentifiers':response.invalidProductIdentifiers + 'products': productsMap, + 'invalidProductIdentifiers': response.invalidProductIdentifiers }; } From e08744abf6cd7fd69efc2819f0c19a57d4c88314 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 6 Mar 2019 17:42:20 -0800 Subject: [PATCH 5/7] build fixes and some spelling fixes --- .../sk_payment_queue_wrapper.dart | 4 +-- .../sk_product_wrapper.dart | 4 +-- .../sk_product_wrapper.g.dart | 26 +++++++++---------- .../store_kit_wrappers/sk_request_maker.dart | 2 +- .../store_kit_wrappers/sk_product_test.dart | 4 +-- .../sk_test_stub_objects.dart | 12 ++++----- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index 456071fefb6b..d9a1e45f8987 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -215,7 +215,7 @@ enum SKPaymentTransactionStateWrapper { /// Indicates the transaction is being processed in App Store. /// /// You should update your UI to indicate the process and waiting for the transaction to update to the next state. - /// Never complte a transaction that is in purchasing state. + /// Never complete a transaction that is in purchasing state. @JsonValue(0) purchasing, @@ -529,7 +529,7 @@ class SKPaymentWrapper { /// An opaque id for the user's account. /// /// Used to help the store detect irregular activity. See https://developer.apple.com/documentation/storekit/skpayment/1506116-applicationusername?language=objc for more details. - /// For example, you can use a one-way hash of the user’s account name on your server. Don’t use the Apple ID for your developer account, the user’s Apple ID, or the user’s unhashed account name on your server. + /// For example, you can use a one-way hash of the user’s account name on your server. Don’t use the Apple ID for your developer account, the user’s Apple ID, or the user’s not hashed account name on your server. final String applicationUsername; /// Reserved for future use. diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart index 3dc61e9ae61d..633f6b4ba1b9 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.dart @@ -60,7 +60,7 @@ class SkProductResponseWrapper { /// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc). /// -/// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minium is a day and maxium is a year. +/// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minimum is a day and maximum is a year. // The values of the enum options are matching the [SKProductPeriodUnit]'s values. Should there be an update or addition // in the [SKProductPeriodUnit], this need to be updated to match. enum SKSubscriptionPeriodUnit { @@ -269,7 +269,7 @@ class SKProductWrapper { /// The object represents the duration of single subscription period. /// /// This is only available if you set up the introductory price in the App Store Connect, otherwise it will be null. - /// Programmar is also responsible to determine if the user is eligible to receive it. See https://developer.apple.com/documentation/storekit/in-app_purchase/offering_introductory_pricing_in_your_app?language=objc + /// Programmer is also responsible to determine if the user is eligible to receive it. See https://developer.apple.com/documentation/storekit/in-app_purchase/offering_introductory_pricing_in_your_app?language=objc /// for more details. /// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod], /// and their units and duration do not have to be matched. diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart index d6392d8cd1a4..04895402730a 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart @@ -44,11 +44,11 @@ T _$enumDecodeNullable(Map enumValues, dynamic source) { return _$enumDecode(enumValues, source); } -const _$SubscriptionPeriodUnitEnumMap = { - SubscriptionPeriodUnit.day: 0, - SubscriptionPeriodUnit.week: 1, - SubscriptionPeriodUnit.month: 2, - SubscriptionPeriodUnit.year: 3 +const _$SubscriptionPeriodUnitEnumMap = { + SKSubscriptionPeriodUnit.day: 0, + SKSubscriptionPeriodUnit.week: 1, + SKSubscriptionPeriodUnit.month: 2, + SKSubscriptionPeriodUnit.year: 3 }; SKProductDiscountWrapper _$SKProductDiscountWrapperFromJson(Map json) { @@ -56,7 +56,7 @@ SKProductDiscountWrapper _$SKProductDiscountWrapperFromJson(Map json) { price: json['price'] as String, priceLocale: json['priceLocale'] == null ? null - : PriceLocaleWrapper.fromJson(json['priceLocale'] as Map), + : SKPriceLocaleWrapper.fromJson(json['priceLocale'] as Map), numberOfPeriods: json['numberOfPeriods'] as int, paymentMode: _$enumDecodeNullable( _$ProductDiscountPaymentModeEnumMap, json['paymentMode']), @@ -67,10 +67,10 @@ SKProductDiscountWrapper _$SKProductDiscountWrapperFromJson(Map json) { } const _$ProductDiscountPaymentModeEnumMap = - { - ProductDiscountPaymentMode.payAsYouGo: 0, - ProductDiscountPaymentMode.payUpFront: 1, - ProductDiscountPaymentMode.freeTrail: 2 + { + SKProductDiscountPaymentMode.payAsYouGo: 0, + SKProductDiscountPaymentMode.payUpFront: 1, + SKProductDiscountPaymentMode.freeTrail: 2 }; SKProductWrapper _$SKProductWrapperFromJson(Map json) { @@ -80,7 +80,7 @@ SKProductWrapper _$SKProductWrapperFromJson(Map json) { localizedDescription: json['localizedDescription'] as String, priceLocale: json['priceLocale'] == null ? null - : PriceLocaleWrapper.fromJson(json['priceLocale'] as Map), + : SKPriceLocaleWrapper.fromJson(json['priceLocale'] as Map), downloadContentVersion: json['downloadContentVersion'] as String, subscriptionGroupIdentifier: json['subscriptionGroupIdentifier'] as String, @@ -99,6 +99,6 @@ SKProductWrapper _$SKProductWrapperFromJson(Map json) { json['introductoryPrice'] as Map)); } -PriceLocaleWrapper _$PriceLocaleWrapperFromJson(Map json) { - return PriceLocaleWrapper(currencySymbol: json['currencySymbol'] as String); +SKPriceLocaleWrapper _$PriceLocaleWrapperFromJson(Map json) { + return SKPriceLocaleWrapper(currencySymbol: json['currencySymbol'] as String); } diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart index 353d34e5f9be..7d62c0bebb5a 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart @@ -15,7 +15,7 @@ import 'sk_product_wrapper.dart'; class SKRequestMaker { /// Fetches product information for a list of given product identifiers. /// - /// The `productIdentifiers` should contain legit product identifiers that you declared for the products in the Itunes Connect. invalid identifiers + /// The `productIdentifiers` should contain legit product identifiers that you declared for the products in the ITunes Connect. invalid identifiers /// Will be stored and returned in [SkProductResponseWrapper.invalidProductIdentifiers]. Duplicate values in `productIdentifiers` will be omitted. /// If `productIdentifiers` is null, an `storekit_invalid_argument` error will be returned. If `productIdentifiers` is empty, a [SkProductResponseWrapper] /// will still be returned with [SkProductResponseWrapper.products] being null. diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart index 0b3a9ac7d008..107826f3a044 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_product_test.dart @@ -100,8 +100,8 @@ void main() { }); test('LocaleWrapper should have property values consistent with map', () { - final PriceLocaleWrapper wrapper = - PriceLocaleWrapper.fromJson(buildLocaleMap(dummyLocale)); + final SKPriceLocaleWrapper wrapper = + SKPriceLocaleWrapper.fromJson(buildLocaleMap(dummyLocale)); expect(wrapper, equals(dummyLocale)); }); }); diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart index bc6d1f67978a..546ab01552ef 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -45,19 +45,19 @@ final SKPaymentTransactionWrapper dummyTransaction = error: dummyError, ); -final PriceLocaleWrapper dummyLocale = PriceLocaleWrapper(currencySymbol: '\$'); +final SKPriceLocaleWrapper dummyLocale = SKPriceLocaleWrapper(currencySymbol: '\$'); final SKProductSubscriptionPeriodWrapper dummySubscription = SKProductSubscriptionPeriodWrapper( numberOfUnits: 1, - unit: SubscriptionPeriodUnit.month, + unit: SKSubscriptionPeriodUnit.month, ); final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( price: '1.0', priceLocale: dummyLocale, numberOfPeriods: 1, - paymentMode: ProductDiscountPaymentMode.payUpFront, + paymentMode: SKProductDiscountPaymentMode.payUpFront, subscriptionPeriod: dummySubscription, ); @@ -81,7 +81,7 @@ final SkProductResponseWrapper dummyProductResponseWrapper = invalidProductIdentifiers: ['123'], ); -Map buildLocaleMap(PriceLocaleWrapper local) { +Map buildLocaleMap(SKPriceLocaleWrapper local) { return {'currencySymbol': local.currencySymbol}; } @@ -89,7 +89,7 @@ Map buildSubscriptionPeriodMap( SKProductSubscriptionPeriodWrapper sub) { return { 'numberOfUnits': sub.numberOfUnits, - 'unit': SubscriptionPeriodUnit.values.indexOf(sub.unit), + 'unit': SKSubscriptionPeriodUnit.values.indexOf(sub.unit), }; } @@ -99,7 +99,7 @@ Map buildDiscountMap(SKProductDiscountWrapper discount) { 'priceLocale': buildLocaleMap(discount.priceLocale), 'numberOfPeriods': discount.numberOfPeriods, 'paymentMode': - ProductDiscountPaymentMode.values.indexOf(discount.paymentMode), + SKProductDiscountPaymentMode.values.indexOf(discount.paymentMode), 'subscriptionPeriod': buildSubscriptionPeriodMap(discount.subscriptionPeriod), }; From cacf06916ad66545db04bd1be11d3399bda97312 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 6 Mar 2019 17:47:40 -0800 Subject: [PATCH 6/7] formatting --- .../test/store_kit_wrappers/sk_test_stub_objects.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart index 546ab01552ef..7fc8363d21ad 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -45,7 +45,8 @@ final SKPaymentTransactionWrapper dummyTransaction = error: dummyError, ); -final SKPriceLocaleWrapper dummyLocale = SKPriceLocaleWrapper(currencySymbol: '\$'); +final SKPriceLocaleWrapper dummyLocale = + SKPriceLocaleWrapper(currencySymbol: '\$'); final SKProductSubscriptionPeriodWrapper dummySubscription = SKProductSubscriptionPeriodWrapper( From f3f9c80d3b039f8060e1f50bd9205d503c9ee81f Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 7 Mar 2019 14:59:58 -0800 Subject: [PATCH 7/7] review nit fixes --- .../lib/src/store_kit_wrappers/sk_request_maker.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart index 7d62c0bebb5a..43fee6411a01 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_request_maker.dart @@ -15,8 +15,8 @@ import 'sk_product_wrapper.dart'; class SKRequestMaker { /// Fetches product information for a list of given product identifiers. /// - /// The `productIdentifiers` should contain legit product identifiers that you declared for the products in the ITunes Connect. invalid identifiers - /// Will be stored and returned in [SkProductResponseWrapper.invalidProductIdentifiers]. Duplicate values in `productIdentifiers` will be omitted. + /// The `productIdentifiers` should contain legitimate product identifiers that you declared for the products in the iTunes Connect. Invalid identifiers + /// will be stored and returned in [SkProductResponseWrapper.invalidProductIdentifiers]. Duplicate values in `productIdentifiers` will be omitted. /// If `productIdentifiers` is null, an `storekit_invalid_argument` error will be returned. If `productIdentifiers` is empty, a [SkProductResponseWrapper] /// will still be returned with [SkProductResponseWrapper.products] being null. ///