应用内购买消耗品自动恢复

Posted

技术标签:

【中文标题】应用内购买消耗品自动恢复【英文标题】:In-App Purchase consumable being restored automatically 【发布时间】:2017-05-04 17:28:39 【问题描述】:

我正在做我的第一次应用内购买,一个奇怪的行为是正在恢复消耗品而不是创建新交易。

我已经跟着tutohttps://www.raywenderlich.com/122144/in-app-purchase-tutorial

我发现了一种优雅的方法。

这是我的 StoreKit 助手:

import StoreKit

public typealias ProductIdentifier = String

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



open class IAPHelper : NSObject  
    fileprivate let productIdentifiers: Set<ProductIdentifier>
    fileprivate var purchasedProductIdentifiers: Set<ProductIdentifier> = Set()
    fileprivate var productsRequest: SKProductsRequest?
    fileprivate var productsRequestCompletionHandler: ProductsRequestCompletionHandler?

    static let IAPHelperPurchaseNotification = "IAPHelperPurchaseNotification"

    public init(productIds: Set<ProductIdentifier>) 
        productIdentifiers = productIds
        super.init()
        SKPaymentQueue.default().add(self)
    


// MARK: - StoreKit API

extension IAPHelper 

    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 class func canMakePayments() -> Bool 
        return SKPaymentQueue.canMakePayments()
    

    public func restorePurchases() 
        SKPaymentQueue.default().restoreCompletedTransactions()
    


// MARK: - SKProductsRequestDelegate

extension IAPHelper: 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
    


// MARK: - SKPaymentTransactionObserver

extension IAPHelper: 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
            
        
    

    private func complete(transaction: SKPaymentTransaction) 
        print("complete...")
        validateReceipt()
        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 
            if transactionError.code != SKError.paymentCancelled.rawValue 
                print("Transaction Error: \(transaction.error?.localizedDescription)")
            
        

        SKPaymentQueue.default().finishTransaction(transaction)
    

    private func deliverPurchaseNotificationFor(identifier: String?) 
        guard let identifier = identifier else  return 

        purchasedProductIdentifiers.insert(identifier)
        UserDefaults.standard.set(true, forKey: identifier)
        UserDefaults.standard.synchronize()
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: IAPHelper.IAPHelperPurchaseNotification), object: identifier)
    

我可以买一次,第二次自动恢复:

此应用内购买已被购买。它将恢复为 免费

当我收到此消息时,没有调用任何 IAPHelper 方法。

我的 iTunes 显示它是消耗品:

即使卸载购买的应用程序仍在恢复中。

在我的第一次测试中,它看起来确实像一个 Apple 错误,我可以在没有此消息的情况下购买 2..3 次。

如果不是bug,如何防止这种情况发生?

【问题讨论】:

【参考方案1】:

对于那些陷入这种愚蠢情况的人,这里有一些 WA 和解决方案:

queue.finishTransaction(transaction)

重要提示:在您的paymentQueue 方法中完成事务:

public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) 
    for transaction in transactions 
        switch (transaction.transactionState) 
        case .purchased:
            complete(transaction: transaction)
            queue.finishTransaction(transaction)
            break
        case .failed:
            fail(transaction: transaction)
            queue.finishTransaction(transaction)
            break
        case .restored:
            restore(transaction: transaction)
            queue.finishTransaction(transaction)
            break
        case .deferred:
            break
        case .purchasing:
            break
        
    

这样可以避免这种情况。

如果您已经卡在它上面,这里是解决方法,仅用于测试目的

    for transaction in SKPaymentQueue.default().transactions 
        print(transaction)
        SKPaymentQueue.default().finishTransaction(transaction)
    

您可以将它放在一个临时按钮上,并在测试时一键清除所有交易。

至少它今天救了我的命。

【讨论】:

为我工作.. 非常感谢 为我工作。谢谢。 感谢您为我节省了时间! 如何使用 StoreKit 2 做到这一点?

以上是关于应用内购买消耗品自动恢复的主要内容,如果未能解决你的问题,请参考以下文章

如何恢复消耗品应用内购买?

恢复应用内购买

如何正确恢复应用内购买?

尽管没有登录 iTunes 帐户,iOS 仍会恢复应用内购买

如何在 iOS 中取消或退款已购买的消耗品应用内购买?

如何在 iTune Connect 上为我的 iPhone 应用程序设置自动更新应用程序内购买?