Open7

【Flutter アプリ内課金】 RevenueCat の導入に役に立った資料

kotapjpkotapjp

公式資料

RevenueCat

実際のコードサンプル↓
https://www.revenuecat.com/docs/configuring-sdk

ライブラリのアップデートで2,3 年前の資料と書き方が異なって新しくなっているので公式ドキュメントのサンプルにも目を通すのが良さそう

Apple

https://developer.apple.com/documentation/storekit/in-app_purchase

Zennなど

買い切りのアイテム↓
https://zenn.dev/sakutech/articles/revenuecat-flutter-in-app-purchase

サブスクのアイテム↓
https://zenn.dev/moga/books/flutter_revenuecat/viewer/2-about-revenuecat

https://zenn.dev/hal1986/books/react-native-monetize/viewer/7_revenuecat_products

kotapjpkotapjp

実際に書いたコード

class IAPService {
// 初期化する関数
  static Future<void> initPlatformState() async {
    await Purchases.setDebugLogsEnabled(kDebugMode);

    PurchasesConfiguration? configuration;
    if (Platform.isAndroid) {
      configuration = PurchasesConfiguration("API_KEY");
    } else if (Platform.isIOS) {
      configuration =
          PurchasesConfiguration("API_KEY);
    }
    if (configuration == null) {
      return;
    }
    await Purchases.configure(configuration);
  }

  static Future<bool> isPremium() async {
    final customerInfo = await Purchases.getCustomerInfo();
    return customerInfo.entitlements.all[prevenueCatEntitlementID]?.isActive ??
        false;
  }

  static Future<void> purchaseCustomizeDice() async {
    try {
      Offerings offerings = await Purchases.getOfferings();
      if (offerings.current == null) {
        debugPrint("Offering が見つかりませんでした。");
        return;
      }
      final offering = offerings.current;
      if (offering == null) {
        debugPrint("Offering.current が見つかりませんでした。");
        return;
      }
      final custimezeDice = offering.getPackage(revenueCatPackageID);
      if (custimezeDice == null) {
        debugPrint('$revenueCatPackageIDが見つかりませんでした。');
        return;
      }

      CustomerInfo customerInfo =
          await Purchases.purchasePackage(package);
      if (customerInfo.entitlements.all[prevenueCatEntitlementID]?.isActive ??
          false) {
    // 購入情報の永続化
              :
      }
// Analyticsにイベント送信
 :

    } on PlatformException catch (e) {
      var errorCode = PurchasesErrorHelper.getErrorCode(e);
      if (errorCode == PurchasesErrorCode.purchaseCancelledError) {
        return;
      }
      debugPrint(e.toString());
      rethrow;
    }
  }

  // resotre
  static Future<void> restore() async {
    try {
      CustomerInfo customerInfo = await Purchases.restorePurchases();
      // ... check restored purchaserInfo to see if entitlement is now active
      if (customerInfo.entitlements.all[prevenueCatEntitlementID]?.isActive ??
          false) {
          // 購入情報の永続化
      }
     // Analyticsにイベント送信
    } on PlatformException catch (e) {
      var errorCode = PurchasesErrorHelper.getErrorCode(e);
      if (errorCode == PurchasesErrorCode.purchaseCancelledError) {
        // エラー処理
        return;
      }
      rethrow;
    }
  }
}
kotapjpkotapjp

トラブルシューティングの参考資料

以下のエラーに悩まされた

flutter: PlatformException(23, There is an issue with your configuration. Check the underlying error for more details. There's a problem with your configuration. None of the products registered in the RevenueCat dashboard could be fetched from App Store Connect (or the StoreKit Configuration file if one is being used).

公式

Why are offerings or products empty?
https://community.revenuecat.com/sdks-51/why-are-offerings-or-products-empty-124

Zenn など

kotapjpkotapjp

ライブラリのアップデートで2,3 年前の資料と書き方が異なって新しくなっているので公式ドキュメントのサンプルにも目を通すのが良さそう

[Flutter]アプリ内課金管理ツール「RevenueCat」4.0での破壊的変更について
https://minpro.net/revenuecat4-0-breaking-changes

kotapjpkotapjp

await Purchases.setDebugLogsEnabled(kDebugMode);

は廃止されている関数で推奨されなかった

await Purchases.setLogLevel(LogLevel.debug);

でよかった
ただしデフォルトでdebug なので明示的に書かなくてもOKかなという判断

kotapjpkotapjp

listen して購入後に状態を検知するコード

  Future<void> _initPurchaseState() async {
    Purchases.addCustomerInfoUpdateListener((customerInfo) async {
      CustomerInfo customerInfo = await Purchases.getCustomerInfo();
      EntitlementInfo? removeAdsEntitlement =
          customerInfo.entitlements.all[removeAdsEntitlementID];

      setState(() {
        isInterstitialAdsRemoved = removeAdsEntitlement?.isActive ?? false;
      });

      SharedPreferencesHelper.setHasRemoveInterstitialAds(
          removeAdsEntitlement?.isActive ?? false);

      setState(() {});
    });
  }```