Swift 尝试使用StoreKit还原应用内购买时未收到预期的代理调用

Swift 尝试使用StoreKit还原应用内购买时未收到预期的代理调用,swift,in-app-purchase,storekit,Swift,In App Purchase,Storekit,我已经阅读了许多关于在StoreKit API中恢复IAP的问题(没有一致的答案)。在测试/沙箱环境中,似乎非常模糊的部分是恢复时发生的情况 使用此方法时: @IBAction func restorePurchasesButtonPressed(_ sender: Any) { SKPaymentQueue.default().restoreCompletedTransactions(withApplicationUsername: productID) } 我没有得到

我已经阅读了许多关于在StoreKit API中恢复IAP的问题(没有一致的答案)。在测试/沙箱环境中,似乎非常模糊的部分是恢复时发生的情况

使用此方法时:

@IBAction func restorePurchasesButtonPressed(_ sender: Any) {        
   SKPaymentQueue.default().restoreCompletedTransactions(withApplicationUsername: productID)
}
我没有得到任何反馈,除了打电话给

paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue)
功能。是否没有办法确定恢复购买是否成功?我以为调用会通过这个函数:

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for transaction in transactions {
        if transaction.transactionState == .purchased {
            //item already purchased
            print("Transaction successful...")
        } else if transaction.transactionState == .failed {
            print("Transaction failed...")
        } else if transaction.transactionState == .restored {
            print("Restored purchases...")
        }
    }
}
但它没有被调用。如果我尝试购买已经针对某个产品进行的产品,则会调用updatedTransactions func,但仅使用.purchased值

我读过很多关于沙盒怪异和不可预测的东西。我已经建立了许多经过验证的沙箱帐户,但没有一个能像预期的那样工作


购买功能似乎运行良好。如果我购买了已经购买的东西,它将通过通知恢复。但是,我无法调用restoreCompletedTransactions并找出如何获得任何价值—成功或失败。我错过了什么?是否有明确的答案或解决办法?

您可以在应用程序中使用以下模块化swift文件UnlockManager.swift来实施应用程序内购买,尽管我只能保证它适用于单个非消费品,例如解锁购买。。。否则,您需要进行一些修改

不管怎样,这是肉和土豆:

UnlockManager.swift:

//  2019 Boober Bunz. No rights reserved.

import StoreKit

protocol UnlockManagerDelegate: class {
    func showUnlockPurchaseHasBeenRestoredAlert()
}

class UnlockManager : NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
    // Only used in print debugging
    // let transactionStateStrings = ["purchasing","purchased","failed","restored","deferred"]
    
    weak var delegate: UnlockManagerDelegate?
    
    private let UNLOCK_IAP_PRODUCT_ID = "your_product_ID_goes_here" // <------------------------------------ ***
    
    private var requestObject: SKProductsRequest?
    private var skProductObject: SKProduct?
    
    private var onlineAndReadyToPurchase = false
    
    override init() {
        super.init() //important that super.init() comes first
        attemptStoreKitRequest()
        SKPaymentQueue.default().add(self)
    }
    
    deinit {
        SKPaymentQueue.default().remove(self)
    }
    
    // ------------------------------------------------------------------------------------ STOREKIT CALLBACKS
    
    // SKProductsRequestDelegate response
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        
        if response.products.count > 0 {
            for product in response.products {
                if product.productIdentifier == UNLOCK_IAP_PRODUCT_ID {
                    skProductObject = product
                    onlineAndReadyToPurchase = true
                    print("IAP - StoreKit server responded with correct product.  We are ready to purchase.")
                    return // success
                }
            }
        } else { // fail
            print("IAP MODULE - on initial request, StoreKit server responded, but \(UNLOCK_IAP_PRODUCT_ID) not found.")
            print("IAP MODULE - Check for product ID mismatch.")
            print("IAP MODULE - We are not ready to purchase.\n")
        }
    }
    
    // SKProductsRequestDelegate response (fail)
    public func request(_ request: SKRequest, didFailWithError error: Error) {
        print("IAP MODULE - on initial request, StoreKit server responded with explicit error: \(error.localizedDescription)")
    }
    
    
    // SKPaymentTransactionObserver calls this
    public func paymentQueue(_ queue: SKPaymentQueue,
                             updatedTransactions transactions: [SKPaymentTransaction]) {
        
        print("IAP MODULE - SKPaymentTransactionObserver called paymentQueue()...")
        
        //    print("Transaction Queue:")
        
        for transaction in transactions {
            //    print("\n PRODUCT ID: \(transaction.payment.productIdentifier)")
            //    print(" TRANS ID: \(transaction.transactionIdentifier)")
            //    print(" TRANS STATE: \(transactionStateStrings[transaction.transactionState.rawValue])")
            //    print(" TRANS DATE: \(transaction.transactionDate)")
            
            //    print("\nActions taken as a result of trans.state...")
            
            switch transaction.transactionState {
            case .purchased:
                
                if (transaction.payment.productIdentifier == UNLOCK_IAP_PRODUCT_ID) {
                    print("IAP MODULE - successful purchase of \(UNLOCK_IAP_PRODUCT_ID), so unlocking")
                    UserDefaults.standard.set(true, forKey: UNLOCK_IAP_PRODUCT_ID)
                    SKPaymentQueue.default().finishTransaction(transaction)
                }
                break
            case .restored:
                guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
                if (productIdentifier == UNLOCK_IAP_PRODUCT_ID) {
                    if !(appIsUnlocked()){
                        delegate?.showUnlockPurchaseHasBeenRestoredAlert()
                    }
                    print("IAP MODULE - previous purchase of \(UNLOCK_IAP_PRODUCT_ID), so restoring/unlocking")
                    UserDefaults.standard.set(true, forKey: UNLOCK_IAP_PRODUCT_ID)
                    SKPaymentQueue.default().finishTransaction(transaction)
                }
                break
            case .failed:
                if let transactionError = transaction.error as NSError?,
                    let localizedDescription = transaction.error?.localizedDescription,
                    transactionError.code != SKError.paymentCancelled.rawValue {
                    print("IAP MODULE - ... error in transaction \(transaction.transactionIdentifier ?? "no ID?") for product: \((transaction.payment.productIdentifier)): \(localizedDescription)")
                }
                SKPaymentQueue.default().finishTransaction(transaction)
                break
            case .deferred:
                break
            case .purchasing:
                break
            default:
                break
            }
        }
    }
    
    // ------------------------------------------------------------------------------------ PUBLIC ONLY METHODS
    public func purchaseApp() -> Bool {
        if !onlineAndReadyToPurchase {
            print("IAP MODULE - Purchase attempted but we are not ready!")
            return false
        }
        print("IAP MODULE - Buying \(skProductObject!.productIdentifier)...")
        let payment = SKPayment(product: skProductObject!)
        SKPaymentQueue.default().add(payment)
        return true
    }
    
    public func restorePurchases() -> Bool {
        if !onlineAndReadyToPurchase {
            print("IAP MODULE - User attempted restore, but we are presumbly not online!")
            return false
        }
        SKPaymentQueue.default().restoreCompletedTransactions()
        return true
    }
    
    public func appIsUnlocked() -> Bool {
        return UserDefaults.standard.bool(forKey: UNLOCK_IAP_PRODUCT_ID)
    }
    // ------------------------------------------------------------------------------------ PUBLIC AND INTERNAL METHODS
    
    // Presumably called on app start-up
    // AND (for good measure) when user is presented with purchase dialog
    public func attemptStoreKitRequest() {
        if !onlineAndReadyToPurchase {
            requestObject = SKProductsRequest(productIdentifiers: [UNLOCK_IAP_PRODUCT_ID])
            print("IAP MODULE - sending request to StoreKit server for product ID: \(UNLOCK_IAP_PRODUCT_ID)...")
            print("IAP MODULE - waiting for response...")
            requestObject?.delegate = self
            requestObject?.start()
        }
    }
    
    
}
//2019年布伯床。不保留任何权利。
进口存储套件
协议UnlockManagerDelegate:类{
func showUnlockPurchaseBeenRestoredalert()
}
类解锁管理器:NSObject、SKProductsRequestDelegate、SKPaymentTransactionObserver{
//仅用于打印调试
//let transactionStateStrings=[“采购”、“采购”、“失败”、“恢复”、“延期”]
弱var委托:解锁ManagerDelegate?
private let UNLOCK\u IAP\u PRODUCT\u ID=“您的\u PRODUCT\u ID\u在此处打开”//0{
用于响应的产品。产品{
如果product.productIdentifier==解锁\u IAP\u产品\u ID{
skProductObject=产品
Online和AdyToPurchase=true
打印(“IAP-StoreKit服务器响应了正确的产品。我们准备购买。”)
返回//成功
}
}
}否则{//失败
打印(“IAP模块-在初始请求时,StoreKit服务器响应,但未找到\(解锁IAP产品ID)。”
打印(“IAP模块-检查产品ID不匹配。”)
打印(“IAP模块-我们尚未准备好购买。\n”)
}
}
//SKProductsRequestDelegate响应(失败)
公共函数请求(请求:SKRequest,错误:error){
打印(“IAP模块-在初始请求时,StoreKit服务器以显式错误响应:\(error.localizedDescription)”)
}
//SKPaymentTransactionObserver调用此
公共函数paymentQueue(queue:SKPaymentQueue,
更新的交易记录:[SKPaymentTransaction]){
打印(“IAP模块-SKPaymentTransactionObserver调用paymentQueue()…”)
//打印(“事务队列:”)
交易中的交易{
//打印(“\n产品ID:\(transaction.payment.productIdentifier)”)
//打印(“事务ID:\(transaction.transactionIdentifier)”)
//打印(“TRANS-STATE:\(transactionStateStrings[transaction.transactionState.rawValue]))
//打印(“交易日期:\(transaction.transactionDate)”)
//打印(“\n由于传输状态而采取的操作…”)
切换事务.transactionState{
案例。购买:
if(transaction.payment.productIdentifier==解锁\u IAP\u产品\u ID){
打印(“IAP模块-成功购买\(解锁IAP产品ID),因此解锁”)
UserDefaults.standard.set(true,forKey:UNLOCK\u IAP\u PRODUCT\u ID)
SKPaymentQueue.default().finishTransaction(事务)
}
打破
案件.恢复:
guard let productIdentifier=transaction.original?.payment.productIdentifier else{return}
if(productIdentifier==解锁\u IAP\u产品\u ID){
如果!(appIsUnlocked()){
委托?.showUnlockPurchaseBeenRestoredalert()
}
打印(“IAP模块-上次购买\(解锁IAP产品ID),以便恢复/解锁”)
UserDefaults.standard.set(true,forKey:UNLOCK\u IAP\u PRODUCT\u ID)
SKPaymentQueue.default().finishTransaction(事务)
}
打破
案例。失败:
如果让transactionError=transaction.error作为NSError?,
让localizedDescription=transaction.error?.localizedDescription,
transactionError.code!=SKError.paymentCancelled.rawValue{
打印产品:\((transaction.payment.productIdentifier)):\(localizedDescription)的(“IAP模块-…交易\(transaction.transactionIdentifier??“无ID?”)中的错误)
}
SKPaymentQueue.default().finishTransaction(事务)
打破
案件.推迟:
打破
案例.采购:
打破
违约:
打破
}
}
}
//---------------------------------------------------------------------------------------仅公共方法
public func purchaseApp()->Bool{
如果!在线和AdyToPurchase{
打印(“IAP模块-已尝试购买,但我们