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..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 @@ -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'; @@ -214,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, @@ -302,6 +303,24 @@ 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 +422,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 +470,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). @@ -474,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. @@ -493,4 +548,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..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 @@ -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,14 +42,28 @@ 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). /// -/// 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 SubscriptionPeriodUnit { +enum SKSubscriptionPeriodUnit { @JsonValue(0) day, @JsonValue(1) @@ -83,7 +98,19 @@ 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) { + 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). @@ -91,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, @@ -130,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. /// @@ -138,13 +165,29 @@ 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. /// /// 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). @@ -190,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. /// @@ -226,12 +269,35 @@ 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. 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( @@ -250,18 +316,30 @@ 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); } ///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 SKPriceLocaleWrapper typedOther = other; + return typedOther.currencySymbol == currencySymbol; + } } 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_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/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..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. /// 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 new file mode 100644 index 000000000000..75873dd8d438 --- /dev/null +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -0,0 +1,186 @@ +// 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)); + }); + + 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'); + }); + }); +} + +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 = []; + List transactionsFinished = []; + String applicationNameHasTransactionRestored; + + 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(() {}); + case '-[InAppPurchasePlugin finishTransaction:result:]': + transactionsFinished.add(call.arguments); + return Future.sync(() {}); + case '-[InAppPurchasePlugin restoreTransactions:result:]': + applicationNameHasTransactionRestored = 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; + } +} 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 fc342dbbc759..000000000000 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_payment_queue_wrapper_test.dart +++ /dev/null @@ -1,209 +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'; - -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)); - - 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()); - testPayment(payment, dummyPayment); - }); - - test('Should construct correct SKError from json', () { - SKError error = SKError.fromJson(buildErrorMap(dummyError)); - testSKError(error, dummyError); - }); - - test('Should construct correct SKDownloadWrapper from json', () { - SKDownloadWrapper download = - SKDownloadWrapper.fromJson(buildDownloadMap(dummyDownload)); - testDownload(download, 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); - } - }); - - 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); - }); - }); -} - -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..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 @@ -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 'package:in_app_purchase/store_kit_wrappers.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']); - } - + group('product related object wrapper test', () { 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,11 @@ 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 +71,8 @@ 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 +83,9 @@ 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 +100,48 @@ void main() { }); test('LocaleWrapper should have property values consistent with map', () { - final PriceLocaleWrapper wrapper = PriceLocaleWrapper.fromJson(localeMap); - testMatchLocale(wrapper, localeMap); + final SKPriceLocaleWrapper wrapper = + SKPriceLocaleWrapper.fromJson(buildLocaleMap(dummyLocale)); + 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 7bb623860a12..000000000000 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_request_test.dart +++ /dev/null @@ -1,87 +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'; - -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)); - - group('startProductRequest api', () { - test('platform call should get result', () async { - stubPlatform.addResponse( - name: '-[InAppPurchasePlugin startProductRequest:result:]', - value: productResponseMap); - 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, - ); - }); - }); -} 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..7fc8363d21ad --- /dev/null +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -0,0 +1,179 @@ +// 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 SKPriceLocaleWrapper dummyLocale = + SKPriceLocaleWrapper(currencySymbol: '\$'); + +final SKProductSubscriptionPeriodWrapper dummySubscription = + SKProductSubscriptionPeriodWrapper( + numberOfUnits: 1, + unit: SKSubscriptionPeriodUnit.month, +); + +final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( + price: '1.0', + priceLocale: dummyLocale, + numberOfPeriods: 1, + paymentMode: SKProductDiscountPaymentMode.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(SKPriceLocaleWrapper local) { + return {'currencySymbol': local.currencySymbol}; +} + +Map buildSubscriptionPeriodMap( + SKProductSubscriptionPeriodWrapper sub) { + return { + 'numberOfUnits': sub.numberOfUnits, + 'unit': SKSubscriptionPeriodUnit.values.indexOf(sub.unit), + }; +} + +Map buildDiscountMap(SKProductDiscountWrapper discount) { + return { + 'price': discount.price, + 'priceLocale': buildLocaleMap(discount.priceLocale), + 'numberOfPeriods': discount.numberOfPeriods, + 'paymentMode': + SKProductDiscountPaymentMode.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), + }; +} + +Map buildProductResponseMap( + 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; +}