Feat: [CP-9314] PaymentsV2 return transaction result

Refs: https://gitlab.protontech.ch/apple/shared/protoncore/-/merge_requests/2065
This commit is contained in:
Tiziano Bruni 2025-02-05 12:25:59 +00:00
commit 3f0d1e5df2
3 changed files with 48 additions and 32 deletions

View file

@ -48,6 +48,8 @@ public class AvailablePlansViewModel: ObservableObject {
filteredPlans.isEmpty
}
public private(set) var transactionProgress = CurrentValueSubject<TransactionHandlerState, Never>(.idle)
private var cancellables = Set<AnyCancellable>()
let billing = BillingCycle.allCases
@ -151,6 +153,8 @@ public class AvailablePlansViewModel: ObservableObject {
return
}
self.transactionProgress = plan.transactionState
plan.transactionState
.dropFirst()
.receive(on: DispatchQueue.main)
@ -159,12 +163,15 @@ public class AvailablePlansViewModel: ObservableObject {
case .generatingReceipt:
self.purchaseInProgress()
case .transactionCompleted:
self.transactionProgress.send(completion: .finished)
self.transactionCompleted()
case .createNewSubscription:
self.confirmationCompleted = true
case .transactionCancelledByUser:
self.transactionProgress.send(completion: .finished)
self.transactionCancelledByUser()
case .unknownError, .transactionProcessError, .mismatchTransactionIDs, .unableToGetUserTransactionUUID:
self.transactionProgress.send(completion: .finished)
self.transactionProcessError()
default:
break

View file

@ -19,8 +19,9 @@
// You should have received a copy of the GNU General Public License
// along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
import UIKit
import Combine
import ProtonCorePaymentsV2
import UIKit
public enum PresentationMode {
case modal
@ -35,7 +36,12 @@ public enum PaymentsPresentationError: Error {
case unableToFindValidEnvironment
}
final public class PaymentsV2 {
final public class PaymentsV2: Sendable {
public private(set) var transactionProgress = CurrentValueSubject<TransactionHandlerState, Never>(.idle)
private let queue = DispatchQueue(label: "paymentsV2Presenter.syncQueue")
private var presentationMode: PresentationMode = .none
private var paymentsView: PaymentsUIViewControllerV2!
public init() {}
@ -65,23 +71,41 @@ final public class PaymentsV2 {
throw PaymentsPresentationError.transactionsObserverNotActive
}
let vc = try createPaymentsView(sessionID: sessionID,
accessToken: accessToken,
appVersion: appVersion,
hideCurrentPlan: hideCurrentPlan,
presentationMode: presentationMode,
env: env)
self.presentationMode = presentationMode
paymentsView = try createPaymentsView(sessionID: sessionID,
accessToken: accessToken,
appVersion: appVersion,
hideCurrentPlan: hideCurrentPlan,
presentationMode: presentationMode,
env: env)
switch presentationMode {
case .modal:
try presentView(vc: vc)
try presentView(vc: paymentsView)
case .push:
try pushView(vc: vc)
try pushView(vc: paymentsView)
case .none:
throw PaymentsPresentationError.noPresentationModeSet
}
}
public func dismissPayments() {
switch presentationMode {
case .modal:
paymentsView.dismiss(animated: true)
case .push:
guard let viewController = UIApplication.getTopViewController(), let navController = viewController.navigationController else {
debugPrint("PaymentsV2 dismiss error: Impossible to find top viewController or navigation controller")
return
}
navController.popToViewController(paymentsView, animated: true)
case .none:
break
}
}
//MARK: Private functions
private func presentView(vc: UIViewController) throws {
guard let viewController = UIApplication.getTopViewController() else {
@ -110,11 +134,15 @@ final public class PaymentsV2 {
throw PaymentsPresentationError.unableToFindValidEnvironment
}
return PaymentsUIViewControllerV2(sessionId: sessionID,
let vc = PaymentsUIViewControllerV2(sessionId: sessionID,
token: accessToken,
appVersion: appVersion,
env: env,
presentationMode: presentationMode,
hideCurrentPlan: hideCurrentPlan)
queue.sync {
self.transactionProgress = vc.transactionProgress
}
return vc
}
}

View file

@ -24,21 +24,12 @@ import ProtonCorePaymentsV2
import ProtonCoreUI
import Combine
public protocol PaymentsUIViewControllerV2Delegate: AnyObject {
func viewControllerWillAppear(isFirstAppearance: Bool)
func viewControllerWillDisappear()
func planPurchaseError()
func paymentsFlowStateDidChange(_state: AvailablePlansViewModel.State)
}
public final class PaymentsUIViewControllerV2: UIViewController {
private var viewWillAppear: Bool = true
private var cancellables = Set<AnyCancellable>()
@Published private var viewState: AvailablePlansViewModel.State = .idle
public weak var delegate: PaymentsUIViewControllerV2Delegate?
public private(set) var transactionProgress = CurrentValueSubject<TransactionHandlerState, Never>(.idle)
public init(sessionId: String,
token: String,
@ -74,11 +65,7 @@ public final class PaymentsUIViewControllerV2: UIViewController {
hideCurrentPlan: hideCurrentPlan,
presentationMode: presentationMode)
viewModel.$viewState.sink { [weak self] value in
guard let self = self else { return }
self.delegate?.paymentsFlowStateDidChange(_state: value)
}
.store(in: &cancellables)
transactionProgress = viewModel.transactionProgress
let availablePlansView = AvailablePlansView(viewModel: viewModel)
@ -98,15 +85,9 @@ public final class PaymentsUIViewControllerV2: UIViewController {
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
debugPrint("viewControllerWillAppear called with value: \(viewWillAppear)")
delegate?.viewControllerWillAppear(isFirstAppearance: viewWillAppear)
viewWillAppear = false
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
delegate?.viewControllerWillDisappear()
}
private func customNavBarButton() -> UIButton {
let button = UIButton(frame: .zero)
button.setImage(Theme.icon.arrowLeft, for: .normal)