エキサイト株式会社の@mthiroshiです。
Flutterのアプリ内課金の実装には、 in_app_purchase パッケージを使います。 Androidで実装する際に、少し躓いた問題があったのでご紹介します。
動作環境は、下記のpubspec.ymlの内容です。
dependencies: in_app_purchase: ^3.1.10 in_app_purchase_android: ^0.3.0+13
購入商品の問い合わせに失敗するケース
アプリ起動時に課金状態を確認するため、購入商品の問い合わせ処理を実装していました。
下記にサンプルコードを示します。
/// アプリケーション用の課金操作クラス class BillingAndroid { BillingAndroid() { InAppPurchaseAndroidPlatform.registerPlatform(); subscription = inAppPurchase.purchaseStream.listen( // 購入イベントリスナー _listenToPurchaseUpdated, onDone: () { subscription.cancel(); }, onError: (_) { // エラー処理 }, ); } final InAppPurchase inAppPurchase = InAppPurchase.instance; final BillingClientManager clientManager = BillingClientManager(); late StreamSubscription<List<PurchaseDetails>> subscription; /// 購入商品の問い合わせ @override Future<void> query() async { final purchases = await clientManager.runWithClient((client) async { return await client.queryPurchases(ProductType.subs); }); if (purchases.purchasesList.isEmpty) { // 購入商品がない場合の処理 return; } for (final element in purchases.purchasesList) { // アプリケーションのAPIでレシート検証を行う } }
処理の流れは、下記になります。
- InAppPurchaseクラスの初期化
- Androidの課金クライアントを管理するBillingClientManagerの queryPurchasesを実行して、購入商品データ(レシート)を取得
- 購入商品データの検証
BillingAndroidクラスのコンストラクタで、InAppPurchaseの初期化と購入イベントのリスナー設定を行っています。
そして、アプリ起動時に query()
を呼び出して、購入商品の問い合わせを行います。
上記の処理では、商品を購入した状態にも関わらず、稀に空のレスポンスを返す挙動が起きていました。
課金APIの利用可能状態を確認する
InAppPurchase クラスには、 課金APIの利用可能状態を取得する isAvailable()
があります。
isAvailable()
と queryPurchases()
の挙動を確認してみたところ、
isAvailable()
の返り値がfalseのときに、queryPurchases()
が空のレスポンスを返していました。
つまり、queryPurchases()
が空を返していた理由は、課金APIが利用できない状態だったからでした。
そこで、queryPurchases()
の実行前に isAvailable()
によって課金APIの利用可能判定を行うことで、この問題を回避しました。
下記に、利用可能判定を入れたコードを示します。
static const maxRetryQuery = 5; int retryQueryCount = 0; @override Future<void> query() async { if (!await inAppPurchase.isAvailable()) { // 利用不可の場合はリトライ if (retryQueryCount < maxRetryQuery) { await Future<void>.delayed(const Duration(seconds: 1)); await query(); retryQueryCount++; return; } retryQueryCount = 0; return; } retryQueryCount = 0; final purchases = await clientManager.runWithClient((client) async { return await client.queryPurchases(ProductType.subs); }); if (purchases.purchasesList.isEmpty) { // 購入情報がない場合の処理 return; } for (final element in purchases.purchasesList) { // APIでレシート検証を行う } }
isAvailable()
がfalseを返す際には、遅延処理を入れてリトライしています。
これにより、課金APIが利用可能な状態で操作できまして、購入商品を正しく問い合せることができました。
公式のサンプルコード
公式のサンプルコードを確認してみると、ストアの初期化処理で isAvailable()
から利用可能判定を行っていました。
Future<void> initStoreInfo() async { final bool isAvailable = await _inAppPurchase.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = <ProductDetails>[]; _purchases = <PurchaseDetails>[]; _notFoundIds = <String>[]; _consumables = <String>[]; _purchasePending = false; _loading = false; }); return; } 〜省略〜
今回対応した処理は、公式のサンプルコードから見落としていたようでした。
まとめ
FlutterのAndroidの課金実装において、課金APIを操作する前に利用可能判定を行うことについて説明しました。
課金APIの接続タイミングによっては想定した挙動にならないケースがあるので、 isAvailable()
で確認してから行うことを推奨します。
参考になれば幸いです。