05. 広告と課金を実装し、審査を通してリリースする
本番ビルド、開発者アカウント、そして魅力的なストア掲載情報がすべて揃いました。
いよいよ、公開&マネタイズ編(Phase06)の最終ステップです。この記事では、収益化の実装(広告とアプリ内課金)から、その審査要件をクリアして「審査提出」ボタンを押すまでを、ひと続きの作業として扱います。
課金の実装と審査対応を分けて考えると、「作ったあとに必須要素が足りずリジェクトされる」という手戻りが必ず起きます。審査で求められるものを知った上で実装するのが、結局いちばん速い順路です。
この記事のサブコンテンツ
Section titled “この記事のサブコンテンツ”- Google AdMob広告の組み込みとiOS ATT対応
- 自前実装ではなくRevenueCatを使う理由
- ストア側の課金アイテム登録とEntitlement設計
- RevenueCat SDKの導入とペイウォールの実装
- 課金機能の必須審査要件と審査用メモの書き方
- 審査提出とリジェクトされたときの対応
- 公開とマネタイズ編の総まとめと次のフェーズへの橋渡し
1. Google AdMob広告の組み込みとiOS ATT対応
Section titled “1. Google AdMob広告の組み込みとiOS ATT対応”広告を導入する場合は、SDK設定、同意の取得、ストアへのデータ申告を揃えて確認します。
App Tracking Transparency (ATT) の対応
Section titled “App Tracking Transparency (ATT) の対応”iOS 14.5以降、広告トラッキングを行う場合はユーザーに明示的な許諾ダイアログを表示する必要があります。
app.json に許可メッセージを定義します。
{ "expo": { "ios": { "infoPlist": { "NSUserTrackingUsageDescription": "適切な広告を表示し、無料でのアプリ提供を継続するために利用状況の収集を許可してください。" } } }}ダイアログはアプリ起動直後ではなく、最初の習慣を登録したタイミングなど、ユーザーがアプリの価値を少し理解した後に表示するのが承諾率を高めるコツです。
NSUserTrackingUsageDescription を書くだけではダイアログは表示されません。expo-tracking-transparency などを使って適切なタイミングで権限を要求します。拒否時にも基本機能を利用できるようにします。
AdMob本体は react-native-google-mobile-ads の導入、config pluginへの両OSのAdMobアプリID設定、初期化が別途必要です。ライブラリのExpo導入手順に従って開発ビルドを作り直してください。広告への同意が必要な地域の対応はGoogle UMPの導入案内で確認します。ATTと広告同意は同じ仕組みではありません。
2. 自前実装ではなくRevenueCatを使う理由
Section titled “2. 自前実装ではなくRevenueCatを使う理由”アプリ開発者が夢見るゴールのひとつが、毎月安定して発生するサブスクリプション収益です。
しかし、iOSのStoreKitとAndroidのGoogle Play Billingは、仕様、レシート検証、解約・更新通知の処理がそれぞれ異なり、自前でサーバーを立てて連携するのは開発者にとって最大の悪夢でした。
AppleとGoogleの課金システムには、次のような無数の落とし穴があります。
- レシート(購入証明)の暗号署名検証をサーバーサイドで常に行う必要がある
- クレジットカード決済の失敗、猶予期間(Grace Period)、プラン変更(アップグレード / ダウングレード)の追跡
- ユーザーが別端末に乗り換えた時の「購入の復元」処理
その複雑さをすべて解決し、現在のモバイルアプリ開発でデファクトスタンダードとなっているのが RevenueCat です。これらのバックエンド処理、Webhook、レシート検証をすべてクラウド側で肩代わりしてくれます。RevenueCatの料金表では、月間追跡売上(MTR)2,500米ドルまでの無料枠が案内されています(2026年9月8日確認)。アプリ全体の売上や手取り額とは定義が異なるため、対象となる売上と超過後の料金を確認してください。
3. ストア側の課金アイテム登録とEntitlement設計
Section titled “3. ストア側の課金アイテム登録とEntitlement設計”ストア管理画面でプロダクトを作る
Section titled “ストア管理画面でプロダクトを作る”まずは各ストア管理画面で課金プロダクトを作成します。
- プロダクトIDの命名規則:
com.yourname.app.premium.monthlyのように命名します。 - タイプ:
- 自動更新サブスクリプション: 例「HabitFlow Pro(月額350円 / 年額3,000円)」
- 非消耗型アイテム(Non-Consumable): 例「広告非表示(買い切り500円)」
前提として、Appleの有料アプリ契約とGoogleの支払いプロファイルが有効になっている必要があります。広告だけのアプリには不要だった手続きで、契約への同意、銀行口座の登録、税務情報の提出(日本の情報に加えて米国向けの申告フォーム)が含まれます。本人確認に数日かかることがあるので、実装より先に着手しておくのが得です。
RevenueCatでEntitlement(権利)を設計する
Section titled “RevenueCatでEntitlement(権利)を設計する”RevenueCatでは、App StoreのプロダクトとGoogle Playのプロダクトを、1つの抽象的な権利(Entitlement)にまとめます。
- Entitlement Identifier:
pro - Attached Products:
- Apple:
com.yourname.habitflow.pro.monthly - Google:
habitflow_pro_monthly
- Apple:
これにより、コード上では「このユーザーは pro を持っているか」だけを判定すればよく、OSの違いを意識する必要が一切なくなります。
4. RevenueCat SDKの導入とペイウォールの実装
Section titled “4. RevenueCat SDKの導入とペイウォールの実装”SDKの導入と初期化
Section titled “SDKの導入と初期化”Expoプロジェクトに RevenueCat のパッケージをインストールします。
npx expo install react-native-purchasesアプリの起動時(app/_layout.tsx)で初期化します。
import { useEffect, useState } from 'react';import { Platform } from 'react-native';import { Slot } from 'expo-router';import Purchases, { LOG_LEVEL } from 'react-native-purchases';
const APIKeys = { apple: process.env.EXPO_PUBLIC_REVENUECAT_APPLE_KEY || '', google: process.env.EXPO_PUBLIC_REVENUECAT_GOOGLE_KEY || '',};
export default function RootLayout() { const [ready, setReady] = useState(false);
useEffect(() => { Purchases.setLogLevel(__DEV__ ? LOG_LEVEL.DEBUG : LOG_LEVEL.ERROR);
if (Platform.OS === 'ios') { Purchases.configure({ apiKey: APIKeys.apple }); } else if (Platform.OS === 'android') { Purchases.configure({ apiKey: APIKeys.google }); } setReady(true); }, []);
return ready ? <Slot /> : null;}公開SDKキーを両OSに設定し、SDK初期化が完了してから購入画面を開きます。Webの購入はこの例の対象外です。既存のルートProviderと統合し、アプリのユーザーアカウントを使う場合はRevenueCatのApp User IDの連携方針も決めてください。
ペイウォール(購入画面)を実装する
Section titled “ペイウォール(購入画面)を実装する”以下は月額プランだけを表示するモーダル画面の例です。年額や買い切りを追加する場合は、商品ごとに期間と更新条件を表示してください。次の節で解説する審査必須要素(復元ボタン・利用規約リンク・期間と価格の明示)を、最初から組み込んだ形にしてあります。
import React, { useEffect, useState } from 'react';import { View, Text, TouchableOpacity, Alert, StyleSheet, Linking } from 'react-native';import Purchases, { PurchasesPackage } from 'react-native-purchases';
export function PaywallModal({ onClose }: { onClose: () => void }) { const [loading, setLoading] = useState(false); const [packages, setPackages] = useState<PurchasesPackage[]>([]);
useEffect(() => { Purchases.getOfferings() .then((offerings) => setPackages( (offerings.current?.availablePackages ?? []).filter((pkg) => pkg.packageType === 'MONTHLY') )) .catch(() => setPackages([])); }, []);
const handlePurchase = async (pkg: PurchasesPackage) => { try { setLoading(true); const { customerInfo } = await Purchases.purchasePackage(pkg); if (customerInfo.entitlements.active['pro'] !== undefined) { Alert.alert('ありがとうございます!', 'プレミアム機能がアンロックされました。'); onClose(); } } catch (e: any) { if (!e.userCancelled) { Alert.alert('エラー', e.message); } } finally { setLoading(false); } };
const handleRestore = async () => { setLoading(true); try { const customerInfo = await Purchases.restorePurchases(); if (customerInfo.entitlements.active['pro'] !== undefined) { Alert.alert('復元完了', '過去の購入履歴が復元されました。'); onClose(); } else { Alert.alert('お知らせ', '有効な購入履歴が見つかりませんでした。'); } } catch (e: any) { Alert.alert('エラー', e.message); } finally { setLoading(false); } };
return ( <View style={styles.container}> <Text style={styles.title}>HabitFlow Pro</Text> <Text style={styles.subtitle}>無制限の習慣登録とAIコーチによるアドバイス</Text>
{/* 購入ボタン。offerings から取得した package を渡す */} {packages.map((pkg) => ( <TouchableOpacity key={pkg.identifier} onPress={() => handlePurchase(pkg)} disabled={loading} style={[styles.buyBtn, loading && styles.buyBtnDisabled]} activeOpacity={0.7} > <Text style={styles.buyText}> {pkg.product.title} {pkg.product.priceString} / 月 </Text> </TouchableOpacity> ))}
{/* 審査に必須:期間と価格の明示 */} <Text style={styles.terms}> 月額サブスクリプションです。期間終了時に自動更新され、いつでも解約できます。 </Text>
{/* 審査に必須の「購入の復元」ボタン */} <TouchableOpacity onPress={handleRestore} disabled={loading} style={styles.restoreBtn}> <Text style={styles.restoreText}>以前の購入を復元する(Restore Purchases)</Text> </TouchableOpacity>
{/* 審査に必須:利用規約とプライバシーポリシーへのリンク */} <View style={styles.linkRow}> <TouchableOpacity onPress={() => Linking.openURL('https://example.com/terms')}> <Text style={styles.linkText}>利用規約(EULA)</Text> </TouchableOpacity> <Text style={styles.linkSep}>・</Text> <TouchableOpacity onPress={() => Linking.openURL('https://example.com/privacy')}> <Text style={styles.linkText}>プライバシーポリシー</Text> </TouchableOpacity> </View> </View> );}
const styles = StyleSheet.create({ container: { padding: 24, alignItems: 'center' }, title: { fontSize: 24, fontWeight: 'bold', color: '#111827' }, subtitle: { marginTop: 8, color: '#6B7280', textAlign: 'center' }, buyBtn: { marginTop: 24, paddingVertical: 14, paddingHorizontal: 32, borderRadius: 12, backgroundColor: '#10B981', }, buyBtnDisabled: { opacity: 0.5 }, buyText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600' }, terms: { marginTop: 12, fontSize: 12, color: '#6B7280', textAlign: 'center' }, restoreBtn: { marginTop: 20 }, restoreText: { color: '#10B981', textDecorationLine: 'underline', fontSize: 13 }, linkRow: { flexDirection: 'row', marginTop: 16, alignItems: 'center' }, linkSep: { marginHorizontal: 6, color: '#6B7280' }, linkText: { color: '#6B7280', textDecorationLine: 'underline', fontSize: 12 },});課金ステータスを判定する
Section titled “課金ステータスを判定する”ユーザーが有料会員かどうかを判定するカスタムフック(useSubscription)を作ります。
import { useState, useEffect } from 'react';import Purchases from 'react-native-purchases';
export function useSubscription() { const [isPro, setIsPro] = useState<boolean | null>(null); const [loading, setLoading] = useState(true);
useEffect(() => { const checkProStatus = async () => { try { const customerInfo = await Purchases.getCustomerInfo(); setIsPro(customerInfo.entitlements.active['pro'] !== undefined); } catch (e) { setIsPro(null); // 取得失敗を「未購入」と断定しない } finally { setLoading(false); } };
checkProStatus();
// SDKがCustomerInfoを更新したときに反映する(サーバーからの常時Pushではない) const listener: Parameters<typeof Purchases.addCustomerInfoUpdateListener>[0] = (info) => { setIsPro(info.entitlements.active['pro'] !== undefined); }; Purchases.addCustomerInfoUpdateListener(listener); return () => Purchases.removeCustomerInfoUpdateListener(listener); }, []);
return { isPro, loading };}購入状態はProviderなどで共有し、loading と isPro を区別して使います。広告のリクエストは !loading && isPro === false の場合に限定し、取得中や取得失敗(null)を未購入として扱わないでください。アプリが前面に戻った時などにも getCustomerInfo() で状態を確認します。AIなどサーバーで提供する有料機能の権利は、サーバー側でも検証します。
広告の分岐は「表示しない」ではなく「要求しない」位置に置いてください。権利があれば広告のリクエスト自体が起きない形にしておくと、課金したユーザーの端末で一瞬だけ広告枠が見える、といったことがなくなります。
無料トライアルとプロモーションコード
Section titled “無料トライアルとプロモーションコード”初期のユーザー獲得には「最初の2週間無料トライアル」が絶大な威力を発揮します。
StoreKitおよびGoogle Play Console側で「無料試用(Free Trial)」を設定しておくだけで、RevenueCatの購入フローが自動的に「トライアル付き購入」として処理してくれます。リスクなく課金体験を試してもらうことで、有料転換率を最大化できます。
5. 課金機能の必須審査要件と審査用メモの書き方
Section titled “5. 課金機能の必須審査要件と審査用メモの書き方”ペイウォールに必須の4要素
Section titled “ペイウォールに必須の4要素”アプリ内課金(広告削除やサブスクリプション)を導入する場合、Appleのガイドライン(Guideline 3.1.1)により、購入画面に以下の要素が視覚的に明記されていることが絶対条件です。
- 購入の復元(Restore Purchases)ボタン: 端末変更時や再インストール時に再課金させないためのボタン。タップ時に復元処理が走る必要があります。
- 利用規約(Terms of Service / EULA)へのリンク: タップすると規約ページが開くこと。
- プライバシーポリシー(Privacy Policy)へのリンク: タップするとポリシーページが開くこと。
- サブスクリプション期間と価格: 例「月額350円・いつでも解約可能」
これらが欠けているとほぼ確実にリジェクトされるため、購入コンポーネント内に必ず目立つ形で配置しておきます。前節のコードは、この4つを最初から含んだ形になっています。
レビュアーに喜ばれる審査用メモの書き方
Section titled “レビュアーに喜ばれる審査用メモの書き方”App Store Connect の審査提出画面には「App Reviewに関する情報(審査用メモ)」欄があります。
ここに親切な案内を英語で書いておくと、レビュアーが迷わずスムーズに承認してくれます。
【審査用メモの記載テンプレート】Dear App Review Team,
Thank you for taking the time to review HabitFlow.- This app is an offline-first minimalist habit tracker designed with Expo.- Habit records are stored locally in this version. Purchases are handled by the store and RevenueCat.- To test subscriptions, open Settings and select HabitFlow Pro. The paywall includes Restore Purchases and links to the Terms and Privacy Policy.- To test the core features: 1. Tap the '+' button at the bottom right to add a habit (e.g., "Reading"). 2. Tap the checkbox on the habit card to mark it as completed. 3. Navigate to the History tab to review the streak calendar. 4. Navigate to the Settings tab to test data reset.
If you have any questions or need further details, please reach out to:Email: [email protected]6. 審査提出とリジェクトされたときの対応
Section titled “6. 審査提出とリジェクトされたときの対応”提出とステータスの推移
Section titled “提出とステータスの推移”提出前にセキュリティ編を確認し、広告・課金を含む最終ビルドで動作と申告内容を点検してください。審査メモのテンプレートも、実装していない画面・機能の説明は削除し、クラウドやAIを追加した場合は更新します。
すべての項目に緑のチェックがついたら、「審査に追加」 → 「審査に提出」 をクリックします。
- Waiting for Review(審査待ち): 通常数時間〜24時間程度。
- In Review(審査中): レビュアーが実際に操作中(1〜3時間程度)。
- Pending Developer Release(公開待ち): 審査に通っていても、手動リリースを選んだ場合はまだ公開されていません。リリース操作後、ストアで実際に配信されていることを確認します。
もしリジェクトされたら
Section titled “もしリジェクトされたら”「Guideline XX.X に抵触したため却下されました」という通知が届いても、落ち込む必要は全くありません。リジェクトはアプリ開発の日常茶飯事です。
レビュアーから送られてきたメッセージをそのままAIエージェントに貼り付けます。
【プロンプト】Appleの審査チームから以下のリジェクト理由が届きました。ガイドラインの意図を解説し、現在のコードのどこを修正すれば合格できるか具体的なコード修正案を提示してください。また、レビュアーへの丁寧な返信メッセージのドラフト(英語)を作成してください。
[レビュアーからのメッセージ本文]レビュアーへの返信英文テンプレート
Section titled “レビュアーへの返信英文テンプレート”Dear App Review Team,
Thank you for your feedback regarding Guideline [ガイドライン番号].We have updated the app in build [新しいビルド番号] to resolve this issue:- [修正点1: e.g., Added a clear "Restore Purchases" button to the paywall view.]- [修正点2: e.g., Updated privacy links in accordance with the guideline.]
Please review the updated build. Thank you for your support!指摘された箇所をAIと一緒に数十分で直し、新しいビルド番号で再提出すれば、大半は次回ですんなり通過します。
7. 公開とマネタイズ編の総まとめと次のフェーズへの橋渡し
Section titled “7. 公開とマネタイズ編の総まとめと次のフェーズへの橋渡し”おめでとうございます!あなたの手で作られたアプリが、App StoreおよびGoogle Playという世界最高の舞台に並び、誰でもダウンロードできるようになりました!
ここまでの歩みを振り返りましょう。
- ロードマップ: 01. Expoアプリ公開と収益化のロードマップ で全体戦略と収益モデルを確立。
- 本番ビルド: 02. EAS Buildで本番リリース用ビルドを作る で提出用バイナリを自動生成。
- アカウント準備: 03. Apple Developer & Google Play Consoleの準備をする で法規と12人テストを攻略。
- ASO・画像生成: 04. ストア掲載情報とスクリーンショットをAIで作成する で魅力的なショーウィンドウを完成。
- 実装・審査・公開: 05. 広告と課金を実装し、審査を通してリリースする(この記事)で、収益化を組み込み、ついに正式リリースを達成。
次のフェーズ:応用事項編へ
Section titled “次のフェーズ:応用事項編へ”基本のMVPアプリを公開できたあなたは、すでに一流のアプリクリエイターです。
さらに次のステップとして、「複数端末でデータが同期するクラウド連携」「リテンションを高めるPush通知」「Geminiを活用したAIインテリジェント機能」「GA4でアプリの使われ方を計測する仕組み」、そして「リリースのたびに発生するストア更新作業の自動化」など、アプリをさらなる高みへ引き上げる技術に挑戦したくなるはずです。
「PhaseEx:応用事項編」の「01. Firebaseでユーザー認証とクラウド保存を実装する」へ進み、プロフェッショナル級のアプリへと進化させましょう!