尝试使用 StoreKit 恢复应用内购买时未收到预期的委托调用
Not getting expected delegate calls when trying to restore in-app purchases with StoreKit
我已经阅读了很多关于在 StoreKit 中恢复 IAP 的问题(没有一致的答案)API。在 test/sandbox 环境中,似乎令人难以置信的模棱两可的部分是恢复时发生的情况。
使用此方法时:
@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 函数,但仅使用 .purchased 值。
我读过很多关于沙盒怪异且不可预测的内容。我已经设置了许多经过验证的沙盒帐户,none 其中的帐户按预期工作。
购买功能似乎运行良好。如果我购买了已经购买的东西,它会通过通知恢复。但是,我无法调用 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()
}
}
}
我遇到了同样的问题。
似乎,当 iOS 版本低于 13.0 时调用 updatedTransactions 时出现问题
它从 iOS 13.0 开始运行良好。
[我针对低于 13.0 的 iOS 版本的解决方法:
显示如下消息:如果产品已购买,请点击“恢复”按钮。
因为如果我们点击购买,它会显示“此应用内已购买消息”,点击确定后,它不会调用 updatedTransactions 方法。]
我已经阅读了很多关于在 StoreKit 中恢复 IAP 的问题(没有一致的答案)API。在 test/sandbox 环境中,似乎令人难以置信的模棱两可的部分是恢复时发生的情况。
使用此方法时:
@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 函数,但仅使用 .purchased 值。
我读过很多关于沙盒怪异且不可预测的内容。我已经设置了许多经过验证的沙盒帐户,none 其中的帐户按预期工作。
购买功能似乎运行良好。如果我购买了已经购买的东西,它会通过通知恢复。但是,我无法调用 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()
}
}
}
我遇到了同样的问题。 似乎,当 iOS 版本低于 13.0 时调用 updatedTransactions 时出现问题 它从 iOS 13.0 开始运行良好。
[我针对低于 13.0 的 iOS 版本的解决方法: 显示如下消息:如果产品已购买,请点击“恢复”按钮。 因为如果我们点击购买,它会显示“此应用内已购买消息”,点击确定后,它不会调用 updatedTransactions 方法。]