From b35402e9680ddadc21b443982af4b170688d0803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Tue, 29 Aug 2023 18:28:51 +0800 Subject: [PATCH 1/8] Initial refactor to add own purchase controller - Created an InternalPurchaseController. This coordinates the purchasing between provided purchase controllers and the internal purchasing mechanism. - Removed StoreKitCoordinator and removed redundant files like PurchaseManager. - Verification now happens before finishing a product. - No longer checks for restored product after purchase. This means if someone repurchases a product they already have, it'll count as a transaction_complete. This shouldn't be happening anyway, but something to consider. --- CHANGELOG.md | 2 +- .../Delegate/SuperwallDelegateAdapter.swift | 78 +------- .../Dependencies/DependencyContainer.swift | 36 ++-- .../Dependencies/FactoryProtocols.swift | 8 - .../Coordinator/CoordinatorProtocols.swift | 39 ---- .../Coordinator/StoreKitCoordinator.swift | 58 ------ .../StoreKit/InternalPurchaseController.swift | 79 ++++++++ .../Products/ProductsFetcherSK1.swift | 2 +- .../Receipt Manager/ReceiptManager.swift | 6 +- .../StoreKit/StoreKitManager.swift | 30 +-- .../Purchasing/ProductPurchaserLogic.swift | 33 ++-- .../Purchasing/ProductPurchaserSK1.swift | 172 ++++++++---------- .../Purchasing/PurchaseError.swift | 28 +++ .../Purchasing/PurchaseManager.swift | 103 ----------- .../Transactions/TransactionManager.swift | 54 ++++-- .../Transactions/TransactionVerifierSK2.swift | 44 ----- 16 files changed, 270 insertions(+), 502 deletions(-) delete mode 100644 Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift delete mode 100644 Sources/SuperwallKit/StoreKit/Coordinator/StoreKitCoordinator.swift create mode 100644 Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift create mode 100644 Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseError.swift delete mode 100644 Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift delete mode 100644 Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index ed46eee6f..ff7f79c88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup ### Enhancements - Adds the ability to add a paywall exit survey. Surveys are configured via the dashboard and added to paywalls. When added to a paywall, it will attempt to display when the user taps the close button. If the paywall has the `modalPresentationStyle` of `pageSheet`, `formSheet`, or `popover`, the survey will also attempt to display when the user tries to drag to dismiss the paywall. The probability of the survey showing is determined by the survey's configuration in the dashboard. A user will only ever see the survey once unless you reset responses via the dashboard. The survey will always show on exit of the paywall in the debugger. -- Adds the ability to add `survey_close` as a trigger and use the selected option title in rules. +- Adds the ability to add `survey_response` as a trigger and use the selected option title in rules. - Adds new `PaywallCloseReason` `.manualClose`. ### Fixes diff --git a/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift b/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift index 11610680b..e1189e907 100644 --- a/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift +++ b/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift @@ -7,27 +7,12 @@ import Foundation import Combine +import StoreKit /// An adapter between the internal SDK and the public swift/objective c delegate. final class SuperwallDelegateAdapter { - var hasPurchaseController: Bool { - return swiftPurchaseController != nil || objcPurchaseController != nil - } - var swiftDelegate: SuperwallDelegate? var objcDelegate: SuperwallDelegateObjc? - var swiftPurchaseController: PurchaseController? - var objcPurchaseController: PurchaseControllerObjc? - - /// Called on init of the Superwall instance via - /// ``Superwall/configure(apiKey:purchaseController:options:completion:)-52tke``. - init( - swiftPurchaseController: PurchaseController?, - objcPurchaseController: PurchaseControllerObjc? - ) { - self.swiftPurchaseController = swiftPurchaseController - self.objcPurchaseController = objcPurchaseController - } @MainActor func handleCustomPaywallAction(withName name: String) { @@ -136,64 +121,3 @@ final class SuperwallDelegateAdapter { } } } - -// MARK: - Product Purchaser -extension SuperwallDelegateAdapter: ProductPurchaser { - @MainActor - func purchase( - product: StoreProduct - ) async -> PurchaseResult { - if let purchaseController = swiftPurchaseController { - guard let sk1Product = product.sk1Product else { - return .failed(PurchaseError.productUnavailable) - } - return await purchaseController.purchase(product: sk1Product) - } else if let purchaseController = objcPurchaseController { - guard let sk1Product = product.sk1Product else { - return .failed(PurchaseError.productUnavailable) - } - return await withCheckedContinuation { continuation in - purchaseController.purchase(product: sk1Product) { result, error in - if let error = error { - continuation.resume(returning: .failed(error)) - } else { - switch result { - case .purchased: - continuation.resume(returning: .purchased) - case .pending: - continuation.resume(returning: .pending) - case .cancelled: - continuation.resume(returning: .cancelled) - case .failed: - break - } - } - } - } - } - return .cancelled - } -} - -// MARK: - TransactionRestorer -extension SuperwallDelegateAdapter: TransactionRestorer { - @MainActor - func restorePurchases() async -> RestorationResult { - var result: RestorationResult = .failed(nil) - if let purchaseController = swiftPurchaseController { - result = await purchaseController.restorePurchases() - } else if let purchaseController = objcPurchaseController { - result = await withCheckedContinuation { continuation in - purchaseController.restorePurchases { result, error in - switch result { - case .restored: - continuation.resume(returning: .restored) - case .failed: - continuation.resume(returning: .failed(error)) - } - } - } - } - return result - } -} diff --git a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift index d6d819223..653c9f00e 100644 --- a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift +++ b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift @@ -41,11 +41,16 @@ final class DependencyContainer { objcPurchaseController: PurchaseControllerObjc? = nil, options: SuperwallOptions? = nil ) { - storeKitManager = StoreKitManager(factory: self) - delegateAdapter = SuperwallDelegateAdapter( + /* + StoreKitManager needs purchase controller to: restore, + */ + let purchaseController = InternalPurchaseController( + factory: self, swiftPurchaseController: swiftPurchaseController, objcPurchaseController: objcPurchaseController ) + storeKitManager = StoreKitManager(purchaseController: purchaseController) + delegateAdapter = SuperwallDelegateAdapter() storage = Storage(factory: self) network = Network(factory: self) @@ -115,6 +120,9 @@ final class DependencyContainer { sessionEventsManager: sessionEventsManager, factory: self ) + + // Initialise the product purchaser so that it can immediately start listening to transactions. + _ = storeKitManager.purchaseController.productPurchaser } } @@ -376,17 +384,6 @@ extension DependencyContainer: ConfigManagerFactory { } } -// MARK: - StoreKitCoordinatorFactory -extension DependencyContainer: StoreKitCoordinatorFactory { - func makeStoreKitCoordinator() -> StoreKitCoordinator { - return StoreKitCoordinator( - delegateAdapter: delegateAdapter, - storeKitManager: storeKitManager, - factory: self - ) - } -} - // MARK: - StoreTransactionFactory extension DependencyContainer: StoreTransactionFactory { func makeStoreTransaction(from transaction: SK1Transaction) async -> StoreTransaction { @@ -417,22 +414,11 @@ extension DependencyContainer: ProductPurchaserFactory { return ProductPurchaserSK1( storeKitManager: storeKitManager, sessionEventsManager: sessionEventsManager, - delegateAdapter: delegateAdapter, factory: self ) } } -// MARK: - Purchase Manager Factory -extension DependencyContainer: PurchaseManagerFactory { - func makePurchaseManager() -> PurchaseManager { - return PurchaseManager( - storeKitManager: storeKitManager, - hasPurchaseController: delegateAdapter.hasPurchaseController - ) - } -} - // MARK: - Options Factory extension DependencyContainer: OptionsFactory { func makeSuperwallOptions() -> SuperwallOptions { @@ -450,7 +436,7 @@ extension DependencyContainer: TriggerFactory { // MARK: - Purchase Controller Factory extension DependencyContainer: HasPurchaseControllerFactory { func makeHasPurchaseController() -> Bool { - return delegateAdapter.hasPurchaseController + return storeKitManager.purchaseController.isDeveloperProvided } } diff --git a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift index 42aa65395..b6eef8135 100644 --- a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift +++ b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift @@ -80,10 +80,6 @@ protocol ConfigManagerFactory: AnyObject { ) -> Paywall? } -protocol StoreKitCoordinatorFactory: AnyObject { - func makeStoreKitCoordinator() -> StoreKitCoordinator -} - protocol IdentityInfoFactory: AnyObject { func makeIdentityInfo() async -> IdentityInfo } @@ -129,10 +125,6 @@ protocol StoreTransactionFactory: AnyObject { func makeStoreTransaction(from transaction: SK2Transaction) async -> StoreTransaction } -protocol PurchaseManagerFactory: AnyObject { - func makePurchaseManager() -> PurchaseManager -} - protocol OptionsFactory: AnyObject { func makeSuperwallOptions() -> SuperwallOptions } diff --git a/Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift b/Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift deleted file mode 100644 index a8df69f66..000000000 --- a/Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 03/01/2023. -// - -import Foundation - -protocol TransactionChecker: AnyObject { - /// Gets and validates a transaction of a product, if the user isn't using - /// a ``PurchaseController``. - func getAndValidateLatestTransaction( - of productId: String, - hasPurchaseController: Bool - ) async throws -> StoreTransaction? -} - -protocol ProductPurchaser: AnyObject { - /// Purchases a product and returns its result. - func purchase(product: StoreProduct) async -> PurchaseResult -} - -protocol ProductsFetcher: AnyObject { - /// Fetches a set of products from their identifiers. - func products( - identifiers: Set, - forPaywall paywallName: String? - ) async throws -> Set -} - -protocol TransactionRestorer: AnyObject { - /// Restores purchases. - /// - /// - Returns: A boolean indicating whether the restore request succeeded or failed. - /// This doesn't mean that the user is now subscribed, just that there were no errors - /// obtaining the restored transactions - func restorePurchases() async -> RestorationResult -} diff --git a/Sources/SuperwallKit/StoreKit/Coordinator/StoreKitCoordinator.swift b/Sources/SuperwallKit/StoreKit/Coordinator/StoreKitCoordinator.swift deleted file mode 100644 index 854c8cc2a..000000000 --- a/Sources/SuperwallKit/StoreKit/Coordinator/StoreKitCoordinator.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 15/12/2022. -// - -import Foundation - -/// Coordinates the purchasing, restoring and retrieving of products; the checking -/// of transactions; and the determining of the user's subscription status. -struct StoreKitCoordinator { - /// Fetches the products. - let productFetcher: ProductsFetcher - - /// Gets and validates transactions. - let txnChecker: TransactionChecker - - /// Purchases the product. - var productPurchaser: ProductPurchaser - - /// Restores purchases. - var txnRestorer: TransactionRestorer - - /// Checks if the user is subscribed. - unowned let delegateAdapter: SuperwallDelegateAdapter - unowned let storeKitManager: StoreKitManager - private let factory: StoreTransactionFactory & ProductPurchaserFactory - - init( - delegateAdapter: SuperwallDelegateAdapter, - storeKitManager: StoreKitManager, - factory: StoreTransactionFactory & ProductPurchaserFactory, - productsFetcher: ProductsFetcher = ProductsFetcherSK1() - ) { - self.factory = factory - self.delegateAdapter = delegateAdapter - self.storeKitManager = storeKitManager - self.productFetcher = productsFetcher - - let sk1ProductPurchaser = factory.makeSK1ProductPurchaser() - - if #available(iOS 15.0, *) { - self.txnChecker = TransactionVerifierSK2(factory: factory) - } else { - self.txnChecker = sk1ProductPurchaser - } - - let hasPurchaseController = delegateAdapter.hasPurchaseController - if hasPurchaseController { - self.productPurchaser = delegateAdapter - self.txnRestorer = delegateAdapter - } else { - self.productPurchaser = sk1ProductPurchaser - self.txnRestorer = sk1ProductPurchaser - } - } -} diff --git a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift new file mode 100644 index 000000000..0b766ac2b --- /dev/null +++ b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift @@ -0,0 +1,79 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 29/08/2023. +// + +import Foundation +import StoreKit + +final class InternalPurchaseController { + var isDeveloperProvided: Bool { + return swiftPurchaseController != nil || objcPurchaseController != nil + } + private var swiftPurchaseController: PurchaseController? + private var objcPurchaseController: PurchaseControllerObjc? + lazy var productPurchaser = factory.makeSK1ProductPurchaser() + private let factory: ProductPurchaserFactory + + init( + factory: ProductPurchaserFactory, + swiftPurchaseController: PurchaseController?, + objcPurchaseController: PurchaseControllerObjc? + ) { + self.swiftPurchaseController = swiftPurchaseController + self.objcPurchaseController = objcPurchaseController + self.factory = factory + } +} + + // MARK: - Purchase Controller +extension InternalPurchaseController: PurchaseController { + func purchase(product: SKProduct) async -> PurchaseResult { + // TODO: CHeck this is actually on mainactor + if let purchaseController = swiftPurchaseController { + return await purchaseController.purchase(product: product) + } else if let purchaseController = objcPurchaseController { + return await withCheckedContinuation { continuation in + purchaseController.purchase(product: product) { result, error in + if let error = error { + continuation.resume(returning: .failed(error)) + } else { + switch result { + case .purchased: + continuation.resume(returning: .purchased) + case .pending: + continuation.resume(returning: .pending) + case .cancelled: + continuation.resume(returning: .cancelled) + case .failed: + break + } + } + } + } + } else { + return await productPurchaser.purchase(product: product) + } + } + + func restorePurchases() async -> RestorationResult { + if let purchaseController = swiftPurchaseController { + return await purchaseController.restorePurchases() + } else if let purchaseController = objcPurchaseController { + return await withCheckedContinuation { continuation in + purchaseController.restorePurchases { result, error in + switch result { + case .restored: + continuation.resume(returning: .restored) + case .failed: + continuation.resume(returning: .failed(error)) + } + } + } + } else { + return await productPurchaser.restorePurchases() + } + } +} diff --git a/Sources/SuperwallKit/StoreKit/Products/ProductsFetcherSK1.swift b/Sources/SuperwallKit/StoreKit/Products/ProductsFetcherSK1.swift index 683b083de..f74effbad 100644 --- a/Sources/SuperwallKit/StoreKit/Products/ProductsFetcherSK1.swift +++ b/Sources/SuperwallKit/StoreKit/Products/ProductsFetcherSK1.swift @@ -14,7 +14,7 @@ import Foundation import StoreKit -class ProductsFetcherSK1: NSObject, ProductsFetcher { +class ProductsFetcherSK1: NSObject { private var cachedProductsByIdentifier: [String: SKProduct] = [:] private let queue = DispatchQueue(label: "com.superwall.ProductsManager") private var productsByRequest: [SKRequest: Set] = [:] diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift index e43ae71f7..b64970811 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift @@ -12,11 +12,11 @@ actor ReceiptManager: NSObject { var purchasedSubscriptionGroupIds: Set? private var purchases: Set = [] private var receiptRefreshCompletion: ((Bool) -> Void)? - private weak var delegate: ProductsFetcher? + private weak var delegate: ProductsFetcherSK1? private let receiptData: () -> Data? init( - delegate: ProductsFetcher, + delegate: ProductsFetcherSK1, receiptData: @escaping () -> Data? = ReceiptLogic.getReceiptData ) { self.delegate = delegate @@ -27,7 +27,7 @@ actor ReceiptManager: NSObject { /// purchases and active purchases. @discardableResult func loadPurchasedProducts() async -> Set? { - let hasPurchaseController = Superwall.shared.dependencyContainer.delegateAdapter.hasPurchaseController + let hasPurchaseController = Superwall.shared.dependencyContainer.storeKitManager.purchaseController.isDeveloperProvided guard let payload = ReceiptLogic.getPayload(using: receiptData) else { if !hasPurchaseController { diff --git a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift index ed992cede..ff9567727 100644 --- a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift +++ b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift @@ -1,12 +1,13 @@ import Foundation +import StoreKit import Combine actor StoreKitManager { /// Coordinates: The purchasing, restoring and retrieving of products; the checking /// of transactions; and the determining of the user's subscription status. - lazy var coordinator = factory.makeStoreKitCoordinator() - private unowned let factory: StoreKitCoordinatorFactory - private lazy var receiptManager = ReceiptManager(delegate: self) + private let productsFetcher = ProductsFetcherSK1() + private lazy var receiptManager = ReceiptManager(delegate: productsFetcher) + let purchaseController: InternalPurchaseController private(set) var productsById: [String: StoreProduct] = [:] private struct ProductProcessingResult { @@ -15,8 +16,8 @@ actor StoreKitManager { let products: [Product] } - init(factory: StoreKitCoordinatorFactory) { - self.factory = factory + init(purchaseController: InternalPurchaseController) { + self.purchaseController = purchaseController } func getProductVariables(for paywall: Paywall) async -> [ProductVariable] { @@ -52,7 +53,7 @@ actor StoreKitManager { responseProducts: responseProducts ) - let products = try await products( + let products = try await productsFetcher.products( identifiers: processingResult.productIdsToLoad, forPaywall: paywallName ) @@ -133,7 +134,7 @@ extension StoreKitManager { paywallViewController.loadingState = .loadingPurchase - let restorationResult = await coordinator.txnRestorer.restorePurchases() + let restorationResult = await purchaseController.restorePurchases() await processRestoration( restorationResult: restorationResult, @@ -151,7 +152,7 @@ extension StoreKitManager { ) async { let hasRestored = restorationResult == .restored - if !Superwall.shared.dependencyContainer.delegateAdapter.hasPurchaseController { + if !purchaseController.isDeveloperProvided { await refreshReceipt() if hasRestored { await loadPurchasedProducts() @@ -233,16 +234,3 @@ extension StoreKitManager { return await receiptManager.isFreeTrialAvailable(for: product) } } - -// MARK: - ProductsFetcher -extension StoreKitManager: ProductsFetcher { - nonisolated func products( - identifiers: Set, - forPaywall paywallName: String? - ) async throws -> Set { - return try await coordinator.productFetcher.products( - identifiers: identifiers, - forPaywall: paywallName - ) - } -} diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift index 634ee8ecf..8033ad81c 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift @@ -12,20 +12,27 @@ enum ProductPurchaserLogic { static func validate( transaction: SKPaymentTransaction, withProductId productId: String - ) throws { - guard transaction.payment.productIdentifier == productId else { - throw PurchaseError.noTransactionDetected - } - guard transaction.transactionState == .purchased else { - throw PurchaseError.noTransactionDetected - } + ) async throws { + if #available(iOS 15.0, *) { + let verificationResult = await Transaction.latest(for: productId) + guard case .verified = verificationResult else { + throw PurchaseError.unverifiedTransaction + } + } else { + guard transaction.payment.productIdentifier == productId else { + throw PurchaseError.noTransactionDetected + } + guard transaction.transactionState == .purchased else { + throw PurchaseError.noTransactionDetected + } - // Validation - guard let localReceipt = try? InAppReceipt() else { - throw PurchaseError.unverifiedTransaction - } - guard localReceipt.isValid else { - throw PurchaseError.unverifiedTransaction + // Validation + guard let localReceipt = try? InAppReceipt() else { + throw PurchaseError.unverifiedTransaction + } + guard localReceipt.isValid else { + throw PurchaseError.unverifiedTransaction + } } } } diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift index 05b2c4151..1b6a15e4c 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift @@ -13,6 +13,7 @@ final class ProductPurchaserSK1: NSObject { actor Purchasing { private var completion: ((PurchaseResult) -> Void)? private var productId: String? + var lastTransaction: SKPaymentTransaction? func productId(is productId: String) -> Bool { return productId == self.productId @@ -22,17 +23,21 @@ final class ProductPurchaserSK1: NSObject { self.completion = completion } - func setProductId(_ productId: String) { + func beginPurchase(of productId: String) { self.productId = productId } - func completePurchase(result: PurchaseResult) { + func completePurchase( + of transaction: SK1Transaction? = nil, + result: PurchaseResult + ) { + lastTransaction = transaction completion?(result) self.completion = nil self.productId = nil } } - private let purchasing = Purchasing() + let purchasing = Purchasing() // MARK: Restoration final class Restoration { @@ -46,13 +51,9 @@ final class ProductPurchaserSK1: NSObject { } private let restoration = Restoration() - /// The latest transaction, used for purchasing and verifying purchases. - private var latestTransaction: SKPaymentTransaction? - // MARK: Dependencies - private unowned let storeKitManager: StoreKitManager - private unowned let sessionEventsManager: SessionEventsManager - private unowned let delegateAdapter: SuperwallDelegateAdapter + private weak var storeKitManager: StoreKitManager? + private weak var sessionEventsManager: SessionEventsManager? private let factory: StoreTransactionFactory deinit { @@ -62,28 +63,19 @@ final class ProductPurchaserSK1: NSObject { init( storeKitManager: StoreKitManager, sessionEventsManager: SessionEventsManager, - delegateAdapter: SuperwallDelegateAdapter, factory: StoreTransactionFactory ) { self.storeKitManager = storeKitManager self.sessionEventsManager = sessionEventsManager - self.delegateAdapter = delegateAdapter self.factory = factory super.init() SKPaymentQueue.default().add(self) } -} -// MARK: - ProductPurchaser -extension ProductPurchaserSK1: ProductPurchaser { /// Purchases a product, waiting for the completion block to be fired and /// returning a purchase result. - func purchase(product: StoreProduct) async -> PurchaseResult { - guard let sk1Product = product.sk1Product else { - return .failed(PurchaseError.productUnavailable) - } - - await purchasing.setProductId(product.productIdentifier) + func purchase(product: SKProduct) async -> PurchaseResult { + await purchasing.beginPurchase(of: product.productIdentifier) let task = Task { return await withCheckedContinuation { continuation in @@ -94,52 +86,12 @@ extension ProductPurchaserSK1: ProductPurchaser { } } } - let payment = SKPayment(product: sk1Product) + let payment = SKPayment(product: product) SKPaymentQueue.default().add(payment) return await task.value } -} - -// MARK: - TransactionChecker -extension ProductPurchaserSK1: TransactionChecker { - /// Checks that a product has been purchased based on the last transaction - /// received on the queue. If user is not using a ``PurchaseController``, it - /// checks that the receipts are valid. - /// - /// The receipts are updated on successful purchase. - /// - /// Read more in [Apple's docs](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/choosing_a_receipt_validation_technique#//apple_ref/doc/uid/TP40010573). - func getAndValidateLatestTransaction( - of productId: String, - hasPurchaseController: Bool - ) async throws -> StoreTransaction? { - if hasPurchaseController { - if let latestTransaction = latestTransaction { - let storeTransaction = await factory.makeStoreTransaction(from: latestTransaction) - return storeTransaction - } - return nil - } - - guard let latestTransaction = latestTransaction else { - throw PurchaseError.noTransactionDetected - } - try ProductPurchaserLogic.validate( - transaction: latestTransaction, - withProductId: productId - ) - - let storeTransaction = await factory.makeStoreTransaction(from: latestTransaction) - self.latestTransaction = nil - - return storeTransaction - } -} - -// MARK: - TransactionRestorer -extension ProductPurchaserSK1: TransactionRestorer { func restorePurchases() async -> RestorationResult { let result = await withCheckedContinuation { continuation in // Using restoreCompletedTransactions instead of just refreshing @@ -192,11 +144,9 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { let isPaywallPresented = Superwall.shared.isPaywallPresented let paywallViewController = Superwall.shared.paywallViewController for transaction in transactions { - latestTransaction = transaction await checkForTimeout(of: transaction, in: paywallViewController) await updatePurchaseCompletionBlock(for: transaction) await checkForRestoration(transaction, isPaywallPresented: isPaywallPresented) - finishIfPossible(transaction) Task(priority: .background) { await record(transaction) @@ -242,17 +192,38 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { } } + // TODO: REmember, we need this observer to run straight away. + // TODO: REname this function: /// Sends a `PurchaseResult` to the completion block and stores the latest purchased transaction. - private func updatePurchaseCompletionBlock(for transaction: SKPaymentTransaction) async { - guard await purchasing.productId(is: transaction.payment.productIdentifier) else { + private func updatePurchaseCompletionBlock(for skTransaction: SKPaymentTransaction) async { + // Only continue if no purchase controller. The transaction may be + // readded to the queue if finishing fails so we need to make sure + // we can re-finish the transaction. + if storeKitManager?.purchaseController.isDeveloperProvided == true { return } - switch transaction.transactionState { + switch skTransaction.transactionState { case .purchased: - await purchasing.completePurchase(result: .purchased) + do { + try await ProductPurchaserLogic.validate( + transaction: skTransaction, + withProductId: skTransaction.payment.productIdentifier + ) + SKPaymentQueue.default().finishTransaction(skTransaction) + await purchasing.completePurchase( + of: skTransaction, + result: .purchased + ) + } catch { + SKPaymentQueue.default().finishTransaction(skTransaction) + await purchasing.completePurchase(result: .failed(error)) + } + + // TODO: Finishing could fail. Should we store state of transaction. If purchased/restored>? case .failed: - if let error = transaction.error { + SKPaymentQueue.default().finishTransaction(skTransaction) + if let error = skTransaction.error { if let error = error as? SKError { switch error.code { case .paymentCancelled, @@ -265,7 +236,7 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { if #available(iOS 14, *) { switch error.code { case .overlayTimeout: - return await purchasing.completePurchase(result: .cancelled) + await purchasing.completePurchase(result: .cancelled) default: break } @@ -274,7 +245,8 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { await purchasing.completePurchase(result: .failed(error)) } case .deferred: - await purchasing.completePurchase(result: .pending) + SKPaymentQueue.default().finishTransaction(skTransaction) + await purchasing.completePurchase(of: skTransaction, result: .pending) default: break } @@ -285,43 +257,55 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { _ transaction: SKPaymentTransaction, isPaywallPresented: Bool ) async { - guard let product = await storeKitManager.productsById[transaction.payment.productIdentifier] else { + guard case .restored = transaction.transactionState else { return } - guard isPaywallPresented else { + SKPaymentQueue.default().finishTransaction(transaction) + guard let product = await storeKitManager?.productsById[transaction.payment.productIdentifier] else { return } - switch transaction.transactionState { - case .restored: - await sessionEventsManager.triggerSession.trackTransactionRestoration( - withId: transaction.transactionIdentifier, - product: product - ) - default: - break + guard isPaywallPresented else { + return } + + await sessionEventsManager?.triggerSession.trackTransactionRestoration( + withId: transaction.transactionIdentifier, + product: product + ) } - /// Finishes transactions only if the delegate doesn't return a ``PurchaseController``. - private func finishIfPossible(_ transaction: SKPaymentTransaction) { - if delegateAdapter.hasPurchaseController { - return + @available(iOS 15.0, *) + private func hasRestored( + _ transaction: StoreTransaction, + purchaseStartAt: Date? + ) -> Bool { + guard let purchaseStartAt = purchaseStartAt else { + return false } - - switch transaction.transactionState { - case .purchased, - .failed, - .restored: - SKPaymentQueue.default().finishTransaction(transaction) - default: - break + // If has a transaction date and that happened before purchase + // button was pressed... + if let transactionDate = transaction.transactionDate, + transactionDate < purchaseStartAt { + // ...and if it has an expiration date that expires in the future, + // then we must have restored. + if let expirationDate = transaction.expirationDate { + if expirationDate >= Date() { + return true + } + } else { + // If no expiration date, it must be a non-consumable product + // which has been restored. + return true + } } + + return false } /// Sends the transaction to the backend. private func record(_ transaction: SKPaymentTransaction) async { let storeTransaction = await factory.makeStoreTransaction(from: transaction) - await sessionEventsManager.enqueue(storeTransaction) + await sessionEventsManager?.enqueue(storeTransaction) } /// Loads purchased products in the StoreKitManager if a purchase or restore has occurred. @@ -331,6 +315,6 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { ) == nil { return } - await storeKitManager.loadPurchasedProducts() + await storeKitManager?.loadPurchasedProducts() } } diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseError.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseError.swift new file mode 100644 index 000000000..579535e35 --- /dev/null +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseError.swift @@ -0,0 +1,28 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 29/08/2023. +// + +import Foundation + +enum PurchaseError: LocalizedError { + case productUnavailable + case unknown + case noTransactionDetected + case unverifiedTransaction + + var errorDescription: String? { + switch self { + case .productUnavailable: + return "There was an error retrieving the product to purchase." + case .noTransactionDetected: + return "No receipt was found on device for the product transaction." + case .unverifiedTransaction: + return "The product transaction could not be verified." + case .unknown: + return "An unknown error occurred." + } + } +} diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift deleted file mode 100644 index 488126711..000000000 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 06/12/2022. -// - -import Foundation -import StoreKit - -enum PurchaseError: LocalizedError { - case productUnavailable - case unknown - case noTransactionDetected - case unverifiedTransaction - - var errorDescription: String? { - switch self { - case .productUnavailable: - return "There was an error retrieving the product to purchase." - case .noTransactionDetected: - return "No receipt was found on device for the product transaction." - case .unverifiedTransaction: - return "The product transaction could not be verified." - case .unknown: - return "An unknown error occurred." - } - } -} - -struct PurchaseManager { - unowned let storeKitManager: StoreKitManager - let hasPurchaseController: Bool - - /// Purchases the product and then checks for a transaction - func purchase(product: StoreProduct) async -> InternalPurchaseResult { - let purchaseStartAt = Date() - - let result = await storeKitManager.coordinator.productPurchaser.purchase(product: product) - - switch result { - case .failed(let error): - return .failed(error) - case .pending: - return .pending - case .cancelled: - return .cancelled - case .purchased: - do { - let transaction = try await storeKitManager.coordinator.txnChecker.getAndValidateLatestTransaction( - of: product.productIdentifier, - hasPurchaseController: hasPurchaseController - ) - - if hasRestored( - transaction, - hasPurchaseController: hasPurchaseController, - purchaseStartAt: purchaseStartAt - ) { - return .restored - } - - return .purchased(transaction) - } catch { - return .failed(error) - } - } - } - - /// Checks whether the purchased product was actually a restoration. This happens (in sandbox), - /// when a user purchases, then deletes the app, then launches the paywall and purchases again. - private func hasRestored( - _ transaction: StoreTransaction?, - hasPurchaseController: Bool, - purchaseStartAt: Date - ) -> Bool { - if hasPurchaseController { - return false - } - guard let transaction = transaction else { - return false - } - - // If has a transaction date and that happened before purchase - // button was pressed... - if let transactionDate = transaction.transactionDate, - transactionDate < purchaseStartAt { - // ...and if it has an expiration date that expires in the future, - // then we must have restored. - if let expirationDate = transaction.expirationDate { - if expirationDate >= Date() { - return true - } - } else { - // If no expiration date, it must be a non-consumable product - // which has been restored. - return true - } - } - - return false - } -} diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift index 8695745f5..6fa9c11c9 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift @@ -13,8 +13,11 @@ import Combine final class TransactionManager { private unowned let storeKitManager: StoreKitManager private unowned let sessionEventsManager: SessionEventsManager - private let purchaseManager: PurchaseManager - private let factory: PurchaseManagerFactory & OptionsFactory & TriggerFactory + typealias Factories = ProductPurchaserFactory + & OptionsFactory + & TriggerFactory + & StoreTransactionFactory + private let factory: Factories /// The paywall view controller that the last product was purchased from. private var lastPaywallViewController: PaywallViewController? @@ -22,12 +25,11 @@ final class TransactionManager { init( storeKitManager: StoreKitManager, sessionEventsManager: SessionEventsManager, - factory: PurchaseManagerFactory & OptionsFactory & TriggerFactory + factory: Factories ) { self.storeKitManager = storeKitManager self.sessionEventsManager = sessionEventsManager self.factory = factory - purchaseManager = factory.makePurchaseManager() } /// Purchases the given product and handles the result appropriately. @@ -46,14 +48,13 @@ final class TransactionManager { await prepareToStartTransaction(of: product, from: paywallViewController) - let result = await purchaseManager.purchase(product: product) + let result = await purchase(product) switch result { - case .purchased(let transaction): + case .purchased: await didPurchase( product, - from: paywallViewController, - transaction: transaction + from: paywallViewController ) case .failed(let error): let superwallOptions = factory.makeSuperwallOptions() @@ -87,11 +88,11 @@ final class TransactionManager { paywallViewController: paywallViewController ) } - case .restored: - await storeKitManager.processRestoration( - restorationResult: .restored, - paywallViewController: paywallViewController - ) +// case .restored: +// await storeKitManager.processRestoration( +// restorationResult: .restored, +// paywallViewController: paywallViewController +// ) case .pending: await handlePendingTransaction(from: paywallViewController) case .cancelled: @@ -99,6 +100,13 @@ final class TransactionManager { } } + private func purchase(_ product: StoreProduct) async -> PurchaseResult { + guard let sk1Product = product.sk1Product else { + return .failed(PurchaseError.productUnavailable) + } + return await storeKitManager.purchaseController.purchase(product: sk1Product) + } + /// Cancels the transaction timeout when the application resigns active. /// /// When the purchase sheet appears, the application resigns active. @@ -167,8 +175,7 @@ final class TransactionManager { /// Dismisses the view controller, if the developer hasn't disabled the option. private func didPurchase( _ product: StoreProduct, - from paywallViewController: PaywallViewController, - transaction: StoreTransaction? + from paywallViewController: PaywallViewController ) async { Logger.debug( logLevel: .debug, @@ -181,6 +188,8 @@ final class TransactionManager { error: nil ) + let transaction = await getLatestTransaction(of: product.productIdentifier) + if let transaction = transaction { await self.sessionEventsManager.enqueue(transaction) } @@ -201,6 +210,21 @@ final class TransactionManager { } } + private func getLatestTransaction(of productId: String) async -> StoreTransaction? { + if #available(iOS 15.0, *) { + let verificationResult = await Transaction.latest(for: productId) + if let transaction = verificationResult.map({ $0.unsafePayloadValue }) { + return await factory.makeStoreTransaction(from: transaction) + } + return nil + } else { + if let transaction = await storeKitManager.purchaseController.productPurchaser.purchasing.lastTransaction { + return await factory.makeStoreTransaction(from: transaction) + } + return nil + } + } + /// Track the cancelled private func trackCancelled( product: StoreProduct, diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift deleted file mode 100644 index 07975b60e..000000000 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 16/01/2023. -// - -import Foundation -import StoreKit - -@available(iOS 15.0, tvOS 15.0, watchOS 8.0, *) -final class TransactionVerifierSK2: TransactionChecker { - let factory: StoreTransactionFactory - - init(factory: StoreTransactionFactory) { - self.factory = factory - } - - /// An iOS 15+-only function that checks for a transaction of the product. - /// - /// We need this function because on iOS 15+, the `Transaction.updates` listener doesn't notify us - /// of transactions for recent purchases. - func getAndValidateLatestTransaction( - of productId: String, - hasPurchaseController: Bool - ) async throws -> StoreTransaction? { - let verificationResult = await Transaction.latest(for: productId) - - // If the user provided a ``PurchaseController``, return the transaction without - // verifying. Otherwise, verify the transaction. - if hasPurchaseController { - if let transaction = verificationResult.map({ $0.unsafePayloadValue }) { - return await factory.makeStoreTransaction(from: transaction) - } - return nil - } - - guard case let .verified(transaction) = verificationResult else { - throw PurchaseError.unverifiedTransaction - } - - return await factory.makeStoreTransaction(from: transaction) - } -} From 02bbc6f027486aaedda7ded53b890b26231086ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Wed, 30 Aug 2023 14:43:39 +0800 Subject: [PATCH 2/8] More purchase controller refactoring - Stops ASN1 files from being public/open. - Fixes tests --- .../Dependencies/DependencyContainer.swift | 3 - .../StoreKit/InternalPurchaseController.swift | 26 ++++- .../Receipt Manager/ASN1Swift/ASN1Coder.swift | 8 +- .../ASN1Decoder+KeyedDecodingContainer.swift | 52 ++++----- .../ASN1Decoder+SingleValueContainer.swift | 32 +++--- ...ASN1Decoder+UnkeyedDecodingContainer.swift | 6 +- .../ASN1Swift/ASN1Decoder.swift | 41 ++++--- .../ASN1Swift/ASN1Templates.swift | 10 +- .../Receipt Manager/ASN1Swift/ASN1Types.swift | 108 +++++++++--------- .../ASN1Swift/DecodingError+Extensions.swift | 2 +- .../ASN1Swift/Foundation+ASN1Coder.swift | 10 +- .../ASN1Swift/PKCS7/PKCS7.swift | 96 ++++++++-------- .../Receipt Manager/ReceiptManager.swift | 36 ++---- .../StoreKit/StoreKitManager.swift | 53 ++++----- .../Purchasing/ProductPurchaserSK1.swift | 6 +- .../Transactions/TransactionManager.swift | 7 +- .../Internal Tracking/TrackTests.swift | 8 +- .../TrackingLogicTests.swift | 8 -- .../StoreKitCoordinatorFactoryMock.swift | 21 ---- .../Network/NetworkTests.swift | 9 +- ...onfirmPaywallAssignmentOperatorTests.swift | 21 ---- .../Operators/GetPaywallVcOperatorTests.swift | 4 - .../Receipt Manager/ReceiptManagerTests.swift | 34 ++---- .../StoreKit/StoreKitManagerTests.swift | 33 +++--- 24 files changed, 270 insertions(+), 364 deletions(-) delete mode 100644 Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift diff --git a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift index 653c9f00e..e2e8b9b2d 100644 --- a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift +++ b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift @@ -41,9 +41,6 @@ final class DependencyContainer { objcPurchaseController: PurchaseControllerObjc? = nil, options: SuperwallOptions? = nil ) { - /* - StoreKitManager needs purchase controller to: restore, - */ let purchaseController = InternalPurchaseController( factory: self, swiftPurchaseController: swiftPurchaseController, diff --git a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift index 0b766ac2b..c738a5e93 100644 --- a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift +++ b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift @@ -8,6 +8,10 @@ import Foundation import StoreKit +protocol RestoreDelegate: AnyObject { + func didRestore(result: RestorationResult) async +} + final class InternalPurchaseController { var isDeveloperProvided: Bool { return swiftPurchaseController != nil || objcPurchaseController != nil @@ -16,6 +20,7 @@ final class InternalPurchaseController { private var objcPurchaseController: PurchaseControllerObjc? lazy var productPurchaser = factory.makeSK1ProductPurchaser() private let factory: ProductPurchaserFactory + weak var delegate: RestoreDelegate? init( factory: ProductPurchaserFactory, @@ -26,12 +31,25 @@ final class InternalPurchaseController { self.objcPurchaseController = objcPurchaseController self.factory = factory } + + func syncSubscriptionStatus(withPurchases purchases: Set) async { + if isDeveloperProvided { + return + } + let activePurchases = purchases.filter { $0.isActive } + await MainActor.run { + if activePurchases.isEmpty { + Superwall.shared.subscriptionStatus = .inactive + } else { + Superwall.shared.subscriptionStatus = .active + } + } + } } - // MARK: - Purchase Controller +// MARK: - Purchase Controller extension InternalPurchaseController: PurchaseController { func purchase(product: SKProduct) async -> PurchaseResult { - // TODO: CHeck this is actually on mainactor if let purchaseController = swiftPurchaseController { return await purchaseController.purchase(product: product) } else if let purchaseController = objcPurchaseController { @@ -73,7 +91,9 @@ extension InternalPurchaseController: PurchaseController { } } } else { - return await productPurchaser.restorePurchases() + let result = await productPurchaser.restorePurchases() + await delegate?.didRestore(result: result) + return result } } } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Coder.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Coder.swift index 330fe55b5..db0fa84be 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Coder.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Coder.swift @@ -7,15 +7,15 @@ import Foundation -public typealias ASN1Codable = ASN1Encodable & ASN1Decodable +typealias ASN1Codable = ASN1Encodable & ASN1Decodable -public protocol ASN1Encodable: Encodable { } -public protocol ASN1Decodable: Decodable +protocol ASN1Encodable: Encodable { } +protocol ASN1Decodable: Decodable { static var template: ASN1Template { get } } -public protocol ASN1CodingKey: CodingKey +protocol ASN1CodingKey: CodingKey { var template: ASN1Template { get } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+KeyedDecodingContainer.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+KeyedDecodingContainer.swift index 8e35f34f1..c8412cc72 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+KeyedDecodingContainer.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+KeyedDecodingContainer.swift @@ -25,9 +25,9 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain private let state: _ASN1Decoder.State /// The path of coding keys taken to get to this point in decoding. - private(set) public var codingPath: [CodingKey] + private(set) var codingPath: [CodingKey] - public var rawData: Data { return container.rawData } + var rawData: Data { return container.rawData } // MARK: - Initialization @@ -44,12 +44,12 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain // MARK: - KeyedDecodingContainerProtocol Methods - public var allKeys: [Key] + var allKeys: [Key] { return [] } - public func contains(_ key: Key) -> Bool + func contains(_ key: Key) -> Bool { return false } @@ -59,19 +59,19 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return "\(key) (\"\(key.stringValue)\")" } - public func decodeNil(forKey key: Key) throws -> Bool + func decodeNil(forKey key: Key) throws -> Bool { assertionFailure("Not supposed to be here") return false } - public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { assertionFailure("Not supposed to be here") return false } - public func decode(_ type: Int.Type, forKey key: Key) throws -> Int + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { self.decoder.codingPath.append(key) defer { self.decoder.codingPath.removeLast() } @@ -85,18 +85,18 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return value } - public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { self.decoder.codingPath.append(key) defer { self.decoder.codingPath.removeLast() } @@ -110,47 +110,47 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return value } - public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: String.Type, forKey key: Key) throws -> String + func decode(_ type: String.Type, forKey key: Key) throws -> String { self.decoder.codingPath.append(key) defer { self.decoder.codingPath.removeLast() } @@ -164,7 +164,7 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return value } - public func decodeSkippedField(forKey key: Key) throws -> ASN1SkippedField + func decodeSkippedField(forKey key: Key) throws -> ASN1SkippedField { self.decoder.codingPath.append(key) defer { self.decoder.codingPath.removeLast() } @@ -179,7 +179,7 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return value } - public func decodeData(forKey key: Key) throws -> Data + func decodeData(forKey key: Key) throws -> Data { self.decoder.codingPath.append(key) defer { self.decoder.codingPath.removeLast() } @@ -194,7 +194,7 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return value } - public func decode(_ type: T.Type, forKey key: Key) throws -> T + func decode(_ type: T.Type, forKey key: Key) throws -> T { if type == Data.self || type == NSData.self { @@ -239,14 +239,14 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return obj } - public func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer { assertionFailure("Hasn't implemented yet") let container = try ASN1KeyedDecodingContainer(referencing: self.decoder, wrapping: self.container) return KeyedDecodingContainer(container) } - public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { return try ASN1UnkeyedDecodingContainer(referencing: self.decoder, wrapping: objToUnbox(forKey: key)) } @@ -261,13 +261,13 @@ internal struct ASN1KeyedDecodingContainer : KeyedDecodingContain return _ASN1Decoder(referencing: try objToUnbox(forKey: k as! K), at: self.decoder.codingPath, options: self.decoder.options) } - public func superDecoder() throws -> Decoder + func superDecoder() throws -> Decoder { assertionFailure("Hasn't implemented yet") return try _superDecoder(forKey: ASN1Key.super) } - public func superDecoder(forKey key: Key) throws -> Decoder + func superDecoder(forKey key: Key) throws -> Decoder { return try _superDecoder(forKey: key) } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+SingleValueContainer.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+SingleValueContainer.swift index 241ea4cfd..ffc6e34fd 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+SingleValueContainer.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+SingleValueContainer.swift @@ -13,94 +13,94 @@ extension _ASN1Decoder: SingleValueDecodingContainer { // MARK: SingleValueDecodingContainer Methods - public func decodeNil() -> Bool + func decodeNil() -> Bool { assertionFailure("Not supposed to be here") return false } - public func decode(_ type: Bool.Type) throws -> Bool + func decode(_ type: Bool.Type) throws -> Bool { assertionFailure("Not supposed to be here") return false } - public func decode(_ type: Int.Type) throws -> Int + func decode(_ type: Int.Type) throws -> Int { return try self.unbox(self.storage.current, as: Int.self)! } - public func decode(_ type: Int8.Type) throws -> Int8 + func decode(_ type: Int8.Type) throws -> Int8 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Int16.Type) throws -> Int16 + func decode(_ type: Int16.Type) throws -> Int16 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Int32.Type) throws -> Int32 + func decode(_ type: Int32.Type) throws -> Int32 { return try self.unbox(self.storage.current, as: Int32.self)! } - public func decode(_ type: Int64.Type) throws -> Int64 + func decode(_ type: Int64.Type) throws -> Int64 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt.Type) throws -> UInt + func decode(_ type: UInt.Type) throws -> UInt { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt8.Type) throws -> UInt8 + func decode(_ type: UInt8.Type) throws -> UInt8 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt16.Type) throws -> UInt16 + func decode(_ type: UInt16.Type) throws -> UInt16 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt32.Type) throws -> UInt32 + func decode(_ type: UInt32.Type) throws -> UInt32 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: UInt64.Type) throws -> UInt64 + func decode(_ type: UInt64.Type) throws -> UInt64 { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Float.Type) throws -> Float + func decode(_ type: Float.Type) throws -> Float { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: Double.Type) throws -> Double + func decode(_ type: Double.Type) throws -> Double { assertionFailure("Not supposed to be here") return 0 } - public func decode(_ type: String.Type) throws -> String + func decode(_ type: String.Type) throws -> String { return try self.unbox(self.storage.current, as: String.self)! } - public func decode(_ type: T.Type) throws -> T + func decode(_ type: T.Type) throws -> T { if type == ASN1SkippedField.self { diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+UnkeyedDecodingContainer.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+UnkeyedDecodingContainer.swift index 356b5d701..46a4f0fc0 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+UnkeyedDecodingContainer.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder+UnkeyedDecodingContainer.swift @@ -9,7 +9,7 @@ import Foundation // MARK: ASN1UnkeyedDecodingContainer -public protocol ASN1UnkeyedDecodingContainerProtocol: UnkeyedDecodingContainer +protocol ASN1UnkeyedDecodingContainerProtocol: UnkeyedDecodingContainer { var rawData: Data { get } var valueData: Data { get } @@ -136,10 +136,10 @@ internal struct ASN1UnkeyedDecodingContainer private let state: _ASN1Decoder.State /// The path of coding keys taken to get to this point in decoding. - public var codingPath: [CodingKey] + var codingPath: [CodingKey] /// The index of the element we're about to decode. - private(set) public var currentIndex: Int + private(set) var currentIndex: Int var count: Int? diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder.swift index 8f6754d41..669566d1d 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Decoder.swift @@ -9,7 +9,7 @@ import Foundation typealias ASN1DecoderConsumedValue = Int -open class ASN1Decoder +final class ASN1Decoder { //fileprivate //TODO struct EncodingOptions @@ -18,7 +18,7 @@ open class ASN1Decoder let userInfo: [CodingUserInfoKey : Any] = [:] } - public init() {} + init() {} // MARK: - Decoding Values @@ -30,7 +30,7 @@ open class ASN1Decoder /// - returns: A value of the requested type. /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid ASN1. /// - throws: An error if any value throws an error during decoding. - open func decode(_ type: T.Type, from data: Data, template: ASN1Template? = nil) throws -> T + func decode(_ type: T.Type, from data: Data, template: ASN1Template? = nil) throws -> T { let t: ASN1Template = template ?? type.template @@ -55,20 +55,20 @@ open class ASN1Decoder internal struct ASN1Key: CodingKey { - public var stringValue: String - public var intValue: Int? + var stringValue: String + var intValue: Int? - public init?(stringValue: String) { + init?(stringValue: String) { self.stringValue = stringValue self.intValue = nil } - public init?(intValue: Int) { + init?(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } - public init(stringValue: String, intValue: Int?) { + init(stringValue: String, intValue: Int?) { self.stringValue = stringValue self.intValue = intValue } @@ -83,7 +83,7 @@ internal struct ASN1Key: CodingKey // MARK: _ASN1Decoder -public protocol ASN1DecoderProtocol: Decoder +protocol ASN1DecoderProtocol: Decoder { var dataToDecode: Data { get } func extractValueData() throws -> Data @@ -91,18 +91,18 @@ public protocol ASN1DecoderProtocol: Decoder extension _ASN1Decoder { - public var dataToDecode: Data + var dataToDecode: Data { return self.storage.current.rawData } - public func extractValueData() throws -> Data + func extractValueData() throws -> Data { return self.storage.current.valueData } } -//TODO: private -class _ASN1Decoder: ASN1DecoderProtocol + +final class _ASN1Decoder: ASN1DecoderProtocol { internal struct Storage { @@ -141,7 +141,7 @@ class _ASN1Decoder: ASN1DecoderProtocol } } - class State + final class State { var dataPtr: UnsafePointer var consumedMyself: Int @@ -170,9 +170,9 @@ class _ASN1Decoder: ASN1DecoderProtocol } } - public var codingPath: [CodingKey] = [] + var codingPath: [CodingKey] = [] - public var userInfo: [CodingUserInfoKey: Any] { return options.userInfo } + var userInfo: [CodingUserInfoKey: Any] { return options.userInfo } var options: ASN1Decoder.EncodingOptions! @@ -188,8 +188,7 @@ class _ASN1Decoder: ASN1DecoderProtocol self.options = options } - public init() - { + init() { self.storage = Storage() } @@ -200,7 +199,7 @@ class _ASN1Decoder: ASN1DecoderProtocol /// - returns: A keyed decoding container view into this decoder. /// - throws: `DecodingError.typeMismatch` if the encountered stored value is /// not a keyed container. - public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { let container = try ASN1KeyedDecodingContainer(referencing: self, wrapping: self.storage.current) return KeyedDecodingContainer(container) @@ -212,7 +211,7 @@ class _ASN1Decoder: ASN1DecoderProtocol /// - returns: An unkeyed container view into this decoder. /// - throws: `DecodingError.typeMismatch` if the encountered stored value is /// not an unkeyed container. - public func unkeyedContainer() throws -> UnkeyedDecodingContainer + func unkeyedContainer() throws -> UnkeyedDecodingContainer { return try ASN1UnkeyedDecodingContainer(referencing: self, wrapping: self.storage.current) } @@ -223,7 +222,7 @@ class _ASN1Decoder: ASN1DecoderProtocol /// - returns: A single value container view into this decoder. /// - throws: `DecodingError.typeMismatch` if the encountered stored value is /// not a single value container. - public func singleValueContainer() throws -> SingleValueDecodingContainer + func singleValueContainer() throws -> SingleValueDecodingContainer { return self } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Templates.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Templates.swift index 39155e68c..582d55bfd 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Templates.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Templates.swift @@ -7,7 +7,7 @@ import Foundation -public class ASN1Template +class ASN1Template { var expectedTags: [ASN1Tag] = [] @@ -16,19 +16,19 @@ public class ASN1Template expectedTags.append(kind) } - public func implicit(tag: ASN1Tag) -> ASN1Template + func implicit(tag: ASN1Tag) -> ASN1Template { return self } - public func explicit(tag: ASN1Tag) -> ASN1Template + func explicit(tag: ASN1Tag) -> ASN1Template { expectedTags.append(tag) return self } - public func constructed() -> ASN1Template + func constructed() -> ASN1Template { if expectedTags.isEmpty { @@ -45,7 +45,7 @@ public class ASN1Template } } -public extension ASN1Template +extension ASN1Template { static func contextSpecific(_ id: ASN1Tag) -> ASN1Template { diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Types.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Types.swift index 7a93e3a00..500b84c01 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Types.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/ASN1Types.swift @@ -43,18 +43,18 @@ extension ASN1Object } } -public typealias ASN1Tag = UInt8 +typealias ASN1Tag = UInt8 -public struct ASN1SkippedField: ASN1Decodable +struct ASN1SkippedField: ASN1Decodable { - public var rawData: Data + var rawData: Data - public static var template: ASN1Template { ASN1Template.universal(0) } + static var template: ASN1Template { ASN1Template.universal(0) } } -public struct ASN1Null: ASN1Decodable +struct ASN1Null: ASN1Decodable { - public static var template: ASN1Template { ASN1Template.universal(ASN1Identifier.Tag.null) } + static var template: ASN1Template { ASN1Template.universal(ASN1Identifier.Tag.null) } } extension ASN1Tag @@ -86,77 +86,77 @@ extension ASN1Tag } } -public struct ASN1Identifier +struct ASN1Identifier { - public struct Modifiers + struct Modifiers { - public static let methodMask: UInt8 = 0x20 - public static let primitiv: UInt8 = 0x00 - public static let constructed: UInt8 = 0x20 + static let methodMask: UInt8 = 0x20 + static let primitiv: UInt8 = 0x00 + static let constructed: UInt8 = 0x20 - public static let classMask: UInt8 = 0xc0 - public static let universal: UInt8 = 0x00 - public static let application: UInt8 = 0x40 - public static let contextSpecific: UInt8 = 0x80 - public static let `private`: UInt8 = 0xc0 + static let classMask: UInt8 = 0xc0 + static let universal: UInt8 = 0x00 + static let application: UInt8 = 0x40 + static let contextSpecific: UInt8 = 0x80 + static let `private`: UInt8 = 0xc0 - public static let any: UInt32 = 0x00400 + static let any: UInt32 = 0x00400 } - public struct Tag + struct Tag { - public static let tagMask: UInt8 = 0xff - public static let tagNumMask: UInt8 = 0x7f + static let tagMask: UInt8 = 0xff + static let tagNumMask: UInt8 = 0x7f - public static let endOfContent: ASN1Tag = 0x00 - public static let boolean: ASN1Tag = 0x01 - public static let integer: ASN1Tag = 0x02 - public static let bitString: ASN1Tag = 0x03 - public static let octetString: ASN1Tag = 0x04 - public static let null: ASN1Tag = 0x05 - public static let objectIdentifier: ASN1Tag = 0x06 - public static let objectDescriptor: ASN1Tag = 0x07 - public static let external: ASN1Tag = 0x08 - public static let read: ASN1Tag = 0x09 - public static let enumerated: ASN1Tag = 0x0A - public static let embeddedPdv: ASN1Tag = 0x0B - public static let utf8String: ASN1Tag = 0x0C - public static let relativeOid: ASN1Tag = 0x0D - public static let sequence: ASN1Tag = 0x10 - public static let set: ASN1Tag = 0x11 - public static let numericString: ASN1Tag = 0x12 - public static let printableString: ASN1Tag = 0x13 - public static let t61String: ASN1Tag = 0x14 - public static let videotexString: ASN1Tag = 0x15 - public static let ia5String: ASN1Tag = 0x16 - public static let utcTime: ASN1Tag = 0x17 - public static let generalizedTime: ASN1Tag = 0x18 - public static let graphicString: ASN1Tag = 0x19 - public static let visibleString: ASN1Tag = 0x1A - public static let generalString: ASN1Tag = 0x1B - public static let universalString: ASN1Tag = 0x1C - public static let characterString: ASN1Tag = 0x1D - public static let bmpString: ASN1Tag = 0x1E - public static let highTag: ASN1Tag = 0x1f + static let endOfContent: ASN1Tag = 0x00 + static let boolean: ASN1Tag = 0x01 + static let integer: ASN1Tag = 0x02 + static let bitString: ASN1Tag = 0x03 + static let octetString: ASN1Tag = 0x04 + static let null: ASN1Tag = 0x05 + static let objectIdentifier: ASN1Tag = 0x06 + static let objectDescriptor: ASN1Tag = 0x07 + static let external: ASN1Tag = 0x08 + static let read: ASN1Tag = 0x09 + static let enumerated: ASN1Tag = 0x0A + static let embeddedPdv: ASN1Tag = 0x0B + static let utf8String: ASN1Tag = 0x0C + static let relativeOid: ASN1Tag = 0x0D + static let sequence: ASN1Tag = 0x10 + static let set: ASN1Tag = 0x11 + static let numericString: ASN1Tag = 0x12 + static let printableString: ASN1Tag = 0x13 + static let t61String: ASN1Tag = 0x14 + static let videotexString: ASN1Tag = 0x15 + static let ia5String: ASN1Tag = 0x16 + static let utcTime: ASN1Tag = 0x17 + static let generalizedTime: ASN1Tag = 0x18 + static let graphicString: ASN1Tag = 0x19 + static let visibleString: ASN1Tag = 0x1A + static let generalString: ASN1Tag = 0x1B + static let universalString: ASN1Tag = 0x1C + static let characterString: ASN1Tag = 0x1D + static let bmpString: ASN1Tag = 0x1E + static let highTag: ASN1Tag = 0x1f - public init() + init() { assertionFailure("You can't construct this struct") } - public static func custom(raw: UInt8) -> ASN1Tag + static func custom(raw: UInt8) -> ASN1Tag { return raw } } - public enum Method: UInt8 + enum Method: UInt8 { case primitive = 0x00 case constructed = 0x01 } - public enum Class: UInt8, RawRepresentable + enum Class: UInt8, RawRepresentable { case universal = 0x00 //0 case application = 0x01 //1 diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/DecodingError+Extensions.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/DecodingError+Extensions.swift index 0a53fc4df..cbda9944e 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/DecodingError+Extensions.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/DecodingError+Extensions.swift @@ -7,7 +7,7 @@ import Foundation -public extension DecodingError +extension DecodingError { /// Returns a `.typeMismatch` error describing the expected type. /// diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/Foundation+ASN1Coder.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/Foundation+ASN1Coder.swift index 3ae9df322..c592295f8 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/Foundation+ASN1Coder.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/Foundation+ASN1Coder.swift @@ -9,7 +9,7 @@ import Foundation extension String: ASN1Decodable { - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(ASN1Identifier.Tag.utf8String) } @@ -17,7 +17,7 @@ extension String: ASN1Decodable extension Int: ASN1Decodable { - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(2) } @@ -25,7 +25,7 @@ extension Int: ASN1Decodable extension Int32: ASN1Decodable { - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(2) } @@ -33,7 +33,7 @@ extension Int32: ASN1Decodable extension Data: ASN1Decodable { - public static var template: ASN1Template + static var template: ASN1Template { assertionFailure("Provide template") return ASN1Template.universal(0) @@ -43,7 +43,7 @@ extension Data: ASN1Decodable extension String.Encoding { - public var template: ASN1Template + var template: ASN1Template { switch self { diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/PKCS7/PKCS7.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/PKCS7/PKCS7.swift index a0c4f8ce3..0d5f81de4 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/PKCS7/PKCS7.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ASN1Swift/PKCS7/PKCS7.swift @@ -7,7 +7,7 @@ import Foundation -public enum OID: String +enum OID: String { /// NIST Algorithm case sha1 = "1.3.14.3.2.26" @@ -26,7 +26,7 @@ public enum OID: String case encryptedData = "1.2.840.113549.1.7.6" } -public extension OID +extension OID { @available(iOS 10.0, *) func encryptionAlgorithm() -> SecKeyAlgorithm @@ -48,10 +48,10 @@ public extension OID } } -public struct PKCS7Container: ASN1Decodable +struct PKCS7Container: ASN1Decodable { - public var oid: ASN1SkippedField - public private(set) var signedData: SignedData + var oid: ASN1SkippedField + private(set) var signedData: SignedData enum CodingKeys: ASN1CodingKey { @@ -72,20 +72,20 @@ public struct PKCS7Container: ASN1Decodable } -public extension PKCS7Container +extension PKCS7Container { struct SignedData: ASN1Decodable { - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.contextSpecific(0).constructed().explicit(tag: 16).constructed() } - public var version: Int32 - public var alg: DigestAlgorithmIdentifiersContainer - public var contentInfo: ContentInfo - public var certificates: CetrificatesContaner - public var signerInfos: SignerInfos + var version: Int32 + var alg: DigestAlgorithmIdentifiersContainer + var contentInfo: ContentInfo + var certificates: CetrificatesContaner + var signerInfos: SignerInfos enum CodingKeys: ASN1CodingKey { @@ -116,9 +116,9 @@ public extension PKCS7Container struct DigestAlgorithmIdentifiersContainer: ASN1Decodable { - public var items: [Item] + var items: [Item] - public init(from decoder: Decoder) throws + init(from decoder: Decoder) throws { var container: UnkeyedDecodingContainer = try decoder.unkeyedContainer() @@ -132,12 +132,12 @@ public extension PKCS7Container self.items = items } - public static var template: ASN1Template { ASN1Template.universal(ASN1Identifier.Tag.set).constructed() } + static var template: ASN1Template { ASN1Template.universal(ASN1Identifier.Tag.set).constructed() } - public struct Item: ASN1Decodable + struct Item: ASN1Decodable { - public var algorithm: String - public var parameters: ASN1Null + var algorithm: String + var parameters: ASN1Null enum CodingKeys: ASN1CodingKey { @@ -156,7 +156,7 @@ public extension PKCS7Container } } - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(ASN1Identifier.Tag.sequence).constructed() } @@ -166,13 +166,13 @@ public extension PKCS7Container struct ContentInfo: ASN1Decodable { - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(ASN1Identifier.Tag.sequence).constructed() } - public var oid: ASN1SkippedField - public var payload: ASN1SkippedField + var oid: ASN1SkippedField + var payload: ASN1SkippedField enum CodingKeys: ASN1CodingKey { @@ -195,11 +195,11 @@ public extension PKCS7Container struct Certificate: ASN1Decodable { - public var cert: TPSCertificate - public var signatureAlgorithm: ASN1SkippedField - public var signatureValue: Data + var cert: TPSCertificate + var signatureAlgorithm: ASN1SkippedField + var signatureValue: Data - public var rawData: Data + var rawData: Data enum CodingKeys: ASN1CodingKey { @@ -221,7 +221,7 @@ public extension PKCS7Container } } - public init(from decoder: Decoder) throws + init(from decoder: Decoder) throws { let dec = decoder as! ASN1DecoderProtocol let container = try decoder.container(keyedBy: CodingKeys.self) @@ -232,7 +232,7 @@ public extension PKCS7Container self.signatureValue = try container.decode(Data.self, forKey: .signatureValue) } - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(ASN1Identifier.Tag.sequence).constructed() } @@ -240,14 +240,14 @@ public extension PKCS7Container struct TPSCertificate: ASN1Decodable { - public var version: Int - public var serialNumber: Int - public var signature: ASN1SkippedField - public var issuer: ASN1SkippedField - public var validity: ASN1SkippedField - public var subject: ASN1SkippedField - public var subjectPublicKeyInfo: Data // We will need only this field - public var extensions: ASN1SkippedField + var version: Int + var serialNumber: Int + var signature: ASN1SkippedField + var issuer: ASN1SkippedField + var validity: ASN1SkippedField + var subject: ASN1SkippedField + var subjectPublicKeyInfo: Data // We will need only this field + var extensions: ASN1SkippedField enum CodingKeys: ASN1CodingKey { @@ -284,7 +284,7 @@ public extension PKCS7Container } } - public init(from decoder: Decoder) throws + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -301,7 +301,7 @@ public extension PKCS7Container self.extensions = try container.decode(ASN1SkippedField.self, forKey: .extensions) } - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(ASN1Identifier.Tag.sequence).constructed() } @@ -309,9 +309,9 @@ public extension PKCS7Container struct CetrificatesContaner: ASN1Decodable { - public let certificates: [Certificate] + let certificates: [Certificate] - public init(from decoder: Decoder) throws + init(from decoder: Decoder) throws { var container: UnkeyedDecodingContainer = try decoder.unkeyedContainer() @@ -324,7 +324,7 @@ public extension PKCS7Container self.certificates = certificates } - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.contextSpecific(0).constructed().implicit(tag: ASN1Identifier.Tag.sequence) } @@ -332,16 +332,16 @@ public extension PKCS7Container struct SignerInfos: ASN1Decodable { - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(ASN1Identifier.Tag.set).constructed().explicit(tag: ASN1Identifier.Tag.sequence).constructed() } - public var version: Int - public var signerIdentifier: ASN1SkippedField - public var digestAlgorithm: ASN1SkippedField - public var digestEncryptionAlgorithm: ASN1SkippedField - public var encryptedDigest: Data + var version: Int + var signerIdentifier: ASN1SkippedField + var digestAlgorithm: ASN1SkippedField + var digestEncryptionAlgorithm: ASN1SkippedField + var encryptedDigest: Data enum CodingKeys: ASN1CodingKey { @@ -373,7 +373,7 @@ public extension PKCS7Container extension PKCS7Container { - public static var template: ASN1Template + static var template: ASN1Template { return ASN1Template.universal(16).constructed() } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift index b64970811..a819f3843 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift @@ -14,52 +14,34 @@ actor ReceiptManager: NSObject { private var receiptRefreshCompletion: ((Bool) -> Void)? private weak var delegate: ProductsFetcherSK1? private let receiptData: () -> Data? + private let purchaseController: InternalPurchaseController init( delegate: ProductsFetcherSK1, + purchaseController: InternalPurchaseController, receiptData: @escaping () -> Data? = ReceiptLogic.getReceiptData ) { self.delegate = delegate self.receiptData = receiptData + self.purchaseController = purchaseController } /// Loads purchased products from the receipt, storing the purchased subscription group identifiers, /// purchases and active purchases. @discardableResult func loadPurchasedProducts() async -> Set? { - let hasPurchaseController = Superwall.shared.dependencyContainer.storeKitManager.purchaseController.isDeveloperProvided - - guard let payload = ReceiptLogic.getPayload(using: receiptData) else { - if !hasPurchaseController { - await MainActor.run { - Superwall.shared.subscriptionStatus = .inactive - } - } - return nil - } - guard let delegate = delegate else { - if !hasPurchaseController { - await MainActor.run { - Superwall.shared.subscriptionStatus = .inactive - } - } + guard + let payload = ReceiptLogic.getPayload(using: receiptData), + let delegate = delegate + else { + await purchaseController.syncSubscriptionStatus(withPurchases: []) return nil } let purchases = payload.purchases self.purchases = purchases - - if !hasPurchaseController { - let activePurchases = purchases.filter { $0.isActive } - await MainActor.run { - if activePurchases.isEmpty { - Superwall.shared.subscriptionStatus = .inactive - } else { - Superwall.shared.subscriptionStatus = .active - } - } - } + await purchaseController.syncSubscriptionStatus(withPurchases: purchases) let purchasedProductIds = Set(purchases.map { $0.productIdentifier }) diff --git a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift index ff9567727..71fde25b9 100644 --- a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift +++ b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift @@ -3,12 +3,17 @@ import StoreKit import Combine actor StoreKitManager { - /// Coordinates: The purchasing, restoring and retrieving of products; the checking - /// of transactions; and the determining of the user's subscription status. - private let productsFetcher = ProductsFetcherSK1() - private lazy var receiptManager = ReceiptManager(delegate: productsFetcher) + /// Handler purchasing and restoring. let purchaseController: InternalPurchaseController + /// Retrieves products from storekit. + private let productsFetcher: ProductsFetcherSK1 + + /// + private lazy var receiptManager = ReceiptManager( + delegate: productsFetcher, + purchaseController: purchaseController + ) private(set) var productsById: [String: StoreProduct] = [:] private struct ProductProcessingResult { let productIdsToLoad: Set @@ -16,8 +21,13 @@ actor StoreKitManager { let products: [Product] } - init(purchaseController: InternalPurchaseController) { + init( + purchaseController: InternalPurchaseController, + productsFetcher: ProductsFetcherSK1 = ProductsFetcherSK1() + ) { + self.productsFetcher = productsFetcher self.purchaseController = purchaseController + purchaseController.delegate = self } func getProductVariables(for paywall: Paywall) async -> [ProductVariable] { @@ -123,7 +133,16 @@ actor StoreKitManager { } // MARK: - Restoration -extension StoreKitManager { +extension StoreKitManager: RestoreDelegate { + func didRestore(result: RestorationResult) async { + let hasRestored = result == .restored + await refreshReceipt() + if hasRestored { + await loadPurchasedProducts() + } + } + + /// Resto @MainActor func tryToRestore(_ paywallViewController: PaywallViewController) async { Logger.debug( @@ -136,29 +155,7 @@ extension StoreKitManager { let restorationResult = await purchaseController.restorePurchases() - await processRestoration( - restorationResult: restorationResult, - paywallViewController: paywallViewController - ) - } - - /// After restoring, it checks to see whether the user is actually subscribed or not. - /// - /// This is accessed by both the transaction manager and the restoration manager. - @MainActor - func processRestoration( - restorationResult: RestorationResult, - paywallViewController: PaywallViewController - ) async { let hasRestored = restorationResult == .restored - - if !purchaseController.isDeveloperProvided { - await refreshReceipt() - if hasRestored { - await loadPurchasedProducts() - } - } - let isUserSubscribed = Superwall.shared.subscriptionStatus == .active if hasRestored && isUserSubscribed { diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift index 1b6a15e4c..58896e40d 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift @@ -192,11 +192,9 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { } } - // TODO: REmember, we need this observer to run straight away. - // TODO: REname this function: /// Sends a `PurchaseResult` to the completion block and stores the latest purchased transaction. private func updatePurchaseCompletionBlock(for skTransaction: SKPaymentTransaction) async { - // Only continue if no purchase controller. The transaction may be + // Only continue if using internal purchase controller. The transaction may be // readded to the queue if finishing fails so we need to make sure // we can re-finish the transaction. if storeKitManager?.purchaseController.isDeveloperProvided == true { @@ -219,8 +217,6 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { SKPaymentQueue.default().finishTransaction(skTransaction) await purchasing.completePurchase(result: .failed(error)) } - - // TODO: Finishing could fail. Should we store state of transaction. If purchased/restored>? case .failed: SKPaymentQueue.default().finishTransaction(skTransaction) if let error = skTransaction.error { diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift index 6fa9c11c9..9da0f6cc0 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift @@ -88,11 +88,6 @@ final class TransactionManager { paywallViewController: paywallViewController ) } -// case .restored: -// await storeKitManager.processRestoration( -// restorationResult: .restored, -// paywallViewController: paywallViewController -// ) case .pending: await handlePendingTransaction(from: paywallViewController) case .cancelled: @@ -218,7 +213,7 @@ final class TransactionManager { } return nil } else { - if let transaction = await storeKitManager.purchaseController.productPurchaser.purchasing.lastTransaction { + if let transaction = await storeKitManager.purchaseController.productPurchaser.purchasing.lastTransaction { return await factory.makeStoreTransaction(from: transaction) } return nil diff --git a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift index 933a1c6de..64b52b0a2 100644 --- a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift @@ -96,7 +96,6 @@ final class TrackingTests: XCTestCase { func test_surveyResponse() async { // Given - let eventName = "TestName" let survey = Survey.stub() let paywallInfo = PaywallInfo.stub() let event = InternalSuperwallEvent.SurveyResponse( @@ -1242,8 +1241,7 @@ final class TrackingTests: XCTestCase { let paywallInfo: PaywallInfo = .stub() let productId = "abc" let product = StoreProduct(sk1Product: MockSkProduct(productIdentifier: productId)) - let dependencyContainer = DependencyContainer() - let skTransaction = MockSKPaymentTransaction(state: .purchased) + let result = await Superwall.shared.track(InternalSuperwallEvent.SubscriptionStart(paywallInfo: paywallInfo, product: product)) XCTAssertNotNil(result.parameters.eventParams["$app_session_id"]) XCTAssertTrue(result.parameters.eventParams["$is_standard_event"] as! Bool) @@ -1318,8 +1316,6 @@ final class TrackingTests: XCTestCase { let paywallInfo: PaywallInfo = .stub() let productId = "abc" let product = StoreProduct(sk1Product: MockSkProduct(productIdentifier: productId)) - let dependencyContainer = DependencyContainer() - let skTransaction = MockSKPaymentTransaction(state: .purchased) let result = await Superwall.shared.track(InternalSuperwallEvent.FreeTrialStart(paywallInfo: paywallInfo, product: product)) XCTAssertNotNil(result.parameters.eventParams["$app_session_id"]) XCTAssertTrue(result.parameters.eventParams["$is_standard_event"] as! Bool) @@ -1394,8 +1390,6 @@ final class TrackingTests: XCTestCase { let paywallInfo: PaywallInfo = .stub() let productId = "abc" let product = StoreProduct(sk1Product: MockSkProduct(productIdentifier: productId)) - let dependencyContainer = DependencyContainer() - let skTransaction = MockSKPaymentTransaction(state: .purchased) let result = await Superwall.shared.track(InternalSuperwallEvent.NonRecurringProductPurchase(paywallInfo: paywallInfo, product: product)) XCTAssertNotNil(result.parameters.eventParams["$app_session_id"]) XCTAssertTrue(result.parameters.eventParams["$is_standard_event"] as! Bool) diff --git a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift index 74ec10886..e7c4bfb2d 100644 --- a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift @@ -14,7 +14,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_superwallEvent_noParams() async { // Given let event = InternalSuperwallEvent.AppLaunch() - let storage = StorageMock() // When let parameters = await TrackingLogic.processParameters( @@ -112,7 +111,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_attributes_withCustomParams() async { // Given - let eventName = "TestName" let event = InternalSuperwallEvent.Attributes( appInstalledAtString: "abc", customParameters: [ @@ -140,7 +138,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_superwallEvent_customParams_containsDollar() async { // Given - let eventName = "TestName" let event = InternalSuperwallEvent.Attributes( appInstalledAtString: "abc", customParameters: [ @@ -168,7 +165,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_superwallEvent_customParams_containArray() async { // Given - let eventName = "TestName" let event = InternalSuperwallEvent.Attributes( appInstalledAtString: "abc", customParameters: [ @@ -196,7 +192,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_superwallEvent_customParams_containDictionary() async { // Given - let eventName = "TestName" let event = InternalSuperwallEvent.Attributes( appInstalledAtString: "abc", customParameters: [ @@ -225,7 +220,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_superwallEvent_customParams_containsDate() async { // Given let date = Date(timeIntervalSince1970: 1650534735) - let eventName = "TestName" let event = InternalSuperwallEvent.Attributes( appInstalledAtString: "abc", customParameters: [ @@ -254,7 +248,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_superwallEvent_customParams_containsUrl() async { // Given let url = URL(string: "https://www.google.com")! - let eventName = "TestName" let event = InternalSuperwallEvent.Attributes( appInstalledAtString: "abc", customParameters: [ @@ -282,7 +275,6 @@ final class TrackingLogicTests: XCTestCase { func testProcessParameters_superwallEvent_customParams_nilValue() async { // Given - let eventName = "TestName" let event = InternalSuperwallEvent.Attributes( appInstalledAtString: "abc", customParameters: [ diff --git a/Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift b/Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift deleted file mode 100644 index f322a30e0..000000000 --- a/Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 13/01/2023. -// - -import Foundation -@testable import SuperwallKit - -final class StoreKitCoordinatorFactoryMock: StoreKitCoordinatorFactory { - let coordinator: StoreKitCoordinator - - init(coordinator: StoreKitCoordinator) { - self.coordinator = coordinator - } - - func makeStoreKitCoordinator() -> StoreKitCoordinator { - return coordinator - } -} diff --git a/Tests/SuperwallKitTests/Network/NetworkTests.swift b/Tests/SuperwallKitTests/Network/NetworkTests.swift index 1b463a3e1..f373f9aa0 100644 --- a/Tests/SuperwallKitTests/Network/NetworkTests.swift +++ b/Tests/SuperwallKitTests/Network/NetworkTests.swift @@ -16,12 +16,11 @@ final class NetworkTests: XCTestCase { urlSession: CustomURLSessionMock, injectedApplicationStatePublisher: AnyPublisher, completion: @escaping () -> Void - ) async { - let task = Task { + ) { + _ = Task { let dependencyContainer = DependencyContainer() let network = Network(urlSession: urlSession, factory: dependencyContainer) - let requestId = "abc" - + _ = try? await network.getConfig( injectedApplicationStatePublisher: injectedApplicationStatePublisher, isRetryingCallback: {} @@ -37,7 +36,7 @@ final class NetworkTests: XCTestCase { .eraseToAnyPublisher() let expectation = expectation(description: "config completed") expectation.isInverted = true - await configWrapper( + configWrapper( urlSession: urlSession, injectedApplicationStatePublisher: publisher ) { diff --git a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift index 00f90cfaf..3b456361f 100644 --- a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift @@ -82,13 +82,6 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { type: .getPaywall(.stub()) ) - let input = PresentablePipelineOutput( - debugInfo: [:], - paywallViewController: dependencyContainer.makePaywallViewController(for: .stub(), withCache: nil, delegate: nil), - presenter: UIViewController(), - confirmableAssignment: ConfirmableAssignment(experimentId: "", variant: .init(id: "", type: .treatment, paywallId: "")) - ) - Superwall.shared.confirmPaywallAssignment( ConfirmableAssignment(experimentId: "", variant: .init(id: "", type: .treatment, paywallId: "")), request: request, @@ -119,13 +112,6 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { type: .getPresentationResult ) - let input = PresentablePipelineOutput( - debugInfo: [:], - paywallViewController: dependencyContainer.makePaywallViewController(for: .stub(), withCache: nil, delegate: nil), - presenter: UIViewController(), - confirmableAssignment: ConfirmableAssignment(experimentId: "", variant: .init(id: "", type: .treatment, paywallId: "")) - ) - Superwall.shared.confirmPaywallAssignment( ConfirmableAssignment(experimentId: "", variant: .init(id: "", type: .treatment, paywallId: "")), request: request, @@ -156,13 +142,6 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { type: .getImplicitPresentationResult ) - let input = PresentablePipelineOutput( - debugInfo: [:], - paywallViewController: dependencyContainer.makePaywallViewController(for: .stub(), withCache: nil, delegate: nil), - presenter: UIViewController(), - confirmableAssignment: ConfirmableAssignment(experimentId: "", variant: .init(id: "", type: .treatment, paywallId: "")) - ) - Superwall.shared.confirmPaywallAssignment( ConfirmableAssignment(experimentId: "", variant: .init(id: "", type: .treatment, paywallId: "")), request: request, diff --git a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift index 88bcfa32e..b61c22d3c 100644 --- a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift @@ -72,8 +72,6 @@ final class GetPaywallVcOperatorTests: XCTestCase { @MainActor func test_getPaywallViewController_error_userNotSubscribed() async { - let experiment = Experiment(id: "", groupId: "", variant: .init(id: "", type: .treatment, paywallId: "")) - let statePublisher = PassthroughSubject() let stateExpectation = expectation(description: "Output a state") stateExpectation.expectedFulfillmentCount = 2 @@ -127,8 +125,6 @@ final class GetPaywallVcOperatorTests: XCTestCase { @MainActor func test_getPaywallViewController_success_paywallNotAlreadyPresented() async { - let experiment = Experiment(id: "", groupId: "", variant: .init(id: "", type: .treatment, paywallId: "")) - let statePublisher = PassthroughSubject() let stateExpectation = expectation(description: "Output a state") stateExpectation.isInverted = true diff --git a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift index 917b06feb..4c04c49e6 100644 --- a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift @@ -10,37 +10,24 @@ import XCTest @testable import SuperwallKit class ReceiptManagerTests: XCTestCase { - var storeKitCoordinatorFactoryMock: StoreKitCoordinatorFactoryMock! - - // MARK: - loadPurchasedProducts - private func makeStoreKitManager(with productsFetcher: ProductsFetcherSK1) -> StoreKitManager { - let dependencyContainer = DependencyContainer() - let coordinator = StoreKitCoordinator( - delegateAdapter: dependencyContainer.delegateAdapter, - storeKitManager: dependencyContainer.storeKitManager, - factory: dependencyContainer, - productsFetcher: productsFetcher - ) - storeKitCoordinatorFactoryMock = StoreKitCoordinatorFactoryMock( - coordinator: coordinator - ) - let storeKitManager = StoreKitManager(factory: storeKitCoordinatorFactoryMock) - - return storeKitManager - } + let dependencyContainer = DependencyContainer() + lazy var purchaseController = InternalPurchaseController( + factory: dependencyContainer, + swiftPurchaseController: nil, + objcPurchaseController: nil + ) func test_loadPurchasedProducts_nilProducts() async { let product = MockSkProduct(subscriptionGroupIdentifier: "abc") let productsFetcher = ProductsFetcherSK1Mock( productCompletionResult: .success([StoreProduct(sk1Product: product)]) ) - let manager = makeStoreKitManager(with: productsFetcher) - let getReceiptData: () -> Data = { return MockReceiptData.newReceipt } let receiptManager = ReceiptManager( - delegate: manager, + delegate: productsFetcher, + purchaseController: purchaseController, receiptData: getReceiptData ) @@ -53,13 +40,12 @@ class ReceiptManagerTests: XCTestCase { let productsFetcher = ProductsFetcherSK1Mock( productCompletionResult: .failure(TestError("error")) ) - let manager = makeStoreKitManager(with: productsFetcher) - let getReceiptData: () -> Data = { return MockReceiptData.newReceipt } let receiptManager = ReceiptManager( - delegate: manager, + delegate: productsFetcher, + purchaseController: purchaseController, receiptData: getReceiptData ) diff --git a/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift b/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift index cc2977560..6aaef24c3 100644 --- a/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift @@ -11,23 +11,12 @@ import XCTest import StoreKit class StoreKitManagerTests: XCTestCase { - var storeKitCoordinatorFactoryMock: StoreKitCoordinatorFactoryMock! - - private func makeStoreKitManager(with productsFetcher: ProductsFetcherSK1) -> StoreKitManager { - let dependencyContainer = DependencyContainer() - let coordinator = StoreKitCoordinator( - delegateAdapter: dependencyContainer.delegateAdapter, - storeKitManager: dependencyContainer.storeKitManager, - factory: dependencyContainer, - productsFetcher: productsFetcher - ) - storeKitCoordinatorFactoryMock = StoreKitCoordinatorFactoryMock( - coordinator: coordinator - ) - let storeKitManager = StoreKitManager(factory: storeKitCoordinatorFactoryMock) - - return storeKitManager - } + let dependencyContainer = DependencyContainer() + lazy var purchaseController = InternalPurchaseController( + factory: dependencyContainer, + swiftPurchaseController: nil, + objcPurchaseController: nil + ) func test_getProducts_primaryProduct() async { let dependencyContainer = DependencyContainer() @@ -107,7 +96,10 @@ class StoreKitManagerTests: XCTestCase { func test_getProducts_substitutePrimaryProduct_oneResponseProduct() async { let productsResult: Result, Error> = .success([]) let productsFetcher = ProductsFetcherSK1Mock(productCompletionResult: productsResult) - let manager = makeStoreKitManager(with: productsFetcher) + let manager = StoreKitManager( + purchaseController: purchaseController, + productsFetcher: productsFetcher + ) let primary = MockSkProduct(productIdentifier: "abc") let substituteProducts = PaywallProducts( @@ -131,7 +123,10 @@ class StoreKitManagerTests: XCTestCase { StoreProduct(sk1Product: responseProduct2) ]) let productsFetcher = ProductsFetcherSK1Mock(productCompletionResult: productsResult) - let manager = makeStoreKitManager(with: productsFetcher) + let manager = StoreKitManager( + purchaseController: purchaseController, + productsFetcher: productsFetcher + ) let primary = MockSkProduct(productIdentifier: "abc") let substituteProducts = PaywallProducts( From 04c9caa6b5858fec5914fa7cb1ae6a689a0b7a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Wed, 30 Aug 2023 18:16:53 +0800 Subject: [PATCH 3/8] Updated GitHub PR template and changelog --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- CHANGELOG.md | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8f784db80..26063ff01 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,11 @@ ## Changes in this pull request -Issue fixed: # +- ### Checklist - [ ] All unit and UI tests pass. Demo project builds and runs. -- [ ] I added tests, an experiment, or detailed why my change isn't tested. +- [ ] I added/updated tests or detailed why my change isn't tested. - [ ] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes. - [ ] I have run `swiftlint` in the main directory and fixed any issues. - [ ] I have updated the SDK documentation as well as the online docs. diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7f79c88..71e4dd16c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall-me/Superwall-iOS/releases) on GitHub. +## 3.3.3 + +### Fixes + +- Fixes issue where verification was happening after the finishing of transactions when not using a `PurchaseController`. + ## 3.3.2 ### Fixes From c9366034a00637b128ba70711811b756575e0592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Wed, 30 Aug 2023 18:22:40 +0800 Subject: [PATCH 4/8] Update StoreKitManager.swift --- Sources/SuperwallKit/StoreKit/StoreKitManager.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift index 71fde25b9..a57973fa2 100644 --- a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift +++ b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift @@ -9,7 +9,6 @@ actor StoreKitManager { /// Retrieves products from storekit. private let productsFetcher: ProductsFetcherSK1 - /// private lazy var receiptManager = ReceiptManager( delegate: productsFetcher, purchaseController: purchaseController From 0449121f30086ff607ee204eee075b961351a494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 31 Aug 2023 12:01:49 +0800 Subject: [PATCH 5/8] Updated some wording --- .../Trackable Events/TrackableSuperwallEvent.swift | 4 ++-- .../SuperwallKit/Dependencies/DependencyContainer.swift | 4 ++-- Sources/SuperwallKit/Dependencies/FactoryProtocols.swift | 4 ++-- Sources/SuperwallKit/Storage/Storage.swift | 8 ++++---- Sources/SuperwallKit/StoreKit/StoreKitManager.swift | 1 - .../Analytics/Internal Tracking/TrackTests.swift | 2 +- .../Analytics/Internal Tracking/TrackingLogicTests.swift | 6 +++--- Tests/SuperwallKitTests/Storage/StorageMock.swift | 4 ++-- 8 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift index 265709de9..f7926cd47 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift @@ -35,12 +35,12 @@ enum InternalSuperwallEvent { struct AppInstall: TrackableSuperwallEvent { let superwallEvent: SuperwallEvent = .appInstall let appInstalledAtString: String - let hasPurchaseController: Bool + let hasExternalPurchaseController: Bool var customParameters: [String: Any] = [:] func getSuperwallParameters() async -> [String: Any] { return [ "application_installed_at": appInstalledAtString, - "using_purchase_controller": hasPurchaseController + "using_purchase_controller": hasExternalPurchaseController ] } } diff --git a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift index e2e8b9b2d..0820740fa 100644 --- a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift +++ b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift @@ -431,8 +431,8 @@ extension DependencyContainer: TriggerFactory { } // MARK: - Purchase Controller Factory -extension DependencyContainer: HasPurchaseControllerFactory { - func makeHasPurchaseController() -> Bool { +extension DependencyContainer: HasExternalPurchaseControllerFactory { + func makeHasExternalPurchaseController() -> Bool { return storeKitManager.purchaseController.isDeveloperProvided } } diff --git a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift index b6eef8135..5e24af7f1 100644 --- a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift +++ b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift @@ -93,8 +93,8 @@ protocol DeviceHelperFactory: AnyObject { func makeIsSandbox() -> Bool } -protocol HasPurchaseControllerFactory: AnyObject { - func makeHasPurchaseController() -> Bool +protocol HasExternalPurchaseControllerFactory: AnyObject { + func makeHasExternalPurchaseController() -> Bool } protocol ApiFactory: AnyObject { diff --git a/Sources/SuperwallKit/Storage/Storage.swift b/Sources/SuperwallKit/Storage/Storage.swift index aa5a50637..603580a0a 100644 --- a/Sources/SuperwallKit/Storage/Storage.swift +++ b/Sources/SuperwallKit/Storage/Storage.swift @@ -73,12 +73,12 @@ class Storage { /// The disk cache. private let cache: Cache - private unowned let factory: DeviceHelperFactory & HasPurchaseControllerFactory + private unowned let factory: DeviceHelperFactory & HasExternalPurchaseControllerFactory // MARK: - Configuration init( - factory: DeviceHelperFactory & HasPurchaseControllerFactory, + factory: DeviceHelperFactory & HasExternalPurchaseControllerFactory, cache: Cache = Cache(), coreDataManager: CoreDataManager = CoreDataManager() ) { @@ -172,13 +172,13 @@ class Storage { return } - let hasPurchaseController = factory.makeHasPurchaseController() + let hasExternalPurchaseController = factory.makeHasExternalPurchaseController() let deviceInfo = factory.makeDeviceInfo() Task { let event = InternalSuperwallEvent.AppInstall( appInstalledAtString: deviceInfo.appInstalledAtString, - hasPurchaseController: hasPurchaseController + hasExternalPurchaseController: hasExternalPurchaseController ) _ = await trackEvent(event) } diff --git a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift index a57973fa2..9148d0ff1 100644 --- a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift +++ b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift @@ -141,7 +141,6 @@ extension StoreKitManager: RestoreDelegate { } } - /// Resto @MainActor func tryToRestore(_ paywallViewController: PaywallViewController) async { Logger.debug( diff --git a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift index 64b52b0a2..3a1d9de3e 100644 --- a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackTests.swift @@ -32,7 +32,7 @@ final class TrackingTests: XCTestCase { func test_appInstall() async { let appInstalledAtString = "now" - let result = await Superwall.shared.track(InternalSuperwallEvent.AppInstall(appInstalledAtString: appInstalledAtString, hasPurchaseController: true)) + let result = await Superwall.shared.track(InternalSuperwallEvent.AppInstall(appInstalledAtString: appInstalledAtString, hasExternalPurchaseController: true)) XCTAssertNotNil(result.parameters.eventParams["$app_session_id"]) XCTAssertTrue(result.parameters.eventParams["$is_standard_event"] as! Bool) XCTAssertTrue(result.parameters.eventParams["$using_purchase_controller"] as! Bool) diff --git a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift index e7c4bfb2d..752c8c026 100644 --- a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift @@ -326,7 +326,7 @@ final class TrackingLogicTests: XCTestCase { ) let outcome = TrackingLogic.canTriggerPaywall( - InternalSuperwallEvent.AppInstall(appInstalledAtString: "", hasPurchaseController: false), + InternalSuperwallEvent.AppInstall(appInstalledAtString: "", hasExternalPurchaseController: false), triggers: Set(["app_install"]), paywallViewController: paywallVc ) @@ -335,7 +335,7 @@ final class TrackingLogicTests: XCTestCase { func testDidStartNewSession_canTriggerPaywall_isntTrigger() { let outcome = TrackingLogic.canTriggerPaywall( - InternalSuperwallEvent.AppInstall(appInstalledAtString: "", hasPurchaseController: false), + InternalSuperwallEvent.AppInstall(appInstalledAtString: "", hasExternalPurchaseController: false), triggers: [], paywallViewController: nil ) @@ -344,7 +344,7 @@ final class TrackingLogicTests: XCTestCase { func testDidStartNewSession_canTriggerPaywall_isAllowedInternalEvent() { let outcome = TrackingLogic.canTriggerPaywall( - InternalSuperwallEvent.AppInstall(appInstalledAtString: "", hasPurchaseController: false), + InternalSuperwallEvent.AppInstall(appInstalledAtString: "", hasExternalPurchaseController: false), triggers: ["app_install"], paywallViewController: nil ) diff --git a/Tests/SuperwallKitTests/Storage/StorageMock.swift b/Tests/SuperwallKitTests/Storage/StorageMock.swift index 8a06c51a8..1d2199d7d 100644 --- a/Tests/SuperwallKitTests/Storage/StorageMock.swift +++ b/Tests/SuperwallKitTests/Storage/StorageMock.swift @@ -18,7 +18,7 @@ final class StorageMock: Storage { var didClearCachedSessionEvents = false var didSave = false - class DeviceInfoFactoryMock: DeviceHelperFactory, HasPurchaseControllerFactory { + class DeviceInfoFactoryMock: DeviceHelperFactory, HasExternalPurchaseControllerFactory { func makeDeviceInfo() -> DeviceInfo { return DeviceInfo(appInstalledAtString: "a", locale: "b") } @@ -27,7 +27,7 @@ final class StorageMock: Storage { return true } - func makeHasPurchaseController() -> Bool { + func makeHasExternalPurchaseController() -> Bool { return false } } From 7144a9b85cd48762a4192e0b210ef5f456d2f53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 31 Aug 2023 12:35:13 +0800 Subject: [PATCH 6/8] Moved all restore logic into internal purchase controller --- .../StoreKit/InternalPurchaseController.swift | 109 ++++++++++++++---- .../StoreKit/StoreKitManager.swift | 53 --------- 2 files changed, 85 insertions(+), 77 deletions(-) diff --git a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift index c738a5e93..1929a6c8c 100644 --- a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift +++ b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift @@ -12,7 +12,7 @@ protocol RestoreDelegate: AnyObject { func didRestore(result: RestorationResult) async } -final class InternalPurchaseController { +final class InternalPurchaseController: PurchaseController { var isDeveloperProvided: Bool { return swiftPurchaseController != nil || objcPurchaseController != nil } @@ -31,7 +31,10 @@ final class InternalPurchaseController { self.objcPurchaseController = objcPurchaseController self.factory = factory } +} +// MARK: - Subscription Status +extension InternalPurchaseController { func syncSubscriptionStatus(withPurchases purchases: Set) async { if isDeveloperProvided { return @@ -47,8 +50,87 @@ final class InternalPurchaseController { } } -// MARK: - Purchase Controller -extension InternalPurchaseController: PurchaseController { +// MARK: - Restoration +extension InternalPurchaseController { + @MainActor + func restorePurchases() async -> RestorationResult { + if let purchaseController = swiftPurchaseController { + return await purchaseController.restorePurchases() + } else if let purchaseController = objcPurchaseController { + return await withCheckedContinuation { continuation in + purchaseController.restorePurchases { result, error in + switch result { + case .restored: + continuation.resume(returning: .restored) + case .failed: + continuation.resume(returning: .failed(error)) + } + } + } + } else { + let result = await productPurchaser.restorePurchases() + await delegate?.didRestore(result: result) + return result + } + } + + @MainActor + func tryToRestore(_ paywallViewController: PaywallViewController) async { + Logger.debug( + logLevel: .debug, + scope: .paywallTransactions, + message: "Attempting Restore" + ) + + paywallViewController.loadingState = .loadingPurchase + + let restorationResult = await restorePurchases() + + let hasRestored = restorationResult == .restored + let isUserSubscribed = Superwall.shared.subscriptionStatus == .active + + if hasRestored && isUserSubscribed { + Logger.debug( + logLevel: .debug, + scope: .paywallTransactions, + message: "Transactions Restored" + ) + await transactionWasRestored(paywallViewController: paywallViewController) + } else { + Logger.debug( + logLevel: .debug, + scope: .paywallTransactions, + message: "Transactions Failed to Restore" + ) + + paywallViewController.presentAlert( + title: Superwall.shared.options.paywalls.restoreFailed.title, + message: Superwall.shared.options.paywalls.restoreFailed.message, + closeActionTitle: Superwall.shared.options.paywalls.restoreFailed.closeButtonTitle + ) + } + } + + private func transactionWasRestored(paywallViewController: PaywallViewController) async { + let paywallInfo = await paywallViewController.info + + let trackedEvent = InternalSuperwallEvent.Transaction( + state: .restore, + paywallInfo: paywallInfo, + product: nil, + model: nil + ) + await Superwall.shared.track(trackedEvent) + + if Superwall.shared.options.paywalls.automaticallyDismiss { + await Superwall.shared.dismiss(paywallViewController, result: .restored) + } + } +} + +// MARK: - Purchasing +extension InternalPurchaseController { + @MainActor func purchase(product: SKProduct) async -> PurchaseResult { if let purchaseController = swiftPurchaseController { return await purchaseController.purchase(product: product) @@ -75,25 +157,4 @@ extension InternalPurchaseController: PurchaseController { return await productPurchaser.purchase(product: product) } } - - func restorePurchases() async -> RestorationResult { - if let purchaseController = swiftPurchaseController { - return await purchaseController.restorePurchases() - } else if let purchaseController = objcPurchaseController { - return await withCheckedContinuation { continuation in - purchaseController.restorePurchases { result, error in - switch result { - case .restored: - continuation.resume(returning: .restored) - case .failed: - continuation.resume(returning: .failed(error)) - } - } - } - } else { - let result = await productPurchaser.restorePurchases() - await delegate?.didRestore(result: result) - return result - } - } } diff --git a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift index 9148d0ff1..90f36e08b 100644 --- a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift +++ b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift @@ -140,59 +140,6 @@ extension StoreKitManager: RestoreDelegate { await loadPurchasedProducts() } } - - @MainActor - func tryToRestore(_ paywallViewController: PaywallViewController) async { - Logger.debug( - logLevel: .debug, - scope: .paywallTransactions, - message: "Attempting Restore" - ) - - paywallViewController.loadingState = .loadingPurchase - - let restorationResult = await purchaseController.restorePurchases() - - let hasRestored = restorationResult == .restored - let isUserSubscribed = Superwall.shared.subscriptionStatus == .active - - if hasRestored && isUserSubscribed { - Logger.debug( - logLevel: .debug, - scope: .paywallTransactions, - message: "Transactions Restored" - ) - await transactionWasRestored(paywallViewController: paywallViewController) - } else { - Logger.debug( - logLevel: .debug, - scope: .paywallTransactions, - message: "Transactions Failed to Restore" - ) - - paywallViewController.presentAlert( - title: Superwall.shared.options.paywalls.restoreFailed.title, - message: Superwall.shared.options.paywalls.restoreFailed.message, - closeActionTitle: Superwall.shared.options.paywalls.restoreFailed.closeButtonTitle - ) - } - } - - private func transactionWasRestored(paywallViewController: PaywallViewController) async { - let paywallInfo = await paywallViewController.info - - let trackedEvent = InternalSuperwallEvent.Transaction( - state: .restore, - paywallInfo: paywallInfo, - product: nil, - model: nil - ) - await Superwall.shared.track(trackedEvent) - - if Superwall.shared.options.paywalls.automaticallyDismiss { - await Superwall.shared.dismiss(paywallViewController, result: .restored) - } - } } // MARK: - Receipt API From 6ff90dd1639df68a8e2b0785b127ad01d1618732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 31 Aug 2023 12:41:08 +0800 Subject: [PATCH 7/8] Fix to restore flow from Superwall class --- Sources/SuperwallKit/Superwall.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SuperwallKit/Superwall.swift b/Sources/SuperwallKit/Superwall.swift index 3e1e40478..408bc5d42 100644 --- a/Sources/SuperwallKit/Superwall.swift +++ b/Sources/SuperwallKit/Superwall.swift @@ -494,7 +494,7 @@ extension Superwall: PaywallViewControllerEventDelegate { from: paywallViewController ) case .initiateRestore: - await dependencyContainer.storeKitManager.tryToRestore(paywallViewController) + await dependencyContainer.storeKitManager.purchaseController.tryToRestore(paywallViewController) case .openedURL(let url): dependencyContainer.delegateAdapter.paywallWillOpenURL(url: url) case .openedUrlInSafari(let url): From 244aca04a09da56fbf1c81496fe87fd14f58e534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 31 Aug 2023 12:51:32 +0800 Subject: [PATCH 8/8] Name change and spelling fix --- .../SuperwallKit/StoreKit/InternalPurchaseController.swift | 4 ++-- .../StoreKit/Transactions/TransactionManager.swift | 2 +- Sources/SuperwallKit/Superwall.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift index 1929a6c8c..31748cdd4 100644 --- a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift +++ b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift @@ -18,8 +18,8 @@ final class InternalPurchaseController: PurchaseController { } private var swiftPurchaseController: PurchaseController? private var objcPurchaseController: PurchaseControllerObjc? - lazy var productPurchaser = factory.makeSK1ProductPurchaser() private let factory: ProductPurchaserFactory + lazy var productPurchaser = factory.makeSK1ProductPurchaser() weak var delegate: RestoreDelegate? init( @@ -75,7 +75,7 @@ extension InternalPurchaseController { } @MainActor - func tryToRestore(_ paywallViewController: PaywallViewController) async { + func tryToRestore(from paywallViewController: PaywallViewController) async { Logger.debug( logLevel: .debug, scope: .paywallTransactions, diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift index 9da0f6cc0..31dbe9197 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift @@ -37,7 +37,7 @@ final class TransactionManager { /// - Parameters: /// - productId: The ID of the product to purchase. /// - paywallViewController: The `PaywallViewController` that the product is being - /// purhcased from. + /// purchased from. func purchase( _ productId: String, from paywallViewController: PaywallViewController diff --git a/Sources/SuperwallKit/Superwall.swift b/Sources/SuperwallKit/Superwall.swift index 408bc5d42..ee081e203 100644 --- a/Sources/SuperwallKit/Superwall.swift +++ b/Sources/SuperwallKit/Superwall.swift @@ -494,7 +494,7 @@ extension Superwall: PaywallViewControllerEventDelegate { from: paywallViewController ) case .initiateRestore: - await dependencyContainer.storeKitManager.purchaseController.tryToRestore(paywallViewController) + await dependencyContainer.storeKitManager.purchaseController.tryToRestore(from: paywallViewController) case .openedURL(let url): dependencyContainer.delegateAdapter.paywallWillOpenURL(url: url) case .openedUrlInSafari(let url):