From d03dea2336e829e3ebf1292c2d825d858d91ec13 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Mon, 21 Jun 2021 10:45:18 +0200 Subject: [PATCH 1/6] Added launchPriceChangeConfirmation --- .../inapppurchase/InAppPurchasePlugin.java | 2 + .../inapppurchase/MethodCallHandlerImpl.java | 43 ++++++++++++++++++- .../example/lib/main.dart | 20 +++++++-- .../billing_client_wrapper.dart | 19 ++++++++ ...pp_purchase_android_platform_addition.dart | 4 ++ 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java index e4719f030d53..f5d70a42fa95 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java @@ -38,6 +38,8 @@ static final class MethodNames { "BillingClient#consumeAsync(String, ConsumeResponseListener)"; static final String ACKNOWLEDGE_PURCHASE = "BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)"; + static final String LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW = + "BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)"; private MethodNames() {}; } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index cfcb81ae05b5..13a6f0adef37 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -24,6 +24,8 @@ import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.PriceChangeConfirmationListener; +import com.android.billingclient.api.PriceChangeFlowParams; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.PurchaseHistoryResponseListener; import com.android.billingclient.api.SkuDetails; @@ -41,7 +43,7 @@ class MethodCallHandlerImpl private static final String TAG = "InAppPurchasePlugin"; private static final String LOAD_SKU_DOC_URL = - "https://github.com/flutter/plugins/blob/master/packages/in_app_purchase/README.md#loading-products-for-sale"; + "https://github.com/flutter/plugins/blob/master/packages/in_app_purchase/in_app_purchase/README.md#loading-products-for-sale"; @Nullable private BillingClient billingClient; private final BillingClientFactory billingClientFactory; @@ -145,6 +147,11 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { case InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE: acknowledgePurchase((String) call.argument("purchaseToken"), result); break; + case InAppPurchasePlugin.MethodNames.LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW: + launchPriceChangeConfirmationFlow( + (String) call.argument("sku"), + result); + break; default: result.notImplemented(); } @@ -371,6 +378,40 @@ private void updateCachedSkus(@Nullable List skuDetailsList) { } } + private void launchPriceChangeConfirmationFlow(String sku, MethodChannel.Result result) { + if (activity == null) { + result.error( + "ACTIVITY_UNAVAILABLE", + "launchPriceChangeConfirmationFlow is not available. " + + "This method must be run with the app in foreground.", + null); + return; + } + if(billingClientError(result)){ + return; + } + assert billingClient != null; + + SkuDetails skuDetails = cachedSkus.get(sku); + if (skuDetails == null) { + result.error( + "NOT_FOUND", + String.format( + "Details for sku %s are not available. It might because skus were not fetched prior to the call. Please fetch the skus first. An example of how to fetch the skus could be found here: %s", + sku, LOAD_SKU_DOC_URL), + null); + return; + } + + PriceChangeFlowParams params = new PriceChangeFlowParams + .Builder() + .setSkuDetails(skuDetails) + .build(); + billingClient.launchPriceChangeConfirmationFlow(activity, params, billingResult -> { + result.success(Translator.fromBillingResult(billingResult)); + }); + } + private boolean billingClientError(MethodChannel.Result result) { if (billingClient != null) { return false; diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart index c5726c4ade76..fab912eea188 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart @@ -31,8 +31,8 @@ const bool _kAutoConsume = true; const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; -const String _kSilverSubscriptionId = 'subscription_silver'; -const String _kGoldSubscriptionId = 'subscription_gold'; +const String _kSilverSubscriptionId = 'subscription_silver1'; +const String _kGoldSubscriptionId = 'subscription_gold1'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, @@ -250,7 +250,21 @@ class _MyAppState extends State<_MyApp> { productDetails.description, ), trailing: previousPurchase != null - ? Icon(Icons.check) + ? IconButton( + onPressed: () { + final InAppPurchaseAndroidPlatformAddition addition = + InAppPurchasePlatformAddition.instance + as InAppPurchaseAndroidPlatformAddition; + var skuDetails = + (productDetails as GooglePlayProductDetails) + .skuDetails; + addition + .launchPriceChangeConfirmationFlow( + sku: skuDetails.sku) + .then((value) => print( + "confirmationResponse: ${value.responseCode}")); + }, + icon: Icon(Icons.upgrade)) : TextButton( child: Text(productDetails.price), style: TextButton.styleFrom( diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index 1f43b3a8fbdd..e46d2f50d048 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -301,6 +301,25 @@ class BillingClient { {}); } + /// Initiates a flow to confirm the change of price for an item subscribed by the user. + /// + /// When the price of a user subscribed item has changed, launch this flow to take users to + /// a screen with price change information. User can confirm the new price or cancel the flow. + /// + /// The skuDetails needs to have already been fetched in a [querySkuDetails] + /// call. + Future launchPriceChangeConfirmationFlow({required String sku}) async { + assert(sku != null); + final Map arguments = { + 'sku': sku, + }; + return BillingResultWrapper.fromJson( + (await channel.invokeMapMethod( + 'BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)', + arguments)) ?? + {}); + } + /// The method call handler for [channel]. @visibleForTesting Future callHandler(MethodCall call) async { diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index 84f8b9ef1787..4d31f7e881d5 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -135,4 +135,8 @@ class InAppPurchaseAndroidPlatformAddition return QueryPurchaseDetailsResponse( pastPurchases: pastPurchases, error: error); } + + Future launchPriceChangeConfirmationFlow({required String sku}){ + return _billingClient.launchPriceChangeConfirmationFlow(sku: sku); + } } From 7d1f8268acd0cd5b0bacb9d512314ce32f774af8 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 22 Jun 2021 15:50:19 +0200 Subject: [PATCH 2/6] added unit tests --- .../inapppurchase/InAppPurchasePlugin.java | 2 +- .../inapppurchase/MethodCallHandlerImpl.java | 42 +++++++-------- .../inapppurchase/MethodCallHandlerTest.java | 52 ++++++++++++++++++- .../billing_client_wrapper.dart | 9 ++-- ...pp_purchase_android_platform_addition.dart | 10 +++- .../billing_client_wrapper_test.dart | 40 ++++++++++++++ ...rchase_android_platform_addition_test.dart | 40 ++++++++++++++ 7 files changed, 166 insertions(+), 29 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java index 156d841dbe56..b21ab6992608 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java @@ -47,7 +47,7 @@ static final class MethodNames { "BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)"; static final String IS_FEATURE_SUPPORTED = "BillingClient#isFeatureSupported(String)"; static final String LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW = - "BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)"; + "BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)"; private MethodNames() {}; } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index 33f4e56bdb36..ef86121314f3 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -24,7 +24,6 @@ import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; -import com.android.billingclient.api.PriceChangeConfirmationListener; import com.android.billingclient.api.PriceChangeFlowParams; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.PurchaseHistoryResponseListener; @@ -149,11 +148,9 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { break; case InAppPurchasePlugin.MethodNames.IS_FEATURE_SUPPORTED: isFeatureSupported((String) call.argument("feature"), result); - break; + break; case InAppPurchasePlugin.MethodNames.LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW: - launchPriceChangeConfirmationFlow( - (String) call.argument("sku"), - result); + launchPriceChangeConfirmationFlow((String) call.argument("sku"), result); break; default: result.notImplemented(); @@ -384,13 +381,13 @@ private void updateCachedSkus(@Nullable List skuDetailsList) { private void launchPriceChangeConfirmationFlow(String sku, MethodChannel.Result result) { if (activity == null) { result.error( - "ACTIVITY_UNAVAILABLE", - "launchPriceChangeConfirmationFlow is not available. " + - "This method must be run with the app in foreground.", - null); + "ACTIVITY_UNAVAILABLE", + "launchPriceChangeConfirmationFlow is not available. " + + "This method must be run with the app in foreground.", + null); return; } - if(billingClientError(result)){ + if (billingClientError(result)) { return; } assert billingClient != null; @@ -398,21 +395,22 @@ private void launchPriceChangeConfirmationFlow(String sku, MethodChannel.Result SkuDetails skuDetails = cachedSkus.get(sku); if (skuDetails == null) { result.error( - "NOT_FOUND", - String.format( - "Details for sku %s are not available. It might because skus were not fetched prior to the call. Please fetch the skus first. An example of how to fetch the skus could be found here: %s", - sku, LOAD_SKU_DOC_URL), - null); + "NOT_FOUND", + String.format( + "Details for sku %s are not available. It might because skus were not fetched prior to the call. Please fetch the skus first. An example of how to fetch the skus could be found here: %s", + sku, LOAD_SKU_DOC_URL), + null); return; } - PriceChangeFlowParams params = new PriceChangeFlowParams - .Builder() - .setSkuDetails(skuDetails) - .build(); - billingClient.launchPriceChangeConfirmationFlow(activity, params, billingResult -> { - result.success(Translator.fromBillingResult(billingResult)); - }); + PriceChangeFlowParams params = + new PriceChangeFlowParams.Builder().setSkuDetails(skuDetails).build(); + billingClient.launchPriceChangeConfirmationFlow( + activity, + params, + billingResult -> { + result.success(Translator.fromBillingResult(billingResult)); + }); } private boolean billingClientError(MethodChannel.Result result) { diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index 7465e6a56250..91ddff22ad6f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java @@ -10,6 +10,7 @@ import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.IS_FEATURE_SUPPORTED; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.IS_READY; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.LAUNCH_BILLING_FLOW; +import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ON_DISCONNECT; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_PURCHASES; @@ -52,6 +53,8 @@ import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.PriceChangeConfirmationListener; +import com.android.billingclient.api.PriceChangeFlowParams; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase.PurchasesResult; import com.android.billingclient.api.PurchaseHistoryRecord; @@ -722,7 +725,7 @@ public void acknowledgePurchase() { } @Test - public void endConnection_if_activity_dettached() { + public void endConnection_if_activity_detached() { InAppPurchasePlugin plugin = new InAppPurchasePlugin(); plugin.setMethodCallHandler(methodChannelHandler); mockStartConnection(); @@ -768,6 +771,53 @@ public void isFutureSupported_false() { verify(result).success(false); } + @Test + public void launchPriceChangeConfirmationFlow() { + // Set up the sku details + establishConnectedBillingClient(null, null); + String skuId = "foo"; + queryForSkus(singletonList(skuId)); + + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(BillingClient.BillingResponseCode.OK) + .setDebugMessage("dummy debug message") + .build(); + + // Set up the mock billing client + ArgumentCaptor priceChangeConfirmationListenerArgumentCaptor = + ArgumentCaptor.forClass(PriceChangeConfirmationListener.class); + ArgumentCaptor priceChangeFlowParamsArgumentCaptor = + ArgumentCaptor.forClass(PriceChangeFlowParams.class); + doNothing() + .when(mockBillingClient) + .launchPriceChangeConfirmationFlow( + any(), + priceChangeFlowParamsArgumentCaptor.capture(), + priceChangeConfirmationListenerArgumentCaptor.capture()); + + // Call the methodChannelHandler + HashMap arguments = new HashMap<>(); + arguments.put("sku", skuId); + methodChannelHandler.onMethodCall( + new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); + + // Verify the price change params. + PriceChangeFlowParams priceChangeFlowParams = priceChangeFlowParamsArgumentCaptor.getValue(); + assertEquals(skuId, priceChangeFlowParams.getSkuDetails().getSku()); + + // Set the response in the callback + PriceChangeConfirmationListener priceChangeConfirmationListener = + priceChangeConfirmationListenerArgumentCaptor.getValue(); + priceChangeConfirmationListener.onPriceChangeConfirmationResult(billingResult); + + // Verify we pass the response to result + verify(result, never()).error(any(), any(), any()); + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(HashMap.class); + verify(result, times(1)).success(resultCaptor.capture()); + assertEquals(fromBillingResult(billingResult), resultCaptor.getValue()); + } + private ArgumentCaptor mockStartConnection() { Map arguments = new HashMap<>(); arguments.put("handle", 1); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index e7955cd21420..4393d1d72eaf 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -318,16 +318,17 @@ class BillingClient { /// /// The skuDetails needs to have already been fetched in a [querySkuDetails] /// call. - Future launchPriceChangeConfirmationFlow({required String sku}) async { + Future launchPriceChangeConfirmationFlow( + {required String sku}) async { assert(sku != null); final Map arguments = { 'sku': sku, }; - return BillingResultWrapper.fromJson( - (await channel.invokeMapMethod( + return BillingResultWrapper.fromJson((await channel.invokeMapMethod( 'BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)', arguments)) ?? - {}); + {}); } /// The method call handler for [channel]. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index c92d621cca44..11b105aba96c 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -142,7 +142,15 @@ class InAppPurchaseAndroidPlatformAddition return _billingClient.isFeatureSupported(feature); } - Future launchPriceChangeConfirmationFlow({required String sku}){ + /// Initiates a flow to confirm the change of price for an item subscribed by the user. + /// + /// When the price of a user subscribed item has changed, launch this flow to take users to + /// a screen with price change information. User can confirm the new price or cancel the flow. + /// + /// The skuDetails needs to have already been fetched in a + /// [InAppPurchaseAndroidPlatform.queryProductDetails] call. + Future launchPriceChangeConfirmationFlow( + {required String sku}) { return _billingClient.launchPriceChangeConfirmationFlow(sku: sku); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart index 6ab1641984e9..02ae9ba33564 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart @@ -574,4 +574,44 @@ void main() { expect(arguments['feature'], equals('subscriptions')); }); }); + + group('launchPriceChangeConfirmationFlow', () { + const String launchPriceChangeConfirmationFlowMethodName = + 'BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)'; + + final expectedBillingResultPriceChangeConfirmation = BillingResultWrapper( + responseCode: BillingResponse.ok, + debugMessage: 'dummy message', + ); + + test('serializes and deserializes data', () async { + stubPlatform.addResponse( + name: launchPriceChangeConfirmationFlowMethodName, + value: + buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), + ); + + expect( + await billingClient.launchPriceChangeConfirmationFlow( + sku: dummySkuDetails.sku, + ), + equals(expectedBillingResultPriceChangeConfirmation), + ); + }); + + test('passes sku to launchPriceChangeConfirmationFlow', () async { + stubPlatform.addResponse( + name: launchPriceChangeConfirmationFlowMethodName, + value: + buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), + ); + await billingClient.launchPriceChangeConfirmationFlow( + sku: dummySkuDetails.sku, + ); + final MethodCall call = stubPlatform + .previousCallMatching(launchPriceChangeConfirmationFlowMethodName); + expect(call.arguments, + equals({'sku': dummySkuDetails.sku})); + }); + }); } diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart index 0ef17e7eed33..a478cabac89b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart @@ -171,4 +171,44 @@ void main() { expect(arguments['feature'], equals('subscriptions')); }); }); + + group('launchPriceChangeConfirmationFlow', () { + const String launchPriceChangeConfirmationFlowMethodName = + 'BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)'; + const dummySku = 'sku'; + + final expectedBillingResultPriceChangeConfirmation = BillingResultWrapper( + responseCode: BillingResponse.ok, + debugMessage: 'dummy message', + ); + + test('serializes and deserializes data', () async { + stubPlatform.addResponse( + name: launchPriceChangeConfirmationFlowMethodName, + value: + buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), + ); + + expect( + await iapAndroidPlatformAddition.launchPriceChangeConfirmationFlow( + sku: dummySku, + ), + equals(expectedBillingResultPriceChangeConfirmation), + ); + }); + + test('passes sku to launchPriceChangeConfirmationFlow', () async { + stubPlatform.addResponse( + name: launchPriceChangeConfirmationFlowMethodName, + value: + buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), + ); + await iapAndroidPlatformAddition.launchPriceChangeConfirmationFlow( + sku: dummySku, + ); + final MethodCall call = stubPlatform + .previousCallMatching(launchPriceChangeConfirmationFlowMethodName); + expect(call.arguments, equals({'sku': dummySku})); + }); + }); } From 2cc068936dd15cd55d4b0a5f17f6e4816c011e21 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 22 Jun 2021 16:03:56 +0200 Subject: [PATCH 3/6] changelog and pubspec --- packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md | 4 ++++ packages/in_app_purchase/in_app_purchase_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 9066fab84d18..d41032dd7dcf 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.4 + +* Added support for launchPriceChangeConfirmationFlow in the BillingClientWrapper and in InAppPurchaseAndroidPlatformAddition. + ## 0.1.3+1 * Add payment proxy. diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index ad06d85a6d43..4f11cdfbed30 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.1.3+1 +version: 0.1.4 environment: sdk: ">=2.12.0 <3.0.0" From 02653149b5c8a088a57fed84e3b044ea02047885 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 23 Jun 2021 11:30:44 +0200 Subject: [PATCH 4/6] Add exception tests --- .../inapppurchase/MethodCallHandlerTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index 91ddff22ad6f..6f9256cd07bd 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java @@ -818,6 +818,50 @@ public void launchPriceChangeConfirmationFlow() { assertEquals(fromBillingResult(billingResult), resultCaptor.getValue()); } + @Test + public void launchPriceChangeConfirmationFlow_withoutActivity_returnsActivityUnavailableError() { + // Set up the sku details + establishConnectedBillingClient(null, null); + String skuId = "foo"; + queryForSkus(singletonList(skuId)); + + methodChannelHandler.setActivity(null); + + // Call the methodChannelHandler + HashMap arguments = new HashMap<>(); + arguments.put("sku", skuId); + methodChannelHandler.onMethodCall( + new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); + verify(result, times(1)).error(eq("ACTIVITY_UNAVAILABLE"), any(), any()); + } + + @Test + public void launchPriceChangeConfirmationFlow_withoutSkuQuery_returnsNotFoundError() { + // Set up the sku details + establishConnectedBillingClient(null, null); + String skuId = "foo"; + + // Call the methodChannelHandler + HashMap arguments = new HashMap<>(); + arguments.put("sku", skuId); + methodChannelHandler.onMethodCall( + new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); + verify(result, times(1)).error(eq("NOT_FOUND"), contains("sku"), any()); + } + + @Test + public void launchPriceChangeConfirmationFlow_withoutBillingClient_returnsUnavailableError() { + // Set up the sku details + String skuId = "foo"; + + // Call the methodChannelHandler + HashMap arguments = new HashMap<>(); + arguments.put("sku", skuId); + methodChannelHandler.onMethodCall( + new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); + verify(result, times(1)).error(eq("UNAVAILABLE"), contains("BillingClient"), any()); + } + private ArgumentCaptor mockStartConnection() { Map arguments = new HashMap<>(); arguments.put("handle", 1); From d0713274f514010b776f8b8b3aea59f16f28acfc Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 23 Jun 2021 11:32:06 +0200 Subject: [PATCH 5/6] explain assert Co-authored-by: Maurits van Beusekom --- .../flutter/plugins/inapppurchase/MethodCallHandlerImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index ef86121314f3..ff43a8cb7b74 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -390,6 +390,9 @@ private void launchPriceChangeConfirmationFlow(String sku, MethodChannel.Result if (billingClientError(result)) { return; } + // Note that assert doesn't work on Android (see https://stackoverflow.com/a/6176529/5167831 and https://stackoverflow.com/a/8164195/5167831) + // and that this assert is only added to silence the analyser. The actual null check + // is handled by the `billingClientError()` call. assert billingClient != null; SkuDetails skuDetails = cachedSkus.get(sku); From d92204d867ec2a0debea90e8505611e7b85de321 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 23 Jun 2021 11:35:42 +0200 Subject: [PATCH 6/6] format --- .../flutter/plugins/inapppurchase/MethodCallHandlerImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index ff43a8cb7b74..780331848422 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -391,8 +391,8 @@ private void launchPriceChangeConfirmationFlow(String sku, MethodChannel.Result return; } // Note that assert doesn't work on Android (see https://stackoverflow.com/a/6176529/5167831 and https://stackoverflow.com/a/8164195/5167831) - // and that this assert is only added to silence the analyser. The actual null check - // is handled by the `billingClientError()` call. + // and that this assert is only added to silence the analyser. The actual null check + // is handled by the `billingClientError()` call. assert billingClient != null; SkuDetails skuDetails = cachedSkus.get(sku);