Skip to content

Commit

Permalink
[in_app_purchases] Fix mismatching method signature strings (#4040)
Browse files Browse the repository at this point in the history
In the upgrade to billing client v5 in #3752, some method signature strings on the Java side were updated. Unfortunately, as the Dart side was not updated. This mismatch broke the interaction between the Dart and native Android code through the method channels.

This PR updates the strings on the Dart side so the method invocation work correctly again.

This is a follow-up to
- flutter/flutter#110909
- flutter/flutter#107370
- flutter/flutter#114265
  • Loading branch information
JeroenWeener committed May 22, 2023
1 parent 7d64098 commit 83959fb
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 35 deletions.
4 changes: 4 additions & 0 deletions packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
@@ -1,3 +1,7 @@
## 0.3.0+1

* Fixes misaligned method signature strings.

## 0.3.0
* **BREAKING CHANGE**: Removes `launchPriceChangeConfirmationFlow` from `InAppPurchaseAndroidPlatform`. Price changes are now [handled by Google Play](https://developer.android.com/google/play/billing/subscriptions#price-change).
* Returns both base plans and offers when `queryProductDetailsAsync` is called.
Expand Down
Expand Up @@ -2,19 +2,126 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/services.dart';
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_platform_interface/in_app_purchase_platform_interface.dart';
import 'package:integration_test/integration_test.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

setUp(() {
InAppPurchaseAndroidPlatform.registerPlatform();
});

testWidgets('Can create InAppPurchaseAndroid instance',
(WidgetTester tester) async {
InAppPurchaseAndroidPlatform.registerPlatform();
final InAppPurchasePlatform androidPlatform =
InAppPurchasePlatform.instance;
expect(androidPlatform, isNotNull);
});

group('Method channel interaction works for', () {
late final BillingClient billingClient;

setUpAll(() {
billingClient = BillingClient((PurchasesResultWrapper _) {});
});

test('BillingClient.acknowledgePurchase', () async {
try {
await billingClient.acknowledgePurchase('purchaseToken');
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});

test('BillingClient.consumeAsync', () async {
try {
await billingClient.consumeAsync('purchaseToken');
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});

test('BillingClient.endConnection', () async {
try {
await billingClient.endConnection();
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});

test('BillingClient.isFeatureSupported', () async {
try {
await billingClient
.isFeatureSupported(BillingClientFeature.productDetails);
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});

test('BillingClient.isReady', () async {
try {
await billingClient.isReady();
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});

test('BillingClient.launchBillingFlow', () async {
try {
await billingClient.launchBillingFlow(product: 'product');
} on MissingPluginException {
fail('Method channel is not setup correctly');
} on PlatformException catch (e) {
// A [PlatformException] is expected, as we do not fetch products first.
if (e.code != 'NOT_FOUND') {
rethrow;
}
}
});

test('BillingClient.queryProductDetails', () async {
try {
await billingClient
.queryProductDetails(productList: <ProductWrapper>[]);
} on MissingPluginException {
fail('Method channel is not setup correctly');
} on PlatformException catch (e) {
// A [PlatformException] is expected, as we send an empty product list.
if (!(e.message?.startsWith('Product list cannot be empty.') ??
false)) {
rethrow;
}
}
});

test('BillingClient.queryPurchaseHistory', () async {
try {
await billingClient.queryPurchaseHistory(ProductType.inapp);
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});

test('BillingClient.queryPurchases', () async {
try {
await billingClient.queryPurchases(ProductType.inapp);
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});

test('BillingClient.startConnection', () async {
try {
await billingClient.startConnection(
onBillingServiceDisconnected: () {});
} on MissingPluginException {
fail('Method channel is not setup correctly');
}
});
});
}
Expand Up @@ -16,7 +16,7 @@ part 'billing_client_wrapper.g.dart';
/// Method identifier for the OnPurchaseUpdated method channel method.
@visibleForTesting
const String kOnPurchasesUpdated =
'PurchasesUpdatedListener#onPurchasesUpdated(int, List<Purchase>)';
'PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List<Purchase>)';
const String _kOnBillingServiceDisconnected =
'BillingClientStateListener#onBillingServiceDisconnected()';

Expand Down Expand Up @@ -234,16 +234,18 @@ class BillingClient {
/// server if at all possible. See ["Verify a
/// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify).
///
/// This wraps [`BillingClient#queryPurchases(String
/// productType)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(com.android.billingclient.api.QueryPurchasesParams,%20com.android.billingclient.api.PurchasesResponseListener)).
/// This wraps
/// [`BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(com.android.billingclient.api.QueryPurchasesParams,%20com.android.billingclient.api.PurchasesResponseListener)).
Future<PurchasesResultWrapper> queryPurchases(ProductType productType) async {
assert(productType != null);
return PurchasesResultWrapper.fromJson((await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#queryPurchases(String)', <String, dynamic>{
'productType': const ProductTypeConverter().toJson(productType)
})) ??
<String, dynamic>{});
return PurchasesResultWrapper.fromJson(
(await channel.invokeMapMethod<String, dynamic>(
'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)',
<String, dynamic>{
'productType': const ProductTypeConverter().toJson(productType)
},
)) ??
<String, dynamic>{});
}

/// Fetches purchase history for the given [ProductType].
Expand All @@ -256,16 +258,15 @@ class BillingClient {
/// server if at all possible. See ["Verify a
/// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify).
///
/// This wraps [`BillingClient#queryPurchaseHistoryAsync(String productType,
/// PurchaseHistoryResponseListener
/// listener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchaseHistoryAsync(com.android.billingclient.api.QueryPurchaseHistoryParams,%20com.android.billingclient.api.PurchaseHistoryResponseListener)).
/// This wraps
/// [`BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchaseHistoryAsync(com.android.billingclient.api.QueryPurchaseHistoryParams,%20com.android.billingclient.api.PurchaseHistoryResponseListener)).
Future<PurchasesHistoryResult> queryPurchaseHistory(
ProductType productType) async {
assert(productType != null);
return PurchasesHistoryResult.fromJson((await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#queryPurchaseHistoryAsync(String)',
<String, dynamic>{
return PurchasesHistoryResult.fromJson((await channel.invokeMapMethod<
String, dynamic>(
'BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)',
<String, dynamic>{
'productType': const ProductTypeConverter().toJson(productType)
})) ??
<String, dynamic>{});
Expand All @@ -276,13 +277,14 @@ class BillingClient {
/// Consuming can only be done on an item that's owned, and as a result of consumption, the user will no longer own it.
/// Consumption is done asynchronously. The method returns a Future containing a [BillingResultWrapper].
///
/// This wraps [`BillingClient#consumeAsync(String, ConsumeResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#consumeAsync(java.lang.String,%20com.android.billingclient.api.ConsumeResponseListener))
/// This wraps
/// [`BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#consumeAsync(java.lang.String,%20com.android.billingclient.api.ConsumeResponseListener))
Future<BillingResultWrapper> consumeAsync(String purchaseToken) async {
assert(purchaseToken != null);
return BillingResultWrapper.fromJson((await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#consumeAsync(String, ConsumeResponseListener)',
<String, dynamic>{
return BillingResultWrapper.fromJson((await channel.invokeMapMethod<String,
dynamic>(
'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)',
<String, dynamic>{
'purchaseToken': purchaseToken,
})) ??
<String, dynamic>{});
Expand All @@ -304,12 +306,13 @@ class BillingClient {
/// Please refer to [acknowledge](https://developer.android.com/google/play/billing/billing_library_overview#acknowledge) for more
/// details.
///
/// This wraps [`BillingClient#acknowledgePurchase(String, AcknowledgePurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#acknowledgePurchase(com.android.billingclient.api.AcknowledgePurchaseParams,%20com.android.billingclient.api.AcknowledgePurchaseResponseListener))
/// This wraps
/// [`BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#acknowledgePurchase(com.android.billingclient.api.AcknowledgePurchaseParams,%20com.android.billingclient.api.AcknowledgePurchaseResponseListener))
Future<BillingResultWrapper> acknowledgePurchase(String purchaseToken) async {
assert(purchaseToken != null);
return BillingResultWrapper.fromJson((await channel.invokeMapMethod<String,
dynamic>(
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)',
'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)',
<String, dynamic>{
'purchaseToken': purchaseToken,
})) ??
Expand Down
Expand Up @@ -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/packages/tree/main/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.3.0
version: 0.3.0+1

environment:
sdk: ">=2.18.0 <4.0.0"
Expand Down
Expand Up @@ -401,7 +401,7 @@ void main() {

group('queryPurchases', () {
const String queryPurchasesMethodName =
'BillingClient#queryPurchases(String)';
'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)';

test('serializes and deserializes data', () async {
const BillingResponse expectedCode = BillingResponse.ok;
Expand Down Expand Up @@ -467,7 +467,7 @@ void main() {

group('queryPurchaseHistory', () {
const String queryPurchaseHistoryMethodName =
'BillingClient#queryPurchaseHistoryAsync(String)';
'BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)';

test('serializes and deserializes data', () async {
const BillingResponse expectedCode = BillingResponse.ok;
Expand Down Expand Up @@ -531,7 +531,7 @@ void main() {

group('consume purchases', () {
const String consumeMethodName =
'BillingClient#consumeAsync(String, ConsumeResponseListener)';
'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)';
test('consume purchase async success', () async {
const BillingResponse expectedCode = BillingResponse.ok;
const String debugMessage = 'dummy message';
Expand Down Expand Up @@ -564,7 +564,7 @@ void main() {

group('acknowledge purchases', () {
const String acknowledgeMethodName =
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
test('acknowledge purchase success', () async {
const BillingResponse expectedCode = BillingResponse.ok;
const String debugMessage = 'dummy message';
Expand All @@ -579,6 +579,7 @@ void main() {

expect(billingResult, equals(expectedBillingResult));
});

test('handles method channel returning null', () async {
stubPlatform.addResponse(
name: acknowledgeMethodName,
Expand Down
Expand Up @@ -44,7 +44,7 @@ void main() {

group('consume purchases', () {
const String consumeMethodName =
'BillingClient#consumeAsync(String, ConsumeResponseListener)';
'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)';
test('consume purchase async success', () async {
const BillingResponse expectedCode = BillingResponse.ok;
const String debugMessage = 'dummy message';
Expand All @@ -64,7 +64,8 @@ void main() {

group('queryPastPurchase', () {
group('queryPurchaseDetails', () {
const String queryMethodName = 'BillingClient#queryPurchases(String)';
const String queryMethodName =
'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)';
test('handles error', () async {
const String debugMessage = 'dummy message';
const BillingResponse responseCode = BillingResponse.developerError;
Expand Down
Expand Up @@ -25,7 +25,7 @@ void main() {
'BillingClient#startConnection(BillingClientStateListener)';
const String endConnectionCall = 'BillingClient#endConnection()';
const String acknowledgePurchaseCall =
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
const String onBillingServiceDisconnectedCallback =
'BillingClientStateListener#onBillingServiceDisconnected()';

Expand Down Expand Up @@ -213,7 +213,8 @@ void main() {
});

group('restorePurchases', () {
const String queryMethodName = 'BillingClient#queryPurchases(String)';
const String queryMethodName =
'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)';
test('handles error', () async {
const String debugMessage = 'dummy message';
const BillingResponse responseCode = BillingResponse.developerError;
Expand Down Expand Up @@ -331,7 +332,7 @@ void main() {
const String launchMethodName =
'BillingClient#launchBillingFlow(Activity, BillingFlowParams)';
const String consumeMethodName =
'BillingClient#consumeAsync(String, ConsumeResponseListener)';
'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)';

test('buy non consumable, serializes and deserializes data', () async {
const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails;
Expand Down Expand Up @@ -825,7 +826,7 @@ void main() {

group('complete purchase', () {
const String completeMethodName =
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
test('complete purchase success', () async {
const BillingResponse expectedCode = BillingResponse.ok;
const String debugMessage = 'dummy message';
Expand Down

0 comments on commit 83959fb

Please sign in to comment.