blob: 64e0095dcbb7c47bd95769627a3491390e8366ae [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/foundation.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:in_app_purchase_storekit/in_app_purchase_storekit.dart';
export 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'
show
IAPError,
InAppPurchaseException,
ProductDetails,
ProductDetailsResponse,
PurchaseDetails,
PurchaseParam,
PurchaseVerificationData,
PurchaseStatus;
/// Basic API for making in app purchases across multiple platforms.
class InAppPurchase implements InAppPurchasePlatformAdditionProvider {
InAppPurchase._();
static InAppPurchase? _instance;
/// The instance of the [InAppPurchase] to use.
static InAppPurchase get instance => _getOrCreateInstance();
static InAppPurchase _getOrCreateInstance() {
if (_instance != null) {
return _instance!;
}
if (defaultTargetPlatform == TargetPlatform.android) {
InAppPurchaseAndroidPlatform.registerPlatform();
} else if (defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS) {
InAppPurchaseStoreKitPlatform.registerPlatform();
}
_instance = InAppPurchase._();
return _instance!;
}
@override
T getPlatformAddition<T extends InAppPurchasePlatformAddition?>() {
return InAppPurchasePlatformAddition.instance as T;
}
/// Listen to this broadcast stream to get real time update for purchases.
///
/// This stream will never close as long as the app is active.
///
/// Purchase updates can happen in several situations:
/// * When a purchase is triggered by user in the app.
/// * When a purchase is triggered by user from the platform-specific store front.
/// * When a purchase is restored on the device by the user in the app.
/// * If a purchase is not completed ([completePurchase] is not called on the
/// purchase object) from the last app session. Purchase updates will happen
/// when a new app session starts instead.
///
/// IMPORTANT! You must subscribe to this stream as soon as your app launches,
/// preferably before returning your main App Widget in main(). Otherwise you
/// will miss purchase updated made before this stream is subscribed to.
///
/// We also recommend listening to the stream with one subscription at a given
/// time. If you choose to have multiple subscription at the same time, you
/// should be careful at the fact that each subscription will receive all the
/// events after they start to listen.
Stream<List<PurchaseDetails>> get purchaseStream =>
InAppPurchasePlatform.instance.purchaseStream;
/// Returns `true` if the payment platform is ready and available.
Future<bool> isAvailable() => InAppPurchasePlatform.instance.isAvailable();
/// Query product details for the given set of IDs.
///
/// Identifiers in the underlying payment platform, for example, [App Store
/// Connect](https://appstoreconnect.apple.com/) for iOS and [Google Play
/// Console](https://play.google.com/) for Android.
Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers) =>
InAppPurchasePlatform.instance.queryProductDetails(identifiers);
/// Buy a non consumable product or subscription.
///
/// Non consumable items can only be bought once. For example, a purchase that
/// unlocks a special content in your app. Subscriptions are also non
/// consumable products.
///
/// You always need to restore all the non consumable products for user when
/// they switch their phones.
///
/// This method does not return the result of the purchase. Instead, after
/// triggering this method, purchase updates will be sent to
/// [purchaseStream]. You should [Stream.listen] to [purchaseStream] to get
/// [PurchaseDetails] objects in different [PurchaseDetails.status] and update
/// your UI accordingly. When the [PurchaseDetails.status] is
/// [PurchaseStatus.purchased], [PurchaseStatus.restored] or
/// [PurchaseStatus.error] you should deliver the content or handle the error,
/// then call [completePurchase] to finish the purchasing process.
///
/// This method does return whether or not the purchase request was initially
/// sent successfully.
///
/// Consumable items are defined differently by the different underlying
/// payment platforms, and there's no way to query for whether or not the
/// [ProductDetail] is a consumable at runtime.
///
/// See also:
///
/// * [buyConsumable], for buying a consumable product.
/// * [restorePurchases], for restoring non consumable products.
///
/// Calling this method for consumable items will cause unwanted behaviors!
Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) =>
InAppPurchasePlatform.instance.buyNonConsumable(
purchaseParam: purchaseParam,
);
/// Buy a consumable product.
///
/// Consumable items can be "consumed" to mark that they've been used and then
/// bought additional times. For example, a health potion.
///
/// To restore consumable purchases across devices, you should keep track of
/// those purchase on your own server and restore the purchase for your users.
/// Consumed products are no longer considered to be "owned" by payment
/// platforms and will not be delivered by calling [restorePurchases].
///
/// Consumable items are defined differently by the different underlying
/// payment platforms, and there's no way to query for whether or not the
/// [ProductDetail] is a consumable at runtime.
///
/// `autoConsume` is provided as a utility and will instruct the plugin to
/// automatically consume the product after a succesful purchase.
/// `autoConsume` is `true` by default.
///
/// This method does not return the result of the purchase. Instead, after
/// triggering this method, purchase updates will be sent to
/// [purchaseStream]. You should [Stream.listen] to
/// [purchaseStream] to get [PurchaseDetails] objects in different
/// [PurchaseDetails.status] and update your UI accordingly. When the
/// [PurchaseDetails.status] is [PurchaseStatus.purchased] or
/// [PurchaseStatus.error], you should deliver the content or handle the
/// error, then call [completePurchase] to finish the purchasing process.
///
/// This method does return whether or not the purchase request was initially
/// sent succesfully.
///
/// See also:
///
/// * [buyNonConsumable], for buying a non consumable product or
/// subscription.
/// * [restorePurchases], for restoring non consumable products.
///
/// Calling this method for non consumable items will cause unwanted
/// behaviors!
Future<bool> buyConsumable({
required PurchaseParam purchaseParam,
bool autoConsume = true,
}) =>
InAppPurchasePlatform.instance.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: autoConsume,
);
/// Mark that purchased content has been delivered to the user.
///
/// You are responsible for completing every [PurchaseDetails] whose
/// [PurchaseDetails.status] is [PurchaseStatus.purchased] or
/// [PurchaseStatus.restored].
/// Completing a [PurchaseStatus.pending] purchase will cause an exception.
/// For convenience, [PurchaseDetails.pendingCompletePurchase] indicates if a
/// purchase is pending for completion.
///
/// The method will throw a [PurchaseException] when the purchase could not be
/// finished. Depending on the [PurchaseException.errorCode] the developer
/// should try to complete the purchase via this method again, or retry the
/// [completePurchase] method at a later time. If the
/// [PurchaseException.errorCode] indicates you should not retry there might
/// be some issue with the app's code or the configuration of the app in the
/// respective store. The developer is responsible to fix this issue. The
/// [PurchaseException.message] field might provide more information on what
/// went wrong.
Future<void> completePurchase(PurchaseDetails purchase) =>
InAppPurchasePlatform.instance.completePurchase(purchase);
/// Restore 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`.
///
/// Restored purchases are delivered through the [purchaseStream] with a
/// status of [PurchaseStatus.restored]. You should listen for these purchases,
/// validate their receipts, deliver the content and mark the purchase complete
/// by calling the [finishPurchase] method for each purchase.
///
/// 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<void> restorePurchases({String? applicationUserName}) =>
InAppPurchasePlatform.instance.restorePurchases(
applicationUserName: applicationUserName,
);
}