diff --git a/Sources/Networking/Responses/CustomerInfoResponse.swift b/Sources/Networking/Responses/CustomerInfoResponse.swift index 6eff302f9f..9fc1375a44 100644 --- a/Sources/Networking/Responses/CustomerInfoResponse.swift +++ b/Sources/Networking/Responses/CustomerInfoResponse.swift @@ -69,6 +69,7 @@ extension CustomerInfoResponse { var purchaseDate: Date? var originalPurchaseDate: Date? var transactionIdentifier: String? + var storeTransactionIdentifier: String? @IgnoreDecodeErrors var store: Store var isSandbox: Bool @@ -118,6 +119,7 @@ extension CustomerInfoResponse.Transaction: Codable, Hashable { case purchaseDate case originalPurchaseDate case transactionIdentifier = "id" + case storeTransactionIdentifier = "storeTransactionId" case store case isSandbox @@ -174,12 +176,14 @@ extension CustomerInfoResponse.Transaction { purchaseDate: Date?, originalPurchaseDate: Date?, transactionIdentifier: String?, + storeTransactionIdentifier: String?, store: Store, isSandbox: Bool ) { self.purchaseDate = purchaseDate self.originalPurchaseDate = originalPurchaseDate self.transactionIdentifier = transactionIdentifier + self.storeTransactionIdentifier = storeTransactionIdentifier self.store = store self.isSandbox = isSandbox } @@ -221,6 +225,7 @@ extension CustomerInfoResponse.Subscription { return .init(purchaseDate: self.purchaseDate, originalPurchaseDate: self.originalPurchaseDate, transactionIdentifier: nil, + storeTransactionIdentifier: nil, store: self.store, isSandbox: self.isSandbox) } diff --git a/Sources/Purchasing/NonSubscriptionTransaction.swift b/Sources/Purchasing/NonSubscriptionTransaction.swift index b204f4a887..a4e55a4a37 100644 --- a/Sources/Purchasing/NonSubscriptionTransaction.swift +++ b/Sources/Purchasing/NonSubscriptionTransaction.swift @@ -28,11 +28,15 @@ public final class NonSubscriptionTransaction: NSObject { /// The date that App Store charged the user’s account. @objc public let purchaseDate: Date - /// The unique identifier for the transaction. + /// The unique identifier for the transaction created by RevenueCat. @objc public let transactionIdentifier: String + /// The unique identifier for the transaction created by the Store. + @objc internal let storeTransactionIdentifier: String + init?(with transaction: CustomerInfoResponse.Transaction, productID: String) { guard let transactionIdentifier = transaction.transactionIdentifier, + let storeTransactionIdentifier = transaction.storeTransactionIdentifier, let purchaseDate = transaction.purchaseDate else { Logger.error("Couldn't initialize NonSubscriptionTransaction. " + "Reason: missing data: \(transaction).") @@ -40,10 +44,22 @@ public final class NonSubscriptionTransaction: NSObject { } self.transactionIdentifier = transactionIdentifier + self.storeTransactionIdentifier = storeTransactionIdentifier self.purchaseDate = purchaseDate self.productIdentifier = productID } + public override var description: String { + return """ + <\(String(describing: NonSubscriptionTransaction.self)): + productIdentifier=\(self.productIdentifier) + purchaseDate=\(self.purchaseDate) + transactionIdentifier=\(self.transactionIdentifier) + storeTransactionIdentifier=\(self.storeTransactionIdentifier) + > + """ + } + } #if swift(>=5.7) diff --git a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift index 0f1c506093..1579db6df8 100644 --- a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift @@ -99,8 +99,13 @@ class StoreKit1IntegrationTests: BaseStoreKitIntegrationTests { } func testCanPurchaseConsumable() async throws { - let info = try await self.purchaseConsumablePackage().customerInfo + let result = try await self.purchaseConsumablePackage() + let info = result.customerInfo + let transaction = try XCTUnwrap(result.transaction) + let nonSubscription = try XCTUnwrap(info.nonSubscriptions.onlyElement) + expect(nonSubscription.productIdentifier) == Self.consumable10Coins + expect(nonSubscription.storeTransactionIdentifier) == transaction.transactionIdentifier expect(info.allPurchasedProductIdentifiers).to(contain(Self.consumable10Coins)) } diff --git a/Tests/UnitTests/Networking/Responses/CustomerInfoDecodingTests.swift b/Tests/UnitTests/Networking/Responses/CustomerInfoDecodingTests.swift index cc9d3d4d00..a5aadbf440 100644 --- a/Tests/UnitTests/Networking/Responses/CustomerInfoDecodingTests.swift +++ b/Tests/UnitTests/Networking/Responses/CustomerInfoDecodingTests.swift @@ -59,6 +59,7 @@ class CustomerInfoDecodingTests: BaseHTTPResponseTest { expect(transaction.purchaseDate) == dateFormatter.date(from: "2022-02-11T00:03:28Z") expect(transaction.originalPurchaseDate) == dateFormatter.date(from: "2022-03-10T00:04:28Z") expect(transaction.transactionIdentifier) == "17459f5ff7" + expect(transaction.storeTransactionIdentifier) == "340001090153249" expect(transaction.store) == .appStore expect(transaction.isSandbox) == false } diff --git a/Tests/UnitTests/Networking/Responses/Fixtures/CustomerInfo.json b/Tests/UnitTests/Networking/Responses/Fixtures/CustomerInfo.json index b1475485f3..0a5de272b2 100644 --- a/Tests/UnitTests/Networking/Responses/Fixtures/CustomerInfo.json +++ b/Tests/UnitTests/Networking/Responses/Fixtures/CustomerInfo.json @@ -12,6 +12,7 @@ "purchase_date": "2022-02-11T00:03:28Z", "original_purchase_date": "2022-03-10T00:04:28Z", "id": "17459f5ff7", + "store_transaction_id": "340001090153249", "store": "app_store", "is_sandbox": false } diff --git a/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncoding.1.json b/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncoding.1.json index 3100bffd9c..660c0fa267 100644 --- a/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncoding.1.json +++ b/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncoding.1.json @@ -23,7 +23,8 @@ "is_sandbox" : false, "original_purchase_date" : 668563468, "purchase_date" : 666230608, - "store" : "app_store" + "store" : "app_store", + "store_transaction_id" : "340001090153249" } ] }, diff --git a/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithFailedVerificationResponse.1.json b/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithFailedVerificationResponse.1.json index aab4873156..5ed16520ad 100644 --- a/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithFailedVerificationResponse.1.json +++ b/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithFailedVerificationResponse.1.json @@ -23,7 +23,8 @@ "is_sandbox" : false, "original_purchase_date" : 668563468, "purchase_date" : 666230608, - "store" : "app_store" + "store" : "app_store", + "store_transaction_id" : "340001090153249" } ] }, diff --git a/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithVerifiedResponse.1.json b/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithVerifiedResponse.1.json index 38df35e0ae..cee3385d02 100644 --- a/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithVerifiedResponse.1.json +++ b/Tests/UnitTests/Networking/Responses/__Snapshots__/CustomerInfoDecodingTests/testEncodingWithVerifiedResponse.1.json @@ -23,7 +23,8 @@ "is_sandbox" : false, "original_purchase_date" : 668563468, "purchase_date" : 666230608, - "store" : "app_store" + "store" : "app_store", + "store_transaction_id" : "340001090153249" } ] }, diff --git a/Tests/UnitTests/Purchasing/CustomerInfoTests.swift b/Tests/UnitTests/Purchasing/CustomerInfoTests.swift index 6a7e3d4a16..e346c7421b 100644 --- a/Tests/UnitTests/Purchasing/CustomerInfoTests.swift +++ b/Tests/UnitTests/Purchasing/CustomerInfoTests.swift @@ -41,6 +41,7 @@ class BasicCustomerInfoTests: TestCase { "onetime_purchase": [ [ "id": "d6c007ba74", + "store_transaction_id": "340001090153249", "is_sandbox": true, "original_purchase_date": "1990-08-30T02:40:36Z", "purchase_date": "1990-08-30T02:40:36Z", @@ -153,6 +154,7 @@ class BasicCustomerInfoTests: TestCase { expect(transaction.productIdentifier) == "onetime_purchase" expect(transaction.purchaseDate) == ISO8601DateFormatter.default.date(from: "1990-08-30T02:40:36Z") expect(transaction.transactionIdentifier) == "d6c007ba74" + expect(transaction.storeTransactionIdentifier) == "340001090153249" } @available(*, deprecated) // Ignore deprecation warnings diff --git a/Tests/UnitTests/Purchasing/TransactionsFactoryTests.swift b/Tests/UnitTests/Purchasing/TransactionsFactoryTests.swift index 54364509e0..fc5d542eac 100644 --- a/Tests/UnitTests/Purchasing/TransactionsFactoryTests.swift +++ b/Tests/UnitTests/Purchasing/TransactionsFactoryTests.swift @@ -16,20 +16,22 @@ class TransactionsFactoryTests: TestCase { let nonSubscriptionTransactions = try TransactionsFactory.nonSubscriptionTransactions( withSubscriptionsData: Self.sampleTransactions ) - expect(nonSubscriptionTransactions.count) == 5 + expect(nonSubscriptionTransactions).to(haveCount(5)) - try Self.sampleTransactions.forEach { productId, transactionsData in + for (productId, transactionsData) in Self.sampleTransactions { let filteredTransactions = nonSubscriptionTransactions .filter { $0.productIdentifier == productId } - expect(filteredTransactions.count) == transactionsData.count + expect(filteredTransactions).to(haveCount(transactionsData.count)) - try transactionsData.forEach { dictionary in - let transactionId = try XCTUnwrap(dictionary["id"] as? String) - let containsTransaction = filteredTransactions - .contains { $0.transactionIdentifier == transactionId } + for dictionary in transactionsData { + let revenueCatTransactionID = try XCTUnwrap(dictionary["id"] as? String) + let storeTransactionID = try XCTUnwrap(dictionary["store_transaction_id"] as? String) - expect(containsTransaction) == true + expect(filteredTransactions).to(containElementSatisfying { + $0.transactionIdentifier == revenueCatTransactionID && + $0.storeTransactionIdentifier == storeTransactionID + }) } } @@ -48,6 +50,7 @@ private extension TransactionsFactoryTests { "100_coins": [ [ "id": "72c26cc69c", + "store_transaction_id": "1", "is_sandbox": true, "original_purchase_date": "1990-08-30T02:40:36Z", "purchase_date": "2019-07-11T18:36:20Z", @@ -55,6 +58,7 @@ private extension TransactionsFactoryTests { ], [ "id": "6229b0bef1", + "store_transaction_id": "2", "is_sandbox": true, "original_purchase_date": "2019-11-06T03:26:15Z", "purchase_date": "2019-11-06T03:26:15Z", @@ -64,6 +68,7 @@ private extension TransactionsFactoryTests { "500_coins": [ [ "id": "d6c007ba74", + "store_transaction_id": "3", "is_sandbox": true, "original_purchase_date": "2019-07-11T18:36:20Z", "purchase_date": "2019-07-11T18:36:20Z", @@ -71,6 +76,7 @@ private extension TransactionsFactoryTests { ], [ "id": "5b9ba226bc", + "store_transaction_id": "4", "is_sandbox": true, "original_purchase_date": "2019-07-26T22:10:27Z", "purchase_date": "2019-07-26T22:10:27Z", @@ -80,6 +86,7 @@ private extension TransactionsFactoryTests { "lifetime_access": [ [ "id": "d6c097ba74", + "store_transaction_id": "5", "is_sandbox": true, "original_purchase_date": "2018-07-11T18:36:20Z", "purchase_date": "2018-07-11T18:36:20Z",