StoreKit2

準備

・App Store Connectでアプリ登録
・アプリ内課金 or サブスクリプションの登録
・Xcode で IAP Capability ON
左のプロジェクト名 → Targets → Signing & Capabilities → + Capability → 検索窓に「In-App Purchase」と入力し、ダブルクリックして追加
・Xcode で StoreKit Configuration ファイルを作成、ストアと同期

Scheme → Edit Scheme → Run → Options → StoreKit Configuration

Storerkit2 課金システム実装例(サブスク + 買い切り)

特に商品表示に拘りがなければサブスクを扱う場合は SubscriptionStoreView を使うこと。
ユーザー許諾書(EULA)とプライバシーポリシーが明示できてないとリジェクトされます。

あ、変数名の修正忘れてたlol

サブスクを売るならアプリ内に必須条件

  • サブスク名
  • 期間
  • 価格
  • Privacy Policyリンク
  • Terms of Use(EULA)リンク

そしてストアメタデータにも:

  • Privacy Policy URL(App Store Connectの欄)
  • EULAリンク(説明文 or EULA欄)

が必要。

外部購入を感知するリスナー。作成例

StoreKit 2におけるトランザクションリスナーは、アプリ外部での購入(App Storeでの直接購入、他端末での同期、Ask to Buyなど)をリアルタイムで検知するために不可欠です

。 

2026年時点の最新のベストプラクティスに基づき、アプリ起動時にリスナーを初期化する実装方法を解説します。

1. リスナーの基本構造

Transaction.updates を使用して非同期ストリームを監視します。これを Task 内で実行し、アプリのライフサイクル全体で維持することが推奨されます。

2. 実装の重要ポイント

  • 起動直後の開始: 未完了のトランザクション(クラッシュやネットワーク遮断で中断したもの)を回収するため、アプリ起動後すぐにタスクを開始してください。
  • 検証の実施: StoreKit 2は自動的に検証を行いますが、VerificationResult を確認し、.verified の場合のみ処理を継続することで、安全性を担保します。
  • transaction.finish() の呼び出し: コンテンツを提供した後は必ず finish() を呼び出してください。これを忘れると、次回のアプリ起動時に同じトランザクションがリスナーに再送され続けます。
  • 重複処理の回避: リスナー(Transaction.updates)は、アプリ内での直接購入(product.purchase())の結果も受け取ることがありますが、基本的にはアプリ外のイベント用です。直接購入の戻り値とリスナーの両方で finish() を重複して呼んでも問題ありませんが、ロジックの整合性に注意してください。

リスナーがなぜバックグラウンドで動けるか

どこのコードでループしてますか?

結論(超重要)

はい、リスナーは「バックグラウンドで常に待機」しています。

ただし
❌ CPUを回し続けるループではありません
✅ イベント待ちの非同期ストリームです。

「どこでループしているのか?」の答え

あなたのコードのこの部分です👇

これが ループ本体です。

仕組みを正確に説明すると

① Transaction.updates の正体

これは:

  • AsyncSequence
  • イベント駆動
  • Appleが管理するストリーム

です。

👉
何も起きなければ、ここでスレッドは「待機」

② for await は while ではない

見た目はループですが、中身は👇

待つ → イベントが来たら1回処理 → また待つ

ポーリング ❌

無限ループでCPU占有 ❌

バックグラウンドで勝手に回る ❌

実行ライフサイクル(超重要)

いつ動き出す?

を呼んだ瞬間です。

👉 通常は:

  • App.init()
  • EntitlementManager.init()

いつ止まる?

EntitlementManager が解放された時

アプリ終了時

バックグラウンドにいる間は?

macOSの場合

  • アプリが 起動中 → 有効
  • ウィンドウを閉じてもOK
  • Dock / メニューバー常駐中もOK

アプリ終了後

  • ❌ 動かない
  • 再起動時に再度 listener 開始

👉
Appleが未処理トランザクションを保持
→ 次回起動時に流れてくる

なぜリスナーが必要か、currentEntitlements と Transaction.updates(listener)

🔄 結論を一言で

項目役割
currentEntitlements「今この瞬間に有効な権利の一覧」スナップショット
Transaction.updates「これから起きる取引イベント」リアルタイム通知

👉 両方必要です。片方だけでは不完全。

① currentEntitlements とは何か?

正体

  • Appleが保持している
    「未失効・有効なトランザクション一覧」
  • 期限切れ・キャンセル済みは 含まれない
  • 過去に購入した履歴ではない

使うタイミング(超重要)

✅ アプリ起動時
✅ App再起動後
✅ 復元ボタンを押した直後

② Transaction.updates(listener)とは?

正体

  • これから発生する取引イベント
  • 非同期ストリーム
  • イベント駆動

流れてくるもの

  • 新規購入
  • 無料トライアル開始
  • 自動更新
  • 復元
  • 他デバイス購入の同期
  • 保留 → 承認

👉 全部リアルタイム

使う理由

  • purchase() の戻り値だけでは 取り逃がす
  • FaceID完了後にイベントが来る
  • App Store側で完結するケースがある

③ 両者の関係(図解)

④ なぜ片方だけだとダメ?

❌ listener だけの場合

  • App起動直後は何も流れてこない
  • 既存購入を知らない

❌ currentEntitlements だけの場合

  • 購入直後にUIが更新されない
  • バックグラウンド更新を拾えない

⑤ Apple公式推奨パターン(完成形)

👉
「過去」+「未来」を両方カバー

⑥ 実務での使い分け(覚え方)

🧠 覚え方:

  • currentEntitlements → 状態確認
  • Transaction.updates → 状態変化

⑦ よくある落とし穴

「どっちで isPremium を更新する?」

👉 両方

  • 起動時 → currentEntitlements
  • 変化時 → listener

「finish() はどこで?」

👉 listener 側
(purchase() 側では最低限)

.verified 〜

.productID商品識別: どのアプリ内課金商品(例: “com.example.pro_feature”, “100_coins”)が購入されたかを特定します。
.productTypeどのような課金タイプかを安全に判定・取得するためのプロパティ
.nonConsumable: 非消耗型(機能解除など、一度購入すると永続)
.autoRenewable: 自動更新サブスクリプション
.nonRenewable: 非自動更新サブスクリプション
.consumable: 消耗型(ポイント、ゲームのライフなど
.revocationDate効果revocationDatenilでない場合、その購入は過去に行われましたが、現在は無効化されています。
重要性currentEntitlements(現在の有効な購入リスト)から外れているかどうかの判断に使われます。
用途: コンテンツ(プレミアム機能や消耗品)へのアクセスを即座に停止(撤回)するために使用します

Product.products(for:)

  1. 商品情報の取得と表示:
  2. Swiftの非同期処理(async/await)の活用:
  3. 購入フローの簡素化:
  4. プロダクトエンタイトルメントの確認:
Swift Concurrency非同期処理(async/await)が中心で、より直感的にコードを書けます。
ProductApp Store Connectで設定したプロダクト(サブスクリプション)の情報を取得します。
Transactionユーザーの購入やサブスクリプションの状態(有効、期限切れ、アップグレード済みなど)を管理します。
autoRenewableTransactionオブジェクトのプロパティで、自動更新サブスクリプションのトランザクションかどうかを判定します。
  1. 商品情報の取得Productクラスを使って、App Store Connectで設定した自動更新サブスクリプションの情報を取得します。
  2. 購入処理: ユーザーが購入ボタンをタップしたら、Product.purchase()を呼び出します。
  3. トランザクションの監視Transaction.updatesasyncシーquenceとして監視し、購入や更新、キャンセルなどのイベントをリアルタイムで受け取ります。
  4. 状態の検証と適用: 受け取ったTransactionオブジェクトを検証し、transaction.isActivetransaction.expirationDatetransaction.isUpgradedなどのプロパティでサブスクリプションの状態を確認し、アプリ内のコンテンツへのアクセス権を制御します。

Transaction.currentEntitlements

SwiftのStoreKit 2における 

Transaction.currentEntitlements の効果は、アプリ起動時や特定のタイミングで、ユーザーが現在有効にしている(または未完了の)すべての購入トランザクション(非消耗品、有効な自動更新サブスクリプション、非更新サブスクリプション、未完了の消耗品)の最新情報を取得し、アプリの状態を同期・復元できることです。これにより、購入したコンテンツへのアクセス権を即座に復元したり、サブスクリプションの有効性を確認したり、未完了の購入を処理したりする際に非常に強力なツールとなります。 

主な効果と用途

  1. 購入状態の同期と復元:
  2. サブスクリプション管理:
  3. 消耗品トランザクションの処理:
  4. アプリ起動時の状態確認:

Transaction.updates との違い

  • Transaction.currentEntitlementsその時点での有効な/未完了のトランザクションのスナップショットを返します。
  • Transaction.updatesアプリ起動後(または監視開始後)に発生した新しいトランザクションすべて(アプリ外での購入含む)をリアルタイムでストリーミング(非同期シーケンス)で通知します。 

これらを組み合わせることで、アプリはユーザーの購入状態を常に最新に保ち、スムーズな機能提供を実現できます。

Transaction.updates

SwiftのStoreKit 2における

Transaction.updatesは、アプリの外部で発生した購入トランザクション(サブスクリプションの更新・失効、別デバイスでの購入など)を非同期で検知し、アプリ内でその状態を同期・反映させるための非常に重要なAsync Sequenceです。これにより、ユーザーが他のデバイスで購入したコンテンツや、サブスクリプションの自動更新/失効による状態変化をリアルタイムで把握し、アプリ内の購入済みコンテンツを適切に解放(アンロック)できるようになります。 

Transaction.updatesの主な効果と役割

  1. 購入状態の同期:
  2. リアルタイムなコンテンツアンロック:
  3. サブスクリプション管理の自動化:
  4. レシート検証との連携:

実装のポイント

  • アプリ起動時や、購入処理の直後にTransaction.updatesを監視するTaskを開始し、ループ内で新しいトランザクションを待ち受けます。
  • Task内でfor await transaction in Transaction.updatesを使って非同期にトランザクションを受け取り、verificationResultを処理します。
  • 受け取ったトランザクション情報をObservableObjectPurchaseManagerなど)に保持させ、SwiftUIのビューでそれを監視してUIを自動更新させることが一般的です。 

Transaction.updatesはStoreKit 2の「トランザクションの継続的な監視」を実現するための核心部分であり、ユーザー体験の向上とアプリの収益化を安定させる上で不可欠な機能です。

UserDefaults(suiteName: “group.your.app”)

StoreKit 2 とあわせて UserDefaults(suiteName: "group.your.app") を使用する主な効果は、アプリ本体と App Extension(Widget、Share Extension など)の間で課金ステータスや購入情報を共有できることです。 

StoreKit 2 単体でも Transaction.currentEntitlements を通じて最新の購入情報を取得できますが、 UserDefaults を併用することで以下のメリットがあります。 

主な効果とメリット

  1. Extension とのデータ共有:
  2. パフォーマンスの向上(キャッシュ利用):
  3. オフライン時の動作保証:

実装のポイント

  • App Groups の設定: Xcode の Signing & Capabilities で、アプリ本体と Extension の両方に同じ App Group 名(例: group.your.app)を登録する必要があります。
  • セキュリティ上の注意UserDefaults は平文の plist ファイルとして保存されるため、機密性の高いトランザクション ID や署名データそのものではなく、「プレミアム機能が有効か」といった単純なフラグ管理としての利用が推奨されます。 

機密性の高いデータを共有する場合は、Shared Keychain の利用も検討してください

実装例

1. 共有用の管理クラスを作成する

まず、指定した App Group の領域にアクセスするためのユーティリティを作成します。

2. StoreKit 2 の購入・更新処理で保存する

StoreKit 2 の Transaction.updates や購入完了時のタイミングで、この共有ストレージを更新します。

3. Widget (App Extension) 側で参照する

Widget 側では、同じ App Group ID を指定してデータを読み取ります。@AppStorageを使うと、データの変化に合わせて View を自動更新できるため便利です。

実装時の注意点

  • 同期タイミング: メインアプリで値を更新した後、Widget 側で即座に反映させたい場合は WidgetCenter.shared.reloadAllTimelines() を呼び出して Widget の更新を促してください。
  • データの信頼性UserDefaults はユーザーが手動でバックアップから復元したり、特定のツールで書き換えたりする可能性があるため、重要な機能制限は必ずアプリ起動時に Transaction.currentEntitlements で再検証するようにしてください。

Transaction.originalID

StoreKit 2における 

Transaction.originalID (オリジナルID) は、アプリ内課金(特にサブスクリプション)のライフサイクル全体を通じて、初回購入時につけられた一意の識別子を保持するプロパティです。 

自動更新サブスクリプションや、購入の復元(Restore)が行われた際に、新しいトランザクションID (id) が発行されても、この originalID は変わりません。 

originalID の主な効果と用途

  1. サブスクリプションの継続的な識別 (最重要)
  2. 購入の復元(Restore)の特定
  3. サーバー側でのID連携
  4. id (Transaction Identifier) との区別

使用例 (Swift)

まとめ

originalID は、「ユーザーがいつこのサブスクリプションを開始したか」を識別するための信頼できるキーとして機能します。

Product.PurchaseError

StoreKit 2における

Product.PurchaseError(および関連するStoreKitError)は、アプリ内課金処理において購入が正常に完了しなかった場合に発生します。このエラーは、ユーザー体験(UX)と決済プロセスの安定性に影響を与えます。 

主なエラーの種類と効果・対応は以下の通りです。

1. 主要な PurchaseError の種類と影響 

  • userCancelled (ユーザーキャンセル):
  • productUnavailable (商品利用不可):
  • purchaseNotAllowed (購入不可):
  • networkError (ネットワークエラー):
  • ineligibleForOffer (オファー対象外):
  • invalidOffer... (オファー不備):

2. その他の関連エラーと効果 

  • SKError.Code.paymentInvalid (決済無効): カードの有効期限切れや残高不足など。
  • SKError.Code.paymentNotAllowed (決済不可): ユーザーが支払い手段を承認していない。
  • StoreKitError.notEntitled (権利なし): 必要な特権(Entitlement)がアプリにない。 

3. エラー時の一般的なUXへの影響

  • 購入完了の遅延: エラーが発生すると、ユーザーは商品(課金アイテム)をすぐに受け取れず、プレミアム機能が利用できない。
  • フラストレーション: 適切なフィードバック(何が原因か、どうすれば解決するか)がない場合、ユーザーの離脱につながる。
  • サブスクリプションの未更新: サブスクリプションの更新失敗時(Billing Issue)に適切な案内をしないと、解約率が上がる。 

4. 推奨される対応策(実装)

  1. エラーハンドリングの共通化do-catch 文を使用して PurchaseError を捕捉し、エラーに応じたメッセージを表示する。
  2. トランザクションの終了try await transaction.finish() を使用して、たとえエラーであってもトランザクションを最終確定させ、未完了案件をなくす。
  3. App Store Connectのチェック: 商品IDが正しいか、商品が「販売準備完了」になっているか確認する。 

Product.PurchaseError を適切にハンドリングすることで、ユーザーに明確な状況を伝え、購入の再試行を促すなど、購入体験を向上させることができます。

Configuration.state

StoreKit 2における 

Configuration.state(一般にXcodeの.storekitファイルに関連する設定)の主な効果は、App Store Connectの実際のアカウントやサーバーと接続せずに、Xcode上でアプリ内課金(購入、更新、課金エラーなど)のシミュレーションを安全かつ高速に行うことです。 

具体的には、以下のような効果とメリットがあります。

1. 課金シミュレーションの自由な状態操作

Xcodeの「Manage StoreKit Transactions」ウィンドウを使用して、以下の状態を擬似的に作り出せます。 

  • サブスクリプションの更新速度アップ: 1ヶ月のサブスクリプションを数分(または数秒)で更新させ、更新処理をテスト可能。
  • 購入のキャンセル/返金: ユーザーによる解約や返金をシミュレーション可能。
  • Billing Retry(課金失敗): 有効期限切れ後の課金失敗や、その後の復帰(Grace Period)のテスト。
  • 購入履歴のクリア: トランザクションを一度リセットし、新規購入者としての挙動をテスト。 

2. オフラインでのテスト

実際のサーバー通信がないため、インターネット環境がなくてもテストが可能です。これにより、不安定な通信環境でのエラーハンドリング(プロダクト情報が取れない等)も確認できます。 

3. 購入制限(Family Sharing)のシミュレーション

ファミリー共有の「承認が必要な購入(Ask to Buy)」をシミュレーションし、未成年ユーザーが購入をリクエストし、大人が承認する流れをテストできます。 

4. 実際のデータとの連携(Sync)

Xcode 14以降、.storekitファイルをApp Store Connect上のプロダクト設定と同期(Sync)させることができます。これにより、実際のApp Storeの設定(価格や名前)を使いながら、決済自体はローカルの安全な環境でテストできます。 

まとめ

Configuration.state(StoreKitテスト設定)は、本番環境のデータを汚さずにサブスクリプションの有効期限切れ、更新、返金、課金失敗など、複雑なシナリオを高速に検証できる、StoreKit 2開発における必須ツールです。

.storeProductsTask(for:)

SwiftUIの .storeProductsTask(for:) は、StoreKit 2を使用してApp Storeからプロダクト情報(Product)を非同期に取得し、画面に反映させるためのViewモディファイアです。

主な効果と特徴は以下の通りです。 

1. アプリ内課金アイテムの自動読み込み

このモディファイアをビュー(View)に付与すると、そのビューが表示される際に、指定したプロダクトID(IDs)の製品情報をApp Store Connectから自動的に取得(フェッチ)します。これにより、自分で非同期処理(async/await)を記述しなくても、プロダクト情報を簡単に取得できます。 

2. ローカライズされた価格と情報の取得

取得した Product オブジェクトには、ローカライズされた価格(displayPrice)や商品名、説明が含まれています。これにより、ユーザーの環境に合わせた価格表示が自動で対応できます。 

3. プロダクト変更時の再読み込み(Reactive)

ids パラメータが変更されると、タスクが自動的に再開され、最新の製品情報を再取得します。 

4. 状態の監視

action クロージャ内で、製品が正常に取得できたか、あるいはエラーが発生したかなどの状態(Product.CollectionTaskState)を受け取ることができ、それに応じたUIの変更が可能です。

まとめ

.storeProductsTask(for:) は、「ストア情報を表示するためのデータを取得する手間を最小限にし、UIとデータ取得を連動させる」効果があります。

Task.detached 

StoreKit 2における 

Task.detached の主な効果は、App Storeの課金処理(ネットワーク通信や購入検証)をメインスレッド(MainActor)から完全に分離し、アプリのUIフリーズを防ぐ(バックグラウンドで処理する)ことです。 

一般的な Task { ... } は呼び出し元のコンテキスト(例: SwiftUIのView = MainActor)を引き継ぎますが、Task.detached { ... } はそれを引き継がないため、明示的に別スレッドで実行したい場合に有効です。 

StoreKit 2 で Task.detached を使う具体的な効果

1. UIのフリーズ防止(高レスポンス)

購入処理やトランザクションの検証は時間がかかる場合があります。Task.detachedで実行することで、通信中も画面の描画やボタンの反応が止まらず、スムーズなUXを維持できます。 

2. 親タスクのキャンセルから分離

ボタンタップなどで開始された Task が、そのViewが閉じられた際にキャンセルされても、Task.detached で実行された処理はキャンセルされず、最後まで完了させることができます。これにより、購入処理が不完全なまま終了するのを防げます。 

3. 継続的なリスナー処理(Transaction.updates)

アプリ起動時に Transaction.updates を監視する際、Task.detached を使うことで、メインスレッドを占有せずバックグラウンドでトランザクションの更新を監視し続けられます。 

注意点とベストプラクティス

  • メインスレッドへの復帰: Task.detached の中で購入処理を行い、その結果を使ってUIを更新する(例: text = "購入完了")場合は、必ず await MainActor.run { ... } を使用してメインスレッドに戻る必要があります。
  • 不必要な利用は避ける: Task.detached は呼び出し元の優先順位や環境を継承しないため、単純に「メインスレッドから逃がしたい」目的であれば、通常の Task { ... } 内で async 関数を呼び出すだけでもバックグラウンドで実行されます。
  • 変数の共有: クラス内のメンバ変数を扱う場合、データ競合(Data Race)に注意し、actor や MainActor.run を適切に使用してください。 

実装イメージ

※ StoreKit 2の Product.purchase() 自体が async であるため、Task.detachedを使わなくても内部的にはスレッドが適切に管理されることが多いですが、処理の独立性を保証したい場合に detached が利用されます。

引数に Transaction として型エラーが出る場合

なぜ起きるのか(背景)

Swift には:

  • Foundation.Transaction(Swift Concurrency / Observation 系)
  • StoreKit.Transaction(課金の Transaction)

という 同名型が存在します。

Transaction

とだけ書くと、
Swift がどちらを使えばいいか分からないためエラーになります。

修正方法①(最も確実・おすすめ)

型をフルパスで指定する

👉 これが一番安全・一番使われている

修正方法②(import を整理する)

もしファイルの先頭に👇があれば:

この状態だと 曖昧になります。

修正方法③(typealias で短くする)

少し玄人向けですが、可読性は上がります。

StoreKit Configuration テスト購入状態の解除

StoreKit Configurationファイル(.storekit)を使用したStoreKit 2のテスト環境で、購入状態(購入履歴)を解除・リセットして再テストする方法は以下の通りです。

1. Xcodeのデバッグメニューから削除する (推奨)

最も簡単で速い方法です。

  1. Xcodeでアプリを実行中(デバッグ中)に、メニューバーの Debug > StoreKit > Manage Transactions を選択します。
  2. 表示されたトランザクション一覧(購入履歴)の中から、削除したいアイテムを選択します。
  3. ゴミ箱アイコン(Delete)をクリックして、削除します。
  4. アプリを再起動、または「リストア(復元)」ボタンを押すと、購入前状態に戻ります。 

2. StoreKit Configurationファイルを直接クリアする 

特定の商品のテスト中、最初からやり直したい場合に有効です。 

  1. Xcodeで .storekit ファイルを開きます。
  2. メニューの Editor をクリックし、購入状態を制御します。

3. アプリの再インストール

実機やシミュレータ上で、アプリを削除して再インストールすることでも購入履歴はリセットされます。

注意点

  • 非消耗型(Non-Consumable)やサブスクリプションは、購入履歴が残るため、再購入のテストにはこの削除手順が必須です。
  • これらを行っても、App Store Connect上の本番・サンドボックス環境には影響しません。 

これでも解決しない場合は、Debug > StoreKit > Clear Transaction History を使用すると、環境全体がまっさらな状態になります

アプリのアンインストール状態にする方法(macOS)

① アプリ本体を削除

方法A(Finder)

  1. Xcode で一度ビルドして起動
  2. Dock のアプリを 右クリック
  3. Finder で表示
  4. .app をゴミ箱へ

または

を削除

② Sandbox Container を削除(最重要)

StoreKitの復元テストで ここが本体です。

場所

の中にある

これを 丸ごと削除

③ Application Support も確認(念のため)

もし App Sandbox OFF の場合や
独自フォルダを使っているなら:

まとめ

macOS の「アンインストール」とは
アプリ本体 + Container 削除

StoreKit Configuration 表示言語設定

Xcode 上部タブ
Product → Scheme → Edit Scheme → Run → Options → App Language

StoreKit Configuration 失敗設定

Xcode → サイドバーファイル選択で自分で作った .storekit を選択 → Configuration Settings → Purchase Options
各種エラー設定できる、が、なんかしっくりくるテストはできなかった。その下の設定はグレーアウトで触れなかったし。。。

Sandbox テスト以降時

アプリを Archive して App Store Connect にアップロードしておく

App Store Connect → ユーザとアクセス → 上部タブの Sandbox → テストアカウントを追加で作成

名前、メールアドレス等は適当でOK
ここで【購入履歴を消去】、月次更新の間隔を設定できる。期限切れは【購入履歴を消去】で。(恐らくこれは .updetes と .currentEntitlementsで拾えない、try? await AppStore.sync() で強制同期させれば未購入状態にできる)

Xcode 上部タブ
Product → Scheme → Edit Scheme → Run → Options → StoreKit Configuration → None

上記が終わったらシミュレーターでサブスク等購入をクリックする。
※この時にテストアカウントを入力する、本アカウントを使用しない事!

最低限やるテスト

🔹 課金

  •  無料トライアルが開始される
  •  週 / 月 / 年 / 買い切り 全て購入できる
  •  購入後すぐ isPremium == true になる

🔹 復元

  •  アプリ削除 → 再インストール
  •  「購入を復元」で即 Premium になる

🔹 期限・解約

  •  Sandboxで自動更新OFF
  •  期限切れ後に isPremium == false に戻る
再現したい状態方法
サブスク期間切れ状態ネット接続を切って5分(テストアカウント設定で変更可能)経ってからデバッグ

Sandboxで「イベントが来ない」時の原因

0️⃣ まず最初に確認(9割ここ)

✅ listener は「起動時」に開始しているか?

❌ Paywall を開いた時に開始
❌ ボタンタップ時に開始

👉 App起動直後が正解

1️⃣ Sandbox アカウントの罠(最頻出)

❌ App Store にサインインしていない
macOS/iOS:
設定 → App Store
Sandbox Apple ID でサインイン
⚠️ 通常のApple IDではSandboxになりません

❌ Sandbox Apple ID が期限切れ
TestFlight / App Store Connect
Users and Access → Sandbox Testers
👉 作り直すのが一番早い

2️⃣ Xcode 実行設定ミス

❌ Release で実行している
Sandbox は Debug 実行必須

❌ 別Bundle IDで起動している
StoreKit Configuration と
App の Bundle ID
👉 完全一致必須

3️⃣ StoreKit Configuration の見落とし

❌ .storekit ファイルを作ってない

❌ Scheme に設定していない

❌ ProductID が一致していない

👉 1文字違いでもアウト

4️⃣ purchase() は成功しているのに来ない

原因① finish() 済み
前回の transaction を finish 済み
新規イベントが無い
👉 再購入できないのは正常

原因② すでにプレミアム状態

で拾われている場合、
新しい updates は流れません
👉
Sandboxでは アカウントを変えるのが正解

5️⃣ listener が cancel されている

❌ Task が GC されている

これだけだと 参照が保持されず終了

✅ 正解

6️⃣ verified で弾いている

❌ これで continue していない?

Sandboxで:

  • ネットワーク不安定
  • 設定ミス

👉 unverified が来ることもある

デバッグ時はログ必須👇

7️⃣ finish() の位置ミス

❌ purchase() 側で finish()
→ listener 側に来る前に処理済み扱い
→ updates が流れない

✅ listener 側で finish()

8️⃣ macOS 特有の落とし穴

❌ サンドボックスOFF
macOS App:
Signing & Capabilities
App Sandbox → ON
In-App Purchase → ON

❌ ネットワーク権限

ON必須

9️⃣ 最終チェック(これだけ見ればOK)

✔ デバッグ用ログ

これが 1回も出ないなら:

  • listener 起動していない
  • Task が死んでいる

🧠 まとめ(現場ルール)

症状原因
何も来ないlistener未起動
purchase成功後も来ないfinish位置ミス
初回だけ来ないSandbox ID
再購入できない正常動作

コメント

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