ランキング有料化の進め方(初回導入ガイド)
現行運用メモ:
- 現在のリリースでは購入導線を停止し、ランキング画面は全ユーザーに開放している
- 本資料は、将来ランキング有料化を再開する際の設計・実装ガイドとして保持する
本資料は、TeamBaseで初めて有料化を導入するための実務ガイドです。
対象要件(今回の確定方針):
- 無料ユーザーに残す範囲
- ランキング画面のチーム成績のみ
- 課金ユーザーのみ閲覧可能にする範囲
- 上記以外のランキング関連情報(部門別ランキング、個人成績詳細、通算成績 など)
1. まず決めること(実装前)
1-1. 無料/有料の境界を固定する
本件では次の境界で固定します。
- 無料: チーム成績
- 有料: それ以外
ポイント:
- 境界は途中で頻繁に変えない(ユーザー混乱防止)
- 課金価値の説明文は「何が見えるようになるか」を明確にする
1-2. プラン情報を定義する
- 商品ID(例):
- iOS:
TeamBase_monthly_premium - Android:
monthly_premium_300
- iOS:
- 価格: 月額300円(税込想定)
- 提供単位: 1ユーザー購読
1-3. KPIを先に決める
最低限、以下を計測します。
ranking_screen_openpremium_cta_viewpremium_cta_tappurchase_startpurchase_successpurchase_restorepremium_feature_view
判断に使う指標:
- 課金転換率(
premium_cta_tap -> purchase_success) - 継続率(1か月/3か月)
- 無料ユーザーの利用維持(ランキング到達率、試合入力率)
2. 実装の進め方(初回向け・安全順)
Step 1: 表示ガードを実装(先にUI境界を完成)
- ランキング画面で、
isPremiumを参照して表示を出し分ける - 無料ユーザー:
- チーム成績のみ表示
- 有料領域にはロック表示 + 課金CTA表示
- 課金ユーザー:
- 全ランキング情報を表示
狙い:
- 課金処理の実装前でも、画面仕様を確定できる
- QAが無料/有料の見え方を早期確認できる
Step 2: 購入・復元フローを実装
in_app_purchaseで商品取得、購入、復元を実装- 失敗時メッセージを実装(通信失敗、キャンセル、未購入)
必須条件:
- 「復元」ボタンを必ず配置
- 購入中の重複タップ防止
2-補足. 「未購入→購入→有料部分が見える」通しテストはできる?
できます。iOS/Android ともに「未購入状態で起動 → アプリ内で購入 → 購入完了 → 有料タブがアンロック」を一連で確認できます。
ただし、TeamBase の現状実装では 購入完了=即アンロック ではなく、
購入完了 → Functions(recordPremiumPurchaseV1)に購入情報を記録 →(必要なら)ユーザーの premium フラグ更新 → 画面の premium 判定が切り替わる、という流れになります。
事前条件(共通)
- アプリにログインできる(
teamIdが claims で取れる) - Functions
recordPremiumPurchaseV1がデプロイされている - iOS 商品ID
TeamBase_monthly_premiumが App Store Connect(または StoreKit テスト設定)に存在する - Android 商品ID
monthly_premium_300が Play Console に存在する
「購入したら即アンロック」をテスト環境で成立させる(重要)
Functions の recordPremiumPurchaseV1 は、環境変数 PREMIUM_TRUST_CLIENT_PURCHASE=true のときだけ、
users/{uid} に isPremium/premiumActive/premiumEntitlements.rankings=true をセットして即時アンロックします。
- テスト時:
trueにして動作確認(最短で一連が回せる) - 本番:
trueのまま運用しない(サーバー検証実装へ移行推奨)
設定方法(どれか1つ):
- Firebase Console / Google Cloud Console から、該当 Functions(Cloud Run サービス)に環境変数
PREMIUM_TRUST_CLIENT_PURCHASE=trueを追加 - CLI(例)
gcloud run services list --region us-central1 --project <PROJECT_ID>で対象サービス名を確認gcloud run services update <SERVICE_NAME> --region us-central1 --project <PROJECT_ID> --set-env-vars PREMIUM_TRUST_CLIENT_PURCHASE=true
※ 関数名と Cloud Run のサービス名が一致しない場合があるため、まず services list で確認するのが安全です。
2-補足-1. iOS(StoreKit Testing)での通しテスト手順(最短・おすすめ)
App Store Connect を用意しなくても、ローカルの StoreKit テストで「未購入→購入→復元」を回せます。
- Xcode で iOS プロジェクトを開く(
teambase/ios/Runner.xcworkspace) - StoreKit Configuration(例:
Premium.storekit)を追加し、サブスク商品を作成- Product ID:
TeamBase_monthly_premium - 期間: 1か月
- 価格: 300円相当(テスト値でOK)
- Product ID:
- Scheme 設定
Product > Scheme > Edit Scheme... > Run > Options > StoreKit Configurationで上記.storekitを選択
- iOS Simulator(または実機)で起動
- ランキング画面のロックされたタブで「購入」→シートで購入→完了
- 完了後に有料タブが見えること(アンロック)を確認
- 「未購入状態」に戻したい場合
- Xcode の StoreKit 管理(取引/購入履歴のリセット)でトランザクションをクリアして再確認
復元テスト:
- ロック画面の「復元」ボタンを押し、復元→アンロックされることを確認
2-補足-2. iOS(Sandbox / TestFlight)での通しテスト手順
- App Store Connect でサブスク商品を作成
- Product ID:
TeamBase_monthly_premium
- Product ID:
- Sandbox テスターを作成
- 実機でアプリを起動し、購入シートで Sandbox アカウントを使って購入
- 完了後に有料タブが見えることを確認
注意:
- Sandbox はアカウント/端末に購入状態が残るため、「未購入状態」再現はテスターを分けるのが確実です。
2-補足-3. Android(Play Console 内部テスト)での通しテスト手順
- Play Console でサブスク商品を作成
- 商品ID:
monthly_premium_300 - ベースプラン: 1か月 / 300円
- 商品ID:
- 内部テストトラックにビルドを上げ、テスターに配布
- テスト端末で Play 経由でインストール(Play 以外のインストールだと課金テストできないことがあります)
- アプリで「購入」→購入完了→有料タブが見えることを確認
注意:
- Android も購入状態が端末/アカウントに残るので、「未購入状態」再現はテスト用 Google アカウントを分けるのが確実です。
Step 3: サーバー側で購読状態を確定
- Firebase Functionsでレシート/トークン検証(推奨)
premiumEntitlements相当の購読状態を保存- 期限切れ・返金・解約を反映
狙い:
- クライアント改ざん耐性を確保
- 複数端末でも正しい課金状態を維持
Step 4: 計測イベントを埋め込む
- 画面表示、CTAタップ、購入開始/成功/復元、課金機能閲覧を送信
- リリース前にイベントが実際に飛ぶか確認
Step 5: ストア審査準備
- 課金説明文(何が有料か)を審査文言に明記
- サブスク規約、復元導線、サポート導線を確認
3. iOS / Google Play の課金登録手順(初回向け)
3-1. iOS(App Store Connect)
- App Store Connectで対象アプリを開く
- 「サブスクリプション」を作成
- 参照名: TeamBase プレミアム月額
- Product ID:
TeamBase_monthly_premium(iOS アプリ実装と一致させる) - 期間: 1か月
- 価格: 300円帯を選択
- ローカライズ情報を入力
- 表示名
- 説明文(無料範囲: チーム成績のみ、有料範囲: それ以外)
- サブスクリプショングループを作成/紐付け
- 将来プラン追加時のアップグレード/ダウングレード管理に必要
- 審査提出に必要な情報を入力
- スクリーンショット
- 審査メモ(有料機能の場所、復元導線の場所)
- Sandboxテスターを作成し、iOS実機/Simulatorで購入・復元を確認
注意点:
- Product IDは一度運用すると変更しにくいため、命名を固定する
- 「復元」導線がないと審査で指摘されやすい
- 課金対象の説明文は、アプリ内表示とストア表示で矛盾させない
3-2. Google Play(Play Console)
- Play Consoleで対象アプリを開く
- 「収益化 > 商品 > 定期購入」を作成
- 商品ID:
monthly_premium_300(Android アプリ実装と一致させる) - 名前/説明: iOSと同じ方針の文言にする
- 商品ID:
- ベースプランを作成
- 期間: 1か月
- 価格: 300円
- 自動更新を有効
- (必要に応じて)オファーを設定
- 初回導入時は構成を簡単にするためオファーなし推奨
- 内部テストトラックで課金テスト
- ライセンステスターを設定
- 購入、解約、復元相当動作を確認
注意点:
- 商品IDはFlutter実装で取得するIDと完全一致させる
- 公開トラック前に内部テストでステータス反映遅延を確認する
- 返金/解約時の状態遷移をサーバー側で扱えるようにする
3-3. 両ストア共通チェック
- 各ストアでアプリ実装と一致する商品IDを採用する
- iOS:
TeamBase_monthly_premium - Android:
monthly_premium_300
- iOS:
- 無料/有料境界の文言を統一
- プライバシーポリシー、利用規約、問い合わせ先を最新化
- 購入復元導線を画面内に配置
4. 画面仕様(今回要件に合わせた最小構成)
無料ユーザー
- 表示する
- チーム成績
- 表示しない(ロック)
- 部門別ランキング
- 個人成績詳細
- 通算成績
- 代替表示
- 「この機能はプレミアムで利用できます」
- 月額プランCTA
課金ユーザー
- 全ランキング関連機能を表示
5. リスクと対策(初回導入で重要)
リスク1: 無料ユーザー離脱
- 対策
- チーム成績は従来どおり無料で維持
- ロック画面の文言を簡潔にし、押し売り感を減らす
リスク2: 課金状態の不整合
- 対策
- サーバー検証を入れる
- 購入復元を目立つ位置に置く
リスク3: ストア審査差し戻し
- 対策
- 有料/無料境界を説明文に明記
- 利用規約・プライバシーポリシー・問い合わせ導線を揃える
6. リリース手順(推奨)
- 内部テスト版
- 無料/有料出し分けのUI確認
- 購入/復元の動作確認
- 限定公開(小規模)
- KPIの初期値確認(1〜2週間)
- 本番展開
- 課金転換率と離脱率を毎週レビュー
7. 受け入れチェックリスト
- 無料ユーザーはチーム成績のみ閲覧できる
- 無料ユーザーから有料対象情報が直接見えない
- 課金ユーザーは対象機能をすべて閲覧できる
- 購入・復元・失敗時表示が動作する
- 課金状態が再起動/再ログイン後も保持される
- KPIイベントが送信される
- ストア審査向け説明が準備済み
8. 最初の1か月運用ルール
- 週次で次を確認
- 転換率
- 解約/継続率
- 無料ユーザーの入力率低下有無
- 1か月目は「価格変更」より先に「導線文言改善」を優先
- 仕様変更は1回につき1点に絞り、効果検証しやすくする
9. 一部ユーザーを無料にするプロモーションコード運用
「一部のユーザーだけ無料で使えるようにしたい」場合、進め方は大きく2通りあります。
9-1. ストア側のプロモーションコードを使う
対象:
- App Store Connect / Play Console で配布するコードを使いたい場合
- ユーザーにストア経由の購読履歴を残したい場合
向いているケース:
- 期間限定キャンペーン
- 初月無料や一定期間の無償利用
- ストア審査や売上管理をストア側に寄せたい場合
流れ:
- iOS は
TeamBase_monthly_premium、Android はmonthly_premium_300のサブスク商品を作成済みにする - ストア管理画面でプロモーションコード(Offer Code / Promo Code)を発行する
- 対象ユーザーにコードを配布する
- ユーザーはストア側でコードを引き換える
- アプリ起動後、購入/復元相当の処理で entitlement を反映する
注意:
- iOS と Android でコードの種類、発行上限、引き換え導線が異なる
- ストア側コードだけでは TeamBase 内で「誰に配ったか」を細かく管理しづらい
- 将来 Web や管理画面からも同じ仕組みを使いたい場合は拡張しにくい
9-2. TeamBase 独自のプロモーションコードを作る(推奨)
対象:
- 特定ユーザーだけ無料にしたい場合
- ストアを通さず、運営判断で premium を付与したい場合
- iOS / Android で同じ運用にしたい場合
向いているケース:
- 初期導入チームへの無償提供
- 紹介キャンペーン
- 不具合補償・手動救済
- インフルエンサーやテストユーザーへの招待
推奨理由:
- 既存の premium 判定は
users/{uid}のisPremium/premiumActive/premiumEntitlements.rankingsを見ているため、購読と同じアンロック経路を再利用できる - 既存の Functions にあるハッシュ化コード運用(recovery code)と premium 付与ロジックを流用しやすい
9-3. TeamBase 独自コードの実装手順(推奨)
Step 1: コードのルールを決める
- コード文字列: 例
TBPREM-XXXX-XXXX - 有効期限: 例 30日 / 90日 / 無期限
- 利用回数: 1回限り / 先着N人
- 付与内容:
- ランキングのみ解放
- premium 全体を解放
- 付与期間:
- 30日
- 90日
- 無期限
最初は次の最小仕様が安全です。
- 1コード = 1ユーザー
- 付与対象 = ランキング premium のみ
- 有効期限あり
- 管理者だけ発行可能
Step 2: Firestore にコード管理用コレクションを作る
例:
teams/{teamId}/promoCodes/{codeId}
保持項目例:
codeHashkind: 'rankings_premium'durationDays: 30maxRedemptions: 1redeemedCount: 0expiresAtcreatedBycreatedAtdisabled: false
平文コードは原則 Firestore に保存せず、team recovery code と同様にハッシュで検証する運用が安全です。
Step 3: Functions を2本追加する
追加候補:
issuePromoCodeV1- 管理者のみ実行可
- ランダムコードを生成
- ハッシュを保存
- 平文コードは発行時のレスポンスで1回だけ返す
redeemPromoCodeV1- ログイン済みユーザーが実行
- コード照合、有効期限、使用回数、チーム整合性を確認
- 問題なければ
users/{uid}に premium を付与
付与時に更新するフィールド例:
isPremium: truepremiumActive: truepremiumEntitlements.rankings: truepremiumSource: 'promo_code'premiumPromoCodeIdpremiumExpiresAt(期限付きにするなら必須)premiumUpdatedAt
Step 4: 失効処理を決める
期限付きコードを配るなら、次のどちらかが必要です。
- 期限チェックを
premiumEnabledProviderの元データに反映するよう、Functions / バッチでpremiumActiveを落とす - もしくは premium 判定を「フラグ + 期限」で見るように拡張する
初回導入では、定期ジョブで期限切れユーザーの premiumActive を落とす方式が分かりやすいです。
Step 5: 管理UI / 入力UI を追加する
- 管理者向け
- コード発行画面
- 発行済みコード一覧
- 使用済み / 失効 / 無効化 状態の確認
- ユーザー向け
- 「プロモーションコードを入力」ボタン
- コード入力ダイアログ
- 成功 / 期限切れ / 使用済み / 無効 の表示
Step 6: 監査ログを残す
issue_promo_coderedeem_promo_codedisable_promo_codeexpire_promo_premium
購入と同様に audit log を残しておくと、問い合わせ時の追跡がしやすくなります。
9-4. 実運用のおすすめ方針
今回の TeamBase では、次の住み分けが現実的です。
- 通常ユーザー: ストア課金
- 無償提供したい一部ユーザー: TeamBase 独自プロモーションコード
この形にすると、通常課金フローはそのまま維持しつつ、例外的な無料付与だけを安全に扱えます。
9-5. 初回導入で避けたほうがよい構成
- ストアコードだけで全プラットフォーム共通運用しようとする
- クライアントから直接
users/{uid}を更新して premium を付ける - 無期限コードを大量配布して管理不能にする
- 使用履歴を残さない
初回は次の最小構成が安全です。
- 期限付き
- 1回限り
- 管理者発行
- Functions 経由でのみ redeem 可能
- Firestore に使用履歴を保存
9-6. 今回の暫定運用(確定)
当面は管理UIを作らず、共通の固定コードを1つだけ運用します。
- コード:
code123 - 利用回数: 無制限
- 利用ユーザー数: 無制限
- 有効期限: なし
- 解放範囲: 課金対象の premium 機能をすべて閲覧可能にする
実装方針:
- アプリ側に「プロモーションコード入力」導線を置く
- Functions でコード文字列を検証する
- 成功時は通常の premium と同じ
users/{uid}の premium フラグを更新する
この構成なら、エミュレーターでも「premium 会員になった後の画面挙動」をストア課金に近い形で確認できます。
9-7. 今後追加したい有料機能メモ
今後は、年度単位の一覧発行機能も premium 対象に追加する想定です。
候補:
- チーム成績の年度別一覧を発行
- 選手ごとの年度別成績表を発行
配置候補:
- チーム成績ページ
方針:
- まずはランキング閲覧の有料化を安定化
- その後、一覧発行機能を別の premium entitlement として追加検討