StoreKit 2でのサブスクリプション更新処理を実装してつまずいた話

トラブルシューティング

以前StoreKitで課金機能を実装した際は買い切り型のアプリ内課金でしたが、Core Locationでのバックグラウンド位置情報取得が一段落したタイミングで、今度はサブスクリプション型の課金にStoreKit 2で挑戦しました。

1. 一度購入を確認すれば継続を保証できると思っていた

最初、購入時に一度Transactionを確認してプレミアム機能を有効化すれば、それ以降はずっと有効なままにしておいてよいものだと思い込んでいました。

for await result in Transaction.currentEntitlements {
    // 有効な購入を確認
}

実際にはサブスクリプションは更新のたびに有効性が変わるため、アプリ起動時など適切なタイミングでTransaction.currentEntitlementsを毎回確認し直し、期限切れや解約が反映されているかをチェックし続ける必要がありました。一度確認すれば終わりという買い切り型の感覚のままでは対応できない仕組みでした。

2. 有効期限をローカルの日付比較だけで判定しようとした

購入情報に含まれる有効期限を、自前で保存した日付とローカルの現在時刻を比較して判定しようとしていました。

問題があった実装:
端末の時計を購入時に取得した有効期限と単純比較

端末の時計はユーザーが自由に変更できるため、これでは正しい有効性判定になりません。Transactionオブジェクト自体が持つ検証済みの情報を都度取得して判定する方式に変更し、自前で有効期限を保存して比較するロジックを削除しました。

3. 更新通知(Transaction.updates)を購読していなかった

サブスクリプションの更新や解約がアプリを開いていないタイミングで起きた場合、アプリを再度開いてもすぐには状態が反映されないという不具合がありました。

for await update in Transaction.updates {
    // 更新を反映
}

Transaction.updatesを購読してリアルタイムに更新を検知する処理を追加していなかったことが原因でした。アプリ起動時のチェックだけでなく、バックグラウンドで発生した更新イベントも継続的に監視する必要があると気づきました。

4. Sandbox環境と本番環境で更新間隔の感覚が違い混乱した

テスト用のSandbox環境で動作確認していたところ、本番では1ヶ月かかるはずの更新が数分単位で発生し、最初はコードが暴走しているのかと焦りました。

Sandboxでの挙動:
1ヶ月更新のサブスクリプションが数分間隔でシミュレートされる

調べてみるとSandbox環境ではテストをしやすくするために更新サイクルが大幅に短縮される仕様だと分かり、バグではなく想定通りの挙動でした。テスト環境特有の仕様を知らずに、実装のバグだと勘違いして時間を使ってしまいました。

まとめ

  • サブスクリプションは一度確認して終わりではなく、都度Transaction.currentEntitlementsで最新状態を確認し続ける
  • 有効期限の判定は端末のローカル時計と比較せず、検証済みのTransaction情報をそのまま使う
  • Transaction.updatesを購読し、アプリを開いていない間の更新・解約もリアルタイムに検知する
  • Sandbox環境では更新サイクルが短縮される仕様なので、想定外の頻度で更新が来てもバグと決めつけない

買い切り型とは全く違う「継続的に状態を確認し続ける」設計思想を理解するまでに、いくつもの勘違いを重ねた実装でした。


本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。

コメント

タイトルとURLをコピーしました