Skip to content

Commit

Permalink
[Winback] Apply Apple API for subscription cancellation (#2474)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielebogo authored Nov 26, 2024
2 parents 735a65d + b08f8ed commit bf35d2d
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import PocketCastsUtils
import SwiftProtobuf

class SubscriptionStatusTask: ApiBaseTask {
var completion: ((Bool) -> Void)?

override func apiTokenAcquired(token: String) {
let url = ServerConstants.Urls.api() + "subscription/status"
do {
let (response, httpStatus) = getToServer(url: url, token: token)

guard let responseData = response, httpStatus?.statusCode == ServerConstants.HttpConstants.ok else {
FileLog.shared.addMessage("Subscription status failed \(httpStatus?.statusCode ?? -1)")
completion?(false)
return
}
do {
Expand All @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion podcasts/Cancel Subscription/CancelSubscriptionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
18 changes: 17 additions & 1 deletion podcasts/CancelConfirmationViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 36 additions & 1 deletion podcasts/IAPHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }

Expand Down

0 comments on commit bf35d2d

Please sign in to comment.