Skip to content

fix(#163): 購入情報のクライアント検証と復元タイムアウト時のCompleter放置を修正 - #203

Merged
IKcoding-jp merged 1 commit into
mainfrom
fix/#163-purchase-validation-restore-timeout
Jun 17, 2026
Merged

fix(#163): 購入情報のクライアント検証と復元タイムアウト時のCompleter放置を修正#203
IKcoding-jp merged 1 commit into
mainfrom
fix/#163-purchase-validation-restore-timeout

Conversation

@IKcoding-jp

Copy link
Copy Markdown
Owner

概要

Issue #163 のうち、外部設定なしで完結できる範囲(完了条件3項目)を焦点修正。
サーバー側レシート署名検証(対応案#1)と Firestore ルールのロック(#3)は、Google Play / App Store / Google Cloud の外部設定を要するため別Issueに切り出します。

変更内容

1. 購入情報のクライアント側検証(対応案: 不正な購入の拒否)

  • lib/services/purchase/purchase_validator.dart(新規)
    • PurchaseValidator.isValidPremiumPurchase()status(purchased/restored)・productIDserverVerificationData(空・空白のみ不可)を検証する純粋関数
  • _handleSuccessfulPurchase() で検証 → 不正・未完了の PurchaseDetails ではプレミアムを有効化せず拒否ログのみで早期 return
  • ⚠️ これはクライアント側の一次防御であり、暗号学的なレシート検証ではありません(真の改竄対策は別Issueのサーバー検証で対応)

2. 復元タイムアウト時の Completer 放置を修正(対応案#2

  • lib/services/purchase/restore_coordinator.dart(新規)
    • 復元の Completer ライフサイクルを一元管理。wait()タイムアウト/完了/例外いずれの経路でも内部状態を必ずリセット(finally)→ タイムアウト後の再試行が正常動作
  • 潜在バグも解消: 従来 _restoreCompleter を完了させる箇所が存在せず、restorePurchases() は成功時も常時30秒タイムアウトして false を返していた。restored イベントで signalRestored() を呼び完了させるよう修正

テスト(TDD: 先に失敗テスト → 実装)

  • test/services/purchase/purchase_validator_test.dart(新規・8ケース)— 不正な PurchaseDetails(空検証データ/誤ProductID/pending・error・canceled)でプレミアムが有効化されないことを確認
  • test/services/purchase/restore_coordinator_test.dart(新規・6ケース)— タイムアウト→再試行が正常動作、放置されないこと、例外時リセットを確認

完了条件

  • 不正な PurchaseDetails ではプレミアムが有効化されないことをテストで確認
  • 復元タイムアウト→再試行が正常動作する
  • 既存の one_time_purchase_service_test.dart がパス

検証

  • flutter analyze: エラー0
  • flutter test: 全478テストパス(新規14テスト含む)
  • flutter build web: 成功

競合・後方互換

  • 正規の購入は serverVerificationData(Android=購入トークン / iOS=レシート)が常に非空のため、本検証で弾かれません
  • completePurchase()(トランザクション確認)は検証結果に関わらず従来どおり実行(プレミアム付与のみをゲート)

残課題(別Issue化を推奨)

🤖 Generated with Claude Code

- PurchaseValidator を追加。status/productID/serverVerificationData を検証し、
  不正・未完了の PurchaseDetails ではプレミアムを有効化しない(一次防御)
- RestoreCoordinator を追加。復元の Completer ライフサイクルを一元管理し、
  タイムアウト/完了/例外いずれの経路でも内部状態を必ずリセット。
  タイムアウト後の再試行が正常動作する
- 従来 _restoreCompleter は完了されず常時タイムアウトしていた潜在バグも解消
  (restored イベントで signalRestored() を呼び完了させる)
- _handleSuccessfulPurchase で検証を行い、不正な購入は拒否ログのみで早期return

サーバー側レシート署名検証(対応案#1)と Firestore ルールのロック(#3)は
外部設定を要するため別Issueに切り出す。

完了条件:
- 不正な PurchaseDetails でプレミアムが有効化されない → purchase_validator_test.dart
- 復元タイムアウト→再試行が正常動作 → restore_coordinator_test.dart
- 既存 one_time_purchase_service_test.dart パス(全478テストパス・analyze エラー0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit a09e616):

https://maikago2--pr203-fix-163-purchase-va-qz3senm6.web.app

(expires Wed, 24 Jun 2026 01:39:48 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: e99c4741b4cd115a589120c0903a3d28c527ebd8

@IKcoding-jp
IKcoding-jp merged commit afce9c6 into main Jun 17, 2026
8 checks passed
@IKcoding-jp
IKcoding-jp deleted the fix/#163-purchase-validation-restore-timeout branch June 17, 2026 04:46
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.

1 participant