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 10fcd8d76b39..72b8bb36ff3d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.3.0+18 +* Adds new getCountryCode() method to InAppPurchaseAndroidPlatformAddition to get a customer's country code. * Updates compileSdk version to 34. ## 0.3.0+17 diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle index d5e4d965b634..726fdaba29d8 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle @@ -26,10 +26,11 @@ android { if (project.android.hasProperty("namespace")) { namespace 'io.flutter.plugins.inapppurchase' } + compileSdk 34 defaultConfig { - minSdkVersion 16 + minSdk 19 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -61,7 +62,7 @@ dependencies { // org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions. // See: https://youtrack.jetbrains.com/issue/KT-55297/kotlin-stdlib-should-declare-constraints-on-kotlin-stdlib-jdk8-and-kotlin-stdlib-jdk7 implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22")) - implementation 'com.android.billingclient:billing:6.0.1' + implementation 'com.android.billingclient:billing:6.1.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20231013' testImplementation 'org.mockito:mockito-core:5.4.0' 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 514b2675a884..247e037d3202 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 @@ -4,6 +4,7 @@ package io.flutter.plugins.inapppurchase; +import static io.flutter.plugins.inapppurchase.Translator.fromBillingConfig; import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult; import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; @@ -25,6 +26,7 @@ import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.GetBillingConfigParams; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.QueryProductDetailsParams; import com.android.billingclient.api.QueryProductDetailsParams.Product; @@ -62,6 +64,7 @@ static final class MethodNames { "BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)"; static final String IS_FEATURE_SUPPORTED = "BillingClient#isFeatureSupported(String)"; static final String GET_CONNECTION_STATE = "BillingClient#getConnectionState()"; + static final String GET_BILLING_CONFIG = "BillingClient#getBillingConfig()"; private MethodNames() {} } @@ -184,11 +187,22 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result case MethodNames.GET_CONNECTION_STATE: getConnectionState(result); break; + case MethodNames.GET_BILLING_CONFIG: + getBillingConfig(result); + break; default: result.notImplemented(); } } + private void getBillingConfig(final MethodChannel.Result result) { + billingClient.getBillingConfigAsync( + GetBillingConfigParams.newBuilder().build(), + (billingResult, billingConfig) -> { + result.success(fromBillingConfig(billingResult, billingConfig)); + }); + } + private void endConnection(final MethodChannel.Result result) { endBillingClientConnection(); result.success(null); diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java index 9f397e4e9fb6..95976d1a8afc 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.billingclient.api.AccountIdentifiers; +import com.android.billingclient.api.BillingConfig; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.Purchase; @@ -231,6 +232,14 @@ static HashMap fromBillingResult(BillingResult billingResult) { return info; } + /** Converter from {@link BillingResult} and {@link BillingConfig} to map. */ + static HashMap fromBillingConfig( + BillingResult result, BillingConfig billingConfig) { + HashMap info = fromBillingResult(result); + info.put("countryCode", billingConfig.getCountryCode()); + return info; + } + /** * Gets the symbol of for the given currency code for the default {@link Locale.Category#DISPLAY * DISPLAY} locale. For example, for the US Dollar, the symbol is "$" if the default locale is the 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 40aeb7ad68d4..2a826150a649 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 @@ -7,6 +7,7 @@ import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.ACKNOWLEDGE_PURCHASE; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.CONSUME_PURCHASE_ASYNC; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.END_CONNECTION; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.GET_BILLING_CONFIG; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.IS_FEATURE_SUPPORTED; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.IS_READY; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.LAUNCH_BILLING_FLOW; @@ -16,6 +17,7 @@ import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.START_CONNECTION; import static io.flutter.plugins.inapppurchase.PluginPurchaseListener.ON_PURCHASES_UPDATED; +import static io.flutter.plugins.inapppurchase.Translator.fromBillingConfig; import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult; import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; @@ -46,10 +48,13 @@ import com.android.billingclient.api.AcknowledgePurchaseResponseListener; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingConfig; +import com.android.billingclient.api.BillingConfigResponseListener; import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.GetBillingConfigParams; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.ProductDetailsResponseListener; import com.android.billingclient.api.Purchase; @@ -90,6 +95,7 @@ public class MethodCallHandlerTest { @Mock Context context; @Mock ActivityPluginBinding mockActivityPluginBinding; @Captor ArgumentCaptor> resultCaptor; + @Mock BillingConfig mockBillingConfig; @Before public void setUp() { @@ -185,6 +191,35 @@ public void startConnection_multipleCalls() { verify(result, times(1)).success(any()); } + @Test + public void getBillingConfigSuccess() { + mockStartConnection(); + ArgumentCaptor paramsCaptor = + ArgumentCaptor.forClass(GetBillingConfigParams.class); + ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(BillingConfigResponseListener.class); + MethodCall billingCall = new MethodCall(GET_BILLING_CONFIG, null); + methodChannelHandler.onMethodCall(billingCall, mock(Result.class)); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + final String expectedCountryCode = "US"; + final HashMap expectedResult = fromBillingResult(billingResult); + expectedResult.put("countryCode", expectedCountryCode); + + when(mockBillingConfig.getCountryCode()).thenReturn(expectedCountryCode); + doNothing() + .when(mockBillingClient) + .getBillingConfigAsync(paramsCaptor.capture(), listenerCaptor.capture()); + + methodChannelHandler.onMethodCall(billingCall, result); + listenerCaptor.getValue().onBillingConfigResponse(billingResult, mockBillingConfig); + + verify(result, times(1)).success(fromBillingConfig(billingResult, mockBillingConfig)); + } + @Test public void endConnection() { // Set up a connected BillingClient instance diff --git a/packages/in_app_purchase/in_app_purchase_android/example/README.md b/packages/in_app_purchase/in_app_purchase_android/example/README.md index 96b8bb17dbff..a820209c222f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/README.md +++ b/packages/in_app_purchase/in_app_purchase_android/example/README.md @@ -7,3 +7,58 @@ package. Unless you are making changes to this implementation package, this example is very unlikely to be relevant. + +# In App Purchase Example + +### Preparation + +There's a significant amount of setup required for testing in-app purchases +successfully, including registering new app IDs and store entries to use for +testing in the Play Developer Console. Google Play requires developers to +configure an app with in-app items for purchase to call their in-app-purchase +APIs. The Google Play Store has extensive documentation on how to do this, and +we've also included a high level guide below. + +* [Google Play Billing Overview](https://developer.android.com/google/play/billing/billing_overview) + +### Android + +1. Create a new app in the [Play Developer + Console](https://play.google.com/apps/publish/) (PDC). + +2. Sign up for a merchant's account in the PDC. + +3. Create IAPs in the PDC available for purchase in the app. The example assumes + the following SKU IDs exist: + + - `consumable`: A managed product. + - `upgrade`: A managed product. + - `subscription_silver`: A lower level subscription. + - `subscription_gold`: A higher level subscription. + + Make sure that all of the products are set to `ACTIVE`. + +4. Update `APP_ID` in `example/android/app/build.gradle` to match your package + ID in the PDC. + +5. Create an `example/android/keystore.properties` file with all your signing + information. `keystore.example.properties` exists as an example to follow. + It's impossible to use any of the `BillingClient` APIs from an unsigned APK. + See + [keystore](https://developer.android.com/studio/publish/app-signing#secure-shared-keystore) + and [signing](https://developer.android.com/studio/publish/app-signing#sign-apk) and + [flutter-android](https://docs.flutter.dev/deployment/android#signing-the-app) + for more information. + +6. Build a signed apk. `flutter build apk` will work for this, the gradle files + in this project have been configured to sign even debug builds. + +7. Upload the signed APK from step 6 to the PDC, and publish that to the alpha + test channel. Add your test account as an approved tester. The + `BillingClient` APIs won't work unless the app has been fully published to + the alpha channel and is being used by an authorized test account. See + [here](https://support.google.com/googleplay/android-developer/answer/3131213) + for more info. + +8. Sign in to the test device with the test account from step #7. Then use + `flutter run` to install the app to the device and test like normal. 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 6377d2dc5754..d3eafa8d7152 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 @@ -48,6 +48,7 @@ class _MyAppState extends State<_MyApp> { List _products = []; List _purchases = []; List _consumables = []; + String _countryCode = ''; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; @@ -228,6 +229,11 @@ class _MyAppState extends State<_MyApp> { 'This app needs special configuration to run. Please see example/README.md for instructions.'))); } + productList.add(ListTile( + title: Text('User Country Code', + style: TextStyle(color: ThemeData.light().colorScheme.error)), + subtitle: Text(_countryCode))); + // This loading previous purchases code is just a demo. Please do not use this as it is. // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. // We recommend that you use your own server to verify the purchase data. @@ -346,6 +352,12 @@ class _MyAppState extends State<_MyApp> { }); } + Future deliverCountryCode(String countryCode) async { + setState(() { + _countryCode = countryCode; + }); + } + Future deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { @@ -385,6 +397,10 @@ class _MyAppState extends State<_MyApp> { if (purchaseDetails.status == PurchaseStatus.pending) { showPendingUI(); } else { + final InAppPurchaseAndroidPlatformAddition addition = + InAppPurchasePlatformAddition.instance! + as InAppPurchaseAndroidPlatformAddition; + unawaited(deliverCountryCode(await addition.getCountryCode())); if (purchaseDetails.status == PurchaseStatus.error) { handleError(purchaseDetails.error!); } else if (purchaseDetails.status == PurchaseStatus.purchased || @@ -399,10 +415,6 @@ class _MyAppState extends State<_MyApp> { } if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - await addition.consumePurchase(purchaseDetails); } 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 12067d51451a..c54fc54e91b0 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 @@ -10,6 +10,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; import '../channel.dart'; +import 'billing_config_wrapper.dart'; part 'billing_client_wrapper.g.dart'; @@ -324,6 +325,21 @@ class BillingClient { return result ?? false; } + /// BillingConfig method channel string identifier. + // + // Must match the value of GET_BILLING_CONFIG in + // ../../../android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java + @visibleForTesting + final String getBillingConfigMethodString = + 'BillingClient#getBillingConfig()'; + + /// Fetches billing config info into a [BillingConfigWrapper] object. + Future getBillingConfig() async { + return BillingConfigWrapper.fromJson((await channel + .invokeMapMethod(getBillingConfigMethodString)) ?? + {}); + } + /// 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/billing_client_wrappers/billing_config_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart new file mode 100644 index 000000000000..27601285bc1b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart @@ -0,0 +1,74 @@ +// 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 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../../billing_client_wrappers.dart'; + +// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the +// below generated file. Run `flutter packages pub run build_runner watch` to +// rebuild and watch for further changes. +part 'billing_config_wrapper.g.dart'; + +/// The error message shown when the map represents billing config is invalid from method channel. +/// +/// This usually indicates a serious underlining code issue in the plugin. +@visibleForTesting +const String kInvalidBillingConfigErrorMessage = + 'Invalid billing config map from method channel.'; + +/// Params containing the response code and the debug message from the Play Billing API response. +@JsonSerializable() +@BillingResponseConverter() +@immutable +class BillingConfigWrapper implements HasBillingResponse { + /// Constructs the object with [responseCode] and [debugMessage]. + const BillingConfigWrapper( + {required this.responseCode, this.debugMessage, this.countryCode = ''}); + + /// Constructs an instance of this from a key value map of data. + /// + /// The map needs to have named string keys with values matching the names and + /// types of all of the members on this class. + factory BillingConfigWrapper.fromJson(Map? map) { + if (map == null || map.isEmpty) { + return const BillingConfigWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingConfigErrorMessage, + ); + } + return _$BillingConfigWrapperFromJson(map); + } + + /// Response code returned in the Play Billing API calls. + @override + final BillingResponse responseCode; + + /// Debug message returned in the Play Billing API calls. + /// + /// Defaults to `null`. + /// This message uses an en-US locale and should not be shown to users. + @JsonKey(defaultValue: '') + final String? debugMessage; + + /// https://developer.android.com/reference/com/android/billingclient/api/BillingConfig#getCountryCode() + @JsonKey(defaultValue: '') + final String countryCode; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + + return other is BillingConfigWrapper && + other.responseCode == responseCode && + other.debugMessage == debugMessage && + other.countryCode == countryCode; + } + + @override + int get hashCode => Object.hash(responseCode, debugMessage, countryCode); +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart new file mode 100644 index 000000000000..21f98577d91e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'billing_config_wrapper.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +BillingConfigWrapper _$BillingConfigWrapperFromJson(Map json) => + BillingConfigWrapper( + responseCode: const BillingResponseConverter() + .fromJson(json['responseCode'] as int?), + debugMessage: json['debugMessage'] as String? ?? '', + countryCode: json['countryCode'] as String? ?? '', + ); 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 e3050f8888bc..aa660192ee39 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 @@ -7,6 +7,7 @@ import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_inte import '../billing_client_wrappers.dart'; import '../in_app_purchase_android.dart'; +import 'billing_client_wrappers/billing_config_wrapper.dart'; /// Contains InApp Purchase features that are only available on PlayStore. class InAppPurchaseAndroidPlatformAddition @@ -146,4 +147,14 @@ class InAppPurchaseAndroidPlatformAddition (BillingClient client) => client.isFeatureSupported(feature), ); } + + /// Returns Play billing country code based on ISO-3166-1 alpha2 format. + /// + /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingConfig + /// See: https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html + Future getCountryCode() async { + final BillingConfigWrapper billingConfig = await _billingClientManager + .runWithClient((BillingClient client) => client.getBillingConfig()); + return billingConfig.countryCode; + } } 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 ff023ce51426..d97518aa751e 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/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+17 +version: 0.3.0+18 environment: sdk: ">=3.0.0 <4.0.0" 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 22df316f67df..df1c6278a1c3 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 @@ -5,6 +5,7 @@ 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/src/billing_client_wrappers/billing_config_wrapper.dart'; import 'package:in_app_purchase_android/src/channel.dart'; import '../stub_in_app_purchase_platform.dart'; @@ -641,4 +642,45 @@ void main() { expect(arguments['feature'], equals('subscriptions')); }); }); + + group('billingConfig', () { + const String billingConfigMethodName = 'BillingClient#getBillingConfig()'; + test('billingConfig returns object', () async { + const BillingConfigWrapper expected = BillingConfigWrapper( + countryCode: 'US', + responseCode: BillingResponse.ok, + debugMessage: ''); + stubPlatform.addResponse( + name: billingConfigMethodName, + value: buildBillingConfigMap(expected), + ); + final BillingConfigWrapper result = + await billingClient.getBillingConfig(); + expect(result.countryCode, 'US'); + expect(result, expected); + }); + + test('handles method channel returning null', () async { + stubPlatform.addResponse( + name: billingConfigMethodName, + ); + final BillingConfigWrapper result = + await billingClient.getBillingConfig(); + expect( + result, + equals(const BillingConfigWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingConfigErrorMessage, + ))); + }); + }); +} + +Map buildBillingConfigMap(BillingConfigWrapper original) { + return { + 'responseCode': + const BillingResponseConverter().toJson(original.responseCode), + 'debugMessage': original.debugMessage, + 'countryCode': original.countryCode, + }; } 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 4dd65acea907..4a97a43df5e9 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 @@ -7,8 +7,10 @@ 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/billing_client_wrappers/billing_config_wrapper.dart'; import 'package:in_app_purchase_android/src/channel.dart'; +import 'billing_client_wrappers/billing_client_wrapper_test.dart'; import 'billing_client_wrappers/purchase_wrapper_test.dart'; import 'stub_in_app_purchase_platform.dart'; @@ -61,6 +63,26 @@ void main() { }); }); + group('billingConfig', () { + const String billingConfigMethodName = 'BillingClient#getBillingConfig()'; + test('getCountryCode success', () async { + const String expectedCountryCode = 'US'; + const BillingConfigWrapper expected = BillingConfigWrapper( + countryCode: expectedCountryCode, + responseCode: BillingResponse.ok, + debugMessage: 'dummy message'); + + stubPlatform.addResponse( + name: billingConfigMethodName, + value: buildBillingConfigMap(expected), + ); + final String countryCode = + await iapAndroidPlatformAddition.getCountryCode(); + + expect(countryCode, equals(expectedCountryCode)); + }); + }); + group('queryPastPurchase', () { group('queryPurchaseDetails', () { const String queryMethodName =