From c7169c2092272e4028ee9a7ef10bc827933fc50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:32:14 -0600 Subject: [PATCH 1/3] Rename `PaywallView(event:)` to `PaywallView(placement:)` --- CHANGELOG.md | 8 +++- .../EvaluationContext.swift | 2 +- .../Get Paywall/SwiftUI/PaywallView.swift | 48 +++++++++---------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf8f83f98..726c3633f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,18 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub. +## 4.0.0-beta.3 + +### Breaking Changes + +- Renames `PaywallView(event:)` to `PaywallView(placement:)`. + ## 4.0.0-beta.2 ### Fixes - Fixes an issue to do with audience filters. -- Readds unavailable functions from v3 to make the upgrade path smoother. +- Re-adds unavailable functions from v3 to make the upgrade path smoother. ## 4.0.0-beta.1 diff --git a/Sources/SuperwallKit/Paywall/Presentation/Audience Logic/Expression Evaluator/EvaluationContext.swift b/Sources/SuperwallKit/Paywall/Presentation/Audience Logic/Expression Evaluator/EvaluationContext.swift index 1f6108f9a..087451e77 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Audience Logic/Expression Evaluator/EvaluationContext.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Audience Logic/Expression Evaluator/EvaluationContext.swift @@ -78,7 +78,7 @@ func toPassableValue(from anyValue: Any) -> PassableValue { if let number = anyValue as? NSNumber { // Check if it is a boolean if CFGetTypeID(number) == CFBooleanGetTypeID() { - return .bool(number.boolValue) + return .bool(number.boolValue) } // If not a boolean, let the switch handle the rest } diff --git a/Sources/SuperwallKit/Paywall/Presentation/Get Paywall/SwiftUI/PaywallView.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Paywall/SwiftUI/PaywallView.swift index 2d7ba2c2b..5b79d0491 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Get Paywall/SwiftUI/PaywallView.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Get Paywall/SwiftUI/PaywallView.swift @@ -9,7 +9,7 @@ import SwiftUI /// A SwiftUI paywall view that you can embed into your app. /// -/// This uses ``Superwall/getPaywall(forEvent:params:paywallOverrides:delegate:)`` under the hood. +/// This uses ``Superwall/getPaywall(forPlacement:params:paywallOverrides:delegate:)`` under the hood. /// /// - Warning: You're responsible for the deallocation of this view. If you have a `PaywallView` presented somewhere /// and you try to present the same `PaywallView` elsewhere, you will get a crash. @@ -21,10 +21,10 @@ public struct PaywallView< @State private var hasLoaded = false @Environment(\.presentationMode) private var presentationMode - /// The name of the event, as you have defined on the Superwall dashboard. - private let event: String + /// The name of the placement, as you have defined on the Superwall dashboard. + private let placement: String - /// Optional parameters you'd like to pass with your event. Defaults to `nil`. + /// Optional parameters you'd like to pass with your placement. Defaults to `nil`. /// /// These can be referenced within the rules /// of your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Values can be any @@ -60,11 +60,11 @@ public struct PaywallView< /// A SwiftUI paywall view that you can embed into your app. /// - /// This uses ``Superwall/getPaywall(forEvent:params:paywallOverrides:delegate:)`` under the hood. + /// This uses ``Superwall/getPaywall(forPlacement:params:paywallOverrides:delegate:)`` under the hood. /// /// - Parameters: - /// - event: The name of the event, as you have defined on the Superwall dashboard. - /// - params: Optional parameters you'd like to pass with your event. These can be referenced within the rules + /// - placement: The name of the placement, as you have defined on the Superwall dashboard. + /// - params: Optional parameters you'd like to pass with your placement. These can be referenced within the rules /// of your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Values can be any /// JSON encodable value, URLs or Dates. Arrays and dictionaries as values are not supported at this time, and will /// be dropped. @@ -84,7 +84,7 @@ public struct PaywallView< /// - Warning: You're responsible for the deallocation of this view. If you have a `PaywallView` presented somewhere /// and you try to present the same `PaywallView` elsewhere, you will get a crash. public init( - event: String, + placement: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil, onRequestDismiss: ((PaywallInfo, PaywallResult) -> Void)? = nil, @@ -92,7 +92,7 @@ public struct PaywallView< onErrorView: ((Error) -> OnErrorView)? = nil, feature: (() -> Void)? = nil ) { - self.event = event + self.placement = placement self.params = params self.paywallOverrides = paywallOverrides self.onRequestDismiss = onRequestDismiss @@ -140,7 +140,7 @@ public struct PaywallView< hasLoaded = true Task { await manager.getPaywall( - forPlacement: event, + forPlacement: placement, params: params, paywallOverrides: paywallOverrides ) @@ -154,10 +154,10 @@ public struct PaywallView< extension PaywallView where OnSkippedView == Never, OnErrorView == Never { /// A SwiftUI paywall view that you can embed into your app. /// - /// This uses ``Superwall/getPaywall(forEvent:params:paywallOverrides:delegate:)`` under the hood. + /// This uses ``Superwall/getPaywall(forPlacement:params:paywallOverrides:delegate:)`` under the hood. /// /// - Parameters: - /// - event: The name of the event, as you have defined on the Superwall dashboard. + /// - placement: The name of the placement, as you have defined on the Superwall dashboard. /// - params: Optional parameters you'd like to pass with your event. These can be referenced within the rules /// of your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Values can be any /// JSON encodable value, URLs or Dates. Arrays and dictionaries as values are not supported at this time, and will @@ -174,13 +174,13 @@ extension PaywallView where OnSkippedView == Never, OnErrorView == Never { /// - Warning: You're responsible for the deallocation of this view. If you have a `PaywallView` presented somewhere /// and you try to present the same `PaywallView` elsewhere, you will get a crash. public init( - event: String, + placement: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil, onRequestDismiss: ((PaywallInfo, PaywallResult) -> Void)? = nil, feature: (() -> Void)? = nil ) { - self.event = event + self.placement = placement self.params = params self.paywallOverrides = paywallOverrides self.onErrorView = nil @@ -194,11 +194,11 @@ extension PaywallView where OnSkippedView == Never, OnErrorView == Never { extension PaywallView where OnSkippedView == Never { /// A SwiftUI paywall view that you can embed into your app. /// - /// This uses ``Superwall/getPaywall(forEvent:params:paywallOverrides:delegate:)`` under the hood. + /// This uses ``Superwall/getPaywall(forPlacement:params:paywallOverrides:delegate:)`` under the hood. /// /// - Parameters: - /// - event: The name of the event, as you have defined on the Superwall dashboard. - /// - params: Optional parameters you'd like to pass with your event. These can be referenced within the rules + /// - placement: The name of the placement, as you have defined on the Superwall dashboard. + /// - params: Optional parameters you'd like to pass with your placement. These can be referenced within the rules /// of your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Values can be any /// JSON encodable value, URLs or Dates. Arrays and dictionaries as values are not supported at this time, and will /// be dropped. @@ -216,14 +216,14 @@ extension PaywallView where OnSkippedView == Never { /// - Warning: You're responsible for the deallocation of this view. If you have a `PaywallView` presented somewhere /// and you try to present the same `PaywallView` elsewhere, you will get a crash. public init( - event: String, + placement: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil, onRequestDismiss: ((PaywallInfo, PaywallResult) -> Void)? = nil, onErrorView: @escaping (Error) -> OnErrorView, feature: (() -> Void)? = nil ) { - self.event = event + self.placement = placement self.params = params self.paywallOverrides = paywallOverrides self.onRequestDismiss = onRequestDismiss @@ -237,11 +237,11 @@ extension PaywallView where OnSkippedView == Never { extension PaywallView where OnErrorView == Never { /// A SwiftUI paywall view that you can embed into your app. /// - /// This uses ``Superwall/getPaywall(forEvent:params:paywallOverrides:delegate:)`` under the hood. + /// This uses ``Superwall/getPaywall(forPlacement:params:paywallOverrides:delegate:)`` under the hood. /// /// - Parameters: - /// - event: The name of the event, as you have defined on the Superwall dashboard. - /// - params: Optional parameters you'd like to pass with your event. These can be referenced within the rules + /// - placement: The name of the placement, as you have defined on the Superwall dashboard. + /// - params: Optional parameters you'd like to pass with your placement. These can be referenced within the rules /// of your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Values can be any /// JSON encodable value, URLs or Dates. Arrays and dictionaries as values are not supported at this time, and will /// be dropped. @@ -261,14 +261,14 @@ extension PaywallView where OnErrorView == Never { /// - Warning: You're responsible for the deallocation of this view. If you have a `PaywallView` presented somewhere /// and you try to present the same `PaywallView` elsewhere, you will get a crash. public init( - event: String, + placement: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil, onRequestDismiss: ((PaywallInfo, PaywallResult) -> Void)? = nil, onSkippedView: @escaping (PaywallSkippedReason) -> OnSkippedView, feature: (() -> Void)? = nil ) { - self.event = event + self.placement = placement self.params = params self.paywallOverrides = paywallOverrides self.onErrorView = nil From e1fc16db91983935617eaa028d4d3b75a8c3a88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:13:39 -0600 Subject: [PATCH 2/3] Stores the latest transaction for SK2 as fallback --- CHANGELOG.md | 4 ++++ .../Extensions/SuperwallExtension.md | 1 + .../Transactions/Purchasing/PurchaseManager.swift | 1 + .../Purchasing/PurchasingCoordinator.swift | 14 +++++++------- .../StoreKit 1/ProductPurchaserSK1.swift | 5 +++-- .../StoreKit 2/ProductPurchaserSK2.swift | 11 +++++++++-- .../StoreKit/Transactions/TransactionManager.swift | 2 +- 7 files changed, 26 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 726c3633f..2244b5c41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup - Renames `PaywallView(event:)` to `PaywallView(placement:)`. +### Fixes + +- Adds extra check to get StoreKit 2 transaction data on `transaction_complete`. + ## 4.0.0-beta.2 ### Fixes diff --git a/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md b/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md index 658c2fb3d..6f5b00807 100644 --- a/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md +++ b/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md @@ -59,6 +59,7 @@ The `Superwall` class is used to access all the features of the SDK. Before usin - `PaywallViewController` - `PaywallViewControllerDelegate` - `PaywallViewControllerDelegateObjc` +- `PaywallView` ### Handling Purchases diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift index dc4014d44..222d0bfe2 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift @@ -55,6 +55,7 @@ final class PurchaseManager: Purchasing { identityManager: identityManager, receiptManager: receiptManager, storage: storage, + coordinator: coordinator, factory: factory ) _sk2TransactionListener = SK2TransactionListener( diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift index 0dca44ac1..9d25e9df4 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift @@ -137,20 +137,21 @@ actor PurchasingCoordinator { result: PurchaseResult ) async { let transaction = await factory.makeStoreTransaction(from: transaction) - completePurchase(of: transaction, result: result) + storeTransaction(transaction, result: result) + completion?(result) } @available(iOS 15.0, *) - func completePurchase( - of transaction: SK2Transaction, + func storeTransaction( + _ transaction: SK2Transaction, result: PurchaseResult ) async { let transaction = await factory.makeStoreTransaction(from: transaction) - completePurchase(of: transaction, result: result) + storeTransaction(transaction, result: result) } - private func completePurchase( - of transaction: StoreTransaction, + private func storeTransaction( + _ transaction: StoreTransaction, result: PurchaseResult ) { if result == .purchased { @@ -170,7 +171,6 @@ actor PurchasingCoordinator { } } lastInternalTransaction = transaction - completion?(result) } func reset() { diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 1/ProductPurchaserSK1.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 1/ProductPurchaserSK1.swift index 9856abee9..b4847985a 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 1/ProductPurchaserSK1.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 1/ProductPurchaserSK1.swift @@ -209,7 +209,7 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { ) if !isExistingTransaction { let transactionManager = factory.makeTransactionManager() - await transactionManager.observeTransaction(for: skTransaction.payment.productIdentifier) + await transactionManager.observeSK1Transaction(for: skTransaction.payment.productIdentifier) storedIds.insert(skTransaction.payment.productIdentifier) storage.save(storedIds, forType: PurchasingProductIds.self) } @@ -262,7 +262,8 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { } await coordinator.completePurchase( of: skTransaction, - result: .failed(error)) + result: .failed(error) + ) } case .deferred: finishTransaction(skTransaction) diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 2/ProductPurchaserSK2.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 2/ProductPurchaserSK2.swift index 001c2cef5..a76a798ea 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 2/ProductPurchaserSK2.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 2/ProductPurchaserSK2.swift @@ -12,6 +12,7 @@ import StoreKit final class ProductPurchaserSK2: Purchasing { private unowned let identityManager: IdentityManager private unowned let receiptManager: ReceiptManager + private let coordinator: PurchasingCoordinator private unowned let factory: Factory typealias Factory = HasExternalPurchaseControllerFactory & OptionsFactory @@ -30,10 +31,12 @@ final class ProductPurchaserSK2: Purchasing { identityManager: IdentityManager, receiptManager: ReceiptManager, storage: Storage, + coordinator: PurchasingCoordinator, factory: Factory ) { self.identityManager = identityManager self.receiptManager = receiptManager + self.coordinator = coordinator self.factory = factory if #available(iOS 17.2, *) { @@ -105,10 +108,14 @@ final class ProductPurchaserSK2: Purchasing { case let .success(.verified(transaction)): await transaction.finish() await receiptManager.loadPurchasedProducts() - return .purchased + let result = PurchaseResult.purchased + await coordinator.storeTransaction(transaction, result: result) + return result case let .success(.unverified(transaction, error)): await transaction.finish() - return .failed(error) + let result = PurchaseResult.failed(error) + await coordinator.storeTransaction(transaction, result: result) + return result case .userCancelled: return .cancelled case .pending: diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift index 0699623d4..dd5e4bf9c 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift @@ -453,7 +453,7 @@ final class TransactionManager { } } - func observeTransaction(for productId: String) async { + func observeSK1Transaction(for productId: String) async { guard let storeProduct = try? await productsManager.products( identifiers: [productId], From f5289985cd2343ee729679c49f9d36cd38a43e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:11:41 -0600 Subject: [PATCH 3/3] Doc fix --- Sources/SuperwallKit/Config/Options/SuperwallOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift b/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift index 89daa06a4..c880b13ef 100644 --- a/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift +++ b/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift @@ -189,7 +189,7 @@ public final class SuperwallOptions: NSObject, Encodable { /// failure before it times out. Defaults to 6. /// /// Adjust this if you want the SDK to call the ``PaywallPresentationHandler/onError(_:)`` - /// handler of the ``PaywallPresentationHandler`` faster when you call ``Superwall/register(event:)`` + /// handler of the ``PaywallPresentationHandler`` faster when you call ``Superwall/register(placement:)`` public var maxConfigRetryCount = 6 { didSet { // Must be >= 0