blob: 9c167e5ea3be52ccc26ae0835e68e62ca02f90b4 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' as widgets;
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:in_app_purchase_android/src/channel.dart';
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
import 'billing_client_wrappers/purchase_wrapper_test.dart';
import 'billing_client_wrappers/sku_details_wrapper_test.dart';
import 'stub_in_app_purchase_platform.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform();
late InAppPurchaseAndroidPlatform iapAndroidPlatform;
const String startConnectionCall =
'BillingClient#startConnection(BillingClientStateListener)';
const String endConnectionCall = 'BillingClient#endConnection()';
setUpAll(() {
channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler);
});
setUp(() {
widgets.WidgetsFlutterBinding.ensureInitialized();
const String debugMessage = 'dummy message';
final BillingResponse responseCode = BillingResponse.ok;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: responseCode, debugMessage: debugMessage);
stubPlatform.addResponse(
name: startConnectionCall,
value: buildBillingResultMap(expectedBillingResult));
stubPlatform.addResponse(name: endConnectionCall, value: null);
InAppPurchaseAndroidPlatform.registerPlatform();
iapAndroidPlatform =
InAppPurchasePlatform.instance as InAppPurchaseAndroidPlatform;
});
tearDown(() {
stubPlatform.reset();
});
group('connection management', () {
test('connects on initialization', () {
//await iapAndroidPlatform.isAvailable();
expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1));
});
});
group('isAvailable', () {
test('true', () async {
stubPlatform.addResponse(name: 'BillingClient#isReady()', value: true);
expect(await iapAndroidPlatform.isAvailable(), isTrue);
});
test('false', () async {
stubPlatform.addResponse(name: 'BillingClient#isReady()', value: false);
expect(await iapAndroidPlatform.isAvailable(), isFalse);
});
});
group('querySkuDetails', () {
final String queryMethodName =
'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)';
test('handles empty skuDetails', () async {
const String debugMessage = 'dummy message';
final BillingResponse responseCode = BillingResponse.ok;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: responseCode, debugMessage: debugMessage);
stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
'billingResult': buildBillingResultMap(expectedBillingResult),
'skuDetailsList': [],
});
final ProductDetailsResponse response =
await iapAndroidPlatform.queryProductDetails(<String>[''].toSet());
expect(response.productDetails, isEmpty);
});
test('should get correct product details', () async {
const String debugMessage = 'dummy message';
final BillingResponse responseCode = BillingResponse.ok;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: responseCode, debugMessage: debugMessage);
stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
'billingResult': buildBillingResultMap(expectedBillingResult),
'skuDetailsList': <Map<String, dynamic>>[buildSkuMap(dummySkuDetails)]
});
// Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
// of 1.
final ProductDetailsResponse response = await iapAndroidPlatform
.queryProductDetails(<String>['valid'].toSet());
expect(response.productDetails.first.title, dummySkuDetails.title);
expect(response.productDetails.first.description,
dummySkuDetails.description);
expect(response.productDetails.first.price, dummySkuDetails.price);
expect(response.productDetails.first.currencySymbol, r'$');
});
test('should get the correct notFoundIDs', () async {
const String debugMessage = 'dummy message';
final BillingResponse responseCode = BillingResponse.ok;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: responseCode, debugMessage: debugMessage);
stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
'billingResult': buildBillingResultMap(expectedBillingResult),
'skuDetailsList': <Map<String, dynamic>>[buildSkuMap(dummySkuDetails)]
});
// Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
// of 1.
final ProductDetailsResponse response = await iapAndroidPlatform
.queryProductDetails(<String>['invalid'].toSet());
expect(response.notFoundIDs.first, 'invalid');
});
test(
'should have error stored in the response when platform exception is thrown',
() async {
final BillingResponse responseCode = BillingResponse.ok;
stubPlatform.addResponse(
name: queryMethodName,
value: <String, dynamic>{
'responseCode': BillingResponseConverter().toJson(responseCode),
'skuDetailsList': <Map<String, dynamic>>[
buildSkuMap(dummySkuDetails)
]
},
additionalStepBeforeReturn: (_) {
throw PlatformException(
code: 'error_code',
message: 'error_message',
details: {'info': 'error_info'},
);
});
// Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
// of 1.
final ProductDetailsResponse response = await iapAndroidPlatform
.queryProductDetails(<String>['invalid'].toSet());
expect(response.notFoundIDs, ['invalid']);
expect(response.productDetails, isEmpty);
expect(response.error, isNotNull);
expect(response.error!.source, kIAPSource);
expect(response.error!.code, 'error_code');
expect(response.error!.message, 'error_message');
expect(response.error!.details, {'info': 'error_info'});
});
});
group('restorePurchases', () {
const String queryMethodName = 'BillingClient#queryPurchases(String)';
test('handles error', () async {
const String debugMessage = 'dummy message';
final BillingResponse responseCode = BillingResponse.developerError;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: responseCode, debugMessage: debugMessage);
stubPlatform.addResponse(name: queryMethodName, value: <dynamic, dynamic>{
'billingResult': buildBillingResultMap(expectedBillingResult),
'responseCode': BillingResponseConverter().toJson(responseCode),
'purchasesList': <Map<String, dynamic>>[]
});
expect(
iapAndroidPlatform.restorePurchases(),
throwsA(
isA<InAppPurchaseException>()
.having((e) => e.source, 'source', kIAPSource)
.having((e) => e.code, 'code', kRestoredPurchaseErrorCode)
.having((e) => e.message, 'message', responseCode.toString()),
),
);
});
test('should store platform exception in the response', () async {
const String debugMessage = 'dummy message';
final BillingResponse responseCode = BillingResponse.developerError;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: responseCode, debugMessage: debugMessage);
stubPlatform.addResponse(
name: queryMethodName,
value: <dynamic, dynamic>{
'responseCode': BillingResponseConverter().toJson(responseCode),
'billingResult': buildBillingResultMap(expectedBillingResult),
'purchasesList': <Map<String, dynamic>>[]
},
additionalStepBeforeReturn: (_) {
throw PlatformException(
code: 'error_code',
message: 'error_message',
details: {'info': 'error_info'},
);
});
expect(
iapAndroidPlatform.restorePurchases(),
throwsA(
isA<PlatformException>()
.having((e) => e.code, 'code', 'error_code')
.having((e) => e.message, 'message', 'error_message')
.having((e) => e.details, 'details', {'info': 'error_info'}),
),
);
});
test('returns SkuDetailsResponseWrapper', () async {
Completer completer = Completer();
Stream<List<PurchaseDetails>> stream = iapAndroidPlatform.purchaseStream;
late StreamSubscription subscription;
subscription = stream.listen((purchaseDetailsList) {
if (purchaseDetailsList.first.status == PurchaseStatus.restored) {
completer.complete(purchaseDetailsList);
subscription.cancel();
}
});
const String debugMessage = 'dummy message';
final BillingResponse responseCode = BillingResponse.ok;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: responseCode, debugMessage: debugMessage);
stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
'billingResult': buildBillingResultMap(expectedBillingResult),
'responseCode': BillingResponseConverter().toJson(responseCode),
'purchasesList': <Map<String, dynamic>>[
buildPurchaseMap(dummyPurchase),
]
});
// Since queryPastPurchases makes 2 platform method calls (one for each
// SkuType), the result will contain 2 dummyPurchase instances instead
// of 1.
await iapAndroidPlatform.restorePurchases();
final List<PurchaseDetails> restoredPurchases = await completer.future;
expect(restoredPurchases.length, 2);
restoredPurchases.forEach((element) {
GooglePlayPurchaseDetails purchase =
element as GooglePlayPurchaseDetails;
expect(purchase.productID, dummyPurchase.sku);
expect(purchase.purchaseID, dummyPurchase.orderId);
expect(purchase.verificationData.localVerificationData,
dummyPurchase.originalJson);
expect(purchase.verificationData.serverVerificationData,
dummyPurchase.purchaseToken);
expect(purchase.verificationData.source, kIAPSource);
expect(purchase.transactionDate, dummyPurchase.purchaseTime.toString());
expect(purchase.billingClientPurchase, dummyPurchase);
expect(purchase.status, PurchaseStatus.restored);
});
});
});
group('make payment', () {
final String launchMethodName =
'BillingClient#launchBillingFlow(Activity, BillingFlowParams)';
const String consumeMethodName =
'BillingClient#consumeAsync(String, ConsumeResponseListener)';
test('buy non consumable, serializes and deserializes data', () async {
final SkuDetailsWrapper skuDetails = dummySkuDetails;
final String accountId = "hashedAccountId";
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.ok;
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 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);
final bool launchResult = await iapAndroidPlatform.buyNonConsumable(
purchaseParam: purchaseParam);
PurchaseDetails result = await completer.future;
expect(launchResult, isTrue);
expect(result.purchaseID, 'orderID1');
expect(result.status, PurchaseStatus.purchased);
expect(result.productID, dummySkuDetails.sku);
});
test('handles an error with an empty purchases list', () async {
final SkuDetailsWrapper skuDetails = dummySkuDetails;
final String accountId = "hashedAccountId";
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.error;
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': []
});
iapAndroidPlatform.billingClient.callHandler(call);
});
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.buyNonConsumable(purchaseParam: purchaseParam);
PurchaseDetails result = await completer.future;
expect(result.error, isNotNull);
expect(result.error!.source, kIAPSource);
expect(result.status, PurchaseStatus.error);
expect(result.purchaseID, isEmpty);
});
test('buy consumable with auto consume, serializes and deserializes data',
() async {
final SkuDetailsWrapper skuDetails = dummySkuDetails;
final String accountId = "hashedAccountId";
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.ok;
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.ok;
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);
final bool launchResult =
await iapAndroidPlatform.buyConsumable(purchaseParam: purchaseParam);
// Verify that the result has succeeded
GooglePlayPurchaseDetails result = await completer.future;
expect(launchResult, isTrue);
expect(result.billingClientPurchase, isNotNull);
expect(result.billingClientPurchase.purchaseToken,
await consumeCompleter.future);
expect(result.status, PurchaseStatus.purchased);
expect(result.error, isNull);
});
test('buyNonConsumable propagates failures to launch the billing flow',
() async {
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.error;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: sentCode, debugMessage: debugMessage);
stubPlatform.addResponse(
name: launchMethodName,
value: buildBillingResultMap(expectedBillingResult));
final bool result = await iapAndroidPlatform.buyNonConsumable(
purchaseParam: GooglePlayPurchaseParam(
productDetails:
GooglePlayProductDetails.fromSkuDetails(dummySkuDetails)));
// Verify that the failure has been converted and returned
expect(result, isFalse);
});
test('buyConsumable propagates failures to launch the billing flow',
() async {
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.developerError;
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: sentCode, debugMessage: debugMessage);
stubPlatform.addResponse(
name: launchMethodName,
value: buildBillingResultMap(expectedBillingResult),
);
final bool result = await iapAndroidPlatform.buyConsumable(
purchaseParam: GooglePlayPurchaseParam(
productDetails:
GooglePlayProductDetails.fromSkuDetails(dummySkuDetails)));
// Verify that the failure has been converted and returned
expect(result, isFalse);
});
test('adds consumption failures to PurchaseDetails objects', () async {
final SkuDetailsWrapper skuDetails = dummySkuDetails;
final String accountId = "hashedAccountId";
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.ok;
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.error;
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.billingClientPurchase, isNotNull);
expect(result.billingClientPurchase.purchaseToken,
await consumeCompleter.future);
expect(result.status, PurchaseStatus.error);
expect(result.error, isNotNull);
expect(result.error!.code, kConsumptionFailedErrorCode);
});
test(
'buy consumable without auto consume, consume api should not receive calls',
() async {
final SkuDetailsWrapper skuDetails = dummySkuDetails;
final String accountId = "hashedAccountId";
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.developerError;
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.ok;
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));
});
Stream purchaseStream = iapAndroidPlatform.purchaseStream;
late StreamSubscription subscription;
subscription = purchaseStream.listen((_) {
consumeCompleter.complete(null);
subscription.cancel();
}, onDone: () {});
final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam(
productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails),
applicationUserName: accountId);
await iapAndroidPlatform.buyConsumable(
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);
});
test(
'should get purchased purchase status when upgrading subscription by deferred proration mode',
() async {
final SkuDetailsWrapper skuDetails = dummySkuDetails;
final String accountId = "hashedAccountId";
const String debugMessage = 'dummy message';
final BillingResponse sentCode = BillingResponse.ok;
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': []
});
iapAndroidPlatform.billingClient.callHandler(call);
});
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,
changeSubscriptionParam: ChangeSubscriptionParam(
oldPurchaseDetails: GooglePlayPurchaseDetails.fromPurchase(
dummyUnacknowledgedPurchase),
prorationMode: ProrationMode.deferred,
));
await iapAndroidPlatform.buyNonConsumable(purchaseParam: purchaseParam);
PurchaseDetails result = await completer.future;
expect(result.status, PurchaseStatus.purchased);
});
});
group('complete purchase', () {
const String completeMethodName =
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
test('complete purchase success', () async {
final BillingResponse expectedCode = BillingResponse.ok;
const String debugMessage = 'dummy message';
final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
responseCode: expectedCode, debugMessage: debugMessage);
stubPlatform.addResponse(
name: completeMethodName,
value: buildBillingResultMap(expectedBillingResult),
);
PurchaseDetails purchaseDetails =
GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase);
Completer completer = Completer();
purchaseDetails.status = PurchaseStatus.purchased;
if (purchaseDetails.pendingCompletePurchase) {
final BillingResultWrapper billingResultWrapper =
await iapAndroidPlatform.completePurchase(purchaseDetails);
expect(billingResultWrapper, equals(expectedBillingResult));
completer.complete(billingResultWrapper);
}
expect(await completer.future, equals(expectedBillingResult));
});
});
}