You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TestFlight内应用内购买代码无法正常工作求助

问题:TestFlight及苹果审核环境中StoreKit 2购买按钮无响应

我的应用因购买按钮无法正常使用多次被苹果审核拒绝。该功能在Xcode模拟器及真机测试时完全正常,能正常弹出App Store购买窗口,但在TestFlight测试中点击购买按钮完全无响应。我已向苹果说明使用的是StoreKit 2且无需验证收据,但相关指南并未提供有效帮助。以下是一键触发产品购买的代码,请问为何该功能在常规测试正常,却在TestFlight及苹果审核环境中失效?

Button(action: {
  Task { try await storeManager.purchase() }
}) {
  Text("Continue")
}


import Foundation
import StoreKit

let productIDs = ["my_product_1", "my_product_2"]

class StoreManager: ObservableObject {
  static let shared = StoreManager()
  
  @Published var storeProducts: [Product] = []
  @Published var purchasedProducts: Set<Product> = []
  var transacitonListener: Task<Void, Error>? = nil
    
  init() {
    transacitonListener = listenForTransactions()
    Task {
      await loadProducts()
      await updateCurrentEntitlements()
    }
  }
  
  func getProduct(index: Int) -> Product? {
    for product in storeProducts {
      if product.id == productIDs[index] {
        return product
      }
    }
    return nil
  }
  
  func getProductPrice() -> String {
    if let product = getProduct(index: 0) {
      return product.displayPrice
    }
    return "4.99 USD"
  }
  
  func getProductPriceOrigin() -> String {
    if let product = getProduct(index: 1) {
      return product.displayPrice
    }
    return "9.99 USD"
  }
  
  func isPurchased() -> Bool {
    return purchasedProducts.contains { $0.id == productIDs[0] }
  }
  
  @MainActor
  func purchase() async throws -> Transaction? {
    log("enter")
    let product: Product? = getProduct(index: 0)
    if product == nil {
      log("product nil")
      return nil
    }
    log("before purchase")
    let result = try await product!.purchase()
    log("after purchase")
    switch result {
    case let .success(.verified(transaction)):
      purchasedProducts.insert(product!)
      await transaction.finish()
      log("success")
      return transaction
    case let .success(.unverified(_, error)):
      print("purchase.success.unverified:", error)
      log("success unverified")
      break
    case .pending:
      log("pending")
      break
    case .userCancelled:
      log("userCancelled")
      break
    default:
      log("default")
      break
    }
    return nil
  }
  
  @MainActor
  private func loadProducts() async {
    do {
      storeProducts = try await Product.products(for: productIDs)
    } catch {
      print("Failed to load product \(productIDs):", error)
    }
  }
  
  private func listenForTransactions() -> Task < Void, Error > {
    return Task.detached {
      for await result in Transaction.updates {
        await self.handleTransaction(transactionVerification: result)
      }
    }
  }
  
  private func updateCurrentEntitlements() async {
    for await result in Transaction.currentEntitlements {
      await self.handleTransaction(transactionVerification: result)
    }
  }
  
  @MainActor
  private func handleTransaction(transactionVerification result: VerificationResult <Transaction>) async {
    switch result {
    case let.verified(transaction):
      guard
        let product = self.storeProducts.first(where: {
          $0.id == transaction.productID
        })
      else {
        return
      }
      self.purchasedProducts.insert(product)
      await transaction.finish()
    default:
      return
    }
  }
  
  @MainActor
  func restore() async throws {
    try await AppStore.sync()
  }
}

可能的原因及解决方案

1. 产品加载异步时序问题

StoreManager初始化时异步加载产品,但购买按钮可能在产品未完成加载时就被点击,导致getProduct(index:0)返回nil,购买流程直接终止。常规测试中产品加载较快,而TestFlight/审核环境的App Store响应或网络速度较慢,触发了这个边界情况。

  • 解决方法:
    • 给购买按钮添加状态控制,仅当目标产品存在时允许点击:
      Button(action: {
        Task { try await storeManager.purchase() }
      }) {
        Text("Continue")
      }.disabled(storeManager.getProduct(index: 0) == nil)
      
    • 在purchase()方法中,若产品为nil则抛出明确错误,而非静默返回:
      enum StoreError: Error {
          case productNotFound
      }
      
      @MainActor
      func purchase() async throws -> Transaction? {
        log("enter")
        guard let product = getProduct(index: 0) else {
            log("product nil")
            throw StoreError.productNotFound
        }
        // 后续购买逻辑
      }
      

2. 事务监听任务的生命周期与拼写问题

变量名transacitonListener存在拼写错误(应为transactionListener),可能导致引用异常;同时未处理监听任务的取消逻辑,任务可能被意外取消,无法接收购买回调。

  • 解决方法:
    • 修正变量名拼写错误,统一为transactionListener。
    • 在StoreManager的deinit方法中取消监听任务:
      deinit {
          transactionListener?.cancel()
      }
      

3. 未处理购买方法的异常

当前代码中Task { try await storeManager.purchase() }未捕获异常,若购买过程抛出错误(如网络问题),会导致任务静默失败,无任何提示。

  • 解决方法:
    • 捕获异常并添加用户提示:
      Button(action: {
        Task {
            do {
                try await storeManager.purchase()
            } catch {
                print("购买失败:\(error.localizedDescription)")
                // 这里可添加Alert等UI提示告知用户购买失败
            }
        }
      }) {
        Text("Continue")
      }
      

4. App Store Connect产品配置问题

TestFlight和审核环境会严格校验产品配置,可能存在以下问题:

  • 产品IDmy_product_1在App Store Connect中未创建或状态未设为"可供销售"。
  • 应用Bundle ID与App Store Connect配置不匹配。
  • 测试账号未添加为TestFlight测试员,或未启用沙盒购买权限。
  • 解决方法:
    • 核对App Store Connect中产品ID与代码完全一致,且产品状态为激活状态。
    • 确认测试员账号已正确配置,TestFlight默认使用沙盒环境测试。

5. 主线程调度问题

虽然purchase()标注了@MainActor,但复杂场景下仍可能出现线程调度异常,需确保所有UI相关状态更新在主线程执行。

  • 解决方法:
    • 保持handleTransaction等方法的@MainActor标注,确保purchasedProducts等状态更新在主线程完成。

内容的提问来源于stack exchange,提问作者user1385809

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 15:04:53