I am trying to restore Non-Consumable IAP with SKPaymentQueue.default().restoreCompletedTransactions() and nothing work according to plan. In sandbox I get "There's no information available for In-App Purchases. Try again later. 21105" and in Prod just nothing happens. From what I can see that error in Sandbox is thrown with actual SKPaymentQueue.default().restoreCompletedTransactions() line and nothing is processed after that. I am sure everything is right, since I can make my purchase by hand and it works, I can also repurchase anything again with error, that I already have it and then item get activated, but Restore button is not working (restorePurchases()). I do have 38 IAP in this app, but I am not sure if that is related in any way, and they all are Non-Consumable. It is iMessage Extension that sells stickers. What is also strange, is that Apple did kick my app back because I used my Own Art without My own permission (that was halarious), but everything states, that with Non-Consumable must have Restore and Apple check for that, and in this case everything passed while it is not working.
My whiole IAP handlicg class look like this:
import Foundation
import StoreKit
import os.log
class IAPService: NSObject
{
private override init() {} // make sure there is no extra copies
static let shared = IAPService() // makes this singleton
fileprivate var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
let defaults = UserDefaults.standard
var haveData = false
var reference = StickewrsCollectionViewController()
fileprivate var request = SKProductsRequest()
let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "IAPService")
let receiptFetcher = ReceiptFetcher()
func getProducts()
{
let allStickersIAP = “IAP reference”
var iapList = Dictionary<string, string="">()
if let path = Bundle.main.path(forResource: "StickersData", ofType: ".plist")
{
let dict = NSDictionary(contentsOfFile: path) as! Dictionary<string, anyobject="">
let globalData = dict["StickerData"] as! Array
iapList = (globalData[1]) as! Dictionary<string, string="">
}
var products: Set = [allStickersIAP]
for iap in iapList
{
products.insert(iap.value)
}
request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
paymentQueue.add(self)
SKPaymentQueue.default().add(self)
os_log("Getting products")
}
func purchase(product: String)
{
if (SKPaymentQueue.canMakePayments())
{
guard let productToPurchase = products.filter({$0.productIdentifier == product}).first
else {return}
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
}
func restorePurchases()
{
print("Restoring purchases")
os_log("Restoring purchases")
SKPaymentQueue.default().restoreCompletedTransactions()
}
func iapCheck() -> Bool
{
return SKPaymentQueue.canMakePayments() && haveData
}
func getProductData(iap: String) -> SKProduct
{
return products.filter({$0.productIdentifier == iap}).first ?? SKProduct()
}
func priceStringForProduct(item: SKProduct) -> String? {
let price = item.price
if price == NSDecimalNumber(decimal: 0.00) {
return NSLocalizedString("free", comment: "")
} else {
let numberFormatter = NumberFormatter()
let locale = item.priceLocale
numberFormatter.numberStyle = .currency
numberFormatter.locale = locale
return numberFormatter.string(from: price)
}
}
func setReference(ref: StickewrsCollectionViewController)
{
reference = ref
}
public func passPopUp(_ text: String)
{
reference.ShowPopUp(text)
}
// Called when the application is about to terminate.
func applicationWillTerminate(_ application: UIApplication) {
// Remove the observer.
SKPaymentQueue.default().remove(self)
}
}
extension IAPService: SKProductsRequestDelegate, SKPaymentTransactionObserver
{
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
haveData = true
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState
{
case .purchasing: break
case .purchased:
do {
defaults.set(true, forKey: transaction.payment.productIdentifier)
defaults.synchronize() // save changes in PlayerPrefs
print(transaction.payment.productIdentifier)
queue.finishTransaction(transaction)
}
case .restored:
do {
print("Do actual restoring")
os_log("Now restoring %@", transaction.original!.payment.productIdentifier)
reference.ShowPopUp("Restoring purchase \(transaction.original!.payment.productIdentifier)")
defaults.set(true, forKey: transaction.original!.payment.productIdentifier)
defaults.synchronize() // save changes in PlayerPrefs
print(transaction.payment.productIdentifier)
queue.finishTransaction(transaction)
}
default: queue.finishTransaction(transaction)
}
}
reference.redrawnAfterPurchase()
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
defaults.set(true, forKey: prodID)
defaults.synchronize() // save changews in PlayerPrefs
print(prodID)
queue.finishTransaction(transaction)
}
reference.redrawnAfterPurchase()
reference.ShowPopUp(NSLocalizedString("restoreCompleted", comment: ""))
}
}
extension SKPaymentTransactionState
{
func status() -> String
{
switch self {
case .deferred: return "deferred"
case .failed: return "failed"
case .purchased: return "purchased"
case .purchasing: return "purchasing"
case .restored: return "restored"
}
}
}
Whart I did wrong and how I can make RestorePurchases actually do something ?