From 97728432227be3b912f4e0e93a036494e4b5b191 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 17 Feb 2021 13:10:31 -0800 Subject: [PATCH] add types --- packages/in_app_purchase/CHANGELOG.md | 1 + .../in_app_purchase/example/lib/main.dart | 3 +- .../ios/Classes/InAppPurchasePlugin.m | 2 +- .../enum_converters.dart | 28 ++++-- .../purchase_wrapper.dart | 22 ++++- .../purchase_wrapper.g.dart | 67 ++++++++----- .../sku_details_wrapper.dart | 20 +++- .../sku_details_wrapper.g.dart | 97 ++++++------------- .../in_app_purchase/app_store_connection.dart | 3 +- .../src/in_app_purchase/purchase_details.dart | 17 +++- .../store_kit_wrappers/enum_converters.dart | 11 ++- .../store_kit_wrappers/enum_converters.g.dart | 1 + .../sk_payment_queue_wrapper.dart | 25 +++-- .../sk_payment_queue_wrapper.g.dart | 16 +-- .../sk_payment_transaction_wrappers.dart | 12 ++- .../sk_payment_transaction_wrappers.g.dart | 47 ++------- .../sk_product_wrapper.dart | 18 +++- .../sk_product_wrapper.g.dart | 39 ++++---- .../app_store_connection_test.dart | 2 +- .../sk_methodchannel_apis_test.dart | 2 +- 20 files changed, 237 insertions(+), 196 deletions(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 65230572ff09..c226d58f710d 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.4.0-nullsafety.0 * Migrate to nullsafety. +* Deprecate `sandboxTesting`, introduce `simulatesAskToBuyInSandbox`. * **Breaking Change:** * Removed `callbackChannel` in `channels.dart`, see https://github.com/flutter/flutter/issues/69225. diff --git a/packages/in_app_purchase/example/lib/main.dart b/packages/in_app_purchase/example/lib/main.dart index 0e61aa5af90f..dbc1ca505b23 100644 --- a/packages/in_app_purchase/example/lib/main.dart +++ b/packages/in_app_purchase/example/lib/main.dart @@ -255,8 +255,7 @@ class _MyAppState extends State<_MyApp> { onPressed: () { PurchaseParam purchaseParam = PurchaseParam( productDetails: productDetails, - applicationUserName: null, - sandboxTesting: true); + applicationUserName: null); if (productDetails.id == _kConsumableId) { _connection.buyConsumable( purchaseParam: purchaseParam, diff --git a/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m index 691c2537dc2d..46c4ab11836c 100644 --- a/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m @@ -290,7 +290,7 @@ - (void)refreshReceipt:(FlutterMethodCall *)call result:(FlutterResult)result { }]; } -#pragma mark - delegates +#pragma mark - delegatestransactionIdentifier: - (void)handleTransactionsUpdated:(NSArray *)transactions { NSMutableArray *maps = [NSMutableArray new]; diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart index 41c921c79aca..c34215dc0dc9 100644 --- a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart +++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart @@ -12,13 +12,18 @@ part 'enum_converters.g.dart'; /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@BillingResponseConverter()`. -class BillingResponseConverter implements JsonConverter { +class BillingResponseConverter implements JsonConverter { /// Default const constructor. const BillingResponseConverter(); @override - BillingResponse fromJson(int json) => _$enumDecode( + BillingResponse fromJson(int? json) { + if (json == null) { + return BillingResponse.error; + } + return _$enumDecode( _$BillingResponseEnumMap.cast(), json); + } @override int toJson(BillingResponse object) => _$BillingResponseEnumMap[object]!; @@ -28,13 +33,18 @@ class BillingResponseConverter implements JsonConverter { /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@SkuTypeConverter()`. -class SkuTypeConverter implements JsonConverter { +class SkuTypeConverter implements JsonConverter { /// Default const constructor. const SkuTypeConverter(); @override - SkuType fromJson(String json) => _$enumDecode( + SkuType fromJson(String? json) { + if (json == null) { + return SkuType.inapp; + } + return _$enumDecode( _$SkuTypeEnumMap.cast(), json); + } @override String toJson(SkuType object) => _$SkuTypeEnumMap[object]!; @@ -53,15 +63,19 @@ class _SerializedEnums { /// Use these in `@JsonSerializable()` classes by annotating them with /// `@PurchaseStateConverter()`. class PurchaseStateConverter - implements JsonConverter { + implements JsonConverter { /// Default const constructor. const PurchaseStateConverter(); @override - PurchaseStateWrapper fromJson(int json) => - _$enumDecode( + PurchaseStateWrapper fromJson(int? json) { + if (json == null) { + return PurchaseStateWrapper.unspecified_state; + } + return _$enumDecode( _$PurchaseStateWrapperEnumMap.cast(), json); + } @override int toJson(PurchaseStateWrapper object) => diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart index 178777b6ad37..e6e66875efc2 100644 --- a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart +++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart @@ -40,7 +40,7 @@ class PurchaseWrapper { required this.purchaseState}); /// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details. - factory PurchaseWrapper.fromJson(Map map) => _$PurchaseWrapperFromJson(map); + factory PurchaseWrapper.fromJson(Map map) => _$PurchaseWrapperFromJson(map); @override bool operator ==(Object other) { @@ -74,22 +74,28 @@ class PurchaseWrapper { /// The unique ID for this purchase. Corresponds to the Google Payments order /// ID. + @JsonKey(defaultValue: '') final String orderId; /// The package name the purchase was made from. + @JsonKey(defaultValue: '') final String packageName; /// When the purchase was made, as an epoch timestamp. + @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [SkuDetailsWrapper], user, and purchase. + @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. + @JsonKey(defaultValue: '') final String signature; /// The product ID of this purchase. + @JsonKey(defaultValue: '') final String sku; /// True for subscriptions that renew automatically. Does not apply to @@ -105,15 +111,18 @@ class PurchaseWrapper { /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). + @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. + @JsonKey(defaultValue: '') final String developerPayload; /// Whether the purchase has been acknowledged. /// /// A successful purchase has to be acknowledged within 3 days after the purchase via [BillingClient.acknowledgePurchase]. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. + @JsonKey(defaultValue: false) final bool isAcknowledged; /// Determines the current state of the purchase. @@ -146,20 +155,24 @@ class PurchaseHistoryRecordWrapper { }); /// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details. - factory PurchaseHistoryRecordWrapper.fromJson(Map map) => + factory PurchaseHistoryRecordWrapper.fromJson(Map map) => _$PurchaseHistoryRecordWrapperFromJson(map); /// When the purchase was made, as an epoch timestamp. + @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [SkuDetailsWrapper], user, and purchase. + @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. + @JsonKey(defaultValue: '') final String signature; /// The product ID of this purchase. + @JsonKey(defaultValue: '') final String sku; /// Details about this purchase, in JSON. @@ -168,9 +181,11 @@ class PurchaseHistoryRecordWrapper { /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). + @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. + @JsonKey(defaultValue: '') final String developerPayload; @override @@ -232,11 +247,13 @@ class PurchasesResultWrapper { /// /// This can represent either the status of the "query purchase history" half /// of the operation and the "user made purchases" transaction itself. + @JsonKey(defaultValue: BillingResponse.error) final BillingResponse responseCode; /// The list of successful purchases made in this transaction. /// /// May be empty, especially if [responseCode] is not [BillingResponse.ok]. + @JsonKey(defaultValue: []) final List purchasesList; } @@ -273,6 +290,7 @@ class PurchasesHistoryResult { /// The list of queried purchase history records. /// /// May be empty, especially if [billingResult.responseCode] is not [BillingResponse.ok]. + @JsonKey(defaultValue: []) final List purchaseHistoryRecordList; } diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart index ef61425efdf1..6f4c8d86ab5d 100644 --- a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart +++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart @@ -8,16 +8,16 @@ part of 'purchase_wrapper.dart'; PurchaseWrapper _$PurchaseWrapperFromJson(Map json) { return PurchaseWrapper( - orderId: json['orderId'] as String, - packageName: json['packageName'] as String, - purchaseTime: json['purchaseTime'] as int, - purchaseToken: json['purchaseToken'] as String, - signature: json['signature'] as String, - sku: json['sku'] as String, + orderId: json['orderId'] as String? ?? '', + packageName: json['packageName'] as String? ?? '', + purchaseTime: json['purchaseTime'] as int? ?? 0, + purchaseToken: json['purchaseToken'] as String? ?? '', + signature: json['signature'] as String? ?? '', + sku: json['sku'] as String? ?? '', isAutoRenewing: json['isAutoRenewing'] as bool?, - originalJson: json['originalJson'] as String, - developerPayload: json['developerPayload'] as String, - isAcknowledged: json['isAcknowledged'] as bool, + originalJson: json['originalJson'] as String? ?? '', + developerPayload: json['developerPayload'] as String? ?? '', + isAcknowledged: json['isAcknowledged'] as bool? ?? false, purchaseState: _$enumDecode(_$PurchaseStateWrapperEnumMap, json['purchaseState']), ); @@ -72,12 +72,12 @@ const _$PurchaseStateWrapperEnumMap = { PurchaseHistoryRecordWrapper _$PurchaseHistoryRecordWrapperFromJson(Map json) { return PurchaseHistoryRecordWrapper( - purchaseTime: json['purchaseTime'] as int, - purchaseToken: json['purchaseToken'] as String, - signature: json['signature'] as String, - sku: json['sku'] as String, - originalJson: json['originalJson'] as String, - developerPayload: json['developerPayload'] as String, + purchaseTime: json['purchaseTime'] as int? ?? 0, + purchaseToken: json['purchaseToken'] as String? ?? '', + signature: json['signature'] as String? ?? '', + sku: json['sku'] as String? ?? '', + originalJson: json['originalJson'] as String? ?? '', + developerPayload: json['developerPayload'] as String? ?? '', ); } @@ -94,11 +94,16 @@ Map _$PurchaseHistoryRecordWrapperToJson( PurchasesResultWrapper _$PurchasesResultWrapperFromJson(Map json) { return PurchasesResultWrapper( - responseCode: _$enumDecode(_$BillingResponseEnumMap, json['responseCode']), - billingResult: BillingResultWrapper.fromJson(json['billingResult'] as Map), - purchasesList: (json['purchasesList'] as List) - .map((e) => PurchaseWrapper.fromJson(e as Map)) - .toList(), + responseCode: + _$enumDecodeNullable(_$BillingResponseEnumMap, json['responseCode']) ?? + BillingResponse.error, + billingResult: BillingResultWrapper.fromJson( + Map.from(json['billingResult'] as Map)), + purchasesList: (json['purchasesList'] as List?) + ?.map((e) => + PurchaseWrapper.fromJson(Map.from(e as Map))) + .toList() ?? + [], ); } @@ -110,6 +115,17 @@ Map _$PurchasesResultWrapperToJson( 'purchasesList': instance.purchasesList, }; +K? _$enumDecodeNullable( + Map enumValues, + dynamic source, { + K? unknownValue, +}) { + if (source == null) { + return null; + } + return _$enumDecode(enumValues, source, unknownValue: unknownValue); +} + const _$BillingResponseEnumMap = { BillingResponse.serviceTimeout: -3, BillingResponse.featureNotSupported: -2, @@ -127,11 +143,14 @@ const _$BillingResponseEnumMap = { PurchasesHistoryResult _$PurchasesHistoryResultFromJson(Map json) { return PurchasesHistoryResult( - billingResult: BillingResultWrapper.fromJson(json['billingResult'] as Map), + billingResult: BillingResultWrapper.fromJson( + Map.from(json['billingResult'] as Map)), purchaseHistoryRecordList: - (json['purchaseHistoryRecordList'] as List) - .map((e) => PurchaseHistoryRecordWrapper.fromJson(e as Map)) - .toList(), + (json['purchaseHistoryRecordList'] as List?) + ?.map((e) => PurchaseHistoryRecordWrapper.fromJson( + Map.from(e as Map))) + .toList() ?? + [], ); } diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart index 4e319bb64f48..248da43f4119 100644 --- a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart +++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart @@ -45,55 +45,70 @@ class SkuDetailsWrapper { /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. @visibleForTesting - factory SkuDetailsWrapper.fromJson(Map map) => + factory SkuDetailsWrapper.fromJson(Map map) => _$SkuDetailsWrapperFromJson(map); /// Textual description of the product. + @JsonKey(defaultValue: '') final String description; /// Trial period in ISO 8601 format. + @JsonKey(defaultValue: '') final String freeTrialPeriod; /// Introductory price, only applies to [SkuType.subs]. Formatted ("$0.99"). + @JsonKey(defaultValue: '') final String introductoryPrice; /// [introductoryPrice] in micro-units 990000 + @JsonKey(defaultValue: '') final String introductoryPriceMicros; /// The number of billing perios that [introductoryPrice] is valid for ("2"). + @JsonKey(defaultValue: '') final String introductoryPriceCycles; /// The billing period of [introductoryPrice], in ISO 8601 format. + @JsonKey(defaultValue: '') final String introductoryPricePeriod; /// Formatted with currency symbol ("$0.99"). + @JsonKey(defaultValue: '') final String price; /// [price] in micro-units ("990000"). + @JsonKey(defaultValue: 0) final int priceAmountMicros; /// [price] ISO 4217 currency code. + @JsonKey(defaultValue: '') final String priceCurrencyCode; /// The product ID in Google Play Console. + @JsonKey(defaultValue: '') final String sku; /// Applies to [SkuType.subs], formatted in ISO 8601. + @JsonKey(defaultValue: '') final String subscriptionPeriod; /// The product's title. + @JsonKey(defaultValue: '') final String title; /// The [SkuType] of the product. final SkuType type; /// False if the product is paid. + @JsonKey(defaultValue: false) final bool isRewarded; /// The original price that the user purchased this product for. + @JsonKey(defaultValue: '') final String originalPrice; /// [originalPrice] in micro-units ("990000"). + @JsonKey(defaultValue: 0) final int originalPriceAmountMicros; @override @@ -163,6 +178,7 @@ class SkuDetailsResponseWrapper { final BillingResultWrapper billingResult; /// A list of [SkuDetailsWrapper] matching the query to [BillingClient.querySkuDetails]. + @JsonKey(defaultValue: []) final List skuDetailsList; @override @@ -192,7 +208,7 @@ class BillingResultWrapper { /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. - factory BillingResultWrapper.fromJson(Map map) => + factory BillingResultWrapper.fromJson(Map map) => _$BillingResultWrapperFromJson(map); /// Response code returned in the Play Billing API calls. diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart index a2f173a3506f..6a8891b0cdd7 100644 --- a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart +++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart @@ -8,22 +8,22 @@ part of 'sku_details_wrapper.dart'; SkuDetailsWrapper _$SkuDetailsWrapperFromJson(Map json) { return SkuDetailsWrapper( - description: json['description'] as String, - freeTrialPeriod: json['freeTrialPeriod'] as String, - introductoryPrice: json['introductoryPrice'] as String, - introductoryPriceMicros: json['introductoryPriceMicros'] as String, - introductoryPriceCycles: json['introductoryPriceCycles'] as String, - introductoryPricePeriod: json['introductoryPricePeriod'] as String, - price: json['price'] as String, - priceAmountMicros: json['priceAmountMicros'] as int, - priceCurrencyCode: json['priceCurrencyCode'] as String, - sku: json['sku'] as String, - subscriptionPeriod: json['subscriptionPeriod'] as String, - title: json['title'] as String, - type: _$enumDecode(_$SkuTypeEnumMap, json['type']), - isRewarded: json['isRewarded'] as bool, - originalPrice: json['originalPrice'] as String, - originalPriceAmountMicros: json['originalPriceAmountMicros'] as int, + description: json['description'] as String? ?? '', + freeTrialPeriod: json['freeTrialPeriod'] as String? ?? '', + introductoryPrice: json['introductoryPrice'] as String? ?? '', + introductoryPriceMicros: json['introductoryPriceMicros'] as String? ?? '', + introductoryPriceCycles: json['introductoryPriceCycles'] as String? ?? '', + introductoryPricePeriod: json['introductoryPricePeriod'] as String? ?? '', + price: json['price'] as String? ?? '', + priceAmountMicros: json['priceAmountMicros'] as int? ?? 0, + priceCurrencyCode: json['priceCurrencyCode'] as String? ?? '', + sku: json['sku'] as String? ?? '', + subscriptionPeriod: json['subscriptionPeriod'] as String? ?? '', + title: json['title'] as String? ?? '', + type: const SkuTypeConverter().fromJson(json['type'] as String?), + isRewarded: json['isRewarded'] as bool? ?? false, + originalPrice: json['originalPrice'] as String? ?? '', + originalPriceAmountMicros: json['originalPriceAmountMicros'] as int? ?? 0, ); } @@ -41,49 +41,21 @@ Map _$SkuDetailsWrapperToJson(SkuDetailsWrapper instance) => 'sku': instance.sku, 'subscriptionPeriod': instance.subscriptionPeriod, 'title': instance.title, - 'type': _$SkuTypeEnumMap[instance.type], + 'type': const SkuTypeConverter().toJson(instance.type), 'isRewarded': instance.isRewarded, 'originalPrice': instance.originalPrice, 'originalPriceAmountMicros': instance.originalPriceAmountMicros, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - -const _$SkuTypeEnumMap = { - SkuType.inapp: 'inapp', - SkuType.subs: 'subs', -}; - SkuDetailsResponseWrapper _$SkuDetailsResponseWrapperFromJson(Map json) { return SkuDetailsResponseWrapper( - billingResult: BillingResultWrapper.fromJson(json['billingResult'] as Map), - skuDetailsList: (json['skuDetailsList'] as List) - .map((e) => SkuDetailsWrapper.fromJson(e as Map)) - .toList(), + billingResult: BillingResultWrapper.fromJson( + Map.from(json['billingResult'] as Map)), + skuDetailsList: (json['skuDetailsList'] as List?) + ?.map((e) => + SkuDetailsWrapper.fromJson(Map.from(e as Map))) + .toList() ?? + [], ); } @@ -96,7 +68,8 @@ Map _$SkuDetailsResponseWrapperToJson( BillingResultWrapper _$BillingResultWrapperFromJson(Map json) { return BillingResultWrapper( - responseCode: _$enumDecode(_$BillingResponseEnumMap, json['responseCode']), + responseCode: + const BillingResponseConverter().fromJson(json['responseCode'] as int?), debugMessage: json['debugMessage'] as String?, ); } @@ -104,21 +77,7 @@ BillingResultWrapper _$BillingResultWrapperFromJson(Map json) { Map _$BillingResultWrapperToJson( BillingResultWrapper instance) => { - 'responseCode': _$BillingResponseEnumMap[instance.responseCode], + 'responseCode': + const BillingResponseConverter().toJson(instance.responseCode), 'debugMessage': instance.debugMessage, }; - -const _$BillingResponseEnumMap = { - BillingResponse.serviceTimeout: -3, - BillingResponse.featureNotSupported: -2, - BillingResponse.serviceDisconnected: -1, - BillingResponse.ok: 0, - BillingResponse.userCanceled: 1, - BillingResponse.serviceUnavailable: 2, - BillingResponse.billingUnavailable: 3, - BillingResponse.itemUnavailable: 4, - BillingResponse.developerError: 5, - BillingResponse.error: 6, - BillingResponse.itemAlreadyOwned: 7, - BillingResponse.itemNotOwned: 8, -}; diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart index 532e26475847..1c068941061d 100644 --- a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart +++ b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart @@ -60,7 +60,7 @@ class AppStoreConnection implements InAppPurchaseConnection { productIdentifier: purchaseParam.productDetails.id, quantity: 1, applicationUsername: purchaseParam.applicationUserName, - simulatesAskToBuyInSandbox: purchaseParam.sandboxTesting, + simulatesAskToBuyInSandbox: purchaseParam.simulatesAskToBuyInSandbox, requestData: null)); return true; // There's no error feedback from iOS here to return. } @@ -217,6 +217,7 @@ class _TransactionObserver implements SKTransactionObserverWrapper { void updatedTransactions( {required List transactions}) async { + print('update transactions called'); if (_restoreCompleter != null) { if (_restoredTransactions == null) { _restoredTransactions = []; diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart b/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart index 1ac4e4dc9402..88c4a5d6a422 100644 --- a/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart +++ b/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart @@ -89,7 +89,8 @@ class PurchaseParam { PurchaseParam( {required this.productDetails, this.applicationUserName, - this.sandboxTesting = false}); + this.sandboxTesting = false, + this.simulatesAskToBuyInSandbox = false}); /// The product to create payment for. /// @@ -104,8 +105,14 @@ class PurchaseParam { /// For example, you can use a one-way hash of the user’s account name on your server. final String? applicationUserName; - /// The 'sandboxTesting' is only available on iOS, set it to `true` for testing in AppStore's sandbox environment. The default value is `false`. + /// @deprecated Use [simulatesAskToBuyInSandbox] instead. + /// + /// This parameter has no effect and will be removed shortly. + @deprecated final bool sandboxTesting; + + /// Only available on iOS, set it to `true` to produce an "ask to buy" flow for this payment in the sandbox. + final bool simulatesAskToBuyInSandbox; } /// Represents the transaction details of a purchase. @@ -114,7 +121,9 @@ class PurchaseParam { /// This class for simple operations. If you would like to see the detailed representation of the product, instead, use [PurchaseWrapper] on Android and [SKPaymentTransactionWrapper] on iOS. class PurchaseDetails { /// A unique identifier of the purchase. - final String purchaseID; + /// + /// The `value` is null on iOS if it is not a successful purchase. + final String? purchaseID; /// The product identifier of the purchase. final String productID; @@ -185,7 +194,7 @@ class PurchaseDetails { /// Creates a new PurchaseDetails object with the provided data. PurchaseDetails({ - required this.purchaseID, + this.purchaseID, required this.productID, required this.verificationData, required this.transactionDate, diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart index 899d100f6472..7c5769abd8db 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.dart @@ -13,16 +13,20 @@ part 'enum_converters.g.dart'; /// Use these in `@JsonSerializable()` classes by annotating them with /// `@SKTransactionStatusConverter()`. class SKTransactionStatusConverter - implements JsonConverter { + implements JsonConverter { /// Default const constructor. const SKTransactionStatusConverter(); @override - SKPaymentTransactionStateWrapper fromJson(int json) => - _$enumDecode( + SKPaymentTransactionStateWrapper fromJson(int? json) { + if (json == null) { + return SKPaymentTransactionStateWrapper.unspecified; + } + return _$enumDecode( _$SKPaymentTransactionStateWrapperEnumMap .cast(), json); + } /// Converts an [SKPaymentTransactionStateWrapper] to a [PurchaseStatus]. PurchaseStatus toPurchaseStatus(SKPaymentTransactionStateWrapper object) { @@ -34,6 +38,7 @@ class SKTransactionStatusConverter case SKPaymentTransactionStateWrapper.restored: return PurchaseStatus.purchased; case SKPaymentTransactionStateWrapper.failed: + case SKPaymentTransactionStateWrapper.unspecified: return PurchaseStatus.error; } } diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.g.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.g.dart index 9be5d5a6689c..a3b7df0d31f5 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.g.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/enum_converters.g.dart @@ -49,4 +49,5 @@ const _$SKPaymentTransactionStateWrapperEnumMap = { SKPaymentTransactionStateWrapper.failed: 2, SKPaymentTransactionStateWrapper.restored: 3, SKPaymentTransactionStateWrapper.deferred: 4, + SKPaymentTransactionStateWrapper.unspecified: -1, }; 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 cb955cef9d65..0e1f34c0bbda 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 @@ -41,7 +41,7 @@ class SKPaymentQueueWrapper { /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc) Future> transactions() async { return _getTransactionList((await channel - .invokeListMethod('-[SKPaymentQueue transactions]'))!); + .invokeListMethod('-[SKPaymentQueue transactions]'))!); } /// Calls [`-[SKPaymentQueue canMakePayments:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506139-canmakepayments?language=objc). @@ -136,7 +136,7 @@ class SKPaymentQueueWrapper { } // Triage a method channel call from the platform and triggers the correct observer method. - Future _handleObserverCallbacks(MethodCall call) { + Future _handleObserverCallbacks(MethodCall call) async { assert(_observer != null, '[in_app_purchase]: (Fatal)The observer has not been set but we received a purchase transaction notification. Please ensure the observer has been set using `setTransactionObserver`. Make sure the observer is added right at the App Launch.'); switch (call.method) { @@ -145,7 +145,7 @@ class SKPaymentQueueWrapper { final List transactions = _getTransactionList(call.arguments); return Future(() { - _observer!.updatedTransactions(transactions: transactions); + return _observer!.updatedTransactions(transactions: transactions); }); } case 'removedTransactions': @@ -193,12 +193,11 @@ class SKPaymentQueueWrapper { // Get transaction wrapper object list from arguments. List _getTransactionList( - List transactionsData) => - transactionsData - .map((Map map) => - SKPaymentTransactionWrapper.fromJson( - Map.castFrom(map))) - .toList(); + List transactionsData) { + return transactionsData.map((dynamic map) { + return SKPaymentTransactionWrapper.fromJson(Map.castFrom(map)); + }).toList(); + } } /// Dart wrapper around StoreKit's @@ -219,16 +218,19 @@ class SKError { /// Error [code](https://developer.apple.com/documentation/foundation/1448136-nserror_codes) /// as defined in the Cocoa Framework. + @JsonKey(defaultValue: 0) final int code; /// Error /// [domain](https://developer.apple.com/documentation/foundation/nscocoaerrordomain?language=objc) /// as defined in the Cocoa Framework. + @JsonKey(defaultValue: '') final String domain; /// A map that contains more detailed information about the error. /// /// Any key of the map must be a valid [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc). + @JsonKey(defaultValue: {}) final Map userInfo; @override @@ -272,7 +274,7 @@ class SKPaymentWrapper { /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. The `map` parameter must not be /// null. - factory SKPaymentWrapper.fromJson(Map map) { + factory SKPaymentWrapper.fromJson(Map map) { assert(map != null); return _$SKPaymentWrapperFromJson(map); } @@ -289,6 +291,7 @@ class SKPaymentWrapper { } /// The id for the product that the payment is for. + @JsonKey(defaultValue: '') final String productIdentifier; /// An opaque id for the user's account. @@ -315,6 +318,7 @@ class SKPaymentWrapper { /// The amount of the product this payment is for. /// /// The default is 1. The minimum is 1. The maximum is 10. + @JsonKey(defaultValue: 1) final int quantity; /// Produces an "ask to buy" flow in the sandbox if set to true. Default is @@ -322,6 +326,7 @@ class SKPaymentWrapper { /// /// See https://developer.apple.com/in-app-purchase/ for a guide on Sandbox /// testing. + @JsonKey(defaultValue: false) final bool simulatesAskToBuyInSandbox; @override diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.g.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.g.dart index 39190ae53750..d5d068f2d074 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.g.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.g.dart @@ -8,9 +8,12 @@ part of 'sk_payment_queue_wrapper.dart'; SKError _$SKErrorFromJson(Map json) { return SKError( - code: json['code'] as int, - domain: json['domain'] as String, - userInfo: Map.from(json['userInfo'] as Map), + code: json['code'] as int? ?? 0, + domain: json['domain'] as String? ?? '', + userInfo: (json['userInfo'] as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ) ?? + {}, ); } @@ -22,11 +25,12 @@ Map _$SKErrorToJson(SKError instance) => { SKPaymentWrapper _$SKPaymentWrapperFromJson(Map json) { return SKPaymentWrapper( - productIdentifier: json['productIdentifier'] as String, + productIdentifier: json['productIdentifier'] as String? ?? '', applicationUsername: json['applicationUsername'] as String?, requestData: json['requestData'] as String?, - quantity: json['quantity'] as int, - simulatesAskToBuyInSandbox: json['simulatesAskToBuyInSandbox'] as bool, + quantity: json['quantity'] as int? ?? 1, + simulatesAskToBuyInSandbox: + json['simulatesAskToBuyInSandbox'] as bool? ?? false, ); } diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart index e1c2c86b610e..2bb530227a7b 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart @@ -86,6 +86,10 @@ enum SKPaymentTransactionStateWrapper { /// transaction to update to another state. @JsonValue(4) deferred, + + /// Indicates the transaction is in an unspecified state. + @JsonValue(-1) + unspecified, } /// Created when a payment is added to the [SKPaymentQueueWrapper]. @@ -105,7 +109,7 @@ class SKPaymentTransactionWrapper { required this.transactionState, this.originalTransaction, this.transactionTimeStamp, - required this.transactionIdentifier, + this.transactionIdentifier, this.error, }); @@ -151,7 +155,9 @@ class SKPaymentTransactionWrapper { /// [SKPaymentTransactionStateWrapper.restored]. You may wish to record this /// string as part of an audit trail for App Store purchases. The value of /// this string corresponds to the same property in the receipt. - final String transactionIdentifier; + /// + /// The value is `null` if it is an unsuccessful transaction. + final String? transactionIdentifier; /// The error object /// @@ -191,7 +197,7 @@ class SKPaymentTransactionWrapper { /// The payload that is used to finish this transaction. Map toFinishMap() => { - "transactionIdentifier": this.transactionIdentifier, + "transactionIdentifier": this.transactionIdentifier ?? '', "productIdentifier": this.payment.productIdentifier, }; } diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart index 259201216114..4c7af21bc151 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart @@ -8,15 +8,16 @@ part of 'sk_payment_transaction_wrappers.dart'; SKPaymentTransactionWrapper _$SKPaymentTransactionWrapperFromJson(Map json) { return SKPaymentTransactionWrapper( - payment: SKPaymentWrapper.fromJson(json['payment'] as Map), - transactionState: _$enumDecode( - _$SKPaymentTransactionStateWrapperEnumMap, json['transactionState']), + payment: SKPaymentWrapper.fromJson( + Map.from(json['payment'] as Map)), + transactionState: const SKTransactionStatusConverter() + .fromJson(json['transactionState'] as int?), originalTransaction: json['originalTransaction'] == null ? null : SKPaymentTransactionWrapper.fromJson( Map.from(json['originalTransaction'] as Map)), transactionTimeStamp: (json['transactionTimeStamp'] as num?)?.toDouble(), - transactionIdentifier: json['transactionIdentifier'] as String, + transactionIdentifier: json['transactionIdentifier'] as String?, error: json['error'] == null ? null : SKError.fromJson(Map.from(json['error'] as Map)), @@ -26,45 +27,11 @@ SKPaymentTransactionWrapper _$SKPaymentTransactionWrapperFromJson(Map json) { Map _$SKPaymentTransactionWrapperToJson( SKPaymentTransactionWrapper instance) => { - 'transactionState': - _$SKPaymentTransactionStateWrapperEnumMap[instance.transactionState], + 'transactionState': const SKTransactionStatusConverter() + .toJson(instance.transactionState), 'payment': instance.payment, 'originalTransaction': instance.originalTransaction, 'transactionTimeStamp': instance.transactionTimeStamp, 'transactionIdentifier': instance.transactionIdentifier, 'error': instance.error, }; - -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - -const _$SKPaymentTransactionStateWrapperEnumMap = { - SKPaymentTransactionStateWrapper.purchasing: 0, - SKPaymentTransactionStateWrapper.purchased: 1, - SKPaymentTransactionStateWrapper.failed: 2, - SKPaymentTransactionStateWrapper.restored: 3, - SKPaymentTransactionStateWrapper.deferred: 4, -}; 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 a640403771d6..9fdee3032d25 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 @@ -33,6 +33,7 @@ class SkProductResponseWrapper { /// /// One product in this list matches one valid product identifier passed to the [SKRequestMaker.startProductRequest]. /// Will be empty if the [SKRequestMaker.startProductRequest] method does not pass any correct product identifier. + @JsonKey(defaultValue: []) final List products; /// Stores product identifiers in the `productIdentifiers` from [SKRequestMaker.startProductRequest] that are not recognized by the App Store. @@ -40,6 +41,7 @@ class SkProductResponseWrapper { /// The App Store will not recognize a product identifier unless certain criteria are met. A detailed list of the criteria can be /// found here https://developer.apple.com/documentation/storekit/skproductsresponse/1505985-invalidproductidentifiers?language=objc. /// Will be empty if all the product identifiers are valid. + @JsonKey(defaultValue: []) final List invalidProductIdentifiers; @override @@ -100,13 +102,14 @@ class SKProductSubscriptionPeriodWrapper { /// /// This method should only be used with `map` values returned by [SKProductDiscountWrapper.fromJson] or [SKProductWrapper.fromJson]. /// The `map` parameter must not be null. - factory SKProductSubscriptionPeriodWrapper.fromJson(Map map) { + factory SKProductSubscriptionPeriodWrapper.fromJson(Map map) { return _$SKProductSubscriptionPeriodWrapperFromJson(map); } /// The number of [unit] units in this period. /// /// Must be greater than 0. + @JsonKey(defaultValue: 1) final int numberOfUnits; /// The time unit used to specify the length of this period. @@ -146,6 +149,10 @@ enum SKProductDiscountPaymentMode { /// User pays nothing during the discounted period. @JsonValue(2) freeTrail, + + /// Unspecified mode. + @JsonValue(-1) + unspecified, } /// Dart wrapper around StoreKit's [SKProductDiscount](https://developer.apple.com/documentation/storekit/skproductdiscount?language=objc). @@ -170,6 +177,7 @@ class SKProductDiscountWrapper { } /// The discounted price, in the currency that is defined in [priceLocale]. + @JsonKey(defaultValue: '') final String price; /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale. @@ -178,6 +186,7 @@ class SKProductDiscountWrapper { /// The object represent the discount period length. /// /// The value must be >= 0. + @JsonKey(defaultValue: 1) final int numberOfPeriods; /// The object indicates how the discount price is charged. @@ -234,21 +243,23 @@ class SKProductWrapper { /// This method should only be used with `map` values returned by [SkProductResponseWrapper.fromJson]. /// The `map` parameter must not be null. factory SKProductWrapper.fromJson(Map map) { - assert(map != null, 'Map must not be null.'); return _$SKProductWrapperFromJson(map); } /// The unique identifier of the product. + @JsonKey(defaultValue: '') final String productIdentifier; /// The localizedTitle of the product. /// /// It is localized based on the current locale. + @JsonKey(defaultValue: '') final String localizedTitle; /// The localized description of the product. /// /// It is localized based on the current locale. + @JsonKey(defaultValue: '') final String localizedDescription; /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale. @@ -263,6 +274,7 @@ class SKProductWrapper { final String? subscriptionGroupIdentifier; /// The price of the product, in the currency that is defined in [priceLocale]. + @JsonKey(defaultValue: '') final String price; /// The object represents the subscription period of the product. @@ -331,9 +343,11 @@ class SKPriceLocaleWrapper { } ///The currency symbol for the locale, e.g. $ for US locale. + @JsonKey(defaultValue: '') final String currencySymbol; ///The currency code for the locale, e.g. USD for US locale. + @JsonKey(defaultValue: '') final String currencyCode; @override 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 be9f0abc6e0a..5a8696956cda 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 @@ -8,14 +8,16 @@ part of 'sk_product_wrapper.dart'; SkProductResponseWrapper _$SkProductResponseWrapperFromJson(Map json) { return SkProductResponseWrapper( - products: (json['products'] as List) - .map((e) => - SKProductWrapper.fromJson(Map.from(e as Map))) - .toList(), + products: (json['products'] as List?) + ?.map((e) => + SKProductWrapper.fromJson(Map.from(e as Map))) + .toList() ?? + [], invalidProductIdentifiers: - (json['invalidProductIdentifiers'] as List) - .map((e) => e as String) - .toList(), + (json['invalidProductIdentifiers'] as List?) + ?.map((e) => e as String) + .toList() ?? + [], ); } @@ -29,7 +31,7 @@ Map _$SkProductResponseWrapperToJson( SKProductSubscriptionPeriodWrapper _$SKProductSubscriptionPeriodWrapperFromJson( Map json) { return SKProductSubscriptionPeriodWrapper( - numberOfUnits: json['numberOfUnits'] as int, + numberOfUnits: json['numberOfUnits'] as int? ?? 1, unit: _$enumDecode(_$SKSubscriptionPeriodUnitEnumMap, json['unit']), ); } @@ -76,14 +78,14 @@ const _$SKSubscriptionPeriodUnitEnumMap = { SKProductDiscountWrapper _$SKProductDiscountWrapperFromJson(Map json) { return SKProductDiscountWrapper( - price: json['price'] as String, + price: json['price'] as String? ?? '', priceLocale: SKPriceLocaleWrapper.fromJson( Map.from(json['priceLocale'] as Map)), - numberOfPeriods: json['numberOfPeriods'] as int, + numberOfPeriods: json['numberOfPeriods'] as int? ?? 1, paymentMode: _$enumDecode( _$SKProductDiscountPaymentModeEnumMap, json['paymentMode']), subscriptionPeriod: SKProductSubscriptionPeriodWrapper.fromJson( - json['subscriptionPeriod'] as Map), + Map.from(json['subscriptionPeriod'] as Map)), ); } @@ -102,21 +104,22 @@ const _$SKProductDiscountPaymentModeEnumMap = { SKProductDiscountPaymentMode.payAsYouGo: 0, SKProductDiscountPaymentMode.payUpFront: 1, SKProductDiscountPaymentMode.freeTrail: 2, + SKProductDiscountPaymentMode.unspecified: -1, }; SKProductWrapper _$SKProductWrapperFromJson(Map json) { return SKProductWrapper( - productIdentifier: json['productIdentifier'] as String, - localizedTitle: json['localizedTitle'] as String, - localizedDescription: json['localizedDescription'] as String, + productIdentifier: json['productIdentifier'] as String? ?? '', + localizedTitle: json['localizedTitle'] as String? ?? '', + localizedDescription: json['localizedDescription'] as String? ?? '', priceLocale: SKPriceLocaleWrapper.fromJson( Map.from(json['priceLocale'] as Map)), subscriptionGroupIdentifier: json['subscriptionGroupIdentifier'] as String?, - price: json['price'] as String, + price: json['price'] as String? ?? '', subscriptionPeriod: json['subscriptionPeriod'] == null ? null : SKProductSubscriptionPeriodWrapper.fromJson( - json['subscriptionPeriod'] as Map), + Map.from(json['subscriptionPeriod'] as Map)), introductoryPrice: json['introductoryPrice'] == null ? null : SKProductDiscountWrapper.fromJson( @@ -138,8 +141,8 @@ Map _$SKProductWrapperToJson(SKProductWrapper instance) => SKPriceLocaleWrapper _$SKPriceLocaleWrapperFromJson(Map json) { return SKPriceLocaleWrapper( - currencySymbol: json['currencySymbol'] as String, - currencyCode: json['currencyCode'] as String, + currencySymbol: json['currencySymbol'] as String? ?? '', + currencyCode: json['currencyCode'] as String? ?? '', ); } 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 3b2ac5175604..01d76ac7a58f 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 @@ -453,7 +453,7 @@ class FakeIOSPlatform { .updatedTransactions(transactions: [transaction_failed]); } else { SKPaymentTransactionWrapper transaction_finished = - createPurchasedTransaction(id, transaction.transactionIdentifier); + createPurchasedTransaction(id, transaction.transactionIdentifier ?? ''); AppStoreConnection.observer .updatedTransactions(transactions: [transaction_finished]); } 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 337ddf1cccd8..7357e8951b1c 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 @@ -170,7 +170,7 @@ class FakeIOSPlatform { return Future>.value( [buildTransactionMap(dummyTransaction)]); case '-[InAppPurchasePlugin addPayment:result:]': - payments.add(SKPaymentWrapper.fromJson(call.arguments)); + payments.add(SKPaymentWrapper.fromJson(Map.from(call.arguments))); return Future.sync(() {}); case '-[InAppPurchasePlugin finishTransaction:result:]': transactionsFinished.add(Map.from(call.arguments));