[in_app_purchase]IAP/android add cancel status (#4095)

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 c504bd7..78aa8db 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,8 @@
+## 0.2.0
+
+* BREAKING CHANGE : Refactor to handle new `PurchaseStatus` named `canceled`. This means developers
+  can distinguish between an error and user cancellation.
+
 ## 0.1.6
 
 * Require Dart SDK >= 2.14.
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart
index f71132a..7deec6e 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart
@@ -260,8 +260,12 @@
     }
     final List<Future<PurchaseDetails>> purchases =
         resultWrapper.purchasesList.map((PurchaseWrapper purchase) {
-      return _maybeAutoConsumePurchase(
-          GooglePlayPurchaseDetails.fromPurchase(purchase)..error = error);
+      final GooglePlayPurchaseDetails googlePlayPurchaseDetails =
+          GooglePlayPurchaseDetails.fromPurchase(purchase)..error = error;
+      if (resultWrapper.responseCode == BillingResponse.userCanceled) {
+        googlePlayPurchaseDetails.status = PurchaseStatus.canceled;
+      }
+      return _maybeAutoConsumePurchase(googlePlayPurchaseDetails);
     }).toList();
     if (purchases.isNotEmpty) {
       return Future.wait(purchases);
@@ -270,7 +274,9 @@
         PurchaseDetails(
             purchaseID: '',
             productID: '',
-            status: PurchaseStatus.error,
+            status: resultWrapper.responseCode == BillingResponse.userCanceled
+                ? PurchaseStatus.canceled
+                : PurchaseStatus.error,
             transactionDate: null,
             verificationData: PurchaseVerificationData(
                 localVerificationData: '',
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 fef2dab..7d5c610 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 @@
 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.6
+version: 0.2.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -20,7 +20,7 @@
   collection: ^1.15.0
   flutter:
     sdk: flutter
-  in_app_purchase_platform_interface: ^1.1.0
+  in_app_purchase_platform_interface: ^1.3.0
   json_annotation: ^4.3.0
   meta: ^1.3.0
 
diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart
index bd70489..12cca69 100644
--- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart
@@ -616,6 +616,74 @@
           purchaseParam: purchaseParam, autoConsume: false);
       expect(null, await consumeCompleter.future);
     });
+
+    test(
+        'should get canceled purchase status when response code is BillingResponse.userCanceled',
+        () async {
+      final SkuDetailsWrapper skuDetails = dummySkuDetails;
+      final String accountId = "hashedAccountId";
+      const String debugMessage = 'dummy message';
+      final BillingResponse sentCode = BillingResponse.userCanceled;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
+      stubPlatform.addResponse(
+          name: launchMethodName,
+          value: buildBillingResultMap(expectedBillingResult),
+          additionalStepBeforeReturn: (_) {
+            // Mock java update purchase callback.
+            MethodCall call = MethodCall(kOnPurchasesUpdated, {
+              'billingResult': buildBillingResultMap(expectedBillingResult),
+              'responseCode': BillingResponseConverter().toJson(sentCode),
+              'purchasesList': [
+                {
+                  'orderId': 'orderID1',
+                  'sku': skuDetails.sku,
+                  'isAutoRenewing': false,
+                  'packageName': "package",
+                  'purchaseTime': 1231231231,
+                  'purchaseToken': "token",
+                  'signature': 'sign',
+                  'originalJson': 'json',
+                  'developerPayload': 'dummy payload',
+                  'isAcknowledged': true,
+                  'purchaseState': 1,
+                }
+              ]
+            });
+            iapAndroidPlatform.billingClient.callHandler(call);
+          });
+      Completer consumeCompleter = Completer();
+      // adding call back for consume purchase
+      final BillingResponse expectedCode = BillingResponse.userCanceled;
+      final BillingResultWrapper expectedBillingResultForConsume =
+          BillingResultWrapper(
+              responseCode: expectedCode, debugMessage: debugMessage);
+      stubPlatform.addResponse(
+          name: consumeMethodName,
+          value: buildBillingResultMap(expectedBillingResultForConsume),
+          additionalStepBeforeReturn: (dynamic args) {
+            String purchaseToken = args['purchaseToken'];
+            consumeCompleter.complete(purchaseToken);
+          });
+
+      Completer completer = Completer();
+      PurchaseDetails purchaseDetails;
+      Stream purchaseStream = iapAndroidPlatform.purchaseStream;
+      late StreamSubscription subscription;
+      subscription = purchaseStream.listen((_) {
+        purchaseDetails = _.first;
+        completer.complete(purchaseDetails);
+        subscription.cancel();
+      }, onDone: () {});
+      final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam(
+          productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails),
+          applicationUserName: accountId);
+      await iapAndroidPlatform.buyConsumable(purchaseParam: purchaseParam);
+
+      // Verify that the result has an error for the failed consumption
+      GooglePlayPurchaseDetails result = await completer.future;
+      expect(result.status, PurchaseStatus.canceled);
+    });
   });
 
   group('complete purchase', () {