Skip to content

fix(#163): 購入レシートのサーバー側検証を追加(Android先行) - #211

Merged
IKcoding-jp merged 1 commit into
mainfrom
fix/#163-server-side-receipt-verification
Jun 17, 2026
Merged

fix(#163): 購入レシートのサーバー側検証を追加(Android先行)#211
IKcoding-jp merged 1 commit into
mainfrom
fix/#163-server-side-receipt-verification

Conversation

@IKcoding-jp

Copy link
Copy Markdown
Owner

Closes #163

背景

issue #163 の核心「購入検証がクライアント側のみ → 改竄でプレミアム不正取得が可能」に対応する。復元タイムアウト時の Completer 放置(対応案#2)と一次検証(PurchaseValidator)は PR #203 で対応済みのため、本PRは**サーバー側レシート検証(対応案#1)とプレミアム判定の一本化(対応案#3)**を実装する。

変更点

サーバー

  • functions/purchase/android_verifier.js — Google Play Developer API での検証ロジック(APIクライアント注入式で純粋・テスト可能)
  • functions/purchase/android_publisher_client.js — googleapis への薄いグルー
  • functions/index.jsverifyPurchase(onCall) を追加。認証+レート制限+Android検証→検証成功時のみ users/{uid}/purchases/premium_entitlement(サーバー専用doc)へ書込。iOSは unimplemented(TODO)
  • firestore.rulespremium_entitlement をクライアント書込禁止(サーバー専用)に変更

クライアント

  • lib/services/purchase/purchase_verifier.dartverifyPurchase を呼ぶ注入可能ラッパー(Firebase初期化前の構築でも落ちないよう instance アクセスを遅延)
  • lib/services/one_time_purchase_service.dart — Android購入はサーバー検証を必須化、プレミアム読込元を premium_entitlement一本化、レガシーフラグのみの既存ユーザーは起動時の自動再検証(restorePurchases)でシームレス移行。iOSは既存挙動を維持。InAppPurchase.instance アクセスも遅延化
  • lib/services/purchase/purchase_persistence.dartloadServerEntitlement を追加

テスト

  • functions/test/purchase/android_verifier.test.js(8件)— 正規/不正token/productID不一致/キャンセル/API失敗 等
  • test/services/purchase/server_verification_test.dart — 「サーバー未検証ならプレミアム付与しない」を実機なしで検証
  • test/services/purchase/premium_migration_test.dart — 読込元一本化・移行判定(Android/iOS)
  • test/firestore_rules/purchases.test.jspremium_entitlement のクライアント書込拒否(JDK21+ 環境/CIで実行

検証結果

  • flutter analyze:エラー0
  • flutter test --exclude-tags=integration477件パス(既存含む)
  • functions テスト:8件パス
  • flutter build web:成功(Web影響なし)

レビュー時の注意 / 残作業

  • 本番で有効化するには手動作業が必要docs/purchase-server-verification-setup.md 参照): サービスアカウント発行・Play Console 権限付与・Secret Manager に GOOGLE_PLAY_SERVICE_ACCOUNT_JSON 登録・firebase deploy(functions + firestore:rules)
  • iOS は未対応(審査対応中・Android先行の方針)。verifyPurchase は iOS に unimplemented を返し、当面クライアント一次検証のみで付与
  • Firestoreルールの emulator テストはローカル JDK 17 のため未実行(JDK21+ で要実行)

🤖 Generated with Claude Code

クライアント改竄によるプレミアム不正取得を防ぐため、購入をサーバー側
(Cloud Functions)で検証する仕組みを追加。プレミアム判定の信頼できる
唯一のソースをサーバー検証済みフラグに一本化する(対応案#1/#3)。

サーバー:
- functions/purchase/android_verifier.js: Play Developer API での検証
  ロジック(APIクライアントを注入可能にしてユニットテスト済み・8件)
- functions/index.js: verifyPurchase(onCall) を追加。認証+レート制限+
  Android検証→検証成功時のみ premium_entitlement(サーバー専用doc)へ書込。
  iOSは未対応(unimplemented)としTODO化
- firestore.rules: premium_entitlement をクライアント書込禁止(サーバー専用)

クライアント:
- purchase_verifier.dart: verifyPurchase を呼ぶ注入可能ラッパー
  (Firebase初期化前の構築でも落ちないよう instance アクセスを遅延)
- one_time_purchase_service.dart: 購入成功時にAndroidはサーバー検証を必須化。
  読込元を premium_entitlement に一本化。レガシーフラグのみの既存ユーザーは
  起動時に自動再検証(restorePurchases)でシームレス移行。iOSは既存挙動を維持。
  InAppPurchase.instance アクセスを遅延化(構築時の課金接続を回避)

テスト:
- server_verification_test / premium_migration_test を追加(実機なしで
  「サーバー未検証ならプレミアムを付与しない」「移行判定」を検証)
- firestore_rules/purchases.test.js を追加(JDK21+環境/CIで実行)

残作業(IK手動・docs/purchase-server-verification-setup.md 参照):
サービスアカウント発行・Play Console権限付与・Secret Manager登録・deploy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@IKcoding-jp
IKcoding-jp enabled auto-merge June 17, 2026 11:43
@IKcoding-jp
IKcoding-jp merged commit 24d1918 into main Jun 17, 2026
5 checks passed
@IKcoding-jp
IKcoding-jp deleted the fix/#163-server-side-receipt-verification branch June 17, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【中】課金: 購入検証がクライアント側のみ/購入復元タイムアウト時のCompleterが放置される

1 participant