Restore purchase is restoring the purchase when user hasn’t purchased at all

I am using Storekit to implement In-App Purchase in my app. It’s live now. I have implemented a non-consumable purchase to remove ads. Now the issue is if even a new user tap on restore purchase, it’s successfully restoring the purchase which it shouldn’t. I am unable to debug it due to lack of testing device but I have asked multiple people to download the app and restore the purchase and they were successfully transitioned to Premium.

Below is the code I am using:

enum IAPHandlerAlertType {
    case initialize
    case setProductIds
    case disabled
    case restored
    case purchased
    case failed
    case error
    case restoreFailed
    
    var message: String{
        switch self {
        case .error: return "An error occured"
        case .initialize: return ""
        case .setProductIds: return "Product ids not set, call setProductIds method!"
        case .disabled: return "Purchases are disabled in your device!"
        case .restored: return "You've successfully restored your purchase!"
        case .purchased: return "You've successfully bought this purchase!"
        case .failed: return "Failed to buy this purchase!"
        case .restoreFailed: return "Failed to restore this purchase!"
        }
    }
}


class IAPManager: NSObject {
    
    //MARK:- Shared Object
    //MARK:-
    static let shared = IAPManager()
    private override init() { }
    
    //MARK:- Properties
    //MARK:- Private
    fileprivate var productIds = ["com.identifier.appName.removeAds"]
    fileprivate var productID = ""
    fileprivate var productsRequest = SKProductsRequest()
    fileprivate var fetchProductComplition: (([SKProduct])->Void)?
    
    fileprivate var productToPurchase: SKProduct?
    var purchaseProductComplition: ((IAPHandlerAlertType, Error?)->Void)?
    
    //MARK:- Public
    var isLogEnabled: Bool = true
    
    //MARK:- Methods
    //MARK:- Public
    
    //Set Product Ids
    func setProductIds(ids: [String]) {
        self.productIds = ids
    }

    //MAKE PURCHASE OF A PRODUCT
    func canMakePurchases() -> Bool {  return SKPaymentQueue.canMakePayments()  }
    
    func purchase(product: SKProduct, completion: @escaping ((IAPHandlerAlertType, Error?) -> Void)) {
        
        self.purchaseProductComplition = completion
        self.productToPurchase = product

        if self.canMakePurchases() {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(self)
            SKPaymentQueue.default().add(payment)
            
            log("PRODUCT TO PURCHASE: (product.productIdentifier)")
            productID = product.productIdentifier
        }
        else {
            completion(IAPHandlerAlertType.disabled, nil)
        }
    }
    
    // RESTORE PURCHASE
    func restorePurchase(){
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
    
    
    // FETCH AVAILABLE IAP PRODUCTS
    func fetchAvailableProducts(completion: @escaping (([SKProduct])->Void)){
        
        self.fetchProductComplition = completion
        // Put here your IAP Products ID's
        if self.productIds.isEmpty {
            log(IAPHandlerAlertType.setProductIds.message)
            fatalError(IAPHandlerAlertType.setProductIds.message)
        }
        else {
            productsRequest = SKProductsRequest(productIdentifiers: Set(self.productIds))
            productsRequest.delegate = self
            productsRequest.start()
        }
    }
    
    //MARK:- Private
    fileprivate func log <T> (_ object: T) {
        if isLogEnabled {
            NSLog("(object)")
        }
    }
}

//MARK:- Product Request Delegate and Payment Transaction Methods

extension IAPManager: SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
    func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
        
        if let completion = self.fetchProductComplition {
            completion(response.products)
        }
    }
    
    func request(_ request: SKRequest, didFailWithError error: Error) {
        if let completion = self.fetchProductComplition {
            completion([])
        }
    }
    
    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        if let completion = self.purchaseProductComplition {
            completion(IAPHandlerAlertType.restored, nil)
        }
    }
    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
        if let completion = self.purchaseProductComplition {
            completion(IAPHandlerAlertType.restoreFailed, error)
        }
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction:AnyObject in transactions {
            if let trans = transaction as? SKPaymentTransaction {
                switch trans.transactionState {
                case .purchased:
                    log("Product purchase done")
                    SKPaymentQueue.default().finishTransaction(trans)
                    if let completion = self.purchaseProductComplition {
                        completion(IAPHandlerAlertType.purchased, nil)
                    }
                    break
                    
                case .failed:
                    log("Product purchase failed")
                    SKPaymentQueue.default().finishTransaction(trans)
                    if let completion = self.purchaseProductComplition {
                        completion(IAPHandlerAlertType.failed, trans.error)
                    }
                    break
                case .restored:
                    log("Product restored")
                    SKPaymentQueue.default().finishTransaction(trans)
                    if let completion = self.purchaseProductComplition {
                        completion(IAPHandlerAlertType.restored, nil)
                    }
                    break
                    
                default: break
                }
            }
        }
    }
}

ViewModel

func restoreAction() {
            Spinner.start()
            IAPManager.shared.fetchAvailableProducts { products in
                if products.count > 0 {
                    IAPManager.shared.restorePurchase()
                    IAPManager.shared.purchaseProductComplition = { [self] result, error in
                        self.handlePurchaseRestoreResult(result: result, error: error)
                        Spinner.stop()
                    }
                } else {
                    Spinner.stop()
                }
            }
        }
    
    
     func handlePurchaseRestoreResult(result: IAPHandlerAlertType, error: Error?) {
            if error != nil {
                showAlert = .init(id: .error)
                return
            }
            switch result {
            case .disabled:
                showAlert = .init(id: .disabled)
            case .purchased:
                Defaults.isPremiumPurchased = true
                Defaults.totalCoins += 1000
                isPremiumPurchased = 1
                break
            case .restored:
                Defaults.isPremiumPurchased = true
                isPremiumPurchased = 1
                break
            case .failed:
                showAlert = .init(id: .failed)
            default:
                break
            }
        }

Am I doing something wrong here?

1

In your paymentQueueRestoreCompletedTransactionsFinished delegate method you are calling your completionHandler and passing IAPHandlerAlertType.restored.

In your handlePurchaseRestoreResult this status results in you setting isPremiumPurchased = 1

However, you have misunderstood the purpose of the paymentQueueRestoreCompletedTransactionsFinished method – It simply indicates that the restoration process is complete. You would typically use this to update your UI; removing an activity indicator for example.

This delegate method is called regardless of whether there were any purchases to restore, so you should not set isPremiumPurchased = 1 simply because this method was called.

You should only set isPremiumPurchased = 1 in response to a transaction being presented to your payment queue.

The original StoreKit API is also being deprecated in iOS 18. You may want to consider moving to StoreKit2 API. This is much simpler to use, doesn’t require purchase restoration flows and doesn’t event need your to persist your own ‘purchased’ state – You can simply check to see if the product has been purchased.

Recognized by Mobile Development Collective

0

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật