blob: 3917ac6f264f24c8fdf3a69e05037cddb3cc9aba [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 'package:flutter/services.dart';
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
import '../billing_client_wrappers.dart';
import '../in_app_purchase_android.dart';
/// Contains InApp Purchase features that are only available on PlayStore.
class InAppPurchaseAndroidPlatformAddition
extends InAppPurchasePlatformAddition {
/// Creates a [InAppPurchaseAndroidPlatformAddition] which uses the supplied
/// `BillingClientManager` to provide Android specific features.
InAppPurchaseAndroidPlatformAddition(this._billingClientManager);
/// Whether pending purchase is enabled.
///
/// **Deprecation warning:** it is no longer required to call
/// [enablePendingPurchases] when initializing your application. From now on
/// this is handled internally and the [enablePendingPurchase] property will
/// always return `true`.
///
// ignore: deprecated_member_use_from_same_package
/// See also [enablePendingPurchases] for more on pending purchases.
@Deprecated(
'The requirement to call `enablePendingPurchases()` has become obsolete '
"since Google Play no longer accepts app submissions that don't support "
'pending purchases.')
static bool get enablePendingPurchase => true;
/// Enable the [InAppPurchaseConnection] to handle pending purchases.
///
/// **Deprecation warning:** it is no longer required to call
/// [enablePendingPurchases] when initializing your application.
@Deprecated(
'The requirement to call `enablePendingPurchases()` has become obsolete '
"since Google Play no longer accepts app submissions that don't support "
'pending purchases.')
static void enablePendingPurchases() {
// No-op, until it is time to completely remove this method from the API.
}
final BillingClientManager _billingClientManager;
/// Mark that the user has consumed a product.
///
/// You are responsible for consuming all consumable purchases once they are
/// delivered. The user won't be able to buy the same product again until the
/// purchase of the product is consumed.
Future<BillingResultWrapper> consumePurchase(PurchaseDetails purchase) {
if (purchase.verificationData == null) {
throw ArgumentError(
'consumePurchase unsuccessful. The `purchase.verificationData` is not valid');
}
return _billingClientManager.runWithClient(
(BillingClient client) =>
client.consumeAsync(purchase.verificationData.serverVerificationData),
);
}
/// Query all previous purchases.
///
/// The `applicationUserName` should match whatever was sent in the initial
/// `PurchaseParam`, if anything. If no `applicationUserName` was specified in
/// the initial `PurchaseParam`, use `null`.
///
/// This does not return consumed products. If you want to restore unused
/// consumable products, you need to persist consumable product information
/// for your user on your own server.
///
/// See also:
///
/// * [refreshPurchaseVerificationData], for reloading failed
/// [PurchaseDetails.verificationData].
Future<QueryPurchaseDetailsResponse> queryPastPurchases(
{String? applicationUserName}) async {
List<PurchasesResultWrapper> responses;
PlatformException? exception;
try {
responses = await Future.wait(<Future<PurchasesResultWrapper>>[
_billingClientManager.runWithClient(
(BillingClient client) => client.queryPurchases(ProductType.inapp),
),
_billingClientManager.runWithClient(
(BillingClient client) => client.queryPurchases(ProductType.subs),
),
]);
} on PlatformException catch (e) {
exception = e;
responses = <PurchasesResultWrapper>[
PurchasesResultWrapper(
responseCode: BillingResponse.error,
purchasesList: const <PurchaseWrapper>[],
billingResult: BillingResultWrapper(
responseCode: BillingResponse.error,
debugMessage: e.details.toString(),
),
),
PurchasesResultWrapper(
responseCode: BillingResponse.error,
purchasesList: const <PurchaseWrapper>[],
billingResult: BillingResultWrapper(
responseCode: BillingResponse.error,
debugMessage: e.details.toString(),
),
)
];
}
final Set<String> errorCodeSet = responses
.where((PurchasesResultWrapper response) =>
response.responseCode != BillingResponse.ok)
.map((PurchasesResultWrapper response) =>
response.responseCode.toString())
.toSet();
final String errorMessage =
errorCodeSet.isNotEmpty ? errorCodeSet.join(', ') : '';
final List<GooglePlayPurchaseDetails> pastPurchases = responses
.expand((PurchasesResultWrapper response) => response.purchasesList)
.expand((PurchaseWrapper purchaseWrapper) =>
GooglePlayPurchaseDetails.fromPurchase(purchaseWrapper))
.toList();
IAPError? error;
if (exception != null) {
error = IAPError(
source: kIAPSource,
code: exception.code,
message: exception.message ?? '',
details: exception.details);
} else if (errorMessage.isNotEmpty) {
error = IAPError(
source: kIAPSource,
code: kRestoredPurchaseErrorCode,
message: errorMessage);
}
return QueryPurchaseDetailsResponse(
pastPurchases: pastPurchases, error: error);
}
/// Checks if the specified feature or capability is supported by the Play Store.
/// Call this to check if a [BillingClientFeature] is supported by the device.
Future<bool> isFeatureSupported(BillingClientFeature feature) async {
return _billingClientManager.runWithClientNonRetryable(
(BillingClient client) => client.isFeatureSupported(feature),
);
}
}