diff --git a/Modules/Server/Sources/PocketCastsServer/Private/API Tasks/SubscriptionStatusTask.swift b/Modules/Server/Sources/PocketCastsServer/Private/API Tasks/SubscriptionStatusTask.swift index 9b24ff6e04..a467b8d81d 100644 --- a/Modules/Server/Sources/PocketCastsServer/Private/API Tasks/SubscriptionStatusTask.swift +++ b/Modules/Server/Sources/PocketCastsServer/Private/API Tasks/SubscriptionStatusTask.swift @@ -4,6 +4,8 @@ import PocketCastsUtils import SwiftProtobuf class SubscriptionStatusTask: ApiBaseTask { + var completion: ((Bool) -> Void)? + override func apiTokenAcquired(token: String) { let url = ServerConstants.Urls.api() + "subscription/status" do { @@ -11,6 +13,7 @@ class SubscriptionStatusTask: ApiBaseTask { guard let responseData = response, httpStatus?.statusCode == ServerConstants.HttpConstants.ok else { FileLog.shared.addMessage("Subscription status failed \(httpStatus?.statusCode ?? -1)") + completion?(false) return } do { @@ -35,9 +38,11 @@ class SubscriptionStatusTask: ApiBaseTask { if originalSubscriptionStatus, !SubscriptionHelper.hasActiveSubscription() { ServerConfig.shared.syncDelegate?.cleanupCloudOnlyFiles() } + completion?(true) } } catch { FileLog.shared.addMessage("SubscriptionStatusTask: Protobuf Encoding failed \(error.localizedDescription)") + completion?(false) } } diff --git a/Modules/Server/Sources/PocketCastsServer/Public/API/ApiServerHandler+Account.swift b/Modules/Server/Sources/PocketCastsServer/Public/API/ApiServerHandler+Account.swift index 49ff1b8239..8987665eaf 100644 --- a/Modules/Server/Sources/PocketCastsServer/Public/API/ApiServerHandler+Account.swift +++ b/Modules/Server/Sources/PocketCastsServer/Public/API/ApiServerHandler+Account.swift @@ -249,6 +249,17 @@ public extension ApiServerHandler { apiQueue.addOperation(subscriptionStatusTask) } + @discardableResult + func retrieveSubscriptionStatus() async -> Bool { + return await withCheckedContinuation { continuation in + let operation = SubscriptionStatusTask() + operation.completion = { success in + continuation.resume(returning: success) + } + apiQueue.addOperation(operation) + } + } + // MARK: - Subscription Promotion Codes func redeemPromoCode(promoCode: String, completion: @escaping (Int, String?, APIError?) -> Void) { diff --git a/podcasts/Cancel Subscription/CancelSubscriptionView.swift b/podcasts/Cancel Subscription/CancelSubscriptionView.swift index d0445c76b4..37d8e8e7d9 100644 --- a/podcasts/Cancel Subscription/CancelSubscriptionView.swift +++ b/podcasts/Cancel Subscription/CancelSubscriptionView.swift @@ -24,7 +24,8 @@ struct CancelSubscriptionView: View { ForEach(CancelSubscriptionOption.allCases, id: \.id) { option in if case .promotion = option { - if viewModel.isEligibleForOffer, case .promotion = option, let price = viewModel.monthlyPrice() { + //TODO: Need to check the if the promotion can be applied + if case .promotion = option, let price = viewModel.monthlyPrice() { CancelSubscriptionViewRow(option: .promotion(price: price), viewModel: viewModel) } diff --git a/podcasts/CancelConfirmationViewModel.swift b/podcasts/CancelConfirmationViewModel.swift index 58a4e05615..e4b97d0df3 100644 --- a/podcasts/CancelConfirmationViewModel.swift +++ b/podcasts/CancelConfirmationViewModel.swift @@ -25,7 +25,23 @@ class CancelConfirmationViewModel: OnboardingModel { Analytics.track(.cancelConfirmationCancelButtonTapped) if FeatureFlag.winback.enabled { - //TODO: Add Apple API + Task { + guard let windowScene = await navigationController.view.window?.windowScene else { + FileLog.shared.console("[CancelConfirmationViewModel] No window scene available") + return + } + do { + try await IAPHelper.shared.showManageSubscriptions(in: windowScene) + + await ApiServerHandler.shared.retrieveSubscriptionStatus() + + await MainActor.run { + navigationController.dismiss(animated: true) + } + } catch { + FileLog.shared.console("[StoreKit] Error showing manage subscriptions: \(error.localizedDescription)") + } + } } else { let controller = CancelInfoViewController() navigationController.pushViewController(controller, animated: true) diff --git a/podcasts/IAPHelper.swift b/podcasts/IAPHelper.swift index 6886e52e1b..06ec7d060e 100644 --- a/podcasts/IAPHelper.swift +++ b/podcasts/IAPHelper.swift @@ -24,7 +24,7 @@ class IAPHelper: NSObject { private var isRequestingProducts = false /// Whether purchasing is allowed in the current environment or not - private (set) var canMakePurchases = true + private(set) var canMakePurchases = true private var settings: IAPHelperSettings private var networking: IAPHelperNetworking @@ -84,6 +84,41 @@ class IAPHelper: NSObject { return nil } + func findLastSubscriptionPurchased() async -> [StoreKit.Transaction] { + var transactions: [StoreKit.Transaction] = [] + for await result in Transaction.currentEntitlements { + guard case .verified(let transaction) = result else { + continue + } + if transaction.revocationDate == nil { + transactions.append(transaction) + } + } + return transactions + } + + func findLastSubscriptionPurchasedGroupID() async -> String? { + return await findLastSubscriptionPurchased() + .filter { $0.expirationDate != nil } + .sorted { + if let t0 = $0.expirationDate, let t1 = $1.expirationDate { + return t0 > t1 + } + return false + } + .first?.subscriptionGroupID + } + + func showManageSubscriptions(in windowScene: UIWindowScene) async throws { + if let groupID = await findLastSubscriptionPurchasedGroupID(), #available(iOS 17.0, *) { + FileLog.shared.console("[CancelConfirmationViewModel] Last subscription purchased group ID: \(groupID)") + + try await StoreKit.AppStore.showManageSubscriptions(in: windowScene, subscriptionGroupID: groupID) + } else { + try await StoreKit.AppStore.showManageSubscriptions(in: windowScene) + } + } + /// Whether the products have been loaded from StoreKit var hasLoadedProducts: Bool { productsArray.count > 0 }