iOS 应用内购买不适用于 TestFlight 中的非沙盒帐户

Posted

技术标签:

【中文标题】iOS 应用内购买不适用于 TestFlight 中的非沙盒帐户【英文标题】:iOS In-App-Purchases don't work for non-sandbox accounts in TestFlight 【发布时间】:2020-11-01 12:43:00 【问题描述】:

我使用来自https://www.raywenderlich.com/5456-in-app-purchase-tutorial-getting-started 的教程在我的应用中实现了应用内购买。我没有使用我自己的服务器来验证购买或类似的东西。

我创建了一个沙盒用户来测试代码。一切正常,但是,如果我尝试使用我的个人 Apple ID 登录,购买将失败(在 TestFlight 上测试)。

这是预期的行为还是我做错了什么? https://***.com/a/37042040/11912101 声明每个帐户都应该能够购买物品。

另外,我在 App Store Connect 中为沙盒用户启用了“中断购买”选项。下面的代码将运行函数 failed(...),即使购买已通过并且该项目在我的应用程序中已解锁并被添加到购买的 ProductIdentifiers 中。有没有办法处理那些中断的购买?

感谢您的回答!

import StoreKit

public struct InAppPurchases 
    
    static let proVersionID = "myapp.proversion"
    private static let productIdentifiers: Set<ProductIdentifier> = [proVersionID]
    
    public static let helper = InAppPurchaseHelper(productIds: InAppPurchases.productIdentifiers)


func resourceNameForProductIdentifier(_ productIdentifier: String) -> String? 
    return productIdentifier.components(separatedBy: ".").last


public typealias ProductIdentifier = String
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> Void

extension Notification.Name 
    static let IAPHelperPurchaseNotification = Notification.Name("IAPHelperPurchaseNotification")


open class InAppPurchaseHelper: NSObject, ObservableObject  
    
    private let productIdentifiers: Set<ProductIdentifier>
    private var purchasedProductIdentifiers: Set<ProductIdentifier> = [] 
        willSet 
            self.objectWillChange.send()
        
    
    private var productsRequest: SKProductsRequest?
    private var productsRequestCompletionHandler: ProductsRequestCompletionHandler?

    @Published var didFail = false
    
    var availableProducts = [SKProduct]() 
        willSet 
            DispatchQueue.main.async 
                self.objectWillChange.send()
            
        
    
    public init(productIds: Set<ProductIdentifier>) 
        productIdentifiers = productIds
        for productIdentifier in productIds 
            let purchased = UserDefaults.standard.bool(forKey: productIdentifier)
            if purchased 
                purchasedProductIdentifiers.insert(productIdentifier)
                print("Previously purchased: \(productIdentifier)")
             else 
                print("Not purchased: \(productIdentifier)")
            
        
        super.init()
        
        SKPaymentQueue.default().add(self)
        
        reloadInAppPurchases()   
    
    
    func reloadInAppPurchases() 
        DispatchQueue.main.async 
            
            InAppPurchases.helper.requestProducts [weak self] success, products in
                guard let self = self else  return 
                if success 
                    self.availableProducts = products!
                
            
        
    


extension InAppPurchaseHelper 
    
    public func requestProducts(_ completionHandler: @escaping ProductsRequestCompletionHandler) 
        productsRequest?.cancel()
        productsRequestCompletionHandler = completionHandler
        
        productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
        productsRequest!.delegate = self
        productsRequest!.start()
    
    
    public func buyProduct(_ product: SKProduct) 
        print("Buying \(product.productIdentifier)...")
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    
    
    public func isProductPurchased(_ productIdentifier: ProductIdentifier) -> Bool 
        return purchasedProductIdentifiers.contains(productIdentifier)
    
    
    public func isProVersion () -> Bool 
        if(isProductPurchased(InAppPurchases.proVersionID)) 
            return true
        
        return false
    
    
    public class func canMakePayments() -> Bool 
        return SKPaymentQueue.canMakePayments()
    
    
    public func restorePurchases() 
        SKPaymentQueue.default().restoreCompletedTransactions()
    


extension InAppPurchaseHelper: SKProductsRequestDelegate 
    
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) 
        print("Loaded list of products...")
        let products = response.products
        productsRequestCompletionHandler?(true, products)
        clearRequestAndHandler()
        
        for p in products 
            print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
        
    
    
    public func request(_ request: SKRequest, didFailWithError error: Error) 
        print("Failed to load list of products.")
        print("Error: \(error.localizedDescription)")
        productsRequestCompletionHandler?(false, nil)
        clearRequestAndHandler()
    
    
    private func clearRequestAndHandler() 
        productsRequest = nil
        productsRequestCompletionHandler = nil
    



extension InAppPurchaseHelper: SKPaymentTransactionObserver 
    
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) 
        for transaction in transactions 
            switch (transaction.transactionState) 
            case .purchased:
                complete(transaction: transaction)
                break
            case .failed:
                fail(transaction: transaction)
                break
            case .restored:
                restore(transaction: transaction)
                break
            case .deferred:
                break
            case .purchasing:
                break
            @unknown default:
                break
            
        
    
    
    private func complete(transaction: SKPaymentTransaction) 
        print("complete...")
        deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
    
    
    private func restore(transaction: SKPaymentTransaction) 
        guard let productIdentifier = transaction.original?.payment.productIdentifier else  return 
        
        print("restore... \(productIdentifier)")
        deliverPurchaseNotificationFor(identifier: productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
    
    
    private func fail(transaction: SKPaymentTransaction) 
        print("fail...")
        
        if let transactionError = transaction.error as NSError?,
           let localizedDescription = transaction.error?.localizedDescription,
           transactionError.code != SKError.paymentCancelled.rawValue 
            print("Transaction Error: \(localizedDescription)")
        
        
        SKPaymentQueue.default().finishTransaction(transaction)
        
        if(!isProVersion())
            didFail.toggle()
        
    
    
    private func deliverPurchaseNotificationFor(identifier: String?) 
        guard let identifier = identifier else  return 
        
        purchasedProductIdentifiers.insert(identifier)
        UserDefaults.standard.set(true, forKey: identifier)
        NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier)
    


【问题讨论】:

【参考方案1】:

请参阅 this 和 this。

我最近使用其他帐户测试 IAP 没有问题。这很奇怪,因为默认情况下 testflight 是沙盒环境,所以我认为不需要像发布版本那样等待,但可能会等待几个小时以防万一。

【讨论】:

谢谢。我的代码正确吗?还是我需要为发布更改一些内容? 如果使用沙盒账户支付成功,那么您的代码应该没问题。您是否在 testflight 中填写了完整的测试信息?我建议您这样做并等待几个小时再进行测试。 关于“中断购买”问题,我不确定,因为我自己没有遇到过。但是,一旦您从自己的服务器验证购买,也许会得到处理,因为在结束购买之前会有额外的检查。 是的,我在 TestFlight 中填写了所有测试信息。仍然无法使用我的普通 iCloud 帐户登录。测试应用时,输入密码和id后登录页面消失,购买失败。

以上是关于iOS 应用内购买不适用于 TestFlight 中的非沙盒帐户的主要内容,如果未能解决你的问题,请参考以下文章

iOS 应用内购买不适用于真实账户,但可以使用沙盒

应用内购买适用于 iOS 3 和 5,但不适用于 iOS 4

如何通过Apple TestFlight测试应用内购买?

Swift 和 TestFlight 适用于 iOS 8 但不适用于 iOS 7

产品不适用于 Phonegap 中的应用内购买

iTunes 连接“测试用户”不适用于应用内购买