在Storekit应用程序启动时添加事务队列观察器的最佳实践 [英] Best practice to add a transaction queue observer at application launch for Storekit

查看:45
本文介绍了在Storekit应用程序启动时添加事务队列观察器的最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在尝试遵循Apple在didFinishLaunchingWithOptions中添加事务队列观察器的建议时遇到了麻烦.

I am having trouble trying to follow Apple's recommendation of adding a transaction queue observer in didFinishLaunchingWithOptions.

具体来说,我正在尝试改编不执行此操作的Ray Wenderlich教程中的代码-仅在点击购买"按钮后才添加观察者.

Specifically I am trying to adapt code from a Ray Wenderlich tutorial which does not do this - it adds the observer only once the 'buy' button is tapped.

调用buyProduct函数时,我的应用程序崩溃:

My app crashes when the buyProduct function is called:

public func buyProduct(_ product: SKProduct) {
    print("Buying \(product.productIdentifier)...")
    let payment = SKPayment(product: product)
    SKPaymentQueue.default().add(payment)
}

在我的日志中,我看到IAPHelper的init被调用了两次,因此调用了SKPaymentQueue.default().add(self)两次.我敢肯定这就是问题所在,但是我很困惑如何解决.

In my log I can see that the init of IAPHelper is called twice, thus calling SKPaymentQueue.default().add(self) twice. I am sure that's the problem but I'm confused how to fix it.

这是我的代码...

AppDelegate:

AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

  IAPHelper.sharedInstance = IAPHelper() // creates the singleton for IAPHelper

  // other code here //

  return true
}

IAPHelper.swift:

IAPHelper.swift:

import StoreKit

/// Notification that is generated when a product is purchased.
public let IAPHelperPurchaseNotification = "IAPHelperPurchaseNotification"

/// Notification that is generated when a transaction fails.
public let IAPHelperTransactionFailedNotification = "IAPHelperTransactionFailedNotification"

/// Notification that is generated when cannot retrieve IAPs from iTunes.
public let IAPHelperConnectionErrorNotification = "IAPHelperConnectionErrorNotification"

/// Notification that is generated when we need to stop the spinner.
public let IAPHelperStopSpinnerNotification = "IAPHelperStopSpinnerNotification"

/// Product identifiers are unique strings registered on the app store.
public typealias ProductIdentifier = String

/// Completion handler called when products are fetched.
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> ()

open class IAPHelper : NSObject  {

  /// MARK: - User facing API
  fileprivate let productIdentifiers: Set<ProductIdentifier>
  fileprivate var purchasedProductIdentifiers = Set<ProductIdentifier>()
  fileprivate var productsRequest: SKProductsRequest?
  fileprivate var productsRequestCompletionHandler: ProductsRequestCompletionHandler?

  static var sharedInstance = IAPHelper()  //  singleton

  override init() {

    // Set up the list of productIdentifiers
    let PackOf4000Coins =  "com.xxx.xxx.4000Coins"   
    let PackOf10000Coins =  "com.xxx.xxx.10000Coins"
    let PackOf30000Coins =  "com.xxx.xxx.30000Coins"
    let PackOf75000Coins =  "com.xxx.xxx.75000Coins"
    let PackOf175000Coins = "com.xxx.xxx.175000Coins"
    let PackOf750000Coins = "com.xxx.xxx.750000Coins"
    let RemoveAds =  "com.xxx.xxx.RemoveAds"
    let PlayerEditor =  "com.xxx.xxx.PlayerEditor"

    self.productIdentifiers = [PackOf4000Coins, PackOf10000Coins, PackOf30000Coins, PackOf75000Coins, PackOf175000Coins, PackOf750000Coins, RemoveAds, PlayerEditor]

    for productIdentifier in self.productIdentifiers {
        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)
  }
}

// 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()

  }

  public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    print("Restore queue finished.")
    NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperStopSpinnerNotification), object: nil)
  }

  public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
    print("Restore queue failed.")
    NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperConnectionErrorNotification), object: nil)
  }

}

// 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)
    NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperConnectionErrorNotification), object: nil)
    clearRequestAndHandler()
  }

  fileprivate 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:
            completeTransaction(transaction)
            break
        case .failed:
            failedTransaction(transaction)
            break
        case .restored:
            restoreTransaction(transaction)
            break
        case .deferred:
            break
        case .purchasing:
            break
        }
    }
}

  fileprivate func completeTransaction(_ transaction: SKPaymentTransaction) {
    print("completeTransaction...")

 deliverPurchaseNotificationForIdentifier(
            transaction.payment.productIdentifier)

    SKPaymentQueue.default().finishTransaction(transaction)
  }

  fileprivate func restoreTransaction(_ transaction: SKPaymentTransaction) {
    guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }

    print("restoreTransaction... \(productIdentifier)")
    deliverPurchaseNotificationForIdentifier(productIdentifier)
    SKPaymentQueue.default().finishTransaction(transaction)
}

fileprivate func failedTransaction(_ transaction: SKPaymentTransaction) {
    print("failedTransaction...")
    NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperStopSpinnerNotification), object: nil)
    if transaction.error!._code != SKError.paymentCancelled.rawValue {
        print("Transaction Error: \(String(describing: transaction.error?.localizedDescription))")
        NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperTransactionFailedNotification), object: nil)
    } else {
        print("Transaction Error else statement")
    }

    SKPaymentQueue.default().finishTransaction(transaction)
    NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperTransactionFailedNotification), object: nil)

}

fileprivate func deliverPurchaseNotificationForIdentifier(_ identifier: String?) {
    guard let identifier = identifier else { return }

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

GameStoreViewController.swift(仅相关代码):

GameStoreViewController.swift (relevant code only):

@objc func tableView(_ tableView: UITableView!, didSelectRowAtIndexPath indexPath: IndexPath!) {
    if IAPHelper.canMakePayments() {
        activitySpinnerStart()
        let product = _coinProducts[(indexPath as NSIndexPath).row]
        IAPHelper.sharedInstance.buyProduct(product)  // Purchasing the product. Fires productPurchased(notification:)
    } else {
        showAlertWith(Localization("NoConnectionAlertTitle"), message: Localization("NoIAPAllowedMessage"))
    }
}

推荐答案

最后,我使用了 SwiftyStoreKit 这为我解决了问题.我强烈推荐它.

In the end I used SwiftyStoreKit which solved the problem for me. I highly recommend it.

编辑以显示SwiftyStoreKit.completeTransactions,该内容进入appDelegate中的didFinishLaunchingWithOptions(). unlockIAPContent()是我放置购买逻辑的地方,它将处理促销代码和未完成的交易:

Edit to show SwiftyStoreKit.completeTransactions which goes in didFinishLaunchingWithOptions() in appDelegate. unlockIAPContent() is where I put the purchase logic and this will handle promo codes and unfinished transactions:

    // This registers the transaction observer and listens for unfinished transactions
    SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
        for purchase in purchases {
            switch purchase.transaction.transactionState {
            case .purchased, .restored: 

                // Unlock content
                self.unlockIAPContent(productID: purchase.productId) 

                if purchase.needsFinishTransaction {
                    SwiftyStoreKit.finishTransaction(purchase.transaction)
                }

            case .failed, .purchasing, .deferred:
                break // do nothing
            }
        }
    }

这篇关于在Storekit应用程序启动时添加事务队列观察器的最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆