【问题标题】:How can I dd in-app-purchase functionality to my flutter app?如何将应用内购买功能添加到我的颤振应用程序?
【发布时间】:2021-01-07 04:11:57
【问题描述】:

我将在我的 Flutter 应用中添加应用内购买功能,但找不到任何有关应用内购买的相关信息或示例应用,任何人都可以分享任何包含应用内的链接或示例应用-购买功能? 谢谢

【问题讨论】:

    标签: flutter in-app-purchase


    【解决方案1】:

    看看这个包

    https://pub.dev/packages/in_app_purchase

    dependencies:
      in_app_purchase: ^0.3.4+8
    

    示例代码:

    import 'dart:async';
    import 'dart:io';
    import 'package:flutter/material.dart';
    import 'package:in_app_purchase/in_app_purchase.dart';
    import 'consumable_store.dart';
    
    void main() {
      // For play billing library 2.0 on Android, it is mandatory to call
      // [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases)
      // as part of initializing the app.
      InAppPurchaseConnection.enablePendingPurchases();
      runApp(MyApp());
    }
    
    const bool kAutoConsume = true;
    
    const String _kConsumableId = 'consumable';
    const List<String> _kProductIds = <String>[
      _kConsumableId,
      'upgrade',
      'subscription'
    ];
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance;
      StreamSubscription<List<PurchaseDetails>> _subscription;
      List<String> _notFoundIds = [];
      List<ProductDetails> _products = [];
      List<PurchaseDetails> _purchases = [];
      List<String> _consumables = [];
      bool _isAvailable = false;
      bool _purchasePending = false;
      bool _loading = true;
      String _queryProductError;
    
      @override
      void initState() {
        Stream purchaseUpdated =
            InAppPurchaseConnection.instance.purchaseUpdatedStream;
        _subscription = purchaseUpdated.listen((purchaseDetailsList) {
          _listenToPurchaseUpdated(purchaseDetailsList);
        }, onDone: () {
          _subscription.cancel();
        }, onError: (error) {
          // handle error here.
        });
        initStoreInfo();
        super.initState();
      }
    
      Future<void> initStoreInfo() async {
        final bool isAvailable = await _connection.isAvailable();
        if (!isAvailable) {
          setState(() {
            _isAvailable = isAvailable;
            _products = [];
            _purchases = [];
            _notFoundIds = [];
            _consumables = [];
            _purchasePending = false;
            _loading = false;
          });
          return;
        }
    
        ProductDetailsResponse productDetailResponse =
            await _connection.queryProductDetails(_kProductIds.toSet());
        if (productDetailResponse.error != null) {
          setState(() {
            _queryProductError = productDetailResponse.error.message;
            _isAvailable = isAvailable;
            _products = productDetailResponse.productDetails;
            _purchases = [];
            _notFoundIds = productDetailResponse.notFoundIDs;
            _consumables = [];
            _purchasePending = false;
            _loading = false;
          });
          return;
        }
    
        if (productDetailResponse.productDetails.isEmpty) {
          setState(() {
            _queryProductError = null;
            _isAvailable = isAvailable;
            _products = productDetailResponse.productDetails;
            _purchases = [];
            _notFoundIds = productDetailResponse.notFoundIDs;
            _consumables = [];
            _purchasePending = false;
            _loading = false;
          });
          return;
        }
    
        final QueryPurchaseDetailsResponse purchaseResponse =
            await _connection.queryPastPurchases();
        if (purchaseResponse.error != null) {
          // handle query past purchase error..
        }
        final List<PurchaseDetails> verifiedPurchases = [];
        for (PurchaseDetails purchase in purchaseResponse.pastPurchases) {
          if (await _verifyPurchase(purchase)) {
            verifiedPurchases.add(purchase);
          }
        }
        List<String> consumables = await ConsumableStore.load();
        setState(() {
          _isAvailable = isAvailable;
          _products = productDetailResponse.productDetails;
          _purchases = verifiedPurchases;
          _notFoundIds = productDetailResponse.notFoundIDs;
          _consumables = consumables;
          _purchasePending = false;
          _loading = false;
        });
      }
    
      @override
      void dispose() {
        _subscription.cancel();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        List<Widget> stack = [];
        if (_queryProductError == null) {
          stack.add(
            ListView(
              children: [
                _buildConnectionCheckTile(),
                _buildProductList(),
                _buildConsumableBox(),
              ],
            ),
          );
        } else {
          stack.add(Center(
            child: Text(_queryProductError),
          ));
        }
        if (_purchasePending) {
          stack.add(
            Stack(
              children: [
                Opacity(
                  opacity: 0.3,
                  child: const ModalBarrier(dismissible: false, color: Colors.grey),
                ),
                Center(
                  child: CircularProgressIndicator(),
                ),
              ],
            ),
          );
        }
    
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('IAP Example'),
            ),
            body: Stack(
              children: stack,
            ),
          ),
        );
      }
    
      Card _buildConnectionCheckTile() {
        if (_loading) {
          return Card(child: ListTile(title: const Text('Trying to connect...')));
        }
        final Widget storeHeader = ListTile(
          leading: Icon(_isAvailable ? Icons.check : Icons.block,
              color: _isAvailable ? Colors.green : ThemeData.light().errorColor),
          title: Text(
              'The store is ' + (_isAvailable ? 'available' : 'unavailable') + '.'),
        );
        final List<Widget> children = <Widget>[storeHeader];
    
        if (!_isAvailable) {
          children.addAll([
            Divider(),
            ListTile(
              title: Text('Not connected',
                  style: TextStyle(color: ThemeData.light().errorColor)),
              subtitle: const Text(
                  'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'),
            ),
          ]);
        }
        return Card(child: Column(children: children));
      }
    
      Card _buildProductList() {
        if (_loading) {
          return Card(
              child: (ListTile(
                  leading: CircularProgressIndicator(),
                  title: Text('Fetching products...'))));
        }
        if (!_isAvailable) {
          return Card();
        }
        final ListTile productHeader = ListTile(title: Text('Products for Sale'));
        List<ListTile> productList = <ListTile>[];
        if (_notFoundIds.isNotEmpty) {
          productList.add(ListTile(
              title: Text('[${_notFoundIds.join(", ")}] not found',
                  style: TextStyle(color: ThemeData.light().errorColor)),
              subtitle: Text(
                  'This app needs special configuration to run. Please see example/README.md for instructions.')));
        }
    
        // 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 verity the purchase data.
        Map<String, PurchaseDetails> purchases =
            Map.fromEntries(_purchases.map((PurchaseDetails purchase) {
          if (purchase.pendingCompletePurchase) {
            InAppPurchaseConnection.instance.completePurchase(purchase);
          }
          return MapEntry<String, PurchaseDetails>(purchase.productID, purchase);
        }));
        productList.addAll(_products.map(
          (ProductDetails productDetails) {
            PurchaseDetails previousPurchase = purchases[productDetails.id];
            return ListTile(
                title: Text(
                  productDetails.title,
                ),
                subtitle: Text(
                  productDetails.description,
                ),
                trailing: previousPurchase != null
                    ? Icon(Icons.check)
                    : FlatButton(
                        child: Text(productDetails.price),
                        color: Colors.green[800],
                        textColor: Colors.white,
                        onPressed: () {
                          PurchaseParam purchaseParam = PurchaseParam(
                              productDetails: productDetails,
                              applicationUserName: null,
                              sandboxTesting: true);
                          if (productDetails.id == _kConsumableId) {
                            _connection.buyConsumable(
                                purchaseParam: purchaseParam,
                                autoConsume: kAutoConsume || Platform.isIOS);
                          } else {
                            _connection.buyNonConsumable(
                                purchaseParam: purchaseParam);
                          }
                        },
                      ));
          },
        ));
    
        return Card(
            child:
                Column(children: <Widget>[productHeader, Divider()] + productList));
      }
    
      Card _buildConsumableBox() {
        if (_loading) {
          return Card(
              child: (ListTile(
                  leading: CircularProgressIndicator(),
                  title: Text('Fetching consumables...'))));
        }
        if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) {
          return Card();
        }
        final ListTile consumableHeader =
            ListTile(title: Text('Purchased consumables'));
        final List<Widget> tokens = _consumables.map((String id) {
          return GridTile(
            child: IconButton(
              icon: Icon(
                Icons.stars,
                size: 42.0,
                color: Colors.orange,
              ),
              splashColor: Colors.yellowAccent,
              onPressed: () => consume(id),
            ),
          );
        }).toList();
        return Card(
            child: Column(children: <Widget>[
          consumableHeader,
          Divider(),
          GridView.count(
            crossAxisCount: 5,
            children: tokens,
            shrinkWrap: true,
            padding: EdgeInsets.all(16.0),
          )
        ]));
      }
    
      Future<void> consume(String id) async {
        await ConsumableStore.consume(id);
        final List<String> consumables = await ConsumableStore.load();
        setState(() {
          _consumables = consumables;
        });
      }
    
      void showPendingUI() {
        setState(() {
          _purchasePending = true;
        });
      }
    
      void deliverProduct(PurchaseDetails purchaseDetails) async {
        // IMPORTANT!! Always verify a purchase purchase details before delivering the product.
        if (purchaseDetails.productID == _kConsumableId) {
          await ConsumableStore.save(purchaseDetails.purchaseID);
          List<String> consumables = await ConsumableStore.load();
          setState(() {
            _purchasePending = false;
            _consumables = consumables;
          });
        } else {
          setState(() {
            _purchases.add(purchaseDetails);
            _purchasePending = false;
          });
        }
      }
    
      void handleError(IAPError error) {
        setState(() {
          _purchasePending = false;
        });
      }
    
      Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) {
        // IMPORTANT!! Always verify a purchase before delivering the product.
        // For the purpose of an example, we directly return true.
        return Future<bool>.value(true);
      }
    
      void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
        // handle invalid purchase here if  _verifyPurchase` failed.
      }
    
      void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
        purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
          if (purchaseDetails.status == PurchaseStatus.pending) {
            showPendingUI();
          } else {
            if (purchaseDetails.status == PurchaseStatus.error) {
              handleError(purchaseDetails.error);
            } else if (purchaseDetails.status == PurchaseStatus.purchased) {
              bool valid = await _verifyPurchase(purchaseDetails);
              if (valid) {
                deliverProduct(purchaseDetails);
              } else {
                _handleInvalidPurchase(purchaseDetails);
                return;
              }
            }
            if (Platform.isAndroid) {
              if (!kAutoConsume && purchaseDetails.productID == _kConsumableId) {
                await InAppPurchaseConnection.instance
                    .consumePurchase(purchaseDetails);
              }
            }
            if (purchaseDetails.pendingCompletePurchase) {
              await InAppPurchaseConnection.instance
                  .completePurchase(purchaseDetails);
            }
          }
        });
      }
    }
    

    【讨论】:

    猜你喜欢
    • 2013-11-02
    • 2019-04-29
    • 2021-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多