FlutterアプリにRevenueCatで買い切りIAPを実装する
個人開発のFlutterアプリに RevenueCat を使って買い切り型のアプリ内課金(IAP)を実装しました。
この記事では、基本的なセットアップから無料版の制限ロジック、ペイウォール設計、App Storeガイドライン対応まで一通りまとめます。
環境
- Flutter 3.x
- purchases_flutter(RevenueCat SDK)
- Riverpod(状態管理)
- Flavor: dev / prod
なぜ RevenueCat か
iOS・Android 両対応の IAPを自前で実装すると、StoreKit / BillingClient それぞれのハンドリングが必要になります。RevenueCatを使うと:
- iOS・Androidを統一APIで扱える
- エンタイトルメント(購入済み判定)の管理が楽
- ダッシュボードで売上・コンバージョンを確認できる
- クライアントサイドのAPIキーなので、ソースコードに含めても問題ない
セットアップ
pubspec.yaml
dependencies:
purchases_flutter: ^8.x.x
Flavor別にAPIキーを設定
dev と prod でRevenueCatのプロジェクトを分けるのがおすすめです。
// flavor.dart
class FlavorConfig {
static String get revenueCatApiKey {
switch (flavor) {
case Flavor.dev:
return 'appl_xxxxx_dev'; // RevenueCat dev project
case Flavor.prod:
return Platform.isIOS
? 'appl_xxxxxxxxxxxxxxxxxxxxxxxx'
: 'goog_xxxxxxxxxxxxxxxxxxxxxxxx';
}
}
}
初期化
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Purchases.configure(
PurchasesConfiguration(FlavorConfig.revenueCatApiKey),
);
runApp(const MyApp());
}
Pro判定プロバイダー(Riverpod)
購入状態をアプリ全体で参照するために isProProvider を作ります。
// purchase_provider.dart
final isProProvider = FutureProvider<bool>((ref) async {
final customerInfo = await Purchases.getCustomerInfo();
return customerInfo.entitlements.active.containsKey('Pro');
});
エンタイトルメントIDは RevenueCat ダッシュボードで設定したものに合わせます(例:Pro、単価いくら Pro など)。
無料版の制限ロジック
無料ユーザーに対して機能を制限する例です。
// folder_provider.dart
const int freeFolderLimit = 3;
const int freeHistoryLimit = 50;
Future<bool> addFolder(String name) async {
final isPro = ref.read(isProProvider).value ?? false;
if (!isPro && state.length >= freeFolderLimit) {
// ペイウォールを表示するなどのハンドリング
return false;
}
// フォルダ追加処理
...
return true;
}
テストでは Riverpod の override で isProProvider をモックします。
// test
final container = ProviderContainer(
overrides: [
isProProvider.overrideWith((ref) => AsyncValue.data(true)), // Pro状態で検証
],
);
ペイウォール画面の設計
App Storeガイドライン上の注意点
ペイウォールを実装する際、スキップ可能にすることが重要です。スキップボタンなしの強制ペイウォールはApp Storeレビューで指摘・リジェクトされる可能性があります。
┌─────────────────────────┐
│ ⭐ Pro にアップグレード │
│ │
│ ✓ 機能A │
│ ✓ 機能B │
│ ✓ 広告・ウォーターマーク除去│
│ │
│ ┌──────────────────┐ │
│ │ ¥980 で購入 │ │
│ └──────────────────┘ │
│ 購入を復元 │
│ │
│ 無料で始める → │ ← スキップ必須
└─────────────────────────┘
価格の表示
RevenueCat からローカライズ済みの価格文字列を取得できます。ハードコードは不要です。
// paywall_screen.dart
final offerings = await Purchases.getOfferings();
final package = offerings.current?.lifetime;
if (package != null) {
final price = package.storeProduct.priceString; // "¥980" や "$4.99" など自動取得
}
ユーザーのストア設定(国・通貨)に応じて自動的に正しい通貨で表示されます。
購入処理
Future<void> purchase(Package package) async {
try {
final customerInfo = await Purchases.purchasePackage(package);
final isPro = customerInfo.entitlements.active.containsKey('Pro');
if (isPro) {
// 購入成功
}
} on PlatformException catch (e) {
final errorCode = PurchasesErrorHelper.getErrorCode(e);
if (errorCode == PurchasesErrorCode.purchaseCancelledError) {
// ユーザーキャンセル(エラー表示不要)
} else {
// エラー表示
}
}
}
購入の復元
App Storeガイドラインでは、消耗品以外のIAPには購入を復元するボタンを設置することが求められています。
Future<void> restore() async {
final customerInfo = await Purchases.restorePurchases();
final isPro = customerInfo.entitlements.active.containsKey('Pro');
// isPro を元に UI を更新
}
Simulatorで価格を確認する
Simulatorでは RevenueCat の価格取得時にデフォルトでUS(ドル表示)になります。
日本円(¥)で確認したい場合:
Xcode > Product > Scheme > Edit Scheme > Options > StoreKit Configuration
を設定して、Storefrontを Japan (JPY) に変更します。
ただし、実機のSandboxアカウント(日本アカウント)で確認するのが一番確実です。
オンボーディングへの導線
onboarding フローの末尾にペイウォール画面を挟むのが定番です。
Value(機能紹介)→ 権限取得 → Pro upsell → ホーム
この時も「無料で始める」ボタンを目立つ位置に置いて、スキップを容易にしておきます。
まとめ
| 項目 | ポイント |
|---|---|
| APIキー | Flavor別に分けて直接記載でOK(クライアントキー) |
| Pro判定 | entitlements.active.containsKey で確認 |
| 価格表示 | storeProduct.priceString でローカライズ自動対応 |
| ペイウォール | スキップ可能にしないとリジェクトリスクあり |
| 復元ボタン | 必須(App Storeガイドライン) |
| テスト | Riverpod の override でPro状態をモック |
RevenueCatを使うと、クロスプラットフォームのIAP実装が大幅に楽になります。特に「購入済み判定」をエンタイトルメントとして扱える点が、自前実装に比べて大きなメリットです。