在上架前聯調 IAP 時,我們遇到一個典型但非常迷惑的問題:

  • 使用者點擊「繼續購買」
  • 按鈕進入「處理中」
  • 幾秒後恢復「繼續購買」
  • 沒有彈出 App Store 購買確認框

這篇文章記錄完整排查路徑、關鍵日誌、根因以及可落地修復方案,適合發佈前做 StoreKit 風險檢查。

1. 問題現象

購買頁商品列表能正常載入,點擊購買按鈕後:

  • purchase() 被呼叫
  • 無系統彈窗
  • UI 最終回到未購買狀態

乍看像是「購買流程沒發起」,但日誌顯示並不這麼簡單。

2. 首輪排查:確認購買鏈路有沒有走通

我們先把購買鏈路關鍵節點打全日誌:

  • 按鈕點擊參數
  • purchaseSelectedProduct() 入口和 guard 分支
  • product.purchase() 前後
  • Transaction.updates
  • refreshEntitlements()
  • Transaction.currentEntitlements
  • Transaction.latest(for:)

結果顯示:

  • purchase() 返回了 .success
  • 交易校驗成功、transaction.finish() 也執行了
  • entitledIDs 仍為空

這說明:不是「沒發起購買」,而是「購買後權益沒被識別」

3. 深挖後發現的關鍵事實

繼續補日誌後,出現了幾個決定性訊號:

  1. Transaction.currentEntitlements 迭代數為 0
  2. Transaction.latest(for: monthly) 有有效記錄,但已過期
  3. 更關鍵的是:購買前 Transaction.unfinished 中存在舊交易
  4. 本次 purchase() 返回的也是舊交易鏈路中的交易,而非新的互動購買流程

這就解釋了為什麼「不彈窗」:

  • StoreKit 先返回了可處理的舊交易(unfinished / 歷史鏈路)
  • 你程式碼把它 finish 掉了
  • 購買請求看起來「成功」,但沒有走到使用者期望的新購買確認彈窗路徑

4. 根因總結

本次問題不是單點,而是兩類因素疊加:

  • 未完成交易(unfinished transaction)干擾購買流程
  • 沙盒訂閱鏈路處於過期狀態(即使有 latest transaction,也可能不是 active entitlement)

最終表現就是:不彈窗、成功返回舊交易、權益仍為空。

5. 修復策略(實踐有效)

5.1 購買前主動清理 unfinished 交易

在啟動和點擊購買前,掃描 Transaction.unfinished,對 verified transaction 執行 finish(),避免舊交易攔截新購買流程。

5.2 保留並強化診斷日誌(上線前建議開啟)

關鍵日誌建議保留到上線前:

  • unfinished transaction 快照
  • subscription status(state、renewal info)
  • currentEntitlements 迭代數量
  • latest(for:) 的 transaction/expiration

5.3 權益判斷採用「雙通道」(可選增強)

若某些環境下 currentEntitlements 短暫為空,可用 latest(for:) 做回退判定(注意仍需校驗是否 active)。

6. 上線前 IAP 檢查清單

  1. 每次測試前確認 Sandbox 帳號訂閱狀態(是否已過期)
  2. 關鍵路徑列印 unfinished 數量
  3. 測試「首次購買 / 過期後續訂 / 恢復購買」三類場景
  4. 檢查 currentEntitlementslatest 是否一致
  5. 若出現「不彈窗」,先看是否被舊交易鏈路命中

7. 結語

「點擊購買不彈窗」不一定是 UI 或呼叫時機問題。
在 StoreKit 2 下,交易狀態、unfinished 佇列、訂閱生命週期都會影響最終表現。

上架前把這些日誌和清理動作補齊,能減少大量線上不可復現的 IAP 問題。