FlutterでRevenueCatを使ってAndroidのアプリ内課金(非消耗型)を実装するときの懸念点

4 min read読了の目安(約3800字

RevenueCatは、モバイルアプリのための課金プラットフォームです。RevenueCatを使うことで、iOSやAndroidでアプリ内課金を実装するときにレシート検証などのサーバ側のコードを一切書かずに済みます。RevenueCatの詳しい説明は、以下記事で紹介されています。

https://zenn.dev/moga/books/flutter_revenuecat
https://zenn.dev/sakutech/articles/revenuecat-flutter-in-app-purchase

個人で開発しているFlutterアプリ(iOS / Android)でも導入してみたのですが、Androidのアプリ内課金を実装する際に課題が残ったのでメモを残しておきます。(ご意見いただけると助かります🙏)

アプリ内課金の種類

アプリ内課金の種類は大きく以下の3つに分類されます。

  1. 消耗型(一度だけ使用可能なアイテム。例. アプリ内で使えるコイン)
  2. 非消耗型(一度だけ購入すれば無期限に使用可能なアイテム。例. 広告の削除)
  3. サブスクリプション

iOSだと1と2の区別があるのですが、Androidのアプリ内課金は1と2の区別がありません(すべて消耗型のイメージ)。 この区別はFlutterやRevenueCatとは関係なく、AppleとGoogleの方針でそうなっています。とはいえ、開発者側からすると1と2の区別はつけないといけない(コインが無限に使えたり、広告の削除が再購入されたりすると困る)ので、Androidのアプリ内課金を実装しようとすると、アプリ内でその考慮を行う必要があります。

RevenueCatではAndroidの非消耗型の再購入は防ぎづらい

ここからが本題です。非消耗型の場合、一度だけ購入すれば無期限に使用可能なため、消耗型と異なり、再購入は不要です。つまり、消耗型と非消耗型の区別がないAndroidで非消耗型を実装する際、そのユーザが購入済みかどうかをチェックする必要があります。しかし、RevenueCatにはその機能がないのです(厳密には、場合によってはその機能が使える)。

RevenueCatでは以下のように簡単にユーザの購入情報を取得できます。ただ、これは購入または復元処理を行ってからでないと取得できません。 ここでいう復元とは、購入後、機種変更やアプリ再インストール時に購入状態に戻すことを指します。

PurchaserInfo purchaserInfo = await Purchases.getPurchaserInfo();

The PurchaserInfo object contains all of the purchase and subscription data available about the user. This object is updated whenever a purchase or restore occurs and periodically throughout the lifecycle of your app. The latest information can always be retrieved by calling getPurchaserInfo():
Determining subscription status – RevenueCat

ここまで聞いて、「購入処理後に購入ボタンを非表示にしてやればいいのでは?」と思った方もいるかと思いますが、購入後にアプリを再インストールすると、再購入や復元をしない限りユーザの購入情報が取得できないため、購入したかどうかのチェックが行えず、再購入を完全には防げません。

一度購入したはずなのに、なぜ購入情報が取得できないのかというと、RevenueCatは初期化時にランダムなユーザIDを作成してデバイスにキャッシュするのですが、アプリ再インストール時には別のランダムなユーザIDを生成するからです。 そのため、上記コードでユーザの購入情報を取得しようとしても、アプリ再インストール前後でユーザIDが異なるため、うまく取得できないのです。

// RevenueCat初期化時にランダムなユーザIDを作成し、デバイスにキャッシュする
await Purchases.setup("public_sdk_key");

別のランダムなユーザIDであっても、購入時や復元時にRevenueCat側でAppleやGoogleのログイン情報と紐つけて同一ユーザと認識させているようです。これについては、ドキュメントに記述がなかったのでもしかしたら間違っているかもしれません。

じゃあどうするか?

記事冒頭でも触れたとおり、すべてのアプリに対応できる根本的な解決策はまだ見つけられていません🥲

ただ、RevenueCatのコミュニティで質問したり、試行錯誤したりして対応をいくつか考えたのでまとめます。

対応その1:アプリ内で利用しているユーザIDをRevenueCatに紐つける

これが1番の対応方法だと思います。 前項で「RevenueCatは初期化時にランダムなユーザIDを作成してデバイスにキャッシュし、アプリ再インストール時には別のランダムなユーザIDを生成する」と話しましたが、アプリ内でメールアドレスや各種SNSでログインシステムを設けている場合、そのユーザIDをRevenueCatに紐つけることができます。具体的には、以下のようなコードで実装できます。

// RevenueCat初期化時にアプリ内で利用しているユーザIDをRevenueCatに紐つける
await Purchases.setup("public_sdk_key", appUserId: "my_app_user_id");

これにより、アプリ再インストール前後でもRevenueCatのユーザIDは同じため、アプリ再インストール後でも再購入や復元を行わずにユーザの購入情報が取得できます。

ただ、この対応はすでにログインシステムがある前提のため、すべてのアプリが対応できるわけではありません。

対応その2:再購入しないようユーザに伝える


上記画面はあくまで一例ですが、復元を促すテキストを表示したり、購入ボタン選択時に再購入しないダイアログを出したりして 「誤ってユーザが再購入しない仕組みを作る」 対応です。ただし、対応その1と比べ、RevenueCatの機能を利用するわけではないので、完全に再購入を防ぐのは難しいと思います。

ちなみに、ユーザが誤って再購入した場合、AndroidだとRevenueCatのダッシュボードから簡単に返金できます。

対応その3:RevenueCatを使わない

対応というか諦めですね😂 AndroidだけRevenueCatを使わない、iOSも含めてRevenueCatを使わないという選択です。Flutter公式のパッケージサードパーティのパッケージを使ってアプリ内課金を実装するのも全然ありだと思います。

まとめ

FlutterでRevenueCatを使ってAndroidのアプリ内課金(非消耗型)を実装するときは課題が残るかもしれませんよ、という話でした。自身はFlutter以前にネイティブ(Swift / Kotlin)の知識も全然ないので、「こうやれば実現できる」みたいなのがあればぜひ教えてほしいです🙇‍♂️

おまけ

RevenueCatの実装ではまって悪戦苦闘していたときの一連のツイート🐥

https://twitter.com/donchan922/status/1352382541265727490?s=20