diff --git a/Hardware/Hardware/CardReader/UnderlyingError.swift b/Hardware/Hardware/CardReader/UnderlyingError.swift index 54f8e55383c..0b8c667e759 100644 --- a/Hardware/Hardware/CardReader/UnderlyingError.swift +++ b/Hardware/Hardware/CardReader/UnderlyingError.swift @@ -450,7 +450,7 @@ extension UnderlyingError: LocalizedError { "the device does not meet minimum requirements.") case .commandNotAllowedDuringCall: return NSLocalizedString("The built-in reader cannot be used during a phone call. Please try again after " + - "you finish your call", + "you finish your call.", comment: "Error message shown when the built-in reader cannot be used because " + "there is a call in progress") case .invalidAmount: diff --git a/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalNonRetryableError.swift b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalNonRetryableError.swift index 95c25bd679c..c765cfb55ab 100644 --- a/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalNonRetryableError.swift +++ b/WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalNonRetryableError.swift @@ -6,9 +6,6 @@ final class CardPresentModalNonRetryableError: CardPresentPaymentsModalViewModel /// Amount charged private let amount: String - /// The error returned by the stack - private let error: Error - /// Called when the view is dismissed private let onDismiss: () -> Void @@ -29,9 +26,7 @@ final class CardPresentModalNonRetryableError: CardPresentPaymentsModalViewModel let auxiliaryButtonTitle: String? = nil - var bottomTitle: String? { - error.localizedDescription - } + let bottomTitle: String? let bottomSubtitle: String? = nil @@ -43,12 +38,16 @@ final class CardPresentModalNonRetryableError: CardPresentPaymentsModalViewModel return topTitle + bottomTitle } - init(amount: String, error: Error, onDismiss: @escaping () -> Void) { + init(amount: String, errorDescription: String?, onDismiss: @escaping () -> Void) { self.amount = amount - self.error = error + self.bottomTitle = errorDescription self.onDismiss = onDismiss } + convenience init(amount: String, error: Error, onDismiss: @escaping () -> Void) { + self.init(amount: amount, errorDescription: error.localizedDescription, onDismiss: onDismiss) + } + func didTapPrimaryButton(in viewController: UIViewController?) { viewController?.dismiss(animated: true) { [weak self] in self?.onDismiss() diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInCardReaderPaymentAlertsProvider.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInCardReaderPaymentAlertsProvider.swift index 8624a076125..2a5c833ad78 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInCardReaderPaymentAlertsProvider.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/BuiltInCardReaderPaymentAlertsProvider.swift @@ -48,32 +48,16 @@ final class BuiltInCardReaderPaymentAlertsProvider: CardReaderTransactionAlertsP } func error(error: Error, tryAgain: @escaping () -> Void, dismissCompletion: @escaping () -> Void) -> CardPresentPaymentsModalViewModel { - let errorDescription: String? - if let error = error as? CardReaderServiceError { - switch error { - case .connection(let underlyingError), - .discovery(let underlyingError), - .disconnection(let underlyingError), - .intentCreation(let underlyingError), - .paymentMethodCollection(let underlyingError), - .paymentCapture(let underlyingError), - .paymentCancellation(let underlyingError), - .softwareUpdate(let underlyingError, _): - errorDescription = Localization.errorDescription(underlyingError: underlyingError) - default: - errorDescription = error.errorDescription - } - } else { - errorDescription = error.localizedDescription - } - return CardPresentModalError(errorDescription: errorDescription, + return CardPresentModalError(errorDescription: builtInReaderDescription(for: error), transactionType: .collectPayment, primaryAction: tryAgain, dismissCompletion: dismissCompletion) } func nonRetryableError(error: Error, dismissCompletion: @escaping () -> Void) -> CardPresentPaymentsModalViewModel { - CardPresentModalNonRetryableError(amount: amount, error: error, onDismiss: dismissCompletion) + CardPresentModalNonRetryableError(amount: amount, + errorDescription: builtInReaderDescription(for: error), + onDismiss: dismissCompletion) } func retryableError(tryAgain: @escaping () -> Void) -> CardPresentPaymentsModalViewModel { @@ -86,6 +70,26 @@ final class BuiltInCardReaderPaymentAlertsProvider: CardReaderTransactionAlertsP } private extension BuiltInCardReaderPaymentAlertsProvider { + func builtInReaderDescription(for error: Error) -> String? { + if let error = error as? CardReaderServiceError { + switch error { + case .connection(let underlyingError), + .discovery(let underlyingError), + .disconnection(let underlyingError), + .intentCreation(let underlyingError), + .paymentMethodCollection(let underlyingError), + .paymentCapture(let underlyingError), + .paymentCancellation(let underlyingError), + .softwareUpdate(let underlyingError, _): + return Localization.errorDescription(underlyingError: underlyingError) + default: + return error.errorDescription + } + } else { + return error.localizedDescription + } + } + enum Localization { static func errorDescription(underlyingError: UnderlyingError) -> String? { switch underlyingError { @@ -102,6 +106,10 @@ private extension BuiltInCardReaderPaymentAlertsProvider { "Sorry, this payment couldn’t be processed", comment: "Error message when the card reader service experiences an unexpected internal service error." ) + case .notConnectedToReader: + return NSLocalizedString( + "The payment was interrupted and cannot be continued. You can retry the payment from the order screen.", + comment: "Error shown when the built-in card reader payment is interrupted by activity on the phone") default: return underlyingError.errorDescription } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/CollectOrderPaymentUseCase.swift b/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/CollectOrderPaymentUseCase.swift index 1ce9cb1cbdd..6ae20d9e45b 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/CollectOrderPaymentUseCase.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/CollectOrderPaymentUseCase.swift @@ -22,7 +22,7 @@ protocol CollectOrderPaymentProtocol { /// - Parameter onCollect: Closure Invoked after the collect process has finished. /// - Parameter onCompleted: Closure Invoked after the flow has been totally completed. /// - Parameter onCancel: Closure invoked after the flow is cancelled - func collectPayment(onCollect: @escaping (Result) -> (), onCancel: @escaping () -> (), onCompleted: @escaping () -> ()) + func collectPayment(onFailure: @escaping (Error) -> (), onCancel: @escaping () -> (), onCompleted: @escaping () -> ()) } /// Use case to collect payments from an order. @@ -129,15 +129,15 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol { /// 7. Tracks payment analytics /// /// - /// - Parameter onCollect: Closure invoked after the collect process has finished. + /// - Parameter onFailure: Closure invoked after the payment process fails. /// - Parameter onCancel: Closure invoked after the flow is cancelled /// - Parameter onCompleted: Closure invoked after the flow has been totally completed, currently after merchant has handled the receipt. - func collectPayment(onCollect: @escaping (Result) -> (), + func collectPayment(onFailure: @escaping (Error) -> (), onCancel: @escaping () -> (), onCompleted: @escaping () -> ()) { guard isTotalAmountValid() else { let error = totalAmountInvalidError() - onCollect(.failure(error)) + onFailure(error) return handleTotalAmountInvalidError(totalAmountInvalidError(), onCompleted: onCancel) } @@ -158,16 +158,14 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol { case .failure(CollectOrderPaymentUseCaseError.flowCanceledByUser): self.rootViewController.presentedViewController?.dismiss(animated: true) return onCancel() - default: - onCollect(result.map { _ in () }) // Transforms Result to Result - } - // Handle payment receipt - guard let paymentData = try? result.get() else { - return onCompleted() + case .failure(let error): + return onFailure(error) + case .success(let paymentData): + // Handle payment receipt + self.presentReceiptAlert(receiptParameters: paymentData.receiptParameters, + alertProvider: paymentAlertProvider, + onCompleted: onCompleted) } - self.presentReceiptAlert(receiptParameters: paymentData.receiptParameters, - alertProvider: paymentAlertProvider, - onCompleted: onCompleted) }) case .canceled: self.handlePaymentCancellation() @@ -333,33 +331,15 @@ private extension CollectOrderPaymentUseCase { trackPaymentFailure(with: error) - // Inform about the error - alertsPresenter.present( - viewModel: paymentAlerts.error(error: error, - tryAgain: { [weak self] in - - // Cancel current payment - self?.paymentOrchestrator.cancelPayment { [weak self] result in - guard let self = self else { return } - - switch result { - case .success: - // Retry payment - self.attemptPayment(alertProvider: paymentAlerts, - onCompletion: onCompletion) - - case .failure(let cancelError): - // Inform that payment can't be retried. - self.alertsPresenter.present( - viewModel: paymentAlerts.nonRetryableError(error: cancelError) { - onCompletion(.failure(error)) - }) - } - } - }, dismissCompletion: { - onCompletion(.failure(error)) - }) - ) + if canRetryPayment(with: error) { + presentRetryableError(error: error, + paymentAlerts: paymentAlerts, + onCompletion: onCompletion) + } else { + presentNonRetryableError(error: error, + paymentAlerts: paymentAlerts, + onCompletion: onCompletion) + } } private func trackPaymentFailure(with error: Error) { @@ -370,6 +350,72 @@ private extension CollectOrderPaymentUseCase { cardReaderModel: connectedReader?.readerType.model)) } + private func canRetryPayment(with error: Error) -> Bool { + guard let serviceError = error as? CardReaderServiceError else { + return true + } + switch serviceError { + case .paymentMethodCollection(let underlyingError), + .paymentCapture(let underlyingError), + .paymentCancellation(let underlyingError): + return canRetryPayment(underlyingError: underlyingError) + default: + return true + } + } + + private func canRetryPayment(underlyingError: UnderlyingError) -> Bool { + switch underlyingError { + case .notConnectedToReader, + .commandNotAllowedDuringCall, + .featureNotAvailableWithConnectedReader: + return false + default: + return true + } + } + + private func presentRetryableError(error: Error, + paymentAlerts: CardReaderTransactionAlertsProviding, + onCompletion: @escaping (Result) -> ()) { + alertsPresenter.present( + viewModel: paymentAlerts.error(error: error, + tryAgain: { [weak self] in + + // Cancel current payment + self?.paymentOrchestrator.cancelPayment { [weak self] result in + guard let self = self else { return } + + switch result { + case .success: + // Retry payment + self.attemptPayment(alertProvider: paymentAlerts, + onCompletion: onCompletion) + + case .failure(let cancelError): + // Inform that payment can't be retried. + self.alertsPresenter.present( + viewModel: paymentAlerts.nonRetryableError(error: cancelError) { + onCompletion(.failure(error)) + }) + } + } + }, dismissCompletion: { + onCompletion(.failure(error)) + }) + ) + } + + private func presentNonRetryableError(error: Error, + paymentAlerts: CardReaderTransactionAlertsProviding, + onCompletion: @escaping (Result) -> ()) { + alertsPresenter.present( + viewModel: paymentAlerts.nonRetryableError(error: error, + dismissCompletion: { + onCompletion(.failure(error)) + })) + } + /// Cancels payment and record analytics. /// func cancelPayment(onCompleted: @escaping () -> ()) { diff --git a/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/LegacyCollectOrderPaymentUseCase.swift b/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/LegacyCollectOrderPaymentUseCase.swift index 48a1562df70..3e8c74f7606 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/LegacyCollectOrderPaymentUseCase.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Collect Payments/LegacyCollectOrderPaymentUseCase.swift @@ -5,10 +5,24 @@ import MessageUI import WooFoundation import protocol Storage.StorageManagerType + +/// Protocol to abstract the `LegacyCollectOrderPaymentUseCase`. +/// Currently only used to facilitate unit tests. +/// +protocol LegacyCollectOrderPaymentProtocol { + /// Starts the collect payment flow. + /// + /// + /// - Parameter onCollect: Closure Invoked after the collect process has finished. + /// - Parameter onCompleted: Closure Invoked after the flow has been totally completed. + /// - Parameter onCancel: Closure invoked after the flow is cancelled + func collectPayment(onCollect: @escaping (Result) -> (), onCancel: @escaping () -> (), onCompleted: @escaping () -> ()) +} + /// Use case to collect payments from an order. /// Orchestrates reader connection, payment, UI alerts, receipt handling and analytics. /// -final class LegacyCollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol { +final class LegacyCollectOrderPaymentUseCase: NSObject, LegacyCollectOrderPaymentProtocol { /// Currency Formatter /// private let currencyFormatter = CurrencyFormatter(currencySettings: ServiceLocator.currencySettings) diff --git a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift index 87dd5db8779..e514af80182 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift @@ -60,7 +60,7 @@ struct PaymentMethodsView: View { Divider() MethodRow(icon: .creditCardImage, title: Localization.card, accessibilityID: Accessibility.cardMethod) { - viewModel.collectPayment(on: rootViewController, onSuccess: dismiss) + viewModel.collectPayment(on: rootViewController, onSuccess: dismiss, onFailure: dismiss) } } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift index 79712ace681..e4ec366fa9b 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift @@ -99,7 +99,7 @@ final class PaymentMethodsViewModel: ObservableObject { /// Retains the use-case so it can perform all of its async tasks. /// - private var legacyCollectPaymentsUseCase: CollectOrderPaymentProtocol? + private var legacyCollectPaymentsUseCase: LegacyCollectOrderPaymentProtocol? private var collectPaymentsUseCase: CollectOrderPaymentProtocol? @@ -112,6 +112,8 @@ final class PaymentMethodsViewModel: ObservableObject { configuration: upsellCardReadersCampaign.configuration) } + private let isTapToPayOnIPhoneEnabled: Bool + struct Dependencies { let presentNoticeSubject: PassthroughSubject let cardPresentPaymentsOnboardingPresenter: CardPresentPaymentsOnboardingPresenting @@ -141,12 +143,14 @@ final class PaymentMethodsViewModel: ObservableObject { paymentLink: URL? = nil, formattedTotal: String, flow: WooAnalyticsEvent.PaymentsFlow.Flow, + isTapToPayOnIPhoneEnabled: Bool = ServiceLocator.generalAppSettings.settings.isTapToPayOnIPhoneSwitchEnabled, dependencies: Dependencies = Dependencies()) { self.siteID = siteID self.orderID = orderID self.paymentLink = paymentLink self.formattedTotal = formattedTotal self.flow = flow + self.isTapToPayOnIPhoneEnabled = isTapToPayOnIPhoneEnabled presentNoticeSubject = dependencies.presentNoticeSubject cardPresentPaymentsOnboardingPresenter = dependencies.cardPresentPaymentsOnboardingPresenter stores = dependencies.stores @@ -199,19 +203,21 @@ final class PaymentMethodsViewModel: ObservableObject { /// - parameter useCase: Assign a custom useCase object for testing purposes. If not provided `CollectOrderPaymentUseCase` will be used. /// func collectPayment(on rootViewController: UIViewController?, - useCase: CollectOrderPaymentProtocol? = nil, - onSuccess: @escaping () -> ()) { - switch ServiceLocator.generalAppSettings.settings.isTapToPayOnIPhoneSwitchEnabled { + useCase: LegacyCollectOrderPaymentProtocol? = nil, + onSuccess: @escaping () -> (), + onFailure: @escaping () -> ()) { + switch isTapToPayOnIPhoneEnabled { case true: - newCollectPayment(on: rootViewController, useCase: useCase, onSuccess: onSuccess) + newCollectPayment(on: rootViewController, onSuccess: onSuccess, onFailure: onFailure) case false: legacyCollectPayment(on: rootViewController, useCase: useCase, onSuccess: onSuccess) } } func newCollectPayment(on rootViewController: UIViewController?, - useCase: CollectOrderPaymentProtocol? = nil, - onSuccess: @escaping () -> ()) { + useCase: CollectOrderPaymentProtocol? = nil, + onSuccess: @escaping () -> (), + onFailure: @escaping () -> ()) { trackCollectIntention(method: .card) guard let rootViewController = rootViewController else { @@ -243,9 +249,12 @@ final class PaymentMethodsViewModel: ObservableObject { configuration: CardPresentConfigurationLoader().configuration) self.collectPaymentsUseCase?.collectPayment( - onCollect: { [weak self] result in - guard result.isFailure else { return } + onFailure: { [weak self] error in self?.trackFlowFailed() + // Update order in case its status and/or other details are updated after a failed in-person payment + self?.updateOrderAsynchronously() + + onFailure() }, onCancel: { // No tracking required because the flow remains on screen to choose other payment methods. @@ -273,8 +282,8 @@ final class PaymentMethodsViewModel: ObservableObject { } func legacyCollectPayment(on rootViewController: UIViewController?, - useCase: CollectOrderPaymentProtocol? = nil, - onSuccess: @escaping () -> ()) { + useCase: LegacyCollectOrderPaymentProtocol? = nil, + onSuccess: @escaping () -> ()) { trackCollectIntention(method: .card) guard let rootViewController = rootViewController else { diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift index c89de7f3157..683d76fc80f 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift @@ -29,6 +29,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(stores: stores) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -54,6 +55,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(stores: stores) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -90,6 +92,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { storage: storage) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -118,6 +121,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(stores: stores) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) stores.whenReceivingAction(ofType: OrderAction.self) { action in switch action { @@ -148,6 +152,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(presentNoticeSubject: noticeSubject, stores: stores) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) stores.whenReceivingAction(ofType: OrderAction.self) { action in switch action { @@ -185,6 +190,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(presentNoticeSubject: noticeSubject, stores: stores) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) stores.whenReceivingAction(ofType: OrderAction.self) { action in switch action { @@ -232,6 +238,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -265,10 +272,11 @@ final class PaymentMethodsViewModelTests: XCTestCase { analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When - viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}) + viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}, onFailure: {}) // Then assertEqual(analytics.receivedEvents.last, WooAnalyticsStat.paymentsFlowCompleted.rawValue) @@ -283,6 +291,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -314,6 +323,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -346,10 +356,11 @@ final class PaymentMethodsViewModelTests: XCTestCase { analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When - viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}) + viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}, onFailure: {}) // Then assertEqual(analytics.receivedEvents.last, WooAnalyticsStat.paymentsFlowFailed.rawValue) @@ -365,6 +376,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -382,6 +394,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -405,10 +418,11 @@ final class PaymentMethodsViewModelTests: XCTestCase { analytics: WooAnalytics(analyticsProvider: analytics)) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When - viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}) + viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}, onFailure: {}) // Then assertEqual(analytics.receivedEvents.last, WooAnalyticsStat.paymentsFlowCollect.rawValue) @@ -433,6 +447,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { orderID: 111, formattedTotal: "$5.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // Then @@ -456,6 +471,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { orderID: 111, formattedTotal: "$5.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // Then @@ -479,6 +495,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { orderID: 111, formattedTotal: "$5.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // Then @@ -502,6 +519,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { orderID: 111, formattedTotal: "$5.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // Then @@ -539,6 +557,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { orderID: 111, formattedTotal: "$5.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // Then @@ -576,6 +595,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { orderID: 111, formattedTotal: "$5.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // Then @@ -584,7 +604,10 @@ final class PaymentMethodsViewModelTests: XCTestCase { func test_paymentLinkRow_is_hidden_if_payment_link_is_not_available() { // Given - let viewModel = PaymentMethodsViewModel(paymentLink: nil, formattedTotal: "$12.00", flow: .simplePayment) + let viewModel = PaymentMethodsViewModel(paymentLink: nil, + formattedTotal: "$12.00", + flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false) // Then XCTAssertFalse(viewModel.showPaymentLinkRow) @@ -594,7 +617,10 @@ final class PaymentMethodsViewModelTests: XCTestCase { func test_paymentLinkRow_is_shown_if_payment_link_is_available() { // Given let paymentURL = URL(string: "http://www.automattic.com") - let viewModel = PaymentMethodsViewModel(paymentLink: paymentURL, formattedTotal: "$12.00", flow: .simplePayment) + let viewModel = PaymentMethodsViewModel(paymentLink: paymentURL, + formattedTotal: "$12.00", + flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false) // Then XCTAssertTrue(viewModel.showPaymentLinkRow) @@ -607,6 +633,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { let dependencies = Dependencies(presentNoticeSubject: noticeSubject) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -651,6 +678,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { storage: storage) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -665,7 +693,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { } .store(in: &self.subscriptions) - viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}) + viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}, onFailure: {}) } // Then @@ -690,13 +718,17 @@ final class PaymentMethodsViewModelTests: XCTestCase { storage: storage) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When let calledOnSuccess: Bool = waitFor { promise in - viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: { + viewModel.collectPayment(on: UIViewController(), + useCase: useCase, + onSuccess: { promise(true) - }) + }, + onFailure: {}) } // Then @@ -723,6 +755,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { storage: storage) let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, dependencies: dependencies) // When @@ -735,7 +768,7 @@ final class PaymentMethodsViewModelTests: XCTestCase { XCTFail("Unexpected action: \(action)") } } - viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}) + viewModel.collectPayment(on: UIViewController(), useCase: useCase, onSuccess: {}, onFailure: {}) } // Then diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Simple Payments/MockCollectOrderPaymentUseCase.swift b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Simple Payments/MockCollectOrderPaymentUseCase.swift index 0e219af1bfc..d918562d656 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Simple Payments/MockCollectOrderPaymentUseCase.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Simple Payments/MockCollectOrderPaymentUseCase.swift @@ -1,9 +1,9 @@ import Foundation @testable import WooCommerce -/// Mock type for `CollectOrderPaymentProtocol` +/// Mock type for `LegacyCollectOrderPaymentProtocol` /// -struct MockCollectOrderPaymentUseCase: CollectOrderPaymentProtocol { +struct MockCollectOrderPaymentUseCase: LegacyCollectOrderPaymentProtocol { /// Assign to be returned on `onCollect` closure. ///