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 c504bd76cdc5..78aa8db43bcc 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 f71132a77ef3..7deec6e6ab69 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 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { } final List> 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 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { 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 fef2dab2ff74..7d5c610aca14 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.6 +version: 0.2.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -20,7 +20,7 @@ dependencies: 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 bd704894daf8..12cca69793ac 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 @@ void main() { 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', () {