Files
purchases-ios/Sources/Networking/Operations/Handling/CustomerInfoResponseHandler.swift
T
Antonio Pallares 8b942f9fd9 Add Internal CustomerInfo source properties (#5737)
* Add `isLoadShedderResponse` and `isFallbackURLResponse` properties to `VerifiedHTTPResponse`

Keeps the values also when returned by the ETagManager

* lint

* Add some tests for ETagManager

* Rename `isFallbackURLResponse` to `isFallbackUrlResponse` to prevent decoding issues

* Add tests for actual source of original cached response of ETagManager

* Add tests to HTTPClientTests regarding the new booleans

* lint

* Add `originalSource` to `CustomerInfo`

* Adds `isLoadedFromCache` boolean to CustomerInfo

* Add docs

* Unit tests for new customer info properties

* Make `OriginalSource.CustomerInfo` a `String` enum

* Add decoding tests for Customer Info with new original source field

* Add `.loadShedder` check for Customer Info obtained in Load Shedder integration tests

* Update fixture

* improve internal docs

* Fix test

* Fix lint

* Use enum for `HTTPResponseSource`

* Update tests

* Adapt to changes in `VerifiedHTTPResponse`

* Lint

* update tests

* Add log when a request to the fallback URL was handled by load shedder (which should never happen)

* rename `HTTPResponseSource` to `HTTPResponseOriginalSource`

* Adapt to changes

* fix lint

* Fix CustomerInfo's OriginalSource implementation + fix unit test
2025-11-06 13:27:46 +01:00

123 lines
4.3 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// CustomerInfoResponseHandler.swift
//
// Created by Joshua Liebowitz on 11/18/21.
import Foundation
class CustomerInfoResponseHandler {
private let offlineCreator: OfflineCustomerInfoCreator?
private let userID: String
private let failIfInvalidSubscriptionKeyDetectedInDebug: Bool
private let isDebug: Bool
/// - Parameter offlineCreator: can be `nil` if offline ``CustomerInfo`` shouldn't or can't be computed.
init(
offlineCreator: OfflineCustomerInfoCreator?,
userID: String,
failIfInvalidSubscriptionKeyDetectedInDebug: Bool,
isDebug: Bool = {
#if DEBUG
return true
#endif
return false
}()
) {
self.offlineCreator = offlineCreator
self.userID = userID
self.failIfInvalidSubscriptionKeyDetectedInDebug = failIfInvalidSubscriptionKeyDetectedInDebug
self.isDebug = isDebug
}
func handle(customerInfoResponse response: VerifiedHTTPResponse<Response>.Result,
completion: @escaping CustomerAPI.CustomerInfoResponseHandler) {
let result: Result<CustomerInfo, BackendError> = response
.map { response in
// If the response was successful we always want to return the `CustomerInfo`.
if !response.body.errorResponse.attributeErrors.isEmpty {
// If there are any, log attribute errors.
// Creating the error implicitly logs it.
_ = response.body.errorResponse.asBackendError(with: response.httpStatusCode)
}
return response.body.customerInfo.copy(with: response.verificationResult,
httpResponseOriginalSource: response.originalSource)
}
.mapError(BackendError.networkError)
self.handle(result: result, completion: completion)
}
private func handle(
result: Result<CustomerInfo, BackendError>,
completion: @escaping CustomerAPI.CustomerInfoResponseHandler
) {
guard let offlineCreator = self.offlineCreator,
result.error?.isServerDown == true,
#available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) else {
completion(result)
return
}
_ = Task<Void, Never> {
do {
switch result {
case .success:
completion(.success(try await offlineCreator.create(for: self.userID)))
case .failure(let failure):
let failIfInvalidSubscriptionKeyDetectedInDebug = self.failIfInvalidSubscriptionKeyDetectedInDebug
if isDebug && failIfInvalidSubscriptionKeyDetectedInDebug,
case let .networkError(networkError) = failure,
case let .errorResponse(errorResponse, _, _) = networkError,
errorResponse.code == .invalidAppleSubscriptionKey {
Logger.warn(Strings.configure.sk2_invalid_inapp_purchase_key)
completion(.failure(failure))
} else {
let customerInfo = try await offlineCreator.create(for: self.userID)
completion(.success(customerInfo))
}
}
} catch {
Logger.error(Strings.offlineEntitlements.computing_offline_customer_info_failed(error))
completion(result)
}
}
}
}
extension CustomerInfoResponseHandler {
struct Response: HTTPResponseBody {
var customerInfo: CustomerInfo
var errorResponse: ErrorResponse
static func create(with data: Data) throws -> Self {
return .init(customerInfo: try CustomerInfo.create(with: data),
errorResponse: ErrorResponse.from(data))
}
func copy(with newRequestDate: Date) -> Self {
var copy = self
copy.customerInfo = copy.customerInfo.copy(with: newRequestDate)
return copy
}
}
}