Flutterコード実装編【Flutter×Revenuecat】サブスクリプションを構築する
私が先日リリースした筋トレ習慣化アプリ(Fitness Slave)で、アプリ内課金によるサブスクリプション機能を実装しました。
◆iOS
◆Android
Revenuecatのシリーズ記事です。
- iOSの各種設定
- Androidの各種設定
- Revenuecatでの設定
- Flutterアプリ内でのコード実装
今回は「4.Flutterアプリ内でのコード実装」編です。
それぞれの記事はこちら
Flutterアプリ内でのコード実装
- purchases_flutterのインストール
- ios/podfile の変更
- Xcodeで「In-App Purchase」を有効にする
- ユーザーがサブスクアイテムを保有しているかの確認
- 購入処理
- 購入の復元
purchases_flutterのインストール
まずは、purchases_flutterライブラリをインストールしましょう
dependencies:
purchases_flutter: 最新バージョンを入れてください
purchases_flutterのインストール
iOSのDeployment Targetを11.0 以上にする必要があります。
Xcodeで「In-App Purchase」を有効にする
Flutterのプロジェクトを、Xcodeで開きます。(Android studioを使っている場合は、「ios」フォルダで右クリック ⇨ Flutter ⇨ open ios module in Xcode)
そして、画像下部の赤枠で囲った 「In App Purchase」が有効になっているかを確認します。
有効になっていなかったら、左上のcapabilityから「In App Purchase」を追加して有効にします。
ユーザーがサブスクアイテムを保有しているかの確認
実際のコードの部分です。
まず、購入の前にユーザーがサブスクアイテムを保有しているかの確認が必要です。そのためのコードは以下で実装しました。
final inAppPurchaseManager =
ChangeNotifierProvider((ref) => InAppPurchaseManager());
class InAppPurchaseManager with ChangeNotifier {
bool isSubscribed = false;
late Offerings offerings;
Future<void> initInAppPurchase() async {
try {
//consoleにdebug情報を出力する
await Purchases.setDebugLogsEnabled(true);
late PurchasesConfiguration configuration;
if (Platform.isAndroid) {
configuration = PurchasesConfiguration(Android用のRevenuecat APIキー);
} else if (Platform.isIOS) {
configuration = PurchasesConfiguration(ios用のRevenuecat APIキー);
}
await Purchases.configure(configuration);
//offeringsを取ってくる
offerings = await Purchases.getOfferings();
//firebaseのidと、revenuecatのuserIdを一緒にしている場合、firebaseAuthのuidでログイン
final result = await Purchases.logIn(auth.currentUser!.uid);
await getPurchaserInfo(result.customerInfo);
//今アクティブになっているアイテムは以下のように取得可能
print("アクティブなアイテム ${result.customerInfo.entitlements.active.keys}");
} catch (e) {
print("initInAppPurchase error caught! ${e.toString()}");
}
}
Future<void> getPurchaserInfo(
CustomerInfo customerInfo) async {
try {
isSubscribed = await updatePurchases(customerInfo, monthly_subscription);//monthly_subscriptionは、適宜ご自身のentitlement名に変えてください
} on PlatformException catch (e) {
print(" getPurchaserInfo error ${e.toString()}");
}
}
Future<bool> updatePurchases(
CustomerInfo purchaserInfo, String entitlement) async {
var isPurchased = false;
final entitlements = purchaserInfo.entitlements.all;
if (entitlements.isEmpty) {
isPurchased = false;
}
if (!entitlements.containsKey(entitlement)) {
///そもそもentitlementが設定されて無い場合
isPurchased = false;
} else if (entitlements[entitlement]!.isActive) {
///設定されていて、activeになっている場合
isPurchased = true;
} else {
isPurchased = false;
}
return isPurchased;
}
}
ポイントとして、私の場合は状態管理をriverpodを使っており「InAppPurchaseManager」に課金関係のロジックを記載しています。
- initInAppPurchase()でrevenuecatの初期化関係処理
- getPurchaserInfo() → updatePurchases() で、サブスクを購入しているかの確認をして、上部にある変数「isSubscribed」に結果を格納します。
購入処理
流れとしては、まず、上記のinitInAppPurchase()を呼び出した後、以下のmakePurchase()を呼び出して実際に課金処理を行います。
Future<void> makePurchase(
String offeringsName) async {
try {
Package? package;
package = offerings.all[offeringsName]?.monthly;//offeringsは適宜ご自身の設定したofferingsの名前に変えてください
if (package != null) {
await Purchases.logIn(auth.currentUser!.uid);
CustomerInfo customerInfo = await Purchases.purchasePackage(package);
await getPurchaserInfo(customerInfo);
}
} on PlatformException catch (e) {
print(" makePurchase error ${e.toString()}");
}
}
購入の復元
iosの場合は、購入の復元(以前の購入履歴を復元する)を実装することが必要になります。(Appleの審査で必要)
自分の場合は、以下のように実装しました。
Future<void> restorePurchase(String entitlement) async {
try {
CustomerInfo customerInfo = await Purchases.restorePurchases();
final isActive = await updatePurchases(customerInfo, entitlement);
if (!isActive) {
print("購入情報なし");
} else {
await getPurchaserInfo(customerInfo);
print("${entitlement} 購入情報あり 復元する");
}
} on PlatformException catch (e) {
print("purchase repo restorePurchase error ${e.toString()}");
}
}
なお、以下のようにしていますが、購入情報がない場合には、その旨をUI側でエラーメッセージなどで表示するのが良いかと思います。
if (!isActive) {
print("購入情報なし");
}
コード全文は以下です。
final inAppPurchaseManager =
ChangeNotifierProvider((ref) => InAppPurchaseManager());
class InAppPurchaseManager with ChangeNotifier {
bool isSubscribed = false;
late Offerings offerings;
FirebaseAuth auth = FirebaseAuth.instance;
Future<void> initInAppPurchase() async {
try {
//consoleにdebug情報を出力する
await Purchases.setDebugLogsEnabled(true);
late PurchasesConfiguration configuration;
if (Platform.isAndroid) {
configuration = PurchasesConfiguration(Android用のRevenuecat APIキー);
} else if (Platform.isIOS) {
configuration = PurchasesConfiguration(ios用のRevenuecat APIキー);
}
await Purchases.configure(configuration);
//offeringsを取ってくる
offerings = await Purchases.getOfferings();
final result = await Purchases.logIn(auth.currentUser!.uid);
await getPurchaserInfo(result.customerInfo);
//今アクティブになっているアイテムは以下のように取得可能
print("アクティブなアイテム ${result.customerInfo.entitlements.active.keys}");
} catch (e) {
print("initInAppPurchase error caught! ${e.toString()}");
}
}
Future<void> getPurchaserInfo(CustomerInfo customerInfo) async {
try {
isSubscribed = await updatePurchases(customerInfo,
monthly_subscription); //monthly_subscriptionは、適宜ご自身のentitlement名に変えてください
} on PlatformException catch (e) {
print(
"getPurchaserInfo error ${PurchasesErrorHelper.getErrorCode(e)
.toString()}");
}
}
Future<bool> updatePurchases(CustomerInfo purchaserInfo,
String entitlement) async {
var isPurchased = false;
final entitlements = purchaserInfo.entitlements.all;
if (entitlements.isEmpty) {
isPurchased = false;
}
if (!entitlements.containsKey(entitlement)) {
///そもそもentitlementが設定されて無い場合
isPurchased = false;
} else if (entitlements[entitlement]!.isActive) {
///設定されていて、activeになっている場合
isPurchased = true;
} else {
isPurchased = false;
}
return isPurchased;
}
Future<void> makePurchase(
String offeringsName) async {
try {
Package? package;
package = offerings.all[offeringsName]?.monthly;
if (package != null) {
await Purchases.logIn(auth.currentUser!.uid);
CustomerInfo customerInfo = await Purchases.purchasePackage(package);
await getPurchaserInfo(customerInfo);
}
} on PlatformException catch (e) {
print("purchase repo makePurchase error ${e.toString()}");
}
}
Future<void> restorePurchase(String entitlement) async {
try {
CustomerInfo customerInfo = await Purchases.restorePurchases();
final isActive = await updatePurchases(customerInfo, entitlement);
if (!isActive) {
print("購入情報なし");
} else {
await getPurchaserInfo(customerInfo);
print("${entitlement} 購入情報あり 復元する");
}
} on PlatformException catch (e) {
print("purchase repo restorePurchase error ${e.toString()}");
}
}
}
なお、実際に課金の動きを確認する際は、ios,Androidの実機で確認する必要があります。iosの場合は、sandboxアカウントにログインした状態で行いましょう。この記事が参考になるかと思います。
Discussion