From 87b43a09aa60101aba6ec5aee834b74f6ecdeebb Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Wed, 13 Jul 2022 14:05:07 +0300 Subject: [PATCH 01/12] fix payment discount (iOS Promotional Offers) into being actually usable --- .../in_app_purchase_storekit/CHANGELOG.md | 4 +++ .../ios/Classes/FIAObjectTranslator.m | 4 +-- .../in_app_purchase_storekit_platform.dart | 5 +++- .../sk_payment_queue_wrapper.dart | 3 +- .../src/types/app_store_purchase_param.dart | 4 +++ .../in_app_purchase_storekit/pubspec.yaml | 2 +- ...n_app_purchase_storekit_platform_test.dart | 30 +++++++++++++++++++ .../sk_test_stub_objects.dart | 9 ++++++ 8 files changed, 56 insertions(+), 5 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 52f59efacd6a..329f467ff15c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.3 + +* Fixes `SKProductDiscountWrapper` with being sent trough the method channel as the expected parameter. + ## 0.3.2+2 * Updates imports for `prefer_relative_imports`. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m index d01eb9becf3d..40cfc49b163c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m @@ -237,7 +237,7 @@ + (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront + (SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map withError:(NSString **)error { - if (!map || map.count <= 0) { + if (!map || [map isKindOfClass:[NSNull class]] || map.count <= 0) { return nil; } @@ -277,7 +277,7 @@ + (SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map return nil; } - if (!timestamp || ![timestamp isKindOfClass:NSNumber.class] || [timestamp intValue] <= 0) { + if (!timestamp || ![timestamp isKindOfClass:NSNumber.class] || timestamp <= 0) { if (error) { *error = @"When specifying a payment discount the 'timestamp' field is mandatory."; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart index c03f15f8ce48..0e5e420ece85 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart @@ -75,7 +75,10 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { purchaseParam is AppStorePurchaseParam ? purchaseParam.quantity : 1, applicationUsername: purchaseParam.applicationUserName, simulatesAskToBuyInSandbox: purchaseParam is AppStorePurchaseParam && - purchaseParam.simulatesAskToBuyInSandbox)); + purchaseParam.simulatesAskToBuyInSandbox, + paymentDiscount: purchaseParam is AppStorePurchaseParam + ? purchaseParam.discount + : null)); return true; // There's no error feedback from iOS here to return. } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index 78e16e22416c..d360a2da3fe5 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -405,7 +405,8 @@ class SKPaymentWrapper { 'applicationUsername': applicationUsername, 'requestData': requestData, 'quantity': quantity, - 'simulatesAskToBuyInSandbox': simulatesAskToBuyInSandbox + 'simulatesAskToBuyInSandbox': simulatesAskToBuyInSandbox, + 'paymentDiscount': paymentDiscount?.toMap(), }; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart index 168ef5cea5f4..e377ef2f620c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart @@ -14,6 +14,7 @@ class AppStorePurchaseParam extends PurchaseParam { String? applicationUserName, this.quantity = 1, this.simulatesAskToBuyInSandbox = false, + this.discount, }) : super( productDetails: productDetails, applicationUserName: applicationUserName, @@ -32,4 +33,7 @@ class AppStorePurchaseParam extends PurchaseParam { /// Quantity of the product user requested to buy. final int quantity; + + /// Discount applied to the product (optional). + final SKPaymentDiscountWrapper? discount; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index f2193e53b591..0b6e21a26978 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS platform of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.2+2 +version: 0.3.3 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart index 852599ac3670..209c563ec01c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart @@ -489,6 +489,36 @@ void main() { expect( fakeStoreKitPlatform.finishedTransactions.first.payment.quantity, 5); }); + + test( + 'buying non consumable with discount, should get purchase objects in the purchase update callback', + () async { + final List details = []; + final Completer> completer = + Completer>(); + final Stream> stream = + iapStoreKitPlatform.purchaseStream; + + late StreamSubscription> subscription; + subscription = stream.listen((List purchaseDetailsList) { + details.addAll(purchaseDetailsList); + if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { + completer.complete(details); + subscription.cancel(); + } + }); + final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( + productDetails: + AppStoreProductDetails.fromSKProduct(dummyProductWrapper), + applicationUserName: 'appName', + discount: dummyPaymentDiscountWrapper, + ); + await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); + + final List result = await completer.future; + expect(result.length, 2); + expect(result.first.productID, dummyProductWrapper.productIdentifier); + }); }); group('complete purchase', () { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart index 946fbc81b74c..247d32ebffa2 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -186,3 +186,12 @@ Map buildTransactionMap( }; return map; } + +final SKPaymentDiscountWrapper dummyPaymentDiscountWrapper = + SKPaymentDiscountWrapper.fromJson(const { + 'identifier': 'dummy-discount-identifier', + 'keyIdentifier': 'KEYIDTEST1', + 'nonce': '00000000-0000-0000-0000-000000000000', + 'signature': 'dummy-signature-string', + 'timestamp': 1231231231, +}); From fa38cb59a1e279f1bb5c32cb987161df66c2110e Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Thu, 6 Oct 2022 13:20:49 +0300 Subject: [PATCH 02/12] fix long value for payment discount's timestamp overflowing integer check --- .../in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m index 40cfc49b163c..dedcf7ff3646 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m @@ -277,7 +277,7 @@ + (SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map return nil; } - if (!timestamp || ![timestamp isKindOfClass:NSNumber.class] || timestamp <= 0) { + if (!timestamp || ![timestamp isKindOfClass:NSNumber.class] || [timestamp longLongValue] <= 0) { if (error) { *error = @"When specifying a payment discount the 'timestamp' field is mandatory."; } From cd503993c2a92dcabcefe204795b6ddeb522ba4f Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Fri, 7 Oct 2022 10:11:21 +0300 Subject: [PATCH 03/12] add XCT test regarding discount's timestamp overflow and dart test for SKPaymentWrapper.toMap() --- .../example/ios/RunnerTests/TranslatorTests.m | 16 ++++++++++++++++ .../test/store_kit_wrappers/sk_product_test.dart | 15 +++++++++++++++ .../store_kit_wrappers/sk_test_stub_objects.dart | 9 +++++++++ 3 files changed, 40 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m index ed302d61d9b0..39ba488c014c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m @@ -390,4 +390,20 @@ - (void)testSKPaymentDiscountFromMapMissingTimestamp { } } +- (void)testSKPaymentDiscountFromMapOverflowingTimestamp { + if (@available(iOS 12.2, *)) { + NSDictionary *discountMap = @{ + @"identifier" : @"payment_discount_identifier", + @"keyIdentifier" : @"payment_discount_key_identifier", + @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", + @"signature" : @"this is a encrypted signature", + @"timestamp" : @1665044583595, // timestamp 2022 Oct + }; + NSString *error = nil; + SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; + XCTAssertNil(error); + XCTAssertNotNil(paymentDiscount); + } +} + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_product_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_product_test.dart index de61268e4009..b6de5e035c5e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_product_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_product_test.dart @@ -141,6 +141,21 @@ void main() { expect(payment, equals(dummyPayment)); }); + test('SKPaymentWrapper should have propery values consistent with .toMap()', + () { + final Map mapResult = dummyPaymentWithDiscount.toMap(); + expect(mapResult['productIdentifier'], + dummyPaymentWithDiscount.productIdentifier); + expect(mapResult['applicationUsername'], + dummyPaymentWithDiscount.applicationUsername); + expect(mapResult['requestData'], dummyPaymentWithDiscount.requestData); + expect(mapResult['quantity'], dummyPaymentWithDiscount.quantity); + expect(mapResult['simulatesAskToBuyInSandbox'], + dummyPaymentWithDiscount.simulatesAskToBuyInSandbox); + expect(mapResult['paymentDiscount'], + equals(dummyPaymentWithDiscount.paymentDiscount?.toMap())); + }); + test('Should construct correct SKError from json', () { final SKError error = SKError.fromJson(buildErrorMap(dummyError)); expect(error, equals(dummyError)); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart index 247d32ebffa2..6601a21c4ee4 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -10,6 +10,15 @@ const SKPaymentWrapper dummyPayment = SKPaymentWrapper( requestData: 'fake-data-utf8', quantity: 2, simulatesAskToBuyInSandbox: true); + +final SKPaymentWrapper dummyPaymentWithDiscount = SKPaymentWrapper( + productIdentifier: 'prod-id', + applicationUsername: 'app-user-name', + requestData: 'fake-data-utf8', + quantity: 2, + simulatesAskToBuyInSandbox: true, + paymentDiscount: dummyPaymentDiscountWrapper); + const SKError dummyError = SKError( code: 111, domain: 'dummy-domain', From 8d9aa0989359cdc9ca73955496730ab30cba2040 Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Fri, 7 Oct 2022 10:39:19 +0300 Subject: [PATCH 04/12] patch format of new XCTest --- .../example/ios/RunnerTests/TranslatorTests.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m index 39ba488c014c..bc649dda117a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m @@ -397,10 +397,11 @@ - (void)testSKPaymentDiscountFromMapOverflowingTimestamp { @"keyIdentifier" : @"payment_discount_key_identifier", @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", @"signature" : @"this is a encrypted signature", - @"timestamp" : @1665044583595, // timestamp 2022 Oct + @"timestamp" : @1665044583595, // timestamp 2022 Oct }; NSString *error = nil; - SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; + SKPaymentDiscount *paymentDiscount = + [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNil(error); XCTAssertNotNil(paymentDiscount); } From 62425f790fb926e2dcda1a181a64f4990ec19863 Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Sat, 8 Oct 2022 09:40:33 +0300 Subject: [PATCH 05/12] add members checks in the XCT unit test for the overflowing timestamp --- .../example/ios/RunnerTests/TranslatorTests.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m index bc649dda117a..34d686753762 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/TranslatorTests.m @@ -404,6 +404,12 @@ - (void)testSKPaymentDiscountFromMapOverflowingTimestamp { [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNil(error); XCTAssertNotNil(paymentDiscount); + XCTAssertEqual(paymentDiscount.identifier, discountMap[@"identifier"]); + XCTAssertEqual(paymentDiscount.keyIdentifier, discountMap[@"keyIdentifier"]); + XCTAssertEqualObjects(paymentDiscount.nonce, + [[NSUUID alloc] initWithUUIDString:discountMap[@"nonce"]]); + XCTAssertEqual(paymentDiscount.signature, discountMap[@"signature"]); + XCTAssertEqual(paymentDiscount.timestamp, discountMap[@"timestamp"]); } } From 0bab6d12a5929c729bc275938b17770ee09ce143 Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Sat, 8 Oct 2022 10:38:22 +0300 Subject: [PATCH 06/12] extend fakeStorekitPlatform to intercept paymentDiscount case --- .../test/fakes/fake_storekit_platform.dart | 21 +++++++++++++++++++ ...n_app_purchase_storekit_platform_test.dart | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index 08b9c85961a3..e18a66f4d0a9 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -30,6 +30,7 @@ class FakeStoreKitPlatform { PlatformException? restoreException; SKError? testRestoredError; bool queueIsActive = false; + Map? discountReceived; void reset() { transactions = []; @@ -54,6 +55,7 @@ class FakeStoreKitPlatform { restoreException = null; testRestoredError = null; queueIsActive = false; + discountReceived = null; } SKPaymentTransactionWrapper createPendingTransaction(String id, @@ -169,6 +171,25 @@ class FakeStoreKitPlatform { case '-[InAppPurchasePlugin addPayment:result:]': final String id = call.arguments['productIdentifier'] as String; final int quantity = call.arguments['quantity'] as int; + + // in case of testing paymentDiscount + if (call.arguments['applicationUsername'] == 'userWithDiscount') { + if (call.arguments['paymentDiscount'] != null) { + final Map discountArgument = + call.arguments['paymentDiscount']; + discountReceived = {}; + // can't cast directly the argument to Map, will receive: + // PlatformException(error, type '_InternalLinkedHashMap' is not a subtype of type 'Map' in type cast, null, null) + for (final dynamic key in discountArgument.keys) { + if (key is String) { + discountReceived![key] = discountArgument[key]; + } + } + } else { + discountReceived = null; + } + } + final SKPaymentTransactionWrapper transaction = createPendingTransaction(id, quantity: quantity); transactions.add(transaction); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart index 209c563ec01c..51ff2c229483 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart @@ -510,7 +510,7 @@ void main() { final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), - applicationUserName: 'appName', + applicationUserName: 'userWithDiscount', discount: dummyPaymentDiscountWrapper, ); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); @@ -518,6 +518,8 @@ void main() { final List result = await completer.future; expect(result.length, 2); expect(result.first.productID, dummyProductWrapper.productIdentifier); + expect(fakeStoreKitPlatform.discountReceived, + dummyPaymentDiscountWrapper.toMap()); }); }); From 8fe0cd73280f5bdf97bbdb63b0470eefdddb244e Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Sat, 8 Oct 2022 12:39:48 +0300 Subject: [PATCH 07/12] fix failing test about type annotations --- .../test/fakes/fake_storekit_platform.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index e18a66f4d0a9..af89cfa31070 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -177,7 +177,7 @@ class FakeStoreKitPlatform { if (call.arguments['paymentDiscount'] != null) { final Map discountArgument = call.arguments['paymentDiscount']; - discountReceived = {}; + discountReceived = {}; // can't cast directly the argument to Map, will receive: // PlatformException(error, type '_InternalLinkedHashMap' is not a subtype of type 'Map' in type cast, null, null) for (final dynamic key in discountArgument.keys) { From badbd1d912dfe4cacf199608d38b551550642ce0 Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Thu, 13 Oct 2022 15:49:14 +0300 Subject: [PATCH 08/12] change discountReceived to not nullable and other PR review changes --- .../in_app_purchase_storekit/CHANGELOG.md | 3 ++- .../lib/src/types/app_store_purchase_param.dart | 2 +- .../test/fakes/fake_storekit_platform.dart | 17 +++++------------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 329f467ff15c..324e0608b7f9 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.3.3 -* Fixes `SKProductDiscountWrapper` with being sent trough the method channel as the expected parameter. +* Supports adding discount information to AppStorePurchaseParam. +* Fixes iOS Promotional Offers bug which prevents them from working. ## 0.3.2+2 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart index e377ef2f620c..0e7e24166c4d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart @@ -34,6 +34,6 @@ class AppStorePurchaseParam extends PurchaseParam { /// Quantity of the product user requested to buy. final int quantity; - /// Discount applied to the product (optional). + /// Discount applied to the product. The value is `null` when the product does not have a discount. final SKPaymentDiscountWrapper? discount; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index af89cfa31070..e64876deccba 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -30,7 +30,7 @@ class FakeStoreKitPlatform { PlatformException? restoreException; SKError? testRestoredError; bool queueIsActive = false; - Map? discountReceived; + Map discountReceived = {}; void reset() { transactions = []; @@ -55,7 +55,7 @@ class FakeStoreKitPlatform { restoreException = null; testRestoredError = null; queueIsActive = false; - discountReceived = null; + discountReceived = {}; } SKPaymentTransactionWrapper createPendingTransaction(String id, @@ -172,21 +172,14 @@ class FakeStoreKitPlatform { final String id = call.arguments['productIdentifier'] as String; final int quantity = call.arguments['quantity'] as int; - // in case of testing paymentDiscount + // Keep the received paymentDiscount parameter when testing payment with discount. if (call.arguments['applicationUsername'] == 'userWithDiscount') { if (call.arguments['paymentDiscount'] != null) { final Map discountArgument = call.arguments['paymentDiscount']; - discountReceived = {}; - // can't cast directly the argument to Map, will receive: - // PlatformException(error, type '_InternalLinkedHashMap' is not a subtype of type 'Map' in type cast, null, null) - for (final dynamic key in discountArgument.keys) { - if (key is String) { - discountReceived![key] = discountArgument[key]; - } - } + discountReceived = discountArgument.cast(); } else { - discountReceived = null; + discountReceived = {}; } } From 32c0424c145bfe01606280dd3fc7559b9b37fca6 Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Thu, 13 Oct 2022 20:36:16 +0300 Subject: [PATCH 09/12] move the is-null check out of the getSKPaymentDiscountFromMap and into the caller --- .../ios/Classes/FIAObjectTranslator.m | 2 +- .../ios/Classes/InAppPurchasePlugin.m | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m index dedcf7ff3646..c656b58808b3 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m @@ -237,7 +237,7 @@ + (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront + (SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map withError:(NSString **)error { - if (!map || [map isKindOfClass:[NSNull class]] || map.count <= 0) { + if (!map || map.count <= 0) { return nil; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m index d64c24563b62..da9b08f7b267 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m @@ -200,22 +200,24 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { : [simulatesAskToBuyInSandbox boolValue]; if (@available(iOS 12.2, *)) { - NSString *error = nil; - SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator - getSKPaymentDiscountFromMap:[paymentMap objectForKey:@"paymentDiscount"] - withError:&error]; - - if (error) { - result([FlutterError - errorWithCode:@"storekit_invalid_payment_discount_object" - message:[NSString stringWithFormat:@"You have requested a payment and specified a " - @"payment discount with invalid properties. %@", - error] - details:call.arguments]); - return; + NSDictionary *paymentDiscountMap = [paymentMap objectForKey:@"paymentDiscount"]; + if(![paymentDiscountMap isKindOfClass:[NSNull class]]) { + NSString *error = nil; + SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator + getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; + + if (error) { + result([FlutterError + errorWithCode:@"storekit_invalid_payment_discount_object" + message:[NSString stringWithFormat:@"You have requested a payment and specified a " + @"payment discount with invalid properties. %@", + error] + details:call.arguments]); + return; + } + + payment.paymentDiscount = paymentDiscount; } - - payment.paymentDiscount = paymentDiscount; } if (![self.paymentQueueHandler addPayment:payment]) { From f90c3f56d72a86613f218f942227e2a02f4a4d18 Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Thu, 13 Oct 2022 20:48:37 +0300 Subject: [PATCH 10/12] fix format after the null-check move --- .../ios/Classes/InAppPurchasePlugin.m | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m index da9b08f7b267..5d8fa1421322 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m @@ -201,21 +201,22 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { if (@available(iOS 12.2, *)) { NSDictionary *paymentDiscountMap = [paymentMap objectForKey:@"paymentDiscount"]; - if(![paymentDiscountMap isKindOfClass:[NSNull class]]) { + if (![paymentDiscountMap isKindOfClass:[NSNull class]]) { NSString *error = nil; - SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator - getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; - + SKPaymentDiscount *paymentDiscount = + [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; + if (error) { result([FlutterError errorWithCode:@"storekit_invalid_payment_discount_object" - message:[NSString stringWithFormat:@"You have requested a payment and specified a " - @"payment discount with invalid properties. %@", - error] + message:[NSString + stringWithFormat:@"You have requested a payment and specified a " + @"payment discount with invalid properties. %@", + error] details:call.arguments]); return; } - + payment.paymentDiscount = paymentDiscount; } } From 362120130e183b3da9a38240819abe54fac286a2 Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Sat, 22 Oct 2022 07:54:08 +0300 Subject: [PATCH 11/12] add helper for NSdictionary nil extract for given key --- .../ios/Classes/InAppPurchasePlugin.m | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m index 5d8fa1421322..fb034ab5eb34 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m @@ -200,25 +200,23 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { : [simulatesAskToBuyInSandbox boolValue]; if (@available(iOS 12.2, *)) { - NSDictionary *paymentDiscountMap = [paymentMap objectForKey:@"paymentDiscount"]; - if (![paymentDiscountMap isKindOfClass:[NSNull class]]) { - NSString *error = nil; - SKPaymentDiscount *paymentDiscount = - [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; - - if (error) { - result([FlutterError - errorWithCode:@"storekit_invalid_payment_discount_object" - message:[NSString - stringWithFormat:@"You have requested a payment and specified a " - @"payment discount with invalid properties. %@", - error] - details:call.arguments]); - return; - } + NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap forKey:@"paymentDiscount"]; + NSString *error = nil; + SKPaymentDiscount *paymentDiscount = + [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; - payment.paymentDiscount = paymentDiscount; + if (error) { + result([FlutterError + errorWithCode:@"storekit_invalid_payment_discount_object" + message:[NSString + stringWithFormat:@"You have requested a payment and specified a " + @"payment discount with invalid properties. %@", + error] + details:call.arguments]); + return; } + + payment.paymentDiscount = paymentDiscount; } if (![self.paymentQueueHandler addPayment:payment]) { @@ -370,6 +368,12 @@ - (void)showPriceConsentIfNeeded:(FlutterResult)result { result(nil); } +- (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary + forKey:(NSString *)key { + id value = dictionary[key]; + return [value isKindOfClass:[NSNull class]] ? nil : value; +} + #pragma mark - transaction observer: - (void)handleTransactionsUpdated:(NSArray *)transactions { From ea5cbcf67fddc183538490fe157c7ec2ad63c09f Mon Sep 17 00:00:00 2001 From: Denis Chakarov Date: Sat, 22 Oct 2022 09:52:11 +0300 Subject: [PATCH 12/12] fix format issues --- .../ios/Classes/InAppPurchasePlugin.m | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m index fb034ab5eb34..bfc90ea43716 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m @@ -200,7 +200,8 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { : [simulatesAskToBuyInSandbox boolValue]; if (@available(iOS 12.2, *)) { - NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap forKey:@"paymentDiscount"]; + NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap + forKey:@"paymentDiscount"]; NSString *error = nil; SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; @@ -208,10 +209,9 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { if (error) { result([FlutterError errorWithCode:@"storekit_invalid_payment_discount_object" - message:[NSString - stringWithFormat:@"You have requested a payment and specified a " - @"payment discount with invalid properties. %@", - error] + message:[NSString stringWithFormat:@"You have requested a payment and specified a " + @"payment discount with invalid properties. %@", + error] details:call.arguments]); return; } @@ -368,8 +368,7 @@ - (void)showPriceConsentIfNeeded:(FlutterResult)result { result(nil); } -- (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary - forKey:(NSString *)key { +- (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key { id value = dictionary[key]; return [value isKindOfClass:[NSNull class]] ? nil : value; }