From b32859d0c8a1e1863c9d5a6b0065a6036cd09a30 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 27 Sep 2022 12:50:34 -0700 Subject: [PATCH] `PostReceiptDataOperation`: print receipt data if `debug` logs are enabled (#1940) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For [CSDK-478]. Follow up to #1929. ### Example: > DEBUG: ℹ️ PostReceiptDataOperation: Started INFO: ℹ️ Receipt parsed successfully DEBUG: ℹ️ PostReceiptDataOperation: Posting receipt: { "opaque_value" : "Rn\/39QYAAAA=", "sha1_hash" : "Ii8Z7ocZm524eJcWFOvVauNrNHs=", "bundle_id" : "com.revenuecat.StoreKitTestApp", "in_app_purchases" : [ { "quantity" : 1, "product_id" : "com.revenuecat.monthly_4.99.1_week_intro", "purchase_date" : "2022-09-27T19:09:47Z", "transaction_id" : "0", "is_in_intro_offer_period" : true, "expires_date" : "2022-09-27T19:09:57Z" } ], "application_version" : "1", "creation_date" : "2022-09-27T19:09:47Z", "expiration_date" : "4001-01-01T00:00:00Z" } DEBUG: ℹ️ There are no requests currently running, starting request POST receipts DEBUG: ℹ️ API request started: POST /v1/receipts DEBUG: ℹ️ API request completed: POST /v1/receipts 200 DEBUG: ℹ️ PostReceiptDataOperation: Finished DEBUG: ℹ️ Serial request done: POST receipts, 0 requests left in the queue DEBUG: ℹ️ Sending updated CustomerInfo to delegate. INFO: 😻💰 Purchased product - 'com.revenuecat.monthly_4.99.1_week_intro' INFO: 💰 Finishing transaction com.revenuecat.monthly_4.99.1_week_intro 0 () [CSDK-478]: https://revenuecats.atlassian.net/browse/CSDK-478?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../JSONDecoder+Extensions.swift | 25 ++++++++++++++++ .../BasicTypes/AppleReceipt.swift | 23 +++++--------- .../BasicTypes/InAppPurchase.swift | 30 +++++-------------- .../LocalReceiptParsing/ReceiptParser.swift | 2 ++ Sources/Logging/Strings/ReceiptStrings.swift | 4 +++ .../Operations/NetworkOperation.swift | 4 +-- .../Operations/PostReceiptDataOperation.swift | 22 ++++++++++++++ Sources/Purchasing/Purchases/Purchases.swift | 4 +-- .../StoreKitIntegrationTests.swift | 20 +------------ .../LocalReceiptParserStoreKitTests.swift | 2 +- ...ReceiptParsing+TestsWithRealReceipts.swift | 4 +-- 11 files changed, 76 insertions(+), 64 deletions(-) diff --git a/Sources/FoundationExtensions/JSONDecoder+Extensions.swift b/Sources/FoundationExtensions/JSONDecoder+Extensions.swift index fc04d3499a..522fee7483 100644 --- a/Sources/FoundationExtensions/JSONDecoder+Extensions.swift +++ b/Sources/FoundationExtensions/JSONDecoder+Extensions.swift @@ -122,8 +122,24 @@ extension Encodable { return result } + /// - Throws: if encoding failed + /// - Returns: `nil` if the encoded `Data` can't be serialized into a `String`. + var prettyPrintedJSON: String? { + get throws { + return String(data: try self.prettyPrintedData, encoding: .utf8) + } + } + + var prettyPrintedData: Data { + get throws { + return try JSONEncoder.prettyPrinted.encode(self) + } + } + } +// MARK: - + extension JSONEncoder { static let `default`: JSONEncoder = { @@ -134,6 +150,15 @@ extension JSONEncoder { return encoder }() + static let prettyPrinted: JSONEncoder = { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + encoder.dateEncodingStrategy = .iso8601 + encoder.outputFormatting = .prettyPrinted + + return encoder + }() + } extension JSONDecoder { diff --git a/Sources/LocalReceiptParsing/BasicTypes/AppleReceipt.swift b/Sources/LocalReceiptParsing/BasicTypes/AppleReceipt.swift index f3702683f0..e1a7d98938 100644 --- a/Sources/LocalReceiptParsing/BasicTypes/AppleReceipt.swift +++ b/Sources/LocalReceiptParsing/BasicTypes/AppleReceipt.swift @@ -58,23 +58,14 @@ struct AppleReceipt: Equatable { return Set(productIdentifiers) } - var asDict: [String: Any] { - return [ - "bundleId": bundleId, - "applicationVersion": applicationVersion, - "originalApplicationVersion": originalApplicationVersion ?? "", - "opaqueValue": opaqueValue, - "sha1Hash": sha1Hash, - "creationDate": creationDate, - "expirationDate": expirationDate ?? "", - "inAppPurchases": inAppPurchases.map { $0.asDict } - ] - } +} + +extension AppleReceipt: Codable {} - var description: String { - return String(describing: self.asDict) +extension AppleReceipt: CustomDebugStringConvertible { + + var debugDescription: String { + return (try? self.prettyPrintedJSON) ?? "" } } - -extension AppleReceipt: Codable {} diff --git a/Sources/LocalReceiptParsing/BasicTypes/InAppPurchase.swift b/Sources/LocalReceiptParsing/BasicTypes/InAppPurchase.swift index 339cd94491..90023e9fbd 100644 --- a/Sources/LocalReceiptParsing/BasicTypes/InAppPurchase.swift +++ b/Sources/LocalReceiptParsing/BasicTypes/InAppPurchase.swift @@ -40,29 +40,15 @@ struct InAppPurchase: Equatable { let webOrderLineItemId: Int64? let promotionalOfferIdentifier: String? - var asDict: [String: Any] { - return [ - "quantity": quantity, - "productId": productId, - "transactionId": transactionId, - "originalTransactionId": originalTransactionId ?? "", - "promotionalOfferIdentifier": promotionalOfferIdentifier ?? "", - "purchaseDate": purchaseDate, - "productType": productType?.rawValue ?? "", - "originalPurchaseDate": originalPurchaseDate ?? "", - "expiresDate": expiresDate ?? "", - "cancellationDate": cancellationDate ?? "", - "isInTrialPeriod": isInTrialPeriod ?? "", - "isInIntroOfferPeriod": isInIntroOfferPeriod ?? "", - "webOrderLineItemId": webOrderLineItemId ?? "" - ] - } - - var description: String { - return String(describing: self.asDict) - } - } extension InAppPurchase.ProductType: Codable {} extension InAppPurchase: Codable {} + +extension InAppPurchase: CustomDebugStringConvertible { + + var debugDescription: String { + return (try? self.prettyPrintedJSON) ?? "" + } + +} diff --git a/Sources/LocalReceiptParsing/ReceiptParser.swift b/Sources/LocalReceiptParsing/ReceiptParser.swift index 9d01cf3e8f..596f1bbb35 100644 --- a/Sources/LocalReceiptParsing/ReceiptParser.swift +++ b/Sources/LocalReceiptParsing/ReceiptParser.swift @@ -16,6 +16,8 @@ import Foundation class ReceiptParser { + static let `default`: ReceiptParser = .init() + private let containerBuilder: ASN1ContainerBuilder private let receiptBuilder: AppleReceiptBuilder diff --git a/Sources/Logging/Strings/ReceiptStrings.swift b/Sources/Logging/Strings/ReceiptStrings.swift index 25cf93f797..d63548595c 100644 --- a/Sources/Logging/Strings/ReceiptStrings.swift +++ b/Sources/Logging/Strings/ReceiptStrings.swift @@ -28,6 +28,7 @@ enum ReceiptStrings { case parsing_receipt case refreshing_empty_receipt case unable_to_load_receipt + case posting_receipt(AppleReceipt) } @@ -74,6 +75,9 @@ extension ReceiptStrings: CustomStringConvertible { case .unable_to_load_receipt: return "Unable to load receipt, ensure you are logged in to a valid Apple account." + case let .posting_receipt(receipt): + return "Posting receipt: \(receipt.debugDescription)" + } } diff --git a/Sources/Networking/Operations/NetworkOperation.swift b/Sources/Networking/Operations/NetworkOperation.swift index 4d8045c383..b3b4e67650 100644 --- a/Sources/Networking/Operations/NetworkOperation.swift +++ b/Sources/Networking/Operations/NetworkOperation.swift @@ -125,8 +125,8 @@ class NetworkOperation: Operation { // MARK: - - private func log(_ message: String) { - Logger.debug("\(type(of: self)): \(message)") + internal func log(_ message: CustomStringConvertible) { + Logger.debug("\(type(of: self)): \(message.description)") } // MARK: - diff --git a/Sources/Networking/Operations/PostReceiptDataOperation.swift b/Sources/Networking/Operations/PostReceiptDataOperation.swift index f376bff75a..115fe263fa 100644 --- a/Sources/Networking/Operations/PostReceiptDataOperation.swift +++ b/Sources/Networking/Operations/PostReceiptDataOperation.swift @@ -53,6 +53,10 @@ class PostReceiptDataOperation: CacheableNetworkOperation { } override func begin(completion: @escaping () -> Void) { + if Logger.logLevel == .debug { + self.printReceiptData() + } + self.post(completion: completion) } @@ -71,6 +75,24 @@ class PostReceiptDataOperation: CacheableNetworkOperation { } +// MARK: - Private + +private extension PostReceiptDataOperation { + + func printReceiptData() { + do { + self.log(Strings.receipt.posting_receipt( + try ReceiptParser.default.parse(from: self.postData.receiptData) + )) + } catch { + Logger.appleError(Strings.receipt.parse_receipt_locally_error(error: error)) + } + } + +} + +// MARK: - Request Data + extension PostReceiptDataOperation.PostData: Encodable { private enum CodingKeys: String, CodingKey { diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 0c8a38686d..743fbeec73 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -275,7 +275,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void let offeringsFactory = OfferingsFactory() let userDefaults = userDefaults ?? UserDefaults.standard let deviceCache = DeviceCache(sandboxEnvironmentDetector: systemInfo, userDefaults: userDefaults) - let receiptParser = ReceiptParser() + let receiptParser = ReceiptParser.default let transactionsManager = TransactionsManager(storeKit2Setting: systemInfo.storeKit2Setting, receiptParser: receiptParser) let customerInfoManager = CustomerInfoManager(operationDispatcher: operationDispatcher, @@ -1060,7 +1060,7 @@ internal extension Purchases { func fetchReceipt(_ policy: ReceiptRefreshPolicy) async throws -> AppleReceipt? { let receipt = await self.receiptFetcher.receiptData(refreshPolicy: policy) - return try receipt.map { try ReceiptParser().parse(from: $0) } + return try receipt.map { try ReceiptParser.default.parse(from: $0) } } #endif diff --git a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift index b7a6be4d8f..39bb2fef37 100644 --- a/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift @@ -578,7 +578,7 @@ private extension StoreKit1IntegrationTests { func printReceiptContent() async { do { let receipt = try await Purchases.shared.fetchReceipt(.always) - let description = receipt.map { $0.description } ?? "" + let description = receipt.map { $0.debugDescription } ?? "" Logger.appleWarning("Receipt content:\n\(description)") @@ -605,21 +605,3 @@ private extension AsyncSequence { } } - -private extension AppleReceipt { - - var prettyPrintedData: Data { - get throws { - let encoder: JSONEncoder = { - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - encoder.outputFormatting = .prettyPrinted - encoder.dateEncodingStrategy = .iso8601 - - return encoder - }() - - return try encoder.encode(self) - } - } -} diff --git a/Tests/StoreKitUnitTests/LocalReceiptParserStoreKitTests.swift b/Tests/StoreKitUnitTests/LocalReceiptParserStoreKitTests.swift index d50121811b..58c1addad9 100644 --- a/Tests/StoreKitUnitTests/LocalReceiptParserStoreKitTests.swift +++ b/Tests/StoreKitUnitTests/LocalReceiptParserStoreKitTests.swift @@ -37,7 +37,7 @@ class LocalReceiptParserStoreKitTests: StoreKitConfigTestCase { operationDispatcher: operationDispatcher, storeKit2Setting: .disabled) self.receiptFetcher = ReceiptFetcher(requestFetcher: self.requestFetcher, systemInfo: systemInfo) - self.parser = ReceiptParser() + self.parser = .default } @MainActor diff --git a/Tests/UnitTests/LocalReceiptParsing/TestsAgainstRealReceipts/ReceiptParsing+TestsWithRealReceipts.swift b/Tests/UnitTests/LocalReceiptParsing/TestsAgainstRealReceipts/ReceiptParsing+TestsWithRealReceipts.swift index 2070c97402..6c23ec5ccf 100644 --- a/Tests/UnitTests/LocalReceiptParsing/TestsAgainstRealReceipts/ReceiptParsing+TestsWithRealReceipts.swift +++ b/Tests/UnitTests/LocalReceiptParsing/TestsAgainstRealReceipts/ReceiptParsing+TestsWithRealReceipts.swift @@ -18,7 +18,7 @@ class ReceiptParsingRealReceiptTests: TestCase { func testBasicReceiptAttributesForSample1() throws { let receiptData = sampleReceiptData(receiptName: receipt1Name) - let receipt = try ReceiptParser().parse(from: receiptData) + let receipt = try ReceiptParser.default.parse(from: receiptData) expect(receipt.applicationVersion) == "4" expect(receipt.bundleId) == "com.revenuecat.sampleapp" @@ -29,7 +29,7 @@ class ReceiptParsingRealReceiptTests: TestCase { func testInAppPurchasesAttributesForSample1() throws { let receiptData = sampleReceiptData(receiptName: receipt1Name) - let receipt = try ReceiptParser().parse(from: receiptData) + let receipt = try ReceiptParser.default.parse(from: receiptData) let inAppPurchases = receipt.inAppPurchases expect(inAppPurchases.count) == 9