From 34fa2ba4924dd267f24810f0bd0e067715b5c7dc Mon Sep 17 00:00:00 2001 From: Will Taylor Date: Mon, 26 Aug 2024 12:20:54 -0500 Subject: [PATCH 01/23] POC sending hard coded transaction metadata --- Sources/Networking/Backend.swift | 2 ++ Sources/Networking/CustomerAPI.swift | 1 + Sources/Networking/HTTPClient/HTTPClient.swift | 1 + .../Networking/Operations/PostReceiptDataOperation.swift | 7 ++++++- Sources/Purchasing/Purchases/PurchasesOrchestrator.swift | 6 ++++-- Sources/Purchasing/Purchases/TransactionPoster.swift | 6 +++++- 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Sources/Networking/Backend.swift b/Sources/Networking/Backend.swift index b647344793..9f1562f661 100644 --- a/Sources/Networking/Backend.swift +++ b/Sources/Networking/Backend.swift @@ -123,12 +123,14 @@ class Backend { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String? = nil, + transactionMetadata: [String: String]? = nil, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { self.customer.post(receipt: receipt, productData: productData, transactionData: transactionData, observerMode: observerMode, appTransaction: appTransaction, + transactionMetadata: transactionMetadata, completion: completion) } diff --git a/Sources/Networking/CustomerAPI.swift b/Sources/Networking/CustomerAPI.swift index fd327508e8..c620b28b1d 100644 --- a/Sources/Networking/CustomerAPI.swift +++ b/Sources/Networking/CustomerAPI.swift @@ -93,6 +93,7 @@ final class CustomerAPI { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String?, + transactionMetadata: [String: String]?, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { var subscriberAttributesToPost: SubscriberAttribute.Dictionary? diff --git a/Sources/Networking/HTTPClient/HTTPClient.swift b/Sources/Networking/HTTPClient/HTTPClient.swift index 8e4e2f40d7..d0e65da812 100644 --- a/Sources/Networking/HTTPClient/HTTPClient.swift +++ b/Sources/Networking/HTTPClient/HTTPClient.swift @@ -130,6 +130,7 @@ class HTTPClient { "X-StoreKit2-Enabled": "\(self.systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailable)", "X-StoreKit-Version": "\(self.systemInfo.storeKitVersion.effectiveVersion)", "X-Observer-Mode-Enabled": "\(self.systemInfo.observerMode)", + "X-RC-Canary": "cat-1674", RequestHeader.retryCount.rawValue: "0", RequestHeader.sandbox.rawValue: "\(self.systemInfo.isSandbox)" ] diff --git a/Sources/Networking/Operations/PostReceiptDataOperation.swift b/Sources/Networking/Operations/PostReceiptDataOperation.swift index 4d4a6bc2e3..417dbed77e 100644 --- a/Sources/Networking/Operations/PostReceiptDataOperation.swift +++ b/Sources/Networking/Operations/PostReceiptDataOperation.swift @@ -138,6 +138,8 @@ extension PostReceiptDataOperation { /// The [AppTransaction](https://developer.apple.com/documentation/storekit/apptransaction) JWS token /// retrieved from StoreKit 2. let appTransaction: String? + + let transactionMetadata: [String: String]? } struct Paywall { @@ -185,7 +187,8 @@ extension PostReceiptDataOperation.PostData { subscriberAttributesByKey: data.unsyncedAttributes, aadAttributionToken: data.aadAttributionToken, testReceiptIdentifier: testReceiptIdentifier, - appTransaction: appTransaction + appTransaction: appTransaction, + transactionMetadata: data.transactionMetadata ) } @@ -268,6 +271,7 @@ extension PostReceiptDataOperation.PostData: Encodable { case paywall case testReceiptIdentifier = "test_receipt_identifier" case appTransaction = "app_transaction" + case transactionMetadata = "metadata" } @@ -285,6 +289,7 @@ extension PostReceiptDataOperation.PostData: Encodable { try container.encodeIfPresent(self.fetchToken, forKey: .fetchToken) try container.encodeIfPresent(self.appTransaction, forKey: .appTransaction) + try container.encodeIfPresent(self.transactionMetadata, forKey: .transactionMetadata) try container.encodeIfPresent(self.presentedOfferingIdentifier, forKey: .presentedOfferingIdentifier) try container.encodeIfPresent(self.presentedPlacementIdentifier, forKey: .presentedPlacementIdentifier) try container.encodeIfPresent(self.appliedTargetingRule, forKey: .appliedTargetingRule) diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 6ddd4a36ed..b76663c6a2 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -535,7 +535,7 @@ final class PurchasesOrchestrator { let customerInfo: CustomerInfo if let transaction = transaction { - customerInfo = try await self.handlePurchasedTransaction(transaction, .purchase) + customerInfo = try await self.handlePurchasedTransaction(transaction, .purchase, ["book_number": "1234"]) } else { // `transaction` would be `nil` for `Product.PurchaseResult.pending` and // `Product.PurchaseResult.userCancelled`. @@ -1478,7 +1478,8 @@ extension PurchasesOrchestrator { private func handlePurchasedTransaction( _ transaction: StoreTransaction, - _ initiationSource: ProductRequestData.InitiationSource + _ initiationSource: ProductRequestData.InitiationSource, + _ transactionMetadata: [String: String]? ) async throws -> CustomerInfo { let storefront = await Storefront.currentStorefront let offeringContext = self.getAndRemovePresentedOfferingContext(for: transaction) @@ -1490,6 +1491,7 @@ extension PurchasesOrchestrator { presentedOfferingContext: offeringContext, presentedPaywall: paywall, unsyncedAttributes: unsyncedAttributes, + transactionMetadata: transactionMetadata, aadAttributionToken: adServicesToken, storefront: storefront, source: .init(isRestore: self.allowSharingAppStoreAccount, diff --git a/Sources/Purchasing/Purchases/TransactionPoster.swift b/Sources/Purchasing/Purchases/TransactionPoster.swift index 478dde946a..b725b812ef 100644 --- a/Sources/Purchasing/Purchases/TransactionPoster.swift +++ b/Sources/Purchasing/Purchases/TransactionPoster.swift @@ -28,6 +28,7 @@ struct PurchasedTransactionData { var presentedOfferingContext: PresentedOfferingContext? var presentedPaywall: PaywallEvent? var unsyncedAttributes: SubscriberAttribute.Dictionary? + var transactionMetadata: [String: String]? var aadAttributionToken: String? var storefront: StorefrontType? var source: PurchaseSource @@ -112,6 +113,7 @@ final class TransactionPoster: TransactionPosterType { receipt: encodedReceipt, product: product, appTransaction: appTransaction, + transactionMetadata: data.transactionMetadata, completion: completion) } } @@ -240,6 +242,7 @@ private extension TransactionPoster { receipt: EncodedAppleReceipt, product: StoreProduct?, appTransaction: String?, + transactionMetadata: [String: String]?, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { let productData = product.map { ProductRequestData(with: $0, storefront: purchasedTransactionData.storefront) } @@ -247,7 +250,8 @@ private extension TransactionPoster { productData: productData, transactionData: purchasedTransactionData, observerMode: self.observerMode, - appTransaction: appTransaction) { result in + appTransaction: appTransaction, + transactionMetadata: transactionMetadata) { result in self.handleReceiptPost(withTransaction: transaction, result: result.map { ($0, product) }, subscriberAttributes: purchasedTransactionData.unsyncedAttributes, From 4520043308ecec81324cbbdaf618d3ac2cc25324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Mon, 23 Sep 2024 11:35:31 +0200 Subject: [PATCH 02/23] wip --- Sources/Purchasing/Purchases/Purchases.swift | 28 ++++++++++++------- .../Purchases/PurchasesOrchestrator.swift | 19 +++++++++---- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 72a476490c..ba0959c41a 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -53,6 +53,22 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void * framework handle the singleton instance for you. */ @objc(RCPurchases) public final class Purchases: NSObject, PurchasesType, PurchasesSwiftType { + public func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: nil, completion: completion) + } + + public func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, package: nil, transactionMetadata: nil, completion: completion) + } + + public func purchase(product: StoreProduct, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: transactionMetadata, completion: completion) + } + + public func purchase(package: Package, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: transactionMetadata, completion: completion) + } + /// Returns the already configured instance of ``Purchases``. /// - Warning: this method will crash with `fatalError` if ``Purchases`` has not been initialized through @@ -949,20 +965,10 @@ public extension Purchases { return await productsAsync(productIdentifiers) } - @objc(purchaseProduct:withCompletion:) - func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, package: nil, completion: completion) - } - func purchase(product: StoreProduct) async throws -> PurchaseResultData { return try await purchaseAsync(product: product) } - @objc(purchasePackage:withCompletion:) - func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: package, completion: completion) - } - func purchase(package: Package) async throws -> PurchaseResultData { return try await purchaseAsync(package: package) } @@ -1000,6 +1006,7 @@ public extension Purchases { purchasesOrchestrator.purchase(product: product, package: nil, promotionalOffer: promotionalOffer.signedData, + transactionMetadata: nil, completion: completion) } @@ -1012,6 +1019,7 @@ public extension Purchases { purchasesOrchestrator.purchase(product: package.storeProduct, package: package, promotionalOffer: promotionalOffer.signedData, + transactionMetadata: nil, completion: completion) } diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index b76663c6a2..756f43068b 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -307,6 +307,7 @@ final class PurchasesOrchestrator { func purchase(product: StoreProduct, package: Package?, + transactionMetadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) { Self.logPurchase(product: product, package: package) @@ -325,6 +326,7 @@ final class PurchasesOrchestrator { self.purchase(sk2Product: sk2Product, package: package, promotionalOffer: nil, + transactionMetadata: transactionMetadata, completion: completion) } else if product.isTestProduct { self.handleTestProduct(completion) @@ -336,6 +338,7 @@ final class PurchasesOrchestrator { func purchase(product: StoreProduct, package: Package?, promotionalOffer: PromotionalOffer.SignedData, + transactionMetadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) { Self.logPurchase(product: product, package: package, offer: promotionalOffer) @@ -352,6 +355,7 @@ final class PurchasesOrchestrator { self.purchase(sk2Product: sk2Product, package: package, promotionalOffer: promotionalOffer, + transactionMetadata: transactionMetadata, completion: completion) } else if product.isTestProduct { self.handleTestProduct(completion) @@ -440,12 +444,14 @@ final class PurchasesOrchestrator { func purchase(sk2Product product: SK2Product, package: Package?, promotionalOffer: PromotionalOffer.SignedData?, + transactionMetadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) { _ = Task { do { let result: PurchaseResultData = try await self.purchase(sk2Product: product, package: package, - promotionalOffer: promotionalOffer) + promotionalOffer: promotionalOffer, + transactionMetadata: transactionMetadata) if !result.userCancelled { Logger.rcPurchaseSuccess(Strings.purchase.purchased_product( @@ -479,7 +485,8 @@ final class PurchasesOrchestrator { func purchase( sk2Product: SK2Product, package: Package?, - promotionalOffer: PromotionalOffer.SignedData? + promotionalOffer: PromotionalOffer.SignedData?, + transactionMetadata: [String: String]? ) async throws -> PurchaseResultData { let result: Product.PurchaseResult @@ -535,7 +542,7 @@ final class PurchasesOrchestrator { let customerInfo: CustomerInfo if let transaction = transaction { - customerInfo = try await self.handlePurchasedTransaction(transaction, .purchase, ["book_number": "1234"]) + customerInfo = try await self.handlePurchasedTransaction(transaction, .purchase, transactionMetadata) } else { // `transaction` would be `nil` for `Product.PurchaseResult.pending` and // `Product.PurchaseResult.userCancelled`. @@ -779,14 +786,16 @@ extension PurchasesOrchestrator: PaymentQueueWrapperDelegate { startPurchase = { completion in self.purchase(product: product, package: nil, - promotionalOffer: discount) { transaction, customerInfo, error, cancelled in + promotionalOffer: discount, + transactionMetadata: nil) { transaction, customerInfo, error, cancelled in completion(transaction, customerInfo, error, cancelled) } } } else { startPurchase = { completion in self.purchase(product: product, - package: nil) { transaction, customerInfo, error, cancelled in + package: nil, + transactionMetadata: nil) { transaction, customerInfo, error, cancelled in completion(transaction, customerInfo, error, cancelled) } } From 9263fd816c36728bf37dae62c3801982e759090b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Mon, 23 Sep 2024 16:51:07 +0200 Subject: [PATCH 03/23] remove canary header --- Sources/Networking/HTTPClient/HTTPClient.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Networking/HTTPClient/HTTPClient.swift b/Sources/Networking/HTTPClient/HTTPClient.swift index 6423865de2..be8a73ad4f 100644 --- a/Sources/Networking/HTTPClient/HTTPClient.swift +++ b/Sources/Networking/HTTPClient/HTTPClient.swift @@ -130,7 +130,6 @@ class HTTPClient { "X-StoreKit2-Enabled": "\(self.systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailable)", "X-StoreKit-Version": "\(self.systemInfo.storeKitVersion.effectiveVersion)", "X-Observer-Mode-Enabled": "\(self.systemInfo.observerMode)", - "X-RC-Canary": "cat-1674", RequestHeader.retryCount.rawValue: "0", RequestHeader.sandbox.rawValue: "\(self.systemInfo.isSandbox)" ] From 768145b2d7ecd7d7b629863e378218357a204bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Mon, 30 Sep 2024 13:29:41 +0200 Subject: [PATCH 04/23] merge --- Sources/Purchasing/Purchases/PurchasesOrchestrator.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index e18d724daf..8c857c3b01 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -494,15 +494,10 @@ final class PurchasesOrchestrator { // swiftlint:disable function_body_length @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) - func purchase( - sk2Product: SK2Product, - package: Package?, - promotionalOffer: PromotionalOffer.SignedData?, - transactionMetadata: [String: String]? - ) async throws -> PurchaseResultData { func purchase(sk2Product: SK2Product, package: Package?, - promotionalOffer: PromotionalOffer.SignedData?) async throws -> PurchaseResultData { + promotionalOffer: PromotionalOffer.SignedData?, + transactionMetadata: [String: String]?) async throws -> PurchaseResultData { let result: Product.PurchaseResult do { From f35711e65c06c8d8b6cca698e322420cab02e523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Mon, 30 Sep 2024 13:41:44 +0200 Subject: [PATCH 05/23] Tidy --- Sources/Purchasing/Purchases/Purchases.swift | 34 +++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 19b095d7e6..fc4786daee 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -53,22 +53,6 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void * framework handle the singleton instance for you. */ @objc(RCPurchases) public final class Purchases: NSObject, PurchasesType, PurchasesSwiftType { - public func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: nil, completion: completion) - } - - public func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: nil, transactionMetadata: nil, completion: completion) - } - - public func purchase(product: StoreProduct, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: transactionMetadata, completion: completion) - } - - public func purchase(package: Package, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: transactionMetadata, completion: completion) - } - /// Returns the already configured instance of ``Purchases``. /// - Warning: this method will crash with `fatalError` if ``Purchases`` has not been initialized through @@ -967,10 +951,28 @@ public extension Purchases { return await productsAsync(productIdentifiers) } + @objc(purchaseProduct:withCompletion:) + func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: nil, completion: completion) + } + + func purchase(product: StoreProduct, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: transactionMetadata, completion: completion) + } + func purchase(product: StoreProduct) async throws -> PurchaseResultData { return try await purchaseAsync(product: product) } + @objc(purchasePackage:withCompletion:) + func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: nil, completion: completion) + } + + func purchase(package: Package, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: transactionMetadata, completion: completion) + } + func purchase(package: Package) async throws -> PurchaseResultData { return try await purchaseAsync(package: package) } From 93672d634b5dc51ab7ed0a41700786cec8a8a974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Mon, 30 Sep 2024 16:12:58 +0200 Subject: [PATCH 06/23] Add transactionMetadata to all purchase variants, including async --- .../Misc/Concurrency/Purchases+async.swift | 38 ++++++++ Sources/Purchasing/Purchases/Purchases.swift | 37 ++++++- .../Purchasing/Purchases/PurchasesType.swift | 97 +++++++++++++++++++ 3 files changed, 169 insertions(+), 3 deletions(-) diff --git a/Sources/Misc/Concurrency/Purchases+async.swift b/Sources/Misc/Concurrency/Purchases+async.swift index 1e1414546b..bc1118c9f4 100644 --- a/Sources/Misc/Concurrency/Purchases+async.swift +++ b/Sources/Misc/Concurrency/Purchases+async.swift @@ -74,6 +74,15 @@ extension Purchases { } } + func purchaseAsync(product: StoreProduct, transactionMetadata: [String : String]) async throws -> PurchaseResultData { + return try await withUnsafeThrowingContinuation { continuation in + purchase(product: product, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + continuation.resume(with: Result(customerInfo, error) + .map { PurchaseResultData(transaction, $0, userCancelled) }) + } + } + } + func purchaseAsync(package: Package) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in purchase(package: package) { transaction, customerInfo, error, userCancelled in @@ -83,6 +92,15 @@ extension Purchases { } } + func purchaseAsync(package: Package, transactionMetadata: [String : String]) async throws -> PurchaseResultData { + return try await withUnsafeThrowingContinuation { continuation in + purchase(package: package, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + continuation.resume(with: Result(customerInfo, error) + .map { PurchaseResultData(transaction, $0, userCancelled) }) + } + } + } + func restorePurchasesAsync() async throws -> CustomerInfo { return try await withUnsafeThrowingContinuation { continuation in self.restorePurchases { customerInfo, error in @@ -111,6 +129,16 @@ extension Purchases { } } + func purchaseAsync(product: StoreProduct, transactionMetadata: [String: String], promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { + return try await withUnsafeThrowingContinuation { continuation in + purchase(product: product, + promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + continuation.resume(with: Result(customerInfo, error) + .map { PurchaseResultData(transaction, $0, userCancelled) }) + } + } + } + func purchaseAsync(package: Package, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in purchase(package: package, @@ -121,6 +149,16 @@ extension Purchases { } } + func purchaseAsync(package: Package, transactionMetadata: [String: String], promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { + return try await withUnsafeThrowingContinuation { continuation in + purchase(package: package, + promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + continuation.resume(with: Result(customerInfo, error) + .map { PurchaseResultData(transaction, $0, userCancelled) }) + } + } + } + func customerInfoAsync(fetchPolicy: CacheFetchPolicy) async throws -> CustomerInfo { return try await withUnsafeThrowingContinuation { continuation in getCustomerInfo(fetchPolicy: fetchPolicy) { customerInfo, error in diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index fc4786daee..844b60ea71 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -53,7 +53,6 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void * framework handle the singleton instance for you. */ @objc(RCPurchases) public final class Purchases: NSObject, PurchasesType, PurchasesSwiftType { - /// Returns the already configured instance of ``Purchases``. /// - Warning: this method will crash with `fatalError` if ``Purchases`` has not been initialized through /// ``Purchases/configure(withAPIKey:)`` or one of its overloads. @@ -956,7 +955,7 @@ public extension Purchases { purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: nil, completion: completion) } - func purchase(product: StoreProduct, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { + func purchase(product: StoreProduct, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) { purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: transactionMetadata, completion: completion) } @@ -964,12 +963,16 @@ public extension Purchases { return try await purchaseAsync(product: product) } + func purchase(product: StoreProduct, transactionMetadata: [String : String]) async throws -> PurchaseResultData { + return try await purchaseAsync(product: product, transactionMetadata: transactionMetadata) + } + @objc(purchasePackage:withCompletion:) func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) { purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: nil, completion: completion) } - func purchase(package: Package, transactionMetadata: [String : String]?, completion: @escaping PurchaseCompletedBlock) { + func purchase(package: Package, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) { purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: transactionMetadata, completion: completion) } @@ -977,6 +980,10 @@ public extension Purchases { return try await purchaseAsync(package: package) } + func purchase(package: Package, transactionMetadata: [String : String]) async throws -> PurchaseResultData { + return try await purchaseAsync(package: package, transactionMetadata: transactionMetadata) + } + @objc func restorePurchases(completion: ((CustomerInfo?, PublicError?) -> Void)? = nil) { self.purchasesOrchestrator.restorePurchases { @Sendable in completion?($0.value, $0.error?.asPublicError) @@ -1014,6 +1021,18 @@ public extension Purchases { completion: completion) } + @objc(purchaseProduct:withPromotionalOffer:transactionMetadata:completion:) + func purchase(product: StoreProduct, + promotionalOffer: PromotionalOffer, + transactionMetadata: [String: String], + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, + package: nil, + promotionalOffer: promotionalOffer.signedData, + transactionMetadata: transactionMetadata, + completion: completion) + } + func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer) } @@ -1027,6 +1046,18 @@ public extension Purchases { completion: completion) } + @objc(purchasePackage:withPromotionalOffer:transactionMetadata:completion:) + func purchase(package: Package, + promotionalOffer: PromotionalOffer, + transactionMetadata: [String: String], + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, + package: package, + promotionalOffer: promotionalOffer.signedData, + transactionMetadata: transactionMetadata, + completion: completion) + } + func purchase(package: Package, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer) } diff --git a/Sources/Purchasing/Purchases/PurchasesType.swift b/Sources/Purchasing/Purchases/PurchasesType.swift index 60fc450381..ca88fab429 100644 --- a/Sources/Purchasing/Purchases/PurchasesType.swift +++ b/Sources/Purchasing/Purchases/PurchasesType.swift @@ -285,6 +285,33 @@ public protocol PurchasesType: AnyObject { @objc(purchaseProduct:withCompletion:) func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) + /** + * Initiates a purchase of a ``StoreProduct``. + * + * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``. + * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead. + * + * - Important: Call this method when a user has decided to purchase a product. + * Only call this in direct response to user input. + * + * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``. + * + * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will + * handle this for you. + * + * - Parameter product: The ``StoreProduct`` the user intends to purchase. + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter completion: A completion block that is called when the purchase completes. + * + * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. + * + * If the purchase was not successful, there will be an `NSError`. + * + * If the user cancelled, `userCancelled` will be `true`. + */ + @objc(purchaseProduct:transactionMetadata:withCompletion:) + func purchase(product: StoreProduct, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) + /** * Initiates a purchase of a ``StoreProduct``. * @@ -308,6 +335,31 @@ public protocol PurchasesType: AnyObject { */ func purchase(product: StoreProduct) async throws -> PurchaseResultData + /** + * Initiates a purchase of a ``StoreProduct``. + * + * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``. + * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead. + * + * - Important: Call this method when a user has decided to purchase a product. + * Only call this in direct response to user input. + * + * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``. + * + * - Note: You do not need to finish the transaction yourself after this, ``Purchases`` will + * handle this for you. + * + * - Parameter product: The ``StoreProduct`` the user intends to purchase. + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + + * + * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing + * + * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. + * If the user cancelled the purchase, `userCancelled` will be `true`. + */ + func purchase(product: StoreProduct, transactionMetadata: [String : String]) async throws -> PurchaseResultData + /** * Initiates a purchase of a ``Package``. * @@ -331,6 +383,30 @@ public protocol PurchasesType: AnyObject { @objc(purchasePackage:withCompletion:) func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) + /** + * Initiates a purchase of a ``Package``. + * + * - Important: Call this method when a user has decided to purchase a product. + * Only call this in direct response to user input. + + * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``. + * + * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will + * handle this for you. + * + * - Parameter package: The ``Package`` the user intends to purchase + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter completion: A completion block that is called when the purchase completes. + * + * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. + * + * If the purchase was not successful, there will be an `NSError`. + * + * If the user cancelled, `userCancelled` will be `true`. + */ + @objc(purchasePackage:transactionMetadata:withCompletion:) + func purchase(package: Package, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) + /** * Initiates a purchase of a ``Package``. * @@ -351,6 +427,27 @@ public protocol PurchasesType: AnyObject { */ func purchase(package: Package) async throws -> PurchaseResultData + /** + * Initiates a purchase of a ``Package``. + * + * - Important: Call this method when a user has decided to purchase a product. + * Only call this in direct response to user input. + * + * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``. + * + * - Note: You do not need to finish the transaction yourself after this, Purchases will + * handle this for you. + * + * - Parameter package: The ``Package`` the user intends to purchase + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * + * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing + * + * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. + * If the user cancelled the purchase, `userCancelled` will be `true`. + */ + func purchase(package: Package, transactionMetadata: [String : String]) async throws -> PurchaseResultData + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION /** From 41a878ae2efb3db3532fe780e5a4bf1b1ef5a983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Mon, 30 Sep 2024 18:16:32 +0200 Subject: [PATCH 07/23] use nil as default argument --- RevenueCatUI/Purchasing/MockPurchases.swift | 8 +- .../Purchasing/PaywallPurchasesType.swift | 2 +- RevenueCatUI/Purchasing/PurchaseHandler.swift | 6 +- .../Misc/Concurrency/Purchases+async.swift | 66 ++-------- Sources/Purchasing/Purchases/Purchases.swift | 104 ++++++--------- .../Purchasing/Purchases/PurchasesType.swift | 123 +++--------------- 6 files changed, 78 insertions(+), 231 deletions(-) diff --git a/RevenueCatUI/Purchasing/MockPurchases.swift b/RevenueCatUI/Purchasing/MockPurchases.swift index 200c52be4c..ba4a9c655c 100644 --- a/RevenueCatUI/Purchasing/MockPurchases.swift +++ b/RevenueCatUI/Purchasing/MockPurchases.swift @@ -53,7 +53,7 @@ final class MockPurchases: PaywallPurchasesType { return try await self.customerInfoBlock() } - func purchase(package: Package) async throws -> PurchaseResultData { + func purchase(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await self.purchaseBlock(package) } @@ -76,7 +76,9 @@ extension PaywallPurchasesType { restore: @escaping (@escaping MockPurchases.RestoreBlock) -> MockPurchases.RestoreBlock ) -> PaywallPurchasesType { return MockPurchases { package in - try await purchase(self.purchase(package:))(package) + try await purchase { package in + try await self.purchase(package: package, transactionMetadata: nil) + }(package) } restorePurchases: { try await restore(self.restorePurchases)() } trackEvent: { event in @@ -91,7 +93,7 @@ extension PaywallPurchasesType { trackEvent: @escaping (@escaping MockPurchases.TrackEventBlock) -> MockPurchases.TrackEventBlock ) -> PaywallPurchasesType { return MockPurchases { package in - try await self.purchase(package: package) + try await self.purchase(package: package, transactionMetadata: nil) } restorePurchases: { try await self.restorePurchases() } trackEvent: { event in diff --git a/RevenueCatUI/Purchasing/PaywallPurchasesType.swift b/RevenueCatUI/Purchasing/PaywallPurchasesType.swift index 505fa1a539..baad1989cf 100644 --- a/RevenueCatUI/Purchasing/PaywallPurchasesType.swift +++ b/RevenueCatUI/Purchasing/PaywallPurchasesType.swift @@ -20,7 +20,7 @@ protocol PaywallPurchasesType: Sendable { var purchasesAreCompletedBy: PurchasesAreCompletedBy { get } @Sendable - func purchase(package: Package) async throws -> PurchaseResultData + func purchase(package: Package, transactionMetadata: [String : String]?) async throws -> PurchaseResultData @Sendable func restorePurchases() async throws -> CustomerInfo diff --git a/RevenueCatUI/Purchasing/PurchaseHandler.swift b/RevenueCatUI/Purchasing/PurchaseHandler.swift index 3549f1046c..18a1a30920 100644 --- a/RevenueCatUI/Purchasing/PurchaseHandler.swift +++ b/RevenueCatUI/Purchasing/PurchaseHandler.swift @@ -151,7 +151,7 @@ extension PurchaseHandler { } @MainActor - func performPurchase(package: Package) async throws { + func performPurchase(package: Package, transactionMetadata: [String : String]? = nil) async throws { Logger.debug(Strings.executing_purchase_logic) self.packageBeingPurchased = package self.purchaseResult = nil @@ -165,7 +165,7 @@ extension PurchaseHandler { self.startAction() do { - let result = try await self.purchases.purchase(package: package) + let result = try await self.purchases.purchase(package: package, transactionMetadata: transactionMetadata) self.purchaseResult = result if result.userCancelled { @@ -403,7 +403,7 @@ private final class NotConfiguredPurchases: PaywallPurchasesType { return info } - func purchase(package: Package) async throws -> PurchaseResultData { + func purchase(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { throw ErrorCode.configurationError } diff --git a/Sources/Misc/Concurrency/Purchases+async.swift b/Sources/Misc/Concurrency/Purchases+async.swift index bc1118c9f4..c8e8211b12 100644 --- a/Sources/Misc/Concurrency/Purchases+async.swift +++ b/Sources/Misc/Concurrency/Purchases+async.swift @@ -65,36 +65,38 @@ extension Purchases { } } - func purchaseAsync(product: StoreProduct) async throws -> PurchaseResultData { + func purchaseAsync(product: StoreProduct, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product) { transaction, customerInfo, error, userCancelled in + purchase(product: product, completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - } + .map { PurchaseResultData(transaction, $0, userCancelled) }) + }, transactionMetadata: transactionMetadata) } } - func purchaseAsync(product: StoreProduct, transactionMetadata: [String : String]) async throws -> PurchaseResultData { + func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + purchase(product: product, + promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) } } } - func purchaseAsync(package: Package) async throws -> PurchaseResultData { + func purchaseAsync(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package) { transaction, customerInfo, error, userCancelled in + purchase(package: package, completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - } + .map { PurchaseResultData(transaction, $0, userCancelled) }) + }, transactionMetadata: transactionMetadata) } } - func purchaseAsync(package: Package, transactionMetadata: [String : String]) async throws -> PurchaseResultData { + func purchaseAsync(package: Package, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + purchase(package: package, + promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) } @@ -119,46 +121,6 @@ extension Purchases { } } - func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { - return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product, - promotionalOffer: promotionalOffer) { transaction, customerInfo, error, userCancelled in - continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - } - } - } - - func purchaseAsync(product: StoreProduct, transactionMetadata: [String: String], promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { - return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product, - promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in - continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - } - } - } - - func purchaseAsync(package: Package, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { - return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package, - promotionalOffer: promotionalOffer) { transaction, customerInfo, error, userCancelled in - continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - } - } - } - - func purchaseAsync(package: Package, transactionMetadata: [String: String], promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { - return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package, - promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in - continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - } - } - } - func customerInfoAsync(fetchPolicy: CacheFetchPolicy) async throws -> CustomerInfo { return try await withUnsafeThrowingContinuation { continuation in getCustomerInfo(fetchPolicy: fetchPolicy) { customerInfo, error in diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 844b60ea71..b913beb51a 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -950,40 +950,62 @@ public extension Purchases { return await productsAsync(productIdentifiers) } - @objc(purchaseProduct:withCompletion:) - func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: nil, completion: completion) + func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) { + purchasesOrchestrator.purchase(product: product, package: nil, promotionalOffer: promotionalOffer.signedData, transactionMetadata: transactionMetadata, completion: completion) } - func purchase(product: StoreProduct, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) { + func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]? = nil) { purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: transactionMetadata, completion: completion) } - func purchase(product: StoreProduct) async throws -> PurchaseResultData { - return try await purchaseAsync(product: product) + func purchase(product: StoreProduct, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(product: product, transactionMetadata: transactionMetadata) } - func purchase(product: StoreProduct, transactionMetadata: [String : String]) async throws -> PurchaseResultData { - return try await purchaseAsync(product: product, transactionMetadata: transactionMetadata) + func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) } - @objc(purchasePackage:withCompletion:) - func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: nil, completion: completion) + func purchase(package: Package, promotionalOffer: PromotionalOffer, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) { + purchasesOrchestrator.purchase(product: package.storeProduct, package: package, promotionalOffer: promotionalOffer.signedData, transactionMetadata: transactionMetadata, completion: completion) } - func purchase(package: Package, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) { + func purchase(package: Package, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]? = nil) { purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: transactionMetadata, completion: completion) } - func purchase(package: Package) async throws -> PurchaseResultData { - return try await purchaseAsync(package: package) + func purchase(package: Package, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]?) async throws -> PurchaseResultData { + return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) } - func purchase(package: Package, transactionMetadata: [String : String]) async throws -> PurchaseResultData { + func purchase(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await purchaseAsync(package: package, transactionMetadata: transactionMetadata) } + @objc(purchaseProduct:withPromotionalOffer:transactionMetadata:completion:) + func purchase(product: StoreProduct, + promotionalOffer: PromotionalOffer, + transactionMetadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, + package: nil, + promotionalOffer: promotionalOffer.signedData, + transactionMetadata: transactionMetadata, + completion: completion) + } + + @objc(purchasePackage:withPromotionalOffer:transactionMetadata:completion:) + func purchase(package: Package, + promotionalOffer: PromotionalOffer, + transactionMetadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, + package: package, + promotionalOffer: promotionalOffer.signedData, + transactionMetadata: transactionMetadata, + completion: completion) + } + @objc func restorePurchases(completion: ((CustomerInfo?, PublicError?) -> Void)? = nil) { self.purchasesOrchestrator.restorePurchases { @Sendable in completion?($0.value, $0.error?.asPublicError) @@ -1010,58 +1032,6 @@ public extension Purchases { return try await syncPurchasesAsync() } - @objc(purchaseProduct:withPromotionalOffer:completion:) - func purchase(product: StoreProduct, - promotionalOffer: PromotionalOffer, - completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, - package: nil, - promotionalOffer: promotionalOffer.signedData, - transactionMetadata: nil, - completion: completion) - } - - @objc(purchaseProduct:withPromotionalOffer:transactionMetadata:completion:) - func purchase(product: StoreProduct, - promotionalOffer: PromotionalOffer, - transactionMetadata: [String: String], - completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, - package: nil, - promotionalOffer: promotionalOffer.signedData, - transactionMetadata: transactionMetadata, - completion: completion) - } - - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { - return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer) - } - - @objc(purchasePackage:withPromotionalOffer:completion:) - func purchase(package: Package, promotionalOffer: PromotionalOffer, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, - package: package, - promotionalOffer: promotionalOffer.signedData, - transactionMetadata: nil, - completion: completion) - } - - @objc(purchasePackage:withPromotionalOffer:transactionMetadata:completion:) - func purchase(package: Package, - promotionalOffer: PromotionalOffer, - transactionMetadata: [String: String], - completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, - package: package, - promotionalOffer: promotionalOffer.signedData, - transactionMetadata: transactionMetadata, - completion: completion) - } - - func purchase(package: Package, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData { - return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer) - } - @objc(checkTrialOrIntroDiscountEligibility:completion:) func checkTrialOrIntroDiscountEligibility(productIdentifiers: [String], completion: @escaping ([String: IntroEligibility]) -> Void) { diff --git a/Sources/Purchasing/Purchases/PurchasesType.swift b/Sources/Purchasing/Purchases/PurchasesType.swift index ca88fab429..77b40cacef 100644 --- a/Sources/Purchasing/Purchases/PurchasesType.swift +++ b/Sources/Purchasing/Purchases/PurchasesType.swift @@ -275,33 +275,7 @@ public protocol PurchasesType: AnyObject { * * - Parameter product: The ``StoreProduct`` the user intends to purchase. * - Parameter completion: A completion block that is called when the purchase completes. - * - * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. - * - * If the purchase was not successful, there will be an `NSError`. - * - * If the user cancelled, `userCancelled` will be `true`. - */ - @objc(purchaseProduct:withCompletion:) - func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) - - /** - * Initiates a purchase of a ``StoreProduct``. - * - * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``. - * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead. - * - * - Important: Call this method when a user has decided to purchase a product. - * Only call this in direct response to user input. - * - * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``. - * - * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will - * handle this for you. - * - * - Parameter product: The ``StoreProduct`` the user intends to purchase. * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. - * - Parameter completion: A completion block that is called when the purchase completes. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * @@ -309,31 +283,8 @@ public protocol PurchasesType: AnyObject { * * If the user cancelled, `userCancelled` will be `true`. */ - @objc(purchaseProduct:transactionMetadata:withCompletion:) - func purchase(product: StoreProduct, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) - - /** - * Initiates a purchase of a ``StoreProduct``. - * - * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``. - * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead. - * - * - Important: Call this method when a user has decided to purchase a product. - * Only call this in direct response to user input. - * - * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``. - * - * - Note: You do not need to finish the transaction yourself after this, ``Purchases`` will - * handle this for you. - * - * - Parameter product: The ``StoreProduct`` the user intends to purchase. - * - * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing - * - * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. - * If the user cancelled the purchase, `userCancelled` will be `true`. - */ - func purchase(product: StoreProduct) async throws -> PurchaseResultData + @objc(purchaseProduct:withCompletion:transactionMetadata:) + func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) /** * Initiates a purchase of a ``StoreProduct``. @@ -351,14 +302,13 @@ public protocol PurchasesType: AnyObject { * * - Parameter product: The ``StoreProduct`` the user intends to purchase. * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. - * * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing * * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(product: StoreProduct, transactionMetadata: [String : String]) async throws -> PurchaseResultData + func purchase(product: StoreProduct, transactionMetadata: [String : String]?) async throws -> PurchaseResultData /** * Initiates a purchase of a ``Package``. @@ -373,30 +323,7 @@ public protocol PurchasesType: AnyObject { * * - Parameter package: The ``Package`` the user intends to purchase * - Parameter completion: A completion block that is called when the purchase completes. - * - * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. - * - * If the purchase was not successful, there will be an `NSError`. - * - * If the user cancelled, `userCancelled` will be `true`. - */ - @objc(purchasePackage:withCompletion:) - func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) - - /** - * Initiates a purchase of a ``Package``. - * - * - Important: Call this method when a user has decided to purchase a product. - * Only call this in direct response to user input. - - * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``. - * - * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will - * handle this for you. - * - * - Parameter package: The ``Package`` the user intends to purchase * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. - * - Parameter completion: A completion block that is called when the purchase completes. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * @@ -404,28 +331,8 @@ public protocol PurchasesType: AnyObject { * * If the user cancelled, `userCancelled` will be `true`. */ - @objc(purchasePackage:transactionMetadata:withCompletion:) - func purchase(package: Package, transactionMetadata: [String : String], completion: @escaping PurchaseCompletedBlock) - - /** - * Initiates a purchase of a ``Package``. - * - * - Important: Call this method when a user has decided to purchase a product. - * Only call this in direct response to user input. - * - * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``. - * - * - Note: You do not need to finish the transaction yourself after this, Purchases will - * handle this for you. - * - * - Parameter package: The ``Package`` the user intends to purchase - * - * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing - * - * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. - * If the user cancelled the purchase, `userCancelled` will be `true`. - */ - func purchase(package: Package) async throws -> PurchaseResultData + @objc(purchasePackage:withCompletion:transactionMetadata:) + func purchase(package: Package, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) /** * Initiates a purchase of a ``Package``. @@ -446,7 +353,7 @@ public protocol PurchasesType: AnyObject { * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(package: Package, transactionMetadata: [String : String]) async throws -> PurchaseResultData + func purchase(package: Package, transactionMetadata: [String : String]?) async throws -> PurchaseResultData #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION @@ -551,6 +458,7 @@ public protocol PurchasesType: AnyObject { * - Parameter product: The ``StoreProduct`` the user intends to purchase. * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase. * - Parameter completion: A completion block that is called when the purchase completes. + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * If the purchase was not successful, there will be an `NSError`. @@ -561,10 +469,11 @@ public protocol PurchasesType: AnyObject { * - ``StoreProduct/eligiblePromotionalOffers()`` * - ``Purchases/promotionalOffer(forProductDiscount:product:)`` */ - @objc(purchaseProduct:withPromotionalOffer:completion:) + @objc(purchaseProduct:withPromotionalOffer:completion:transactionMetadata:) func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, - completion: @escaping PurchaseCompletedBlock) + completion: @escaping PurchaseCompletedBlock, + transactionMetadata: [String : String]?) /** * Use this function if you are not using the Offerings system to purchase a ``StoreProduct`` with an @@ -581,13 +490,14 @@ public protocol PurchasesType: AnyObject { * * - Parameter product: The ``StoreProduct`` the user intends to purchase * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. * * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing * * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData + func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]?) async throws -> PurchaseResultData /** * Purchase the passed ``Package``. @@ -601,15 +511,17 @@ public protocol PurchasesType: AnyObject { * - Parameter package: The ``Package`` the user intends to purchase * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase * - Parameter completion: A completion block that is called when the purchase completes. + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * If the purchase was not successful, there will be an `NSError`. * If the user cancelled, `userCancelled` will be `true`. */ - @objc(purchasePackage:withPromotionalOffer:completion:) + @objc(purchasePackage:withPromotionalOffer:completion:transactionMetadata:) func purchase(package: Package, promotionalOffer: PromotionalOffer, - completion: @escaping PurchaseCompletedBlock) + completion: @escaping PurchaseCompletedBlock, + transactionMetadata: [String : String]?) /** * Purchase the passed ``Package``. @@ -622,13 +534,14 @@ public protocol PurchasesType: AnyObject { * * - Parameter package: The ``Package`` the user intends to purchase * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase + * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. * * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing * * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(package: Package, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData + func purchase(package: Package, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]?) async throws -> PurchaseResultData /** * Computes whether or not a user is eligible for the introductory pricing period of a given product. From 128b924725cb7b919e26a09e3074bee5c3a24b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Mon, 30 Sep 2024 18:27:54 +0200 Subject: [PATCH 08/23] reorder code --- Sources/Misc/Concurrency/Purchases+async.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Misc/Concurrency/Purchases+async.swift b/Sources/Misc/Concurrency/Purchases+async.swift index c8e8211b12..086e1c7b9b 100644 --- a/Sources/Misc/Concurrency/Purchases+async.swift +++ b/Sources/Misc/Concurrency/Purchases+async.swift @@ -74,22 +74,22 @@ extension Purchases { } } - func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product, - promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + purchase(package: package, completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - } + .map { PurchaseResultData(transaction, $0, userCancelled) }) + }, transactionMetadata: transactionMetadata) } } - func purchaseAsync(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package, completion: { transaction, customerInfo, error, userCancelled in + purchase(product: product, + promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) - }, transactionMetadata: transactionMetadata) + .map { PurchaseResultData(transaction, $0, userCancelled) }) + } } } From fb3867df22f7750c29625181f3c1270e71a04987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Tue, 1 Oct 2024 09:56:18 +0200 Subject: [PATCH 09/23] complete refactor --- RevenueCatUI/Purchasing/MockPurchases.swift | 6 +- .../Purchasing/PaywallPurchasesType.swift | 2 +- RevenueCatUI/Purchasing/PurchaseHandler.swift | 6 +- .../Misc/Concurrency/Purchases+async.swift | 22 ++++---- Sources/Networking/Backend.swift | 4 +- Sources/Networking/CustomerAPI.swift | 2 +- .../Operations/PostReceiptDataOperation.swift | 8 +-- Sources/Purchasing/Purchases/Purchases.swift | 56 ++++++------------- .../Purchases/PurchasesOrchestrator.swift | 24 ++++---- .../Purchasing/Purchases/PurchasesType.swift | 44 +++++++-------- .../Purchases/TransactionPoster.swift | 8 +-- .../SwiftAPITester/PurchasesAPI.swift | 20 +++++++ .../BaseStoreKitIntegrationTests.swift | 5 +- .../StoreKitIntegrationTests.swift | 11 ++++ 14 files changed, 114 insertions(+), 104 deletions(-) diff --git a/RevenueCatUI/Purchasing/MockPurchases.swift b/RevenueCatUI/Purchasing/MockPurchases.swift index ba4a9c655c..358f3b2338 100644 --- a/RevenueCatUI/Purchasing/MockPurchases.swift +++ b/RevenueCatUI/Purchasing/MockPurchases.swift @@ -53,7 +53,7 @@ final class MockPurchases: PaywallPurchasesType { return try await self.customerInfoBlock() } - func purchase(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchase(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await self.purchaseBlock(package) } @@ -77,7 +77,7 @@ extension PaywallPurchasesType { ) -> PaywallPurchasesType { return MockPurchases { package in try await purchase { package in - try await self.purchase(package: package, transactionMetadata: nil) + try await self.purchase(package: package, metadata: nil) }(package) } restorePurchases: { try await restore(self.restorePurchases)() @@ -93,7 +93,7 @@ extension PaywallPurchasesType { trackEvent: @escaping (@escaping MockPurchases.TrackEventBlock) -> MockPurchases.TrackEventBlock ) -> PaywallPurchasesType { return MockPurchases { package in - try await self.purchase(package: package, transactionMetadata: nil) + try await self.purchase(package: package, metadata: nil) } restorePurchases: { try await self.restorePurchases() } trackEvent: { event in diff --git a/RevenueCatUI/Purchasing/PaywallPurchasesType.swift b/RevenueCatUI/Purchasing/PaywallPurchasesType.swift index baad1989cf..07f332f1d9 100644 --- a/RevenueCatUI/Purchasing/PaywallPurchasesType.swift +++ b/RevenueCatUI/Purchasing/PaywallPurchasesType.swift @@ -20,7 +20,7 @@ protocol PaywallPurchasesType: Sendable { var purchasesAreCompletedBy: PurchasesAreCompletedBy { get } @Sendable - func purchase(package: Package, transactionMetadata: [String : String]?) async throws -> PurchaseResultData + func purchase(package: Package, metadata: [String: String]?) async throws -> PurchaseResultData @Sendable func restorePurchases() async throws -> CustomerInfo diff --git a/RevenueCatUI/Purchasing/PurchaseHandler.swift b/RevenueCatUI/Purchasing/PurchaseHandler.swift index 18a1a30920..a029f11cf7 100644 --- a/RevenueCatUI/Purchasing/PurchaseHandler.swift +++ b/RevenueCatUI/Purchasing/PurchaseHandler.swift @@ -151,7 +151,7 @@ extension PurchaseHandler { } @MainActor - func performPurchase(package: Package, transactionMetadata: [String : String]? = nil) async throws { + func performPurchase(package: Package, metadata: [String: String]? = nil) async throws { Logger.debug(Strings.executing_purchase_logic) self.packageBeingPurchased = package self.purchaseResult = nil @@ -165,7 +165,7 @@ extension PurchaseHandler { self.startAction() do { - let result = try await self.purchases.purchase(package: package, transactionMetadata: transactionMetadata) + let result = try await self.purchases.purchase(package: package, metadata: metadata) self.purchaseResult = result if result.userCancelled { @@ -403,7 +403,7 @@ private final class NotConfiguredPurchases: PaywallPurchasesType { return info } - func purchase(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchase(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { throw ErrorCode.configurationError } diff --git a/Sources/Misc/Concurrency/Purchases+async.swift b/Sources/Misc/Concurrency/Purchases+async.swift index 086e1c7b9b..693e8794e5 100644 --- a/Sources/Misc/Concurrency/Purchases+async.swift +++ b/Sources/Misc/Concurrency/Purchases+async.swift @@ -65,38 +65,40 @@ extension Purchases { } } - func purchaseAsync(product: StoreProduct, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(product: StoreProduct, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product, completion: { transaction, customerInfo, error, userCancelled in + purchase(product: product, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) - }, transactionMetadata: transactionMetadata) + }) } } - func purchaseAsync(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package, completion: { transaction, customerInfo, error, userCancelled in + purchase(package: package, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) - }, transactionMetadata: transactionMetadata) + }) } } - func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in purchase(product: product, - promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + promotionalOffer: promotionalOffer, + metadata: metadata) { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) } } } - func purchaseAsync(package: Package, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in purchase(package: package, - promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) { transaction, customerInfo, error, userCancelled in + promotionalOffer: promotionalOffer, + metadata: metadata) { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) } diff --git a/Sources/Networking/Backend.swift b/Sources/Networking/Backend.swift index 9f1562f661..26fed526bf 100644 --- a/Sources/Networking/Backend.swift +++ b/Sources/Networking/Backend.swift @@ -123,14 +123,14 @@ class Backend { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String? = nil, - transactionMetadata: [String: String]? = nil, + metadata: [String: String]? = nil, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { self.customer.post(receipt: receipt, productData: productData, transactionData: transactionData, observerMode: observerMode, appTransaction: appTransaction, - transactionMetadata: transactionMetadata, + metadata: metadata, completion: completion) } diff --git a/Sources/Networking/CustomerAPI.swift b/Sources/Networking/CustomerAPI.swift index c620b28b1d..d80d7077c9 100644 --- a/Sources/Networking/CustomerAPI.swift +++ b/Sources/Networking/CustomerAPI.swift @@ -93,7 +93,7 @@ final class CustomerAPI { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String?, - transactionMetadata: [String: String]?, + metadata: [String: String]?, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { var subscriberAttributesToPost: SubscriberAttribute.Dictionary? diff --git a/Sources/Networking/Operations/PostReceiptDataOperation.swift b/Sources/Networking/Operations/PostReceiptDataOperation.swift index 417dbed77e..38bdae8b2a 100644 --- a/Sources/Networking/Operations/PostReceiptDataOperation.swift +++ b/Sources/Networking/Operations/PostReceiptDataOperation.swift @@ -139,7 +139,7 @@ extension PostReceiptDataOperation { /// retrieved from StoreKit 2. let appTransaction: String? - let transactionMetadata: [String: String]? + let metadata: [String: String]? } struct Paywall { @@ -188,7 +188,7 @@ extension PostReceiptDataOperation.PostData { aadAttributionToken: data.aadAttributionToken, testReceiptIdentifier: testReceiptIdentifier, appTransaction: appTransaction, - transactionMetadata: data.transactionMetadata + metadata: data.metadata ) } @@ -271,7 +271,7 @@ extension PostReceiptDataOperation.PostData: Encodable { case paywall case testReceiptIdentifier = "test_receipt_identifier" case appTransaction = "app_transaction" - case transactionMetadata = "metadata" + case metadata = "metadata" } @@ -289,7 +289,7 @@ extension PostReceiptDataOperation.PostData: Encodable { try container.encodeIfPresent(self.fetchToken, forKey: .fetchToken) try container.encodeIfPresent(self.appTransaction, forKey: .appTransaction) - try container.encodeIfPresent(self.transactionMetadata, forKey: .transactionMetadata) + try container.encodeIfPresent(self.metadata, forKey: .metadata) try container.encodeIfPresent(self.presentedOfferingIdentifier, forKey: .presentedOfferingIdentifier) try container.encodeIfPresent(self.presentedPlacementIdentifier, forKey: .presentedPlacementIdentifier) try container.encodeIfPresent(self.appliedTargetingRule, forKey: .appliedTargetingRule) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index b913beb51a..593dcb1054 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -950,60 +950,36 @@ public extension Purchases { return await productsAsync(productIdentifiers) } - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) { - purchasesOrchestrator.purchase(product: product, package: nil, promotionalOffer: promotionalOffer.signedData, transactionMetadata: transactionMetadata, completion: completion) + func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, package: nil, promotionalOffer: promotionalOffer.signedData, metadata: metadata, completion: completion) } - func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]? = nil) { - purchasesOrchestrator.purchase(product: product, package: nil, transactionMetadata: transactionMetadata, completion: completion) + func purchase(product: StoreProduct, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, package: nil, metadata: metadata, completion: completion) } - func purchase(product: StoreProduct, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { - return try await purchaseAsync(product: product, transactionMetadata: transactionMetadata) + func purchase(product: StoreProduct, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(product: product, metadata: metadata) } - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { - return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) + func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer, metadata: metadata) } - func purchase(package: Package, promotionalOffer: PromotionalOffer, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: package, promotionalOffer: promotionalOffer.signedData, transactionMetadata: transactionMetadata, completion: completion) + func purchase(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, package: package, promotionalOffer: promotionalOffer.signedData, metadata: metadata, completion: completion) } - func purchase(package: Package, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]? = nil) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: package, transactionMetadata: transactionMetadata, completion: completion) + func purchase(package: Package, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, package: package, metadata: metadata, completion: completion) } - func purchase(package: Package, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]?) async throws -> PurchaseResultData { - return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer, transactionMetadata: transactionMetadata) + func purchase(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer, metadata: metadata) } - func purchase(package: Package, transactionMetadata: [String : String]? = nil) async throws -> PurchaseResultData { - return try await purchaseAsync(package: package, transactionMetadata: transactionMetadata) - } - - @objc(purchaseProduct:withPromotionalOffer:transactionMetadata:completion:) - func purchase(product: StoreProduct, - promotionalOffer: PromotionalOffer, - transactionMetadata: [String: String]? = nil, - completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, - package: nil, - promotionalOffer: promotionalOffer.signedData, - transactionMetadata: transactionMetadata, - completion: completion) - } - - @objc(purchasePackage:withPromotionalOffer:transactionMetadata:completion:) - func purchase(package: Package, - promotionalOffer: PromotionalOffer, - transactionMetadata: [String: String]? = nil, - completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, - package: package, - promotionalOffer: promotionalOffer.signedData, - transactionMetadata: transactionMetadata, - completion: completion) + func purchase(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(package: package, metadata: metadata) } @objc func restorePurchases(completion: ((CustomerInfo?, PublicError?) -> Void)? = nil) { diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 8c857c3b01..dc85d376d8 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -315,7 +315,7 @@ final class PurchasesOrchestrator { func purchase(product: StoreProduct, package: Package?, - transactionMetadata: [String: String]?, + metadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) { Self.logPurchase(product: product, package: package) @@ -334,7 +334,7 @@ final class PurchasesOrchestrator { self.purchase(sk2Product: sk2Product, package: package, promotionalOffer: nil, - transactionMetadata: transactionMetadata, + metadata: metadata, completion: completion) } else if product.isTestProduct { self.handleTestProduct(completion) @@ -346,7 +346,7 @@ final class PurchasesOrchestrator { func purchase(product: StoreProduct, package: Package?, promotionalOffer: PromotionalOffer.SignedData, - transactionMetadata: [String: String]?, + metadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) { Self.logPurchase(product: product, package: package, offer: promotionalOffer) @@ -363,7 +363,7 @@ final class PurchasesOrchestrator { self.purchase(sk2Product: sk2Product, package: package, promotionalOffer: promotionalOffer, - transactionMetadata: transactionMetadata, + metadata: metadata, completion: completion) } else if product.isTestProduct { self.handleTestProduct(completion) @@ -455,14 +455,14 @@ final class PurchasesOrchestrator { func purchase(sk2Product product: SK2Product, package: Package?, promotionalOffer: PromotionalOffer.SignedData?, - transactionMetadata: [String: String]?, + metadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) { _ = Task { do { let result: PurchaseResultData = try await self.purchase(sk2Product: product, package: package, promotionalOffer: promotionalOffer, - transactionMetadata: transactionMetadata) + metadata: metadata) if !result.userCancelled { Logger.rcPurchaseSuccess(Strings.purchase.purchased_product( @@ -497,7 +497,7 @@ final class PurchasesOrchestrator { func purchase(sk2Product: SK2Product, package: Package?, promotionalOffer: PromotionalOffer.SignedData?, - transactionMetadata: [String: String]?) async throws -> PurchaseResultData { + metadata: [String: String]?) async throws -> PurchaseResultData { let result: Product.PurchaseResult do { @@ -556,7 +556,7 @@ final class PurchasesOrchestrator { let customerInfo: CustomerInfo if let transaction = transaction { - customerInfo = try await self.handlePurchasedTransaction(transaction, .purchase, transactionMetadata) + customerInfo = try await self.handlePurchasedTransaction(transaction, .purchase, metadata) } else { // `transaction` would be `nil` for `Product.PurchaseResult.pending` and // `Product.PurchaseResult.userCancelled`. @@ -806,7 +806,7 @@ extension PurchasesOrchestrator: PaymentQueueWrapperDelegate { self.purchase(product: product, package: nil, promotionalOffer: discount, - transactionMetadata: nil) { transaction, customerInfo, error, cancelled in + metadata: nil) { transaction, customerInfo, error, cancelled in completion(transaction, customerInfo, error, cancelled) } } @@ -814,7 +814,7 @@ extension PurchasesOrchestrator: PaymentQueueWrapperDelegate { startPurchase = { completion in self.purchase(product: product, package: nil, - transactionMetadata: nil) { transaction, customerInfo, error, cancelled in + metadata: nil) { transaction, customerInfo, error, cancelled in completion(transaction, customerInfo, error, cancelled) } } @@ -1526,7 +1526,7 @@ extension PurchasesOrchestrator { private func handlePurchasedTransaction( _ transaction: StoreTransaction, _ initiationSource: ProductRequestData.InitiationSource, - _ transactionMetadata: [String: String]? + _ metadata: [String: String]? ) async throws -> CustomerInfo { let storefront = await Storefront.currentStorefront let offeringContext = self.getAndRemovePresentedOfferingContext(for: transaction) @@ -1538,7 +1538,7 @@ extension PurchasesOrchestrator { presentedOfferingContext: offeringContext, presentedPaywall: paywall, unsyncedAttributes: unsyncedAttributes, - transactionMetadata: transactionMetadata, + metadata: metadata, aadAttributionToken: adServicesToken, storefront: storefront, source: .init(isRestore: self.allowSharingAppStoreAccount, diff --git a/Sources/Purchasing/Purchases/PurchasesType.swift b/Sources/Purchasing/Purchases/PurchasesType.swift index 77b40cacef..e52fb61685 100644 --- a/Sources/Purchasing/Purchases/PurchasesType.swift +++ b/Sources/Purchasing/Purchases/PurchasesType.swift @@ -275,7 +275,7 @@ public protocol PurchasesType: AnyObject { * * - Parameter product: The ``StoreProduct`` the user intends to purchase. * - Parameter completion: A completion block that is called when the purchase completes. - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * @@ -283,8 +283,8 @@ public protocol PurchasesType: AnyObject { * * If the user cancelled, `userCancelled` will be `true`. */ - @objc(purchaseProduct:withCompletion:transactionMetadata:) - func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) + @objc(purchaseProduct:metadata:withCompletion:) + func purchase(product: StoreProduct, metadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) /** * Initiates a purchase of a ``StoreProduct``. @@ -301,14 +301,14 @@ public protocol PurchasesType: AnyObject { * handle this for you. * * - Parameter product: The ``StoreProduct`` the user intends to purchase. - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing * * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(product: StoreProduct, transactionMetadata: [String : String]?) async throws -> PurchaseResultData + func purchase(product: StoreProduct, metadata: [String: String]?) async throws -> PurchaseResultData /** * Initiates a purchase of a ``Package``. @@ -323,7 +323,7 @@ public protocol PurchasesType: AnyObject { * * - Parameter package: The ``Package`` the user intends to purchase * - Parameter completion: A completion block that is called when the purchase completes. - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * @@ -331,8 +331,8 @@ public protocol PurchasesType: AnyObject { * * If the user cancelled, `userCancelled` will be `true`. */ - @objc(purchasePackage:withCompletion:transactionMetadata:) - func purchase(package: Package, completion: @escaping PurchaseCompletedBlock, transactionMetadata: [String : String]?) + @objc(purchasePackage:metadata:withCompletion:) + func purchase(package: Package, metadata: [String: String]?, completion: @escaping PurchaseCompletedBlock) /** * Initiates a purchase of a ``Package``. @@ -346,14 +346,14 @@ public protocol PurchasesType: AnyObject { * handle this for you. * * - Parameter package: The ``Package`` the user intends to purchase - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing * * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(package: Package, transactionMetadata: [String : String]?) async throws -> PurchaseResultData + func purchase(package: Package, metadata: [String: String]?) async throws -> PurchaseResultData #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION @@ -457,8 +457,8 @@ public protocol PurchasesType: AnyObject { * * - Parameter product: The ``StoreProduct`` the user intends to purchase. * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase. + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * - Parameter completion: A completion block that is called when the purchase completes. - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * If the purchase was not successful, there will be an `NSError`. @@ -469,11 +469,11 @@ public protocol PurchasesType: AnyObject { * - ``StoreProduct/eligiblePromotionalOffers()`` * - ``Purchases/promotionalOffer(forProductDiscount:product:)`` */ - @objc(purchaseProduct:withPromotionalOffer:completion:transactionMetadata:) + @objc(purchaseProduct:withPromotionalOffer:metadata:completion:) func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, - completion: @escaping PurchaseCompletedBlock, - transactionMetadata: [String : String]?) + metadata: [String: String]?, + completion: @escaping PurchaseCompletedBlock) /** * Use this function if you are not using the Offerings system to purchase a ``StoreProduct`` with an @@ -490,14 +490,14 @@ public protocol PurchasesType: AnyObject { * * - Parameter product: The ``StoreProduct`` the user intends to purchase * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing * * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]?) async throws -> PurchaseResultData + func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]?) async throws -> PurchaseResultData /** * Purchase the passed ``Package``. @@ -510,18 +510,18 @@ public protocol PurchasesType: AnyObject { * * - Parameter package: The ``Package`` the user intends to purchase * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * - Parameter completion: A completion block that is called when the purchase completes. - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. * * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``. * If the purchase was not successful, there will be an `NSError`. * If the user cancelled, `userCancelled` will be `true`. */ - @objc(purchasePackage:withPromotionalOffer:completion:transactionMetadata:) + @objc(purchasePackage:withPromotionalOffer:metadata:completion:) func purchase(package: Package, promotionalOffer: PromotionalOffer, - completion: @escaping PurchaseCompletedBlock, - transactionMetadata: [String : String]?) + metadata: [String: String]?, + completion: @escaping PurchaseCompletedBlock) /** * Purchase the passed ``Package``. @@ -534,14 +534,14 @@ public protocol PurchasesType: AnyObject { * * - Parameter package: The ``Package`` the user intends to purchase * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase - * - Parameter transactionMetadata: Key-value pairs of metadata to attatch to the purchase. + * - Parameter metadata: Key-value pairs of metadata to attatch to the purchase. * * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing * * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(package: Package, promotionalOffer: PromotionalOffer, transactionMetadata: [String : String]?) async throws -> PurchaseResultData + func purchase(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]?) async throws -> PurchaseResultData /** * Computes whether or not a user is eligible for the introductory pricing period of a given product. diff --git a/Sources/Purchasing/Purchases/TransactionPoster.swift b/Sources/Purchasing/Purchases/TransactionPoster.swift index b725b812ef..1b1ef8f136 100644 --- a/Sources/Purchasing/Purchases/TransactionPoster.swift +++ b/Sources/Purchasing/Purchases/TransactionPoster.swift @@ -28,7 +28,7 @@ struct PurchasedTransactionData { var presentedOfferingContext: PresentedOfferingContext? var presentedPaywall: PaywallEvent? var unsyncedAttributes: SubscriberAttribute.Dictionary? - var transactionMetadata: [String: String]? + var metadata: [String: String]? var aadAttributionToken: String? var storefront: StorefrontType? var source: PurchaseSource @@ -113,7 +113,7 @@ final class TransactionPoster: TransactionPosterType { receipt: encodedReceipt, product: product, appTransaction: appTransaction, - transactionMetadata: data.transactionMetadata, + metadata: data.metadata, completion: completion) } } @@ -242,7 +242,7 @@ private extension TransactionPoster { receipt: EncodedAppleReceipt, product: StoreProduct?, appTransaction: String?, - transactionMetadata: [String: String]?, + metadata: [String: String]?, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { let productData = product.map { ProductRequestData(with: $0, storefront: purchasedTransactionData.storefront) } @@ -251,7 +251,7 @@ private extension TransactionPoster { transactionData: purchasedTransactionData, observerMode: self.observerMode, appTransaction: appTransaction, - transactionMetadata: transactionMetadata) { result in + metadata: metadata) { result in self.handleReceiptPost(withTransaction: transaction, result: result.map { ($0, product) }, subscriberAttributes: purchasedTransactionData.unsyncedAttributes, diff --git a/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift b/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift index 7d33ed1e18..4446bad36c 100644 --- a/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift +++ b/Tests/APITesters/AllAPITests/SwiftAPITester/PurchasesAPI.swift @@ -137,9 +137,12 @@ private func checkPurchasesPurchasingAPI(purchases: Purchases) { let discount: StoreProductDiscount! = nil let pack: Package! = nil let offer: PromotionalOffer! = nil + let meta: [String: String] = ["sample": "data"] purchases.purchase(product: storeProduct) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } + purchases.purchase(product: storeProduct, metadata: meta) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } purchases.purchase(package: pack) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } + purchases.purchase(package: pack, metadata: meta) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } purchases.restorePurchases { (_: CustomerInfo?, _: Error?) in } purchases.syncPurchases { (_: CustomerInfo?, _: Error?) in } @@ -153,8 +156,14 @@ private func checkPurchasesPurchasingAPI(purchases: Purchases) { ) { (_: PromotionalOffer?, _: Error?) in } purchases.purchase(product: storeProduct, promotionalOffer: offer) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } + purchases.purchase(product: storeProduct, + promotionalOffer: offer, + metadata: meta) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } purchases.purchase(package: pack, promotionalOffer: offer) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } + purchases.purchase(package: pack, + promotionalOffer: offer, + metadata: meta) { (_: StoreTransaction?, _: CustomerInfo?, _: Error?, _: Bool) in } purchases.invalidateCustomerInfoCache() @@ -230,6 +239,7 @@ private func checkAsyncMethods(purchases: Purchases) async { let stp: StoreProduct! = nil let discount: StoreProductDiscount! = nil let offer: PromotionalOffer! = nil + let meta: [String: String] = ["sample": "data"] do { let _: IntroEligibilityStatus = await purchases.checkTrialOrIntroDiscountEligibility(product: stp) @@ -250,11 +260,21 @@ private func checkAsyncMethods(purchases: Purchases) async { let _: [StoreProduct] = await purchases.products([]) let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(package: pack) + let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(package: pack, + metadata: meta) let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(package: pack, promotionalOffer: offer) + let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(package: pack, + promotionalOffer: offer, + metadata: meta) let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(product: stp) + let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(product: stp, + metadata: meta) let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(product: stp, promotionalOffer: offer) + let _: (StoreTransaction?, CustomerInfo, Bool) = try await purchases.purchase(product: stp, + promotionalOffer: offer, + metadata: meta) let _: CustomerInfo = try await purchases.customerInfo() let _: CustomerInfo = try await purchases.customerInfo(fetchPolicy: .default) let _: CustomerInfo = try await purchases.restorePurchases() diff --git a/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift b/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift index be843d1175..49fa7354cf 100644 --- a/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift @@ -153,11 +153,12 @@ extension BaseStoreKitIntegrationTests { func purchaseMonthlyProduct( allowOfflineEntitlements: Bool = false, file: FileString = #file, - line: UInt = #line + line: UInt = #line, + metadata: [String: String]? = nil ) async throws -> PurchaseResultData { let logger = TestLogHandler() - let data = try await self.purchases.purchase(product: self.monthlyPackage.storeProduct) + let data = try await self.purchases.purchase(product: self.monthlyPackage.storeProduct, metadata: metadata) try await self.verifyEntitlementWentThrough(data.customerInfo, file: file, diff --git a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift index e2ca631ee2..a4cb78e883 100644 --- a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift @@ -45,6 +45,17 @@ class StoreKit2IntegrationTests: StoreKit1IntegrationTests { let originalPurchaseDate = try await Purchases.shared.customerInfo().originalPurchaseDate expect(originalPurchaseDate).toNot(beNil()) } + + @available(iOS 16.0, tvOS 16.0, watchOS 9.0, macOS 13.0, *) + func testPurchaseMetadataIsInResponse() async throws { + // In this scenario, the AppTransaction should be posted with the SK2 transaction JWT + + try await self.signInAsNewAppUserID() + try await self.purchaseMonthlyProduct(metadata: ["sample":"data"]) + + let customerInfo = try await Purchases.shared.customerInfo() + print(customerInfo) + } @available(iOS 16.0, tvOS 16.0, watchOS 9.0, macOS 13.0, *) func testOriginalApplicationVersionAvailableAfterPurchase() async throws { From 3c8e3381e685f0400467f3c5d993b52b9365c063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Thu, 3 Oct 2024 22:32:44 +0200 Subject: [PATCH 10/23] Add metadata parameter to paywall purchase --- RevenueCatUI/Views/LoadingPaywallView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RevenueCatUI/Views/LoadingPaywallView.swift b/RevenueCatUI/Views/LoadingPaywallView.swift index c5f2eb2ef6..52a98fe19a 100644 --- a/RevenueCatUI/Views/LoadingPaywallView.swift +++ b/RevenueCatUI/Views/LoadingPaywallView.swift @@ -154,7 +154,7 @@ private final class LoadingPaywallPurchases: PaywallPurchasesType { fatalError("Should not be able to purchase") } - func purchase(package: Package) async throws -> PurchaseResultData { + func purchase(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { fatalError("Should not be able to purchase") } From 5ea1102c3518d5a6fb90135becde1ef3ae49d20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Thu, 3 Oct 2024 22:33:07 +0200 Subject: [PATCH 11/23] Parse metadata from CustomerInfo response --- Sources/Networking/Responses/CustomerInfoResponse.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Networking/Responses/CustomerInfoResponse.swift b/Sources/Networking/Responses/CustomerInfoResponse.swift index 9fc1375a44..3d3513fe4b 100644 --- a/Sources/Networking/Responses/CustomerInfoResponse.swift +++ b/Sources/Networking/Responses/CustomerInfoResponse.swift @@ -61,6 +61,7 @@ extension CustomerInfoResponse { @IgnoreDecodeErrors var ownershipType: PurchaseOwnershipType var productPlanIdentifier: String? + var metadata: [String: String]? } From dfd6d4ea2b97f30f7dadd7aaa23c2fec2305482f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Thu, 3 Oct 2024 22:33:27 +0200 Subject: [PATCH 12/23] Add integration test for transaction metadata --- .../StoreKitIntegrationTests.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift index a4cb78e883..e864ffb227 100644 --- a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift @@ -45,16 +45,19 @@ class StoreKit2IntegrationTests: StoreKit1IntegrationTests { let originalPurchaseDate = try await Purchases.shared.customerInfo().originalPurchaseDate expect(originalPurchaseDate).toNot(beNil()) } - + @available(iOS 16.0, tvOS 16.0, watchOS 9.0, macOS 13.0, *) func testPurchaseMetadataIsInResponse() async throws { // In this scenario, the AppTransaction should be posted with the SK2 transaction JWT try await self.signInAsNewAppUserID() - try await self.purchaseMonthlyProduct(metadata: ["sample":"data"]) + try await self.purchaseMonthlyProduct(metadata: ["sample": "data"]) let customerInfo = try await Purchases.shared.customerInfo() - print(customerInfo) + expect(customerInfo.subscriber.subscriptions).to(haveCount(1)) + + let subscription = try XCTUnwrap(customerInfo.subscriber.subscriptions.first?.value) + expect(subscription.metadata).to(equal(["sample": "data"])) } @available(iOS 16.0, tvOS 16.0, watchOS 9.0, macOS 13.0, *) From e00d61bbf9b391bd03d78a04f6b9fec2aa63c4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Fri, 4 Oct 2024 00:01:32 +0200 Subject: [PATCH 13/23] Format --- Sources/Misc/Concurrency/Purchases+async.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/Misc/Concurrency/Purchases+async.swift b/Sources/Misc/Concurrency/Purchases+async.swift index 693e8794e5..0cc59b130a 100644 --- a/Sources/Misc/Concurrency/Purchases+async.swift +++ b/Sources/Misc/Concurrency/Purchases+async.swift @@ -67,7 +67,7 @@ extension Purchases { func purchaseAsync(product: StoreProduct, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in + purchase(product: product, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) }) @@ -76,7 +76,7 @@ extension Purchases { func purchaseAsync(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in + purchase(package: package, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) }) @@ -87,9 +87,10 @@ extension Purchases { return try await withUnsafeThrowingContinuation { continuation in purchase(product: product, promotionalOffer: promotionalOffer, - metadata: metadata) { transaction, customerInfo, error, userCancelled in + metadata: metadata) + { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) + .map { PurchaseResultData(transaction, $0, userCancelled) }) } } } @@ -98,9 +99,10 @@ extension Purchases { return try await withUnsafeThrowingContinuation { continuation in purchase(package: package, promotionalOffer: promotionalOffer, - metadata: metadata) { transaction, customerInfo, error, userCancelled in + metadata: metadata) + { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) - .map { PurchaseResultData(transaction, $0, userCancelled) }) + .map { PurchaseResultData(transaction, $0, userCancelled) }) } } } From 64191e312e4d7b6cf74f3c7c5355941ff739d88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 20:53:03 +0200 Subject: [PATCH 14/23] Fix linting errors --- .../DocCDocumentation.docc/Purchases.md | 16 +++---- .../DocCDocumentation.docc/RevenueCat.md | 16 +++---- .../V4_API_Migration_guide.md | 10 ++--- .../Misc/Concurrency/Purchases+async.swift | 22 ++++++---- .../Operations/PostReceiptDataOperation.swift | 1 - Sources/Purchasing/Purchases/Purchases.swift | 43 ++++++++++++++----- .../Purchasing/Purchases/PurchasesType.swift | 22 ++++++---- .../PromotionalOffer.swift | 8 ++-- 8 files changed, 86 insertions(+), 52 deletions(-) diff --git a/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md b/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md index a81188e7c7..35d460839b 100644 --- a/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md +++ b/Sources/DocCDocumentation/DocCDocumentation.docc/Purchases.md @@ -40,10 +40,10 @@ Most features require configuring the SDK before using it. - ``Purchases/getProducts(_:completion:)`` ### Making Purchases -- ``Purchases/purchase(package:)`` -- ``Purchases/purchase(package:completion:)`` -- ``Purchases/purchase(product:)`` -- ``Purchases/purchase(product:completion:)`` +- ``Purchases/purchase(package:metadata:)`` +- ``Purchases/purchase(package:metadata:completion:)`` +- ``Purchases/purchase(product:metadata:)`` +- ``Purchases/purchase(product:metadata:completion:)`` - ``Purchases/simulatesAskToBuyInSandbox`` - ``Purchases/canMakePayments()`` @@ -54,10 +54,10 @@ Most features require configuring the SDK before using it. - ``Purchases/checkTrialOrIntroDiscountEligibility(product:completion:)`` - ``Purchases/promotionalOffer(forProductDiscount:product:)`` - ``Purchases/getPromotionalOffer(forProductDiscount:product:completion:)`` -- ``Purchases/purchase(package:promotionalOffer:)`` -- ``Purchases/purchase(package:promotionalOffer:completion:)`` -- ``Purchases/purchase(product:promotionalOffer:)`` -- ``Purchases/purchase(product:promotionalOffer:completion:)`` +- ``Purchases/purchase(package:promotionalOffer:metadata:)`` +- ``Purchases/purchase(package:promotionalOffer:metadata:completion:)`` +- ``Purchases/purchase(product:promotionalOffer:metadata:)`` +- ``Purchases/purchase(product:promotionalOffer:metadata:completion:)`` - ``Purchases/presentCodeRedemptionSheet()`` ### Subscription Status diff --git a/Sources/DocCDocumentation/DocCDocumentation.docc/RevenueCat.md b/Sources/DocCDocumentation/DocCDocumentation.docc/RevenueCat.md index a02f82daf5..be9fd63968 100644 --- a/Sources/DocCDocumentation/DocCDocumentation.docc/RevenueCat.md +++ b/Sources/DocCDocumentation/DocCDocumentation.docc/RevenueCat.md @@ -79,10 +79,10 @@ Or browse our iOS sample apps: ### Making Purchases - ``StoreTransaction`` -- ``Purchases/purchase(package:)`` -- ``Purchases/purchase(package:completion:)`` -- ``Purchases/purchase(product:)`` -- ``Purchases/purchase(product:completion:)`` +- ``Purchases/purchase(package:metadata:)`` +- ``Purchases/purchase(package:metadata:completion:)`` +- ``Purchases/purchase(product:metadata:)`` +- ``Purchases/purchase(product:metadata:completion:)`` ### Making Purchases with Subscription Offers - ``IntroEligibility`` @@ -95,10 +95,10 @@ Or browse our iOS sample apps: - ``Purchases/checkTrialOrIntroDiscountEligibility(product:completion:)`` - ``Purchases/promotionalOffer(forProductDiscount:product:)`` - ``Purchases/getPromotionalOffer(forProductDiscount:product:completion:)`` -- ``Purchases/purchase(package:promotionalOffer:)`` -- ``Purchases/purchase(package:promotionalOffer:completion:)`` -- ``Purchases/purchase(product:promotionalOffer:)`` -- ``Purchases/purchase(product:promotionalOffer:completion:)`` +- ``Purchases/purchase(package:promotionalOffer:metadata:)`` +- ``Purchases/purchase(package:promotionalOffer:metadata:completion:)`` +- ``Purchases/purchase(product:promotionalOffer:metadata:)`` +- ``Purchases/purchase(product:promotionalOffer:metadata:completion:)`` ### Subscription Status - ``CustomerInfo`` diff --git a/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md b/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md index 91dad7ff81..b53f0eabc3 100644 --- a/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md +++ b/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md @@ -239,13 +239,13 @@ These types replace native StoreKit types in all public API methods that used th | purchaserInfo(_ completion:) | ``Purchases/getCustomerInfo(completion:)`` | | offerings(_ completion:) | ``Purchases/getOfferings(completion:)`` | | products(_ productIdentifiers:, _ completion:) | ``Purchases/getProducts(_:completion:)`` | -| purchaseProduct(_ product:, _ completion:) | ``Purchases/purchase(product:completion:)`` | -| purchasePackage(_ package:, _ completion:) | ``Purchases/purchase(package:completion:)`` | +| purchaseProduct(_ product:, _ completion:) | ``Purchases/purchase(product:metadata:completion:)`` | +| purchasePackage(_ package:, _ completion:) | ``Purchases/purchase(package:metadata:completion:)`` | | restoreTransactions(_ completion:) | ``Purchases/restorePurchases(completion:)`` | | syncPurchases(_ completion:) | ``Purchases/syncPurchases(completion:)`` | -| paymentDiscount(for:product:completion:) | REMOVED - Get eligibility for a discount using ``Purchases/promotionalOffer(forProductDiscount:product:)``, then pass the offer directly to ``Purchases/purchase(package:promotionalOffer:)`` or ``Purchases/purchase(product:promotionalOffer:)`` | -| purchaseProduct(_:discount:_) | ``Purchases/purchase(product:promotionalOffer:completion:)`` | -| purchasePackage(_:discount:_) | ``Purchases/purchase(package:promotionalOffer:completion:)`` | +| paymentDiscount(for:product:completion:) | REMOVED - Get eligibility for a discount using ``Purchases/promotionalOffer(forProductDiscount:product:metadata:)``, then pass the offer directly to ``Purchases/purchase(package:promotionalOffer:metadata:)`` or ``Purchases/purchase(product:promotionalOffer:metadata:)`` | +| purchaseProduct(_:discount:_) | ``Purchases/purchase(product:promotionalOffer:metadata:completion:)`` | +| purchasePackage(_:discount:_) | ``Purchases/purchase(package:promotionalOffer:metadata:completion:)`` | #### PurchasesDelegate | v3 | v4 | diff --git a/Sources/Misc/Concurrency/Purchases+async.swift b/Sources/Misc/Concurrency/Purchases+async.swift index 0cc59b130a..b45f82cfa3 100644 --- a/Sources/Misc/Concurrency/Purchases+async.swift +++ b/Sources/Misc/Concurrency/Purchases+async.swift @@ -67,7 +67,9 @@ extension Purchases { func purchaseAsync(product: StoreProduct, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(product: product, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in + purchase(product: product, + metadata: metadata, + completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) }) @@ -76,31 +78,35 @@ extension Purchases { func purchaseAsync(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in - purchase(package: package, metadata: metadata, completion: { transaction, customerInfo, error, userCancelled in + purchase(package: package, + metadata: metadata, + completion: { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) }) } } - func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(product: StoreProduct, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in purchase(product: product, promotionalOffer: promotionalOffer, - metadata: metadata) - { transaction, customerInfo, error, userCancelled in + metadata: metadata) { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) } } } - func purchaseAsync(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + func purchaseAsync(package: Package, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await withUnsafeThrowingContinuation { continuation in purchase(package: package, promotionalOffer: promotionalOffer, - metadata: metadata) - { transaction, customerInfo, error, userCancelled in + metadata: metadata) { transaction, customerInfo, error, userCancelled in continuation.resume(with: Result(customerInfo, error) .map { PurchaseResultData(transaction, $0, userCancelled) }) } diff --git a/Sources/Networking/Operations/PostReceiptDataOperation.swift b/Sources/Networking/Operations/PostReceiptDataOperation.swift index 399354482e..14311abb34 100644 --- a/Sources/Networking/Operations/PostReceiptDataOperation.swift +++ b/Sources/Networking/Operations/PostReceiptDataOperation.swift @@ -397,5 +397,4 @@ private extension EncodedAppleReceipt { return "empty" } } - } diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 593dcb1054..e1a7f8216d 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -23,7 +23,7 @@ import StoreKit // MARK: Block definitions /** - Result for ``Purchases/purchase(product:)``. + Result for ``Purchases/purchase(product:metadata:)``. Counterpart of `PurchaseCompletedBlock` for `async` APIs. Note that `transaction` will be `nil` when ``Purchases/purchasesAreCompletedBy`` is ``PurchasesAreCompletedBy/myApp`` @@ -33,7 +33,7 @@ public typealias PurchaseResultData = (transaction: StoreTransaction?, userCancelled: Bool) /** - Completion block for ``Purchases/purchase(product:completion:)`` + Completion block for ``Purchases/purchase(product:metadata:completion:)`` */ public typealias PurchaseCompletedBlock = @MainActor @Sendable (StoreTransaction?, CustomerInfo?, @@ -950,11 +950,20 @@ public extension Purchases { return await productsAsync(productIdentifiers) } - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, package: nil, promotionalOffer: promotionalOffer.signedData, metadata: metadata, completion: completion) + func purchase(product: StoreProduct, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, + package: nil, + promotionalOffer: promotionalOffer.signedData, + metadata: metadata, + completion: completion) } - func purchase(product: StoreProduct, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { + func purchase(product: StoreProduct, + metadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock) { purchasesOrchestrator.purchase(product: product, package: nil, metadata: metadata, completion: completion) } @@ -962,19 +971,33 @@ public extension Purchases { return try await purchaseAsync(product: product, metadata: metadata) } - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + func purchase(product: StoreProduct, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer, metadata: metadata) } - func purchase(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: package, promotionalOffer: promotionalOffer.signedData, metadata: metadata, completion: completion) + func purchase(package: Package, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, + package: package, + promotionalOffer: promotionalOffer.signedData, + metadata: metadata, + completion: completion) } func purchase(package: Package, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, package: package, metadata: metadata, completion: completion) + purchasesOrchestrator.purchase(product: package.storeProduct, + package: package, + metadata: metadata, + completion: completion) } - func purchase(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { + func purchase(package: Package, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer, metadata: metadata) } diff --git a/Sources/Purchasing/Purchases/PurchasesType.swift b/Sources/Purchasing/Purchases/PurchasesType.swift index e52fb61685..fb73d8dbfc 100644 --- a/Sources/Purchasing/Purchases/PurchasesType.swift +++ b/Sources/Purchasing/Purchases/PurchasesType.swift @@ -263,7 +263,7 @@ public protocol PurchasesType: AnyObject { * Initiates a purchase of a ``StoreProduct``. * * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``. - * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead. + * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:metadata:completion:)`` instead. * * - Important: Call this method when a user has decided to purchase a product. * Only call this in direct response to user input. @@ -290,7 +290,7 @@ public protocol PurchasesType: AnyObject { * Initiates a purchase of a ``StoreProduct``. * * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``. - * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead. + * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:metadata:completion:)`` instead. * * - Important: Call this method when a user has decided to purchase a product. * Only call this in direct response to user input. @@ -445,7 +445,8 @@ public protocol PurchasesType: AnyObject { * * Use this function if you are not using the Offerings system to purchase a ``StoreProduct`` with an * applied ``PromotionalOffer``. - * If you are using the Offerings system, use ``Purchases/purchase(package:promotionalOffer:completion:)`` instead. + * If you are using the Offerings system, use + * ``Purchases/purchase(package:promotionalOffer:metadata:completion:)`` instead. * * - Important: Call this method when a user has decided to purchase a product with an applied discount. * Only call this in direct response to user input. @@ -478,7 +479,8 @@ public protocol PurchasesType: AnyObject { /** * Use this function if you are not using the Offerings system to purchase a ``StoreProduct`` with an * applied ``PromotionalOffer``. - * If you are using the Offerings system, use ``Purchases/purchase(package:promotionalOffer:completion:)`` instead. + * If you are using the Offerings system, use + * ``Purchases/purchase(package:promotionalOffer:metadata:completion:)`` instead. * * Call this method when a user has decided to purchase a product with an applied discount. * Only call this in direct response to user input. @@ -497,7 +499,9 @@ public protocol PurchasesType: AnyObject { * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]?) async throws -> PurchaseResultData + func purchase(product: StoreProduct, + promotionalOffer: PromotionalOffer, + metadata: [String: String]?) async throws -> PurchaseResultData /** * Purchase the passed ``Package``. @@ -541,7 +545,9 @@ public protocol PurchasesType: AnyObject { * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful. * If the user cancelled the purchase, `userCancelled` will be `true`. */ - func purchase(package: Package, promotionalOffer: PromotionalOffer, metadata: [String: String]?) async throws -> PurchaseResultData + func purchase(package: Package, + promotionalOffer: PromotionalOffer, + metadata: [String: String]?) async throws -> PurchaseResultData /** * Computes whether or not a user is eligible for the introductory pricing period of a given product. @@ -646,8 +652,8 @@ public protocol PurchasesType: AnyObject { /** * Use this method to fetch ``PromotionalOffer`` - * to use in ``Purchases/purchase(package:promotionalOffer:)`` - * or ``Purchases/purchase(product:promotionalOffer:)``. + * to use in ``Purchases/purchase(package:promotionalOffer:metadata:)`` + * or ``Purchases/purchase(product:promotionalOffer:metadata:)``. * [iOS Promotional Offers](https://docs.revenuecat.com/docs/ios-subscription-offers#promotional-offers). * - Note: If you're looking to use free trials or Introductory Offers instead, * use ``Purchases/checkTrialOrIntroDiscountEligibility(productIdentifiers:completion:)``. diff --git a/Sources/Purchasing/StoreKitAbstractions/PromotionalOffer.swift b/Sources/Purchasing/StoreKitAbstractions/PromotionalOffer.swift index cfecff9837..f9cc22d797 100644 --- a/Sources/Purchasing/StoreKitAbstractions/PromotionalOffer.swift +++ b/Sources/Purchasing/StoreKitAbstractions/PromotionalOffer.swift @@ -22,10 +22,10 @@ import StoreKit /// - ``Purchases/getPromotionalOffer(forProductDiscount:product:completion:)`` /// - ``StoreProduct/eligiblePromotionalOffers()`` /// - ``Purchases/eligiblePromotionalOffers(forProduct:)`` -/// - ``Purchases/purchase(package:promotionalOffer:)`` -/// - ``Purchases/purchase(package:promotionalOffer:completion:)`` -/// - ``Purchases/purchase(product:promotionalOffer:)`` -/// - ``Purchases/purchase(product:promotionalOffer:completion:)`` +/// - ``Purchases/purchase(package:promotionalOffer:metadata:)`` +/// - ``Purchases/purchase(package:promotionalOffer:metadata:completion:)`` +/// - ``Purchases/purchase(product:promotionalOffer:metadata:)`` +/// - ``Purchases/purchase(product:promotionalOffer:metadata:completion:)`` @objc(RCPromotionalOffer) public final class PromotionalOffer: NSObject { From f836253b3392a4dc41ca76298247d4138d848aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 21:02:39 +0200 Subject: [PATCH 15/23] fix compilation error --- .../APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m b/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m index a1d47198c5..616fb14777 100644 --- a/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m +++ b/Tests/APITesters/AllAPITests/ObjcAPITester/RCPurchasesAPI.m @@ -131,8 +131,8 @@ + (void)checkAPI { RCOfferings * _Nullable __unused offerings = p.cachedOfferings; [p getProductsWithIdentifiers:@[@""] completion:^(NSArray *products) { }]; - [p purchaseProduct:storeProduct withCompletion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *error, BOOL userCancelled) { }]; - [p purchasePackage:pack withCompletion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *e, BOOL userCancelled) { }]; + [p purchaseProduct:storeProduct metadata:@{} withCompletion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *error, BOOL userCancelled) { }]; + [p purchasePackage:pack metadata:@{} withCompletion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *e, BOOL userCancelled) { }]; [p restorePurchasesWithCompletion:^(RCCustomerInfo *i, NSError *e) {}]; [p syncPurchasesWithCompletion:^(RCCustomerInfo *i, NSError *e) {}]; @@ -141,8 +141,8 @@ + (void)checkAPI { [p getPromotionalOfferForProductDiscount:stpd withProduct:storeProduct withCompletion:^(RCPromotionalOffer *offer, NSError *error) { }]; - [p purchaseProduct:storeProduct withPromotionalOffer:pro completion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *e, BOOL userCancelled) { }]; - [p purchasePackage:pack withPromotionalOffer:pro completion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *e, BOOL userCancelled) { }]; + [p purchaseProduct:storeProduct withPromotionalOffer:pro metadata:@{} completion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *e, BOOL userCancelled) { }]; + [p purchasePackage:pack withPromotionalOffer:pro metadata:@{} completion:^(RCStoreTransaction *t, RCCustomerInfo *i, NSError *e, BOOL userCancelled) { }]; [p logIn:@"" completion:^(RCCustomerInfo *i, BOOL created, NSError *e) { }]; [p logOutWithCompletion:^(RCCustomerInfo *i, NSError *e) { }]; From 41f005bc1cda6ccd50a9c18c54f37db696729fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 21:13:42 +0200 Subject: [PATCH 16/23] fix mock --- Tests/UnitTests/Mocks/MockPurchases.swift | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Tests/UnitTests/Mocks/MockPurchases.swift b/Tests/UnitTests/Mocks/MockPurchases.swift index 5400473db4..e75a440953 100644 --- a/Tests/UnitTests/Mocks/MockPurchases.swift +++ b/Tests/UnitTests/Mocks/MockPurchases.swift @@ -186,25 +186,30 @@ extension MockPurchases: PurchasesType { self.unimplemented() } - func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock) { + func purchase( + product: StoreProduct, + metadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock + ) { self.unimplemented() } - func purchase(product: StoreProduct) async throws -> PurchaseResultData { + func purchase(product: StoreProduct, metadata: [String: String]? = nil) async throws -> PurchaseResultData { self.unimplemented() } - func purchase(package: Package, completion: @escaping PurchaseCompletedBlock) { + func purchase(package: Package, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { self.unimplemented() } - func purchase(package: Package) async throws -> PurchaseResultData { + func purchase(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { self.unimplemented() } func purchase( product: StoreProduct, promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock ) { self.unimplemented() @@ -212,7 +217,8 @@ extension MockPurchases: PurchasesType { func purchase( product: StoreProduct, - promotionalOffer: PromotionalOffer + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil ) async throws -> PurchaseResultData { self.unimplemented() } @@ -220,6 +226,7 @@ extension MockPurchases: PurchasesType { func purchase( package: Package, promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock ) { self.unimplemented() @@ -227,7 +234,8 @@ extension MockPurchases: PurchasesType { func purchase( package: Package, - promotionalOffer: PromotionalOffer + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil ) async throws -> PurchaseResultData { self.unimplemented() } From d929afdbd0dc467c7570c208d1f9c0e5ceff2d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 21:49:50 +0200 Subject: [PATCH 17/23] fix --- Tests/UnitTests/Mocks/MockBackend.swift | 1 + Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Tests/UnitTests/Mocks/MockBackend.swift b/Tests/UnitTests/Mocks/MockBackend.swift index fae49285d9..b3a1a0eb40 100644 --- a/Tests/UnitTests/Mocks/MockBackend.swift +++ b/Tests/UnitTests/Mocks/MockBackend.swift @@ -49,6 +49,7 @@ class MockBackend: Backend { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String? = nil, + metadata: [String: String]? = nil, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { invokedPostReceiptData = true invokedPostReceiptDataCount += 1 diff --git a/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift b/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift index 798eb67735..c2464f2257 100644 --- a/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift +++ b/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift @@ -456,6 +456,7 @@ extension BasePurchasesTests { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String? = nil, + metadata: [String: String]? = nil, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { self.postReceiptDataCalled = true self.postedReceiptData = receipt From 8413027017805eed4488b0ab364de4fee96cf323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 22:37:56 +0200 Subject: [PATCH 18/23] Fix tests --- .../PurchasesOrchestratorSK2Tests.swift | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/Tests/StoreKitUnitTests/PurchasesOrchestratorSK2Tests.swift b/Tests/StoreKitUnitTests/PurchasesOrchestratorSK2Tests.swift index 6b540d1ecf..b231cbbffd 100644 --- a/Tests/StoreKitUnitTests/PurchasesOrchestratorSK2Tests.swift +++ b/Tests/StoreKitUnitTests/PurchasesOrchestratorSK2Tests.swift @@ -52,7 +52,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr _ = try await orchestrator.purchase(sk2Product: product, package: package, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) expect(self.backend.invokedPostReceiptDataCount) == 1 expect(self.backend.invokedPostReceiptData).to(beTrue()) @@ -72,7 +73,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr let product = try await self.fetchSk2Product() let (transaction, customerInfo, userCancelled) = try await orchestrator.purchase(sk2Product: product, package: nil, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) expect(transaction?.sk2Transaction) == mockTransaction.underlyingTransaction expect(userCancelled) == false @@ -92,7 +94,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr do { _ = try await orchestrator.purchase(sk2Product: product, package: nil, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) XCTFail("Expected error") } catch { expect(self.backend.invokedPostReceiptData) == false @@ -116,7 +119,10 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr ) do { - _ = try await orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: offer) + _ = try await orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: offer, + metadata: nil) XCTFail("Expected error") } catch { expect(error).to(matchError(ErrorCode.invalidPromotionalOfferError)) @@ -133,7 +139,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr let (transaction, customerInfo, cancelled) = try await self.orchestrator.purchase(sk2Product: product, package: nil, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) expect(transaction).to(beNil()) expect(customerInfo) == self.mockCustomerInfo @@ -156,7 +163,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr let (transaction, info, cancelled) = try await self.orchestrator.purchase(sk2Product: product, package: nil, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) expect(self.mockStoreKit2TransactionListener?.invokedHandle) == true let purchaseResult = try XCTUnwrap( @@ -189,7 +197,10 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr backend.stubbedPostReceiptResult = .success(.emptyInfo) let product = try await fetchSk2Product() - let result = try await self.orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: nil) + let result = try await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil, + metadata: nil) expect(result.transaction?.sk2Transaction?.appAccountToken).to(equal(uuid)) } @@ -201,7 +212,10 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) let product = try await fetchSk2Product() - let result = try await self.orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: nil) + let result = try await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil, + metadata: nil) expect(result.transaction?.sk2Transaction?.appAccountToken).to(beNil()) } @@ -230,7 +244,10 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr let product = try await fetchSk2Product() self.productsManager.stubbedSk2StoreProductsResult = .success([product]) - let result = try await orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: nil) + let result = try await orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil, + metadata: nil) expect(result.transaction) == transaction.verifiedStoreTransaction expect(self.backend.invokedPostReceiptDataCount) == 1 @@ -258,7 +275,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr _ = try await self.orchestrator.purchase(sk2Product: product, package: nil, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) expect( self.backend.invokedPostReceiptDataParameters?.transactionData.presentedPaywall?.creationData @@ -281,10 +299,16 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo self.backend.stubbedPostReceiptResult = .failure(.unexpectedBackendResponse(.customerInfoNil)) - _ = try? await self.orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: nil) + _ = try? await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil, + metadata: nil) self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) - _ = try await self.orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: nil) + _ = try await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil, + metadata: nil) expect( self.backend.invokedPostReceiptDataParameters?.transactionData.presentedPaywall?.creationData @@ -309,7 +333,10 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr mockListener.mockTransaction = .init(try await self.simulateAnyPurchase()) let product = try await self.fetchSk2Product() - _ = try await self.orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: nil) + _ = try await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil, + metadata: nil) expect(self.backend.invokedPostReceiptDataCount) == 1 expect(self.backend.invokedPostReceiptDataParameters?.transactionData.aadAttributionToken).to(beNil()) @@ -351,7 +378,10 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr mockListener.mockTransaction = .init(try await self.simulateAnyPurchase()) let product = try await self.fetchSk2Product() - _ = try await self.orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: nil) + _ = try await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil, + metadata: nil) expect(self.backend.invokedPostReceiptDataCount) == 1 expect(self.backend.invokedPostReceiptDataParameters?.transactionData.aadAttributionToken) == token @@ -784,7 +814,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr let product = try await self.fetchSk2Product() let (transaction, _, _) = try await orchestrator.purchase(sk2Product: product, package: nil, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) expect(transaction).toNot(beNil()) try await asyncWait( @@ -827,7 +858,10 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr timestamp: Int.random(in: 0..<1000) ) do { - _ = try await orchestrator.purchase(sk2Product: product, package: nil, promotionalOffer: offer) + _ = try await orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: offer, + metadata: nil) XCTFail("Expected error") } catch { try await asyncWait( @@ -870,7 +904,8 @@ class PurchasesOrchestratorSK2Tests: BasePurchasesOrchestratorTests, PurchasesOr do { let (transaction, _, _) = try await orchestrator.purchase(sk2Product: product, package: nil, - promotionalOffer: nil) + promotionalOffer: nil, + metadata: nil) XCTFail("Expected error") } catch { try await asyncWait( From a76f66e11b72884e6b558ad65c63e64d74b8d8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 22:39:37 +0200 Subject: [PATCH 19/23] fix test --- .../StoreKitUnitTests/PurchasesOrchestratorCommonTests.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/StoreKitUnitTests/PurchasesOrchestratorCommonTests.swift b/Tests/StoreKitUnitTests/PurchasesOrchestratorCommonTests.swift index 55ae5626e1..d5e7032164 100644 --- a/Tests/StoreKitUnitTests/PurchasesOrchestratorCommonTests.swift +++ b/Tests/StoreKitUnitTests/PurchasesOrchestratorCommonTests.swift @@ -24,7 +24,7 @@ class PurchasesOrchestratorCommonTests: BasePurchasesOrchestratorTests { func testPurchasingTestProductFails() async throws { let error = await withCheckedContinuation { continuation in - self.orchestrator.purchase(product: Self.testProduct, package: nil) { _, _, error, _ in + self.orchestrator.purchase(product: Self.testProduct, package: nil, metadata: nil) { _, _, error, _ in continuation.resume(returning: error) } } @@ -42,7 +42,8 @@ class PurchasesOrchestratorCommonTests: BasePurchasesOrchestratorTests { self.orchestrator.purchase( product: Self.testProduct, package: nil, - promotionalOffer: offer + promotionalOffer: offer, + metadata: nil ) { _, _, error, _ in continuation.resume(returning: error) } From 211154cbc6796ae8f3c23e27c83b913987167dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 22:47:39 +0200 Subject: [PATCH 20/23] Fix --- Tests/StoreKitUnitTests/PurchasesOrchestratorSK1Tests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/StoreKitUnitTests/PurchasesOrchestratorSK1Tests.swift b/Tests/StoreKitUnitTests/PurchasesOrchestratorSK1Tests.swift index def33396d0..13f653a518 100644 --- a/Tests/StoreKitUnitTests/PurchasesOrchestratorSK1Tests.swift +++ b/Tests/StoreKitUnitTests/PurchasesOrchestratorSK1Tests.swift @@ -179,7 +179,8 @@ class PurchasesOrchestratorSK1Tests: BasePurchasesOrchestratorTests, PurchasesOr let product = try await self.fetchSk1Product() let (transaction, customerInfo, error, userCancelled) = await withCheckedContinuation { continuation in orchestrator.purchase(product: StoreProduct(sk1Product: product), - package: nil) { transaction, customerInfo, error, userCancelled in + package: nil, + metadata: nil) { transaction, customerInfo, error, userCancelled in continuation.resume(returning: (transaction, customerInfo, error, userCancelled)) } } From 3b07693a614afc6c45c11a2b26aaff58278255b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Wed, 9 Oct 2024 23:45:37 +0200 Subject: [PATCH 21/23] Simplify diff --- .../V4_API_Migration_guide.md | 2 +- .../Misc/Concurrency/Purchases+async.swift | 36 +++++----- Sources/Purchasing/Purchases/Purchases.swift | 72 ++++++++++--------- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md b/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md index b53f0eabc3..ec661f0b3f 100644 --- a/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md +++ b/Sources/DocCDocumentation/DocCDocumentation.docc/V4_API_Migration_guide.md @@ -243,7 +243,7 @@ These types replace native StoreKit types in all public API methods that used th | purchasePackage(_ package:, _ completion:) | ``Purchases/purchase(package:metadata:completion:)`` | | restoreTransactions(_ completion:) | ``Purchases/restorePurchases(completion:)`` | | syncPurchases(_ completion:) | ``Purchases/syncPurchases(completion:)`` | -| paymentDiscount(for:product:completion:) | REMOVED - Get eligibility for a discount using ``Purchases/promotionalOffer(forProductDiscount:product:metadata:)``, then pass the offer directly to ``Purchases/purchase(package:promotionalOffer:metadata:)`` or ``Purchases/purchase(product:promotionalOffer:metadata:)`` | +| paymentDiscount(for:product:completion:) | REMOVED - Get eligibility for a discount using ``Purchases/promotionalOffer(forProductDiscount:product:)``, then pass the offer directly to ``Purchases/purchase(package:promotionalOffer:metadata:)`` or ``Purchases/purchase(product:promotionalOffer:metadata:)`` | | purchaseProduct(_:discount:_) | ``Purchases/purchase(product:promotionalOffer:metadata:completion:)`` | | purchasePackage(_:discount:_) | ``Purchases/purchase(package:promotionalOffer:metadata:completion:)`` | diff --git a/Sources/Misc/Concurrency/Purchases+async.swift b/Sources/Misc/Concurrency/Purchases+async.swift index b45f82cfa3..8c0853f8ea 100644 --- a/Sources/Misc/Concurrency/Purchases+async.swift +++ b/Sources/Misc/Concurrency/Purchases+async.swift @@ -87,6 +87,24 @@ extension Purchases { } } + func restorePurchasesAsync() async throws -> CustomerInfo { + return try await withUnsafeThrowingContinuation { continuation in + self.restorePurchases { customerInfo, error in + continuation.resume(with: Result(customerInfo, error)) + } + } + } + + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION + + func syncPurchasesAsync() async throws -> CustomerInfo { + return try await withUnsafeThrowingContinuation { continuation in + syncPurchases { customerInfo, error in + continuation.resume(with: Result(customerInfo, error)) + } + } + } + func purchaseAsync(product: StoreProduct, promotionalOffer: PromotionalOffer, metadata: [String: String]? = nil) async throws -> PurchaseResultData { @@ -113,24 +131,6 @@ extension Purchases { } } - func restorePurchasesAsync() async throws -> CustomerInfo { - return try await withUnsafeThrowingContinuation { continuation in - self.restorePurchases { customerInfo, error in - continuation.resume(with: Result(customerInfo, error)) - } - } - } - - #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION - - func syncPurchasesAsync() async throws -> CustomerInfo { - return try await withUnsafeThrowingContinuation { continuation in - syncPurchases { customerInfo, error in - continuation.resume(with: Result(customerInfo, error)) - } - } - } - func customerInfoAsync(fetchPolicy: CacheFetchPolicy) async throws -> CustomerInfo { return try await withUnsafeThrowingContinuation { continuation in getCustomerInfo(fetchPolicy: fetchPolicy) { customerInfo, error in diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index e1a7f8216d..f0c4e1d4b5 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -950,17 +950,7 @@ public extension Purchases { return await productsAsync(productIdentifiers) } - func purchase(product: StoreProduct, - promotionalOffer: PromotionalOffer, - metadata: [String: String]? = nil, - completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: product, - package: nil, - promotionalOffer: promotionalOffer.signedData, - metadata: metadata, - completion: completion) - } - + @objc(purchaseProduct:metadata:withCompletion:) func purchase(product: StoreProduct, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { @@ -971,23 +961,7 @@ public extension Purchases { return try await purchaseAsync(product: product, metadata: metadata) } - func purchase(product: StoreProduct, - promotionalOffer: PromotionalOffer, - metadata: [String: String]? = nil) async throws -> PurchaseResultData { - return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer, metadata: metadata) - } - - func purchase(package: Package, - promotionalOffer: PromotionalOffer, - metadata: [String: String]? = nil, - completion: @escaping PurchaseCompletedBlock) { - purchasesOrchestrator.purchase(product: package.storeProduct, - package: package, - promotionalOffer: promotionalOffer.signedData, - metadata: metadata, - completion: completion) - } - + @objc(purchasePackage:metadata:withCompletion:) func purchase(package: Package, metadata: [String: String]? = nil, completion: @escaping PurchaseCompletedBlock) { purchasesOrchestrator.purchase(product: package.storeProduct, package: package, @@ -995,12 +969,6 @@ public extension Purchases { completion: completion) } - func purchase(package: Package, - promotionalOffer: PromotionalOffer, - metadata: [String: String]? = nil) async throws -> PurchaseResultData { - return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer, metadata: metadata) - } - func purchase(package: Package, metadata: [String: String]? = nil) async throws -> PurchaseResultData { return try await purchaseAsync(package: package, metadata: metadata) } @@ -1031,6 +999,42 @@ public extension Purchases { return try await syncPurchasesAsync() } + @objc(purchaseProduct:withPromotionalOffer:metadata:completion:) + func purchase(product: StoreProduct, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: product, + package: nil, + promotionalOffer: promotionalOffer.signedData, + metadata: metadata, + completion: completion) + } + + func purchase(product: StoreProduct, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(product: product, promotionalOffer: promotionalOffer, metadata: metadata) + } + + @objc(purchasePackage:withPromotionalOffer:metadata:completion:) + func purchase(package: Package, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil, + completion: @escaping PurchaseCompletedBlock) { + purchasesOrchestrator.purchase(product: package.storeProduct, + package: package, + promotionalOffer: promotionalOffer.signedData, + metadata: metadata, + completion: completion) + } + + func purchase(package: Package, + promotionalOffer: PromotionalOffer, + metadata: [String: String]? = nil) async throws -> PurchaseResultData { + return try await purchaseAsync(package: package, promotionalOffer: promotionalOffer, metadata: metadata) + } + @objc(checkTrialOrIntroDiscountEligibility:completion:) func checkTrialOrIntroDiscountEligibility(productIdentifiers: [String], completion: @escaping ([String: IntroEligibility]) -> Void) { From 649483ac2a171c5b1a1e08c12737fd113692481e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Thu, 10 Oct 2024 13:59:52 +0200 Subject: [PATCH 22/23] Remove unnecessary metadata parameters --- Sources/Networking/Backend.swift | 2 -- Sources/Networking/CustomerAPI.swift | 1 - Sources/Purchasing/Purchases/TransactionPoster.swift | 5 +---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Sources/Networking/Backend.swift b/Sources/Networking/Backend.swift index 26fed526bf..b647344793 100644 --- a/Sources/Networking/Backend.swift +++ b/Sources/Networking/Backend.swift @@ -123,14 +123,12 @@ class Backend { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String? = nil, - metadata: [String: String]? = nil, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { self.customer.post(receipt: receipt, productData: productData, transactionData: transactionData, observerMode: observerMode, appTransaction: appTransaction, - metadata: metadata, completion: completion) } diff --git a/Sources/Networking/CustomerAPI.swift b/Sources/Networking/CustomerAPI.swift index d80d7077c9..fd327508e8 100644 --- a/Sources/Networking/CustomerAPI.swift +++ b/Sources/Networking/CustomerAPI.swift @@ -93,7 +93,6 @@ final class CustomerAPI { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String?, - metadata: [String: String]?, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { var subscriberAttributesToPost: SubscriberAttribute.Dictionary? diff --git a/Sources/Purchasing/Purchases/TransactionPoster.swift b/Sources/Purchasing/Purchases/TransactionPoster.swift index 1b1ef8f136..085916782a 100644 --- a/Sources/Purchasing/Purchases/TransactionPoster.swift +++ b/Sources/Purchasing/Purchases/TransactionPoster.swift @@ -113,7 +113,6 @@ final class TransactionPoster: TransactionPosterType { receipt: encodedReceipt, product: product, appTransaction: appTransaction, - metadata: data.metadata, completion: completion) } } @@ -242,7 +241,6 @@ private extension TransactionPoster { receipt: EncodedAppleReceipt, product: StoreProduct?, appTransaction: String?, - metadata: [String: String]?, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { let productData = product.map { ProductRequestData(with: $0, storefront: purchasedTransactionData.storefront) } @@ -250,8 +248,7 @@ private extension TransactionPoster { productData: productData, transactionData: purchasedTransactionData, observerMode: self.observerMode, - appTransaction: appTransaction, - metadata: metadata) { result in + appTransaction: appTransaction) { result in self.handleReceiptPost(withTransaction: transaction, result: result.map { ($0, product) }, subscriberAttributes: purchasedTransactionData.unsyncedAttributes, From b2ddcc4a15ce24ec75e884da4fbcfb0770177556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20L=C3=B3pez=20Ferrando?= Date: Thu, 10 Oct 2024 14:41:14 +0200 Subject: [PATCH 23/23] Fix tests --- Tests/UnitTests/Mocks/MockBackend.swift | 1 - Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/Tests/UnitTests/Mocks/MockBackend.swift b/Tests/UnitTests/Mocks/MockBackend.swift index b3a1a0eb40..fae49285d9 100644 --- a/Tests/UnitTests/Mocks/MockBackend.swift +++ b/Tests/UnitTests/Mocks/MockBackend.swift @@ -49,7 +49,6 @@ class MockBackend: Backend { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String? = nil, - metadata: [String: String]? = nil, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { invokedPostReceiptData = true invokedPostReceiptDataCount += 1 diff --git a/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift b/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift index c2464f2257..798eb67735 100644 --- a/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift +++ b/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift @@ -456,7 +456,6 @@ extension BasePurchasesTests { transactionData: PurchasedTransactionData, observerMode: Bool, appTransaction: String? = nil, - metadata: [String: String]? = nil, completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { self.postReceiptDataCalled = true self.postedReceiptData = receipt