// // AccountServiceIAPManager.swift // Wallet // // Created by Saveliy Stavitsky on 4/12/21. // Copyright © 2021 AM. All rights reserved. // import Foundation import StoreKit extension AccountOnboarding.Service { class IAP: NSObject { private let newAccountProducatIAPID = "life.malinka.wallet.newaccount" var products: [SKProduct] = [] var transactions = SKPaymentQueue.default().transactions var onReceiveProductsHandler: ((Result<[SKProduct], IAPManagerError>) -> Void)? var onBuyProductHandler: ((Result) -> Void)? static let shared = IAP() private override init() { super.init() } func getProducts(withHandler productsReceiveHandler: ((_ result: Result<[SKProduct], IAPManagerError>) -> Void)? = nil) { onReceiveProductsHandler = productsReceiveHandler let request = SKProductsRequest(productIdentifiers: Set([newAccountProducatIAPID])) request.delegate = self request.start() } enum IAPManagerError: Error { case noProductIDsFound case noProductsFound case paymentWasCancelled case productRequestFailed } func canMakePayments() -> Bool { return SKPaymentQueue.canMakePayments() } func startObserving() { SKPaymentQueue.default().add(self) } func stopObserving() { SKPaymentQueue.default().remove(self) } func buy(product: SKProduct, withHandler handler: @escaping ((_ result: Result) -> Void)) { let payment = SKPayment(product: product) SKPaymentQueue.default().add(payment) onBuyProductHandler = handler } func getAppStoreReceipt() -> String? { if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) print(receiptData) let receiptString = receiptData.base64EncodedString(options: []) return receiptString } catch { print("Couldn't read receipt data with error: " + error.localizedDescription) return nil } } return nil } func finishTransaction(transaction: SKPaymentTransaction) { SKPaymentQueue.default().finishTransaction(transaction) } } } extension AccountOnboarding.Service.IAP.IAPManagerError: LocalizedError { var errorDescription: String? { switch self { case .noProductIDsFound: return "No In-App Purchase product identifiers were found." case .noProductsFound: return "No In-App Purchases were found." case .productRequestFailed: return "Unable to fetch available In-App Purchase products at the moment." case .paymentWasCancelled: return "In-App Purchase process was cancelled." } } } extension AccountOnboarding.Service.IAP: SKProductsRequestDelegate { func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { products = response.products if products.count > 0 { onReceiveProductsHandler?(.success(products)) } else { onReceiveProductsHandler?(.failure(.noProductsFound)) } } func request(_ request: SKRequest, didFailWithError error: Error) { onReceiveProductsHandler?(.failure(.productRequestFailed)) } func requestDidFinish(_ request: SKRequest) { } } extension AccountOnboarding.Service.IAP: SKPaymentTransactionObserver { func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { transactions.forEach { (transaction) in switch transaction.transactionState { case .purchased: onBuyProductHandler?(.success(transaction)) // SKPaymentQueue.default().finishTransaction(transaction) case .restored: break case .failed: if let error = transaction.error as? SKError { if error.code != .paymentCancelled { onBuyProductHandler?(.failure(error)) } else { onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled)) } print("IAP Error:", error.localizedDescription) } SKPaymentQueue.default().finishTransaction(transaction) case .deferred, .purchasing: break @unknown default: break } } } } extension SKProduct { public func localizedPrice() -> String? { let formatter = NumberFormatter() formatter.numberStyle = .currency formatter.locale = self.priceLocale return formatter.string(from: self.price) } }