diff --git a/WooCommerce/Classes/ViewRelated/CardPresentPayments/BuiltInCardReaderConnectionController.swift b/WooCommerce/Classes/ViewRelated/CardPresentPayments/BuiltInCardReaderConnectionController.swift index 206976caa11..432fdfc8dbe 100644 --- a/WooCommerce/Classes/ViewRelated/CardPresentPayments/BuiltInCardReaderConnectionController.swift +++ b/WooCommerce/Classes/ViewRelated/CardPresentPayments/BuiltInCardReaderConnectionController.swift @@ -17,7 +17,7 @@ final class BuiltInCardReaderConnectionController { /// case initializing - /// Preparing for search (fetching the list of any known readers) + /// Preparing for search /// case preparingForSearch @@ -29,14 +29,6 @@ final class BuiltInCardReaderConnectionController { /// case searching - /// Found one card reader - /// - case foundReader - - /// Found two or more card readers - /// - case foundSeveralReaders - /// Attempting to connect to a card reader. The completion passed to `searchAndConnect` /// will be called with a `success` `Bool` `True` result if successful, after which the view controller /// passed to `searchAndConnect` will be dereferenced and the state set to `idle` @@ -69,12 +61,6 @@ final class BuiltInCardReaderConnectionController { case discoveryFailed(Error) } - /// The final state of the card reader connection to return without errors. - enum ConnectionResult { - case connected - case canceled - } - private let storageManager: StorageManagerType private let stores: StoresManager @@ -85,22 +71,9 @@ final class BuiltInCardReaderConnectionController { } private let siteID: Int64 - private let knownCardReaderProvider: CardReaderSettingsKnownReaderProvider private let alertsPresenter: CardPresentPaymentAlertsPresenting private let configuration: CardPresentPaymentsConfiguration - /// Reader(s) discovered by the card reader service - /// - private var foundReaders: [CardReader] - - /// Reader(s) known to us (i.e. we've connected to them in the past) - /// - private var knownReaderID: String? - - /// Reader(s) discovered by the card reader service that the merchant declined to connect to - /// - private var skippedReaderIDs: [String] - /// The reader we want the user to consider connecting to /// private var candidateReader: CardReader? @@ -109,18 +82,11 @@ final class BuiltInCardReaderConnectionController { /// private let analyticsTracker: CardReaderConnectionAnalyticsTracker - /// Since the number of readers can go greater than 1 and then back to 1, and we don't - /// want to keep changing the UI from the several-readers-found list to a single prompt - /// and back (as this would be visually quite annoying), this flag will tell us that we've - /// already switched to list format for this discovery flow, so that stay in list mode - /// even if the number of found readers drops to less than 2 - private var showSeveralFoundReaders: Bool = false - private var softwareUpdateCancelable: FallibleCancelable? = nil private var subscriptions = Set() - private var onCompletion: ((Result) -> Void)? + private var onCompletion: ((Result) -> Void)? private(set) lazy var dataSource: CardReaderSettingsDataSource = { return CardReaderSettingsDataSource(siteID: siteID, storageManager: storageManager) @@ -134,28 +100,19 @@ final class BuiltInCardReaderConnectionController { } } - private let discoveryMethod: CardReaderDiscoveryMethod - init( forSiteID: Int64, - discoveryMethod: CardReaderDiscoveryMethod, storageManager: StorageManagerType = ServiceLocator.storageManager, stores: StoresManager = ServiceLocator.stores, - knownReaderProvider: CardReaderSettingsKnownReaderProvider, alertsPresenter: CardPresentPaymentAlertsPresenting, configuration: CardPresentPaymentsConfiguration, analyticsTracker: CardReaderConnectionAnalyticsTracker ) { siteID = forSiteID - self.discoveryMethod = discoveryMethod self.storageManager = storageManager self.stores = stores state = .idle - knownCardReaderProvider = knownReaderProvider self.alertsPresenter = alertsPresenter - foundReaders = [] - knownReaderID = nil - skippedReaderIDs = [] self.configuration = configuration self.analyticsTracker = analyticsTracker @@ -167,7 +124,7 @@ final class BuiltInCardReaderConnectionController { subscriptions.removeAll() } - func searchAndConnect(onCompletion: @escaping (Result) -> Void) { + func searchAndConnect(onCompletion: @escaping (Result) -> Void) { self.onCompletion = onCompletion self.state = .initializing } @@ -200,10 +157,6 @@ private extension BuiltInCardReaderConnectionController { onBeginSearch() case .searching: onSearching() - case .foundReader: - onFoundReader() - case .foundSeveralReaders: - onFoundSeveralReaders() case .retry: onRetry() case .cancel: @@ -227,47 +180,6 @@ private extension BuiltInCardReaderConnectionController { } } - /// To avoid presenting the "Do you want to connect to reader XXXX" prompt - /// repeatedly for the same reader, keep track of readers the user has tapped - /// "Keep Searching" for. - /// - /// If we have switched to the list view, however, don't prune - /// - func pruneSkippedReaders() { - guard !showSeveralFoundReaders else { - return - } - foundReaders = foundReaders.filter({!skippedReaderIDs.contains($0.id)}) - } - - /// Returns any found reader which is also known - /// - func getFoundKnownReader() -> CardReader? { - foundReaders.filter({knownReaderID == $0.id}).first - } - - /// A helper to return an array of found reader IDs - /// - func getFoundReaderIDs() -> [String] { - foundReaders.compactMap({$0.id}) - } - - /// A helper to return a specific CardReader instance based on the reader ID - /// - func getFoundReaderByID(readerID: String) -> CardReader? { - foundReaders.first(where: {$0.id == readerID}) - } - - /// Updates the show multiple readers flag to indicate that, for this discovery flow, - /// we have already shown the multiple readers UI (so we don't switch back to the - /// single reader found UI for this particular discovery) - /// - func updateShowSeveralFoundReaders() { - if foundReaders.containsMoreThanOne { - showSeveralFoundReaders = true - } - } - /// Initial state of the controller /// func onIdle() { @@ -281,103 +193,45 @@ private extension BuiltInCardReaderConnectionController { } } - /// In preparation for search, initiates a fetch for the list of known readers + /// In preparation for search, sets everything to defaults /// Does NOT open any modal - /// Transitions state to `.beginSearch` after receiving the known readers list + /// Transitions state to `.beginSearch` /// func onPreparingForSearch() { - /// Always start fresh - i.e. we haven't skipped connecting to any reader yet + /// Always start fresh /// - skippedReaderIDs = [] candidateReader = nil - showSeveralFoundReaders = false - /// Fetch the list of known readers - i.e. readers we should automatically connect to when we see them - /// - knownCardReaderProvider.knownReader.sink(receiveValue: { [weak self] readerID in - guard let self = self else { - return - } - - self.knownReaderID = readerID - - /// Only kick off search if we received a known reader update - if case .preparingForSearch = self.state { - self.state = .beginSearch - } - }).store(in: &subscriptions) + if case .preparingForSearch = state { + state = .beginSearch + } } /// Begins the search for a card reader /// Does NOT open any modal /// Transitions state to `.searching` - /// Later, when a reader is found, state transitions to - /// `.foundReader` if one unknown reader is found, - /// `.foundMultipleReaders` if two or more readers are found, - /// or to `.connectToReader` if one known reader is found + /// Later, when a reader is found, state transitions to `.connectToReader` /// func onBeginSearch() { self.state = .searching - var didAutoAdvance = false let action = CardPresentPaymentAction.startCardReaderDiscovery( siteID: siteID, - discoveryMethod: discoveryMethod, + discoveryMethod: .localMobile, onReaderDiscovered: { [weak self] cardReaders in guard let self = self else { return } - /// Update our copy of the foundReaders, evaluate if we should switch to the list view, - /// and prune skipped ones - /// - self.foundReaders = cardReaders - self.updateShowSeveralFoundReaders() - self.pruneSkippedReaders() - /// Note: This completion will be called repeatedly as the list of readers /// discovered changes, so some care around state must be taken here. /// - /// If the found-several-readers view is already presenting, update its list of found readers - /// - if case .foundSeveralReaders = self.state { - self.alertsPresenter.updateSeveralReadersList(readerIDs: self.getFoundReaderIDs()) - } - - /// To avoid interrupting connecting to a known reader, ensure we are - /// in the searching state before proceeding further - /// - guard case .searching = self.state else { - return - } - - /// If we have a known reader, and we haven't auto-advanced to connect - /// already, advance immediately to connect. - /// We only auto-advance once to avoid loops in case the known reader - /// is having connectivity issues (e.g low battery) - /// - if let foundKnownReader = self.getFoundKnownReader() { - if !didAutoAdvance { - didAutoAdvance = true - self.candidateReader = foundKnownReader - self.state = .connectToReader - return - } - } - - /// If we have found multiple readers, advance to foundMultipleReaders - /// - if self.showSeveralFoundReaders { - self.state = .foundSeveralReaders - return - } - - /// If we have a found reader, advance to foundReader + /// If we have a found reader, advance to `connectToReader` /// - if self.foundReaders.isNotEmpty { - self.candidateReader = self.foundReaders.first - self.state = .foundReader + if cardReaders.isNotEmpty { + self.candidateReader = cardReaders.first + self.state = .connectToReader return } }, @@ -399,30 +253,11 @@ private extension BuiltInCardReaderConnectionController { /// If the user cancels the modal will trigger a transition to `.endSearch` /// func onSearching() { - /// If we enter this state and another reader was discovered while the - /// "Do you want to connect to" modal was being displayed and if that reader - /// is known and the merchant tapped keep searching on the first - /// (unknown) reader, auto-connect to that known reader - if let foundKnownReader = self.getFoundKnownReader() { - self.candidateReader = foundKnownReader - self.state = .connectToReader - return - } - - /// If we already have found readers - /// display the list view if so enabled, or... - /// - if showSeveralFoundReaders { - self.state = .foundSeveralReaders - return - } - /// Display the single view and ask the merchant if they'd /// like to connect to it /// - if foundReaders.isNotEmpty { - self.candidateReader = foundReaders.first - self.state = .foundReader + if candidateReader != nil { + self.state = .connectToReader return } @@ -434,50 +269,6 @@ private extension BuiltInCardReaderConnectionController { })) } - /// A (unknown) reader has been found - /// Opens a confirmation modal for the user to accept the candidate reader (or keep searching) - /// - func onFoundReader() { - guard let candidateReader = candidateReader else { - return - } - - alertsPresenter.present( - viewModel: CardPresentModalFoundReader( - name: candidateReader.id, - connect: { - self.state = .connectToReader - }, - continueSearch: { - self.skippedReaderIDs.append(candidateReader.id) - self.candidateReader = nil - self.pruneSkippedReaders() - self.state = .searching - }, - cancel: { [weak self] in - self?.state = .cancel - })) - } - - /// Several readers have been found - /// Opens a continually updating list modal for the user to pick one (or cancel the search) - /// - func onFoundSeveralReaders() { - alertsPresenter.foundSeveralReaders( - readerIDs: getFoundReaderIDs(), - connect: { [weak self] readerID in - guard let self = self else { - return - } - self.candidateReader = self.getFoundReaderByID(readerID: readerID) - self.state = .connectToReader - }, - cancelSearch: { [weak self] in - self?.state = .cancel - } - ) - } - /// A mandatory update is being installed /// func onUpdateProgress(progress: Float) { @@ -563,7 +354,6 @@ private extension BuiltInCardReaderConnectionController { switch result { case .success(let reader): - self.knownCardReaderProvider.rememberCardReader(cardReaderID: reader.id) ServiceLocator.analytics.track( event: WooAnalyticsEvent.InPersonPayments .cardReaderConnectionSuccess(forGatewayID: self.gatewayID, @@ -575,10 +365,10 @@ private extension BuiltInCardReaderConnectionController { // actually see a success message showing the installation was complete if case .updating(progress: 1) = self.state { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { - self.returnSuccess(result: .connected) + self.returnSuccess(result: .connected(reader)) } } else { - self.returnSuccess(result: .connected) + self.returnSuccess(result: .connected(reader)) } case .failure(let error): ServiceLocator.analytics.track( @@ -598,11 +388,11 @@ private extension BuiltInCardReaderConnectionController { /// An error occurred while connecting /// private func onConnectingFailed(error: Error) { - /// Clear our copy of found readers to avoid connecting to a reader that isn't + /// Clear our candidateReader to avoid connecting to a reader that isn't /// there while we wait for `onReaderDiscovered` to receive an update. /// See also https://github.com/stripe/stripe-terminal-ios/issues/104#issuecomment-916285167 /// - self.foundReaders = [] + self.candidateReader = nil if case CardReaderServiceError.softwareUpdate(underlyingError: let underlyingError, batteryLevel: _) = error, underlyingError.isSoftwareUpdateError { @@ -738,7 +528,7 @@ private extension BuiltInCardReaderConnectionController { /// Calls the completion with a success result /// - private func returnSuccess(result: ConnectionResult) { + private func returnSuccess(result: CardReaderConnectionResult) { onCompletion?(.success(result)) alertsPresenter.dismiss() state = .idle diff --git a/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardPresentPaymentPreflightController.swift b/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardPresentPaymentPreflightController.swift index 3d1835d0697..75140038f50 100644 --- a/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardPresentPaymentPreflightController.swift +++ b/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardPresentPaymentPreflightController.swift @@ -45,7 +45,7 @@ final class CardPresentPaymentPreflightController { /// Controller to connect a card reader. /// - private var builtInConnectionController: CardReaderConnectionController + private var builtInConnectionController: BuiltInCardReaderConnectionController private(set) var readerConnection = CurrentValueSubject(nil) @@ -66,19 +66,15 @@ final class CardPresentPaymentPreflightController { let analyticsTracker = CardReaderConnectionAnalyticsTracker(configuration: configuration, stores: stores, analytics: analytics) - // TODO: Replace this with a refactored (New)LegacyCardReaderConnectionController self.connectionController = CardReaderConnectionController( forSiteID: siteID, - discoveryMethod: .bluetoothScan, knownReaderProvider: CardReaderSettingsKnownReaderStorage(), alertsPresenter: alertsPresenter, configuration: configuration, analyticsTracker: analyticsTracker) - self.builtInConnectionController = CardReaderConnectionController( + self.builtInConnectionController = BuiltInCardReaderConnectionController( forSiteID: siteID, - discoveryMethod: .localMobile, - knownReaderProvider: CardReaderSettingsKnownReaderStorage(), alertsPresenter: alertsPresenter, configuration: configuration, analyticsTracker: analyticsTracker) @@ -122,23 +118,23 @@ final class CardPresentPaymentPreflightController { } private func localMobileReaderSupported() -> Bool { - #if !targetEnvironment(simulator) + #if targetEnvironment(simulator) + return true + #else if #available(iOS 15.4, *) { return PaymentCardReader.isSupported } else { return false } #endif - return true } - private func handleConnectionResult(_ result: Result) { + private func handleConnectionResult(_ result: Result) { let connectionResult = result.map { connection in switch connection { - case .connected: - // TODO: pass the reader from the (New)CardReaderConnectionController - guard let connectedReader = self.connectedReader else { return CardReaderConnectionResult.canceled } - return CardReaderConnectionResult.connected(connectedReader) + case .connected(let reader): + self.connectedReader = reader + return CardReaderConnectionResult.connected(reader) case .canceled: return CardReaderConnectionResult.canceled } diff --git a/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift b/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift index c94674982df..5e70ee4baa5 100644 --- a/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift +++ b/WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift @@ -69,12 +69,6 @@ final class CardReaderConnectionController { case discoveryFailed(Error) } - /// The final state of the card reader connection to return without errors. - enum ConnectionResult { - case connected - case canceled - } - private let storageManager: StorageManagerType private let stores: StoresManager @@ -120,7 +114,7 @@ final class CardReaderConnectionController { private var subscriptions = Set() - private var onCompletion: ((Result) -> Void)? + private var onCompletion: ((Result) -> Void)? private(set) lazy var dataSource: CardReaderSettingsDataSource = { return CardReaderSettingsDataSource(siteID: siteID, storageManager: storageManager) @@ -134,11 +128,8 @@ final class CardReaderConnectionController { } } - private let discoveryMethod: CardReaderDiscoveryMethod - init( forSiteID: Int64, - discoveryMethod: CardReaderDiscoveryMethod, storageManager: StorageManagerType = ServiceLocator.storageManager, stores: StoresManager = ServiceLocator.stores, knownReaderProvider: CardReaderSettingsKnownReaderProvider, @@ -147,7 +138,6 @@ final class CardReaderConnectionController { analyticsTracker: CardReaderConnectionAnalyticsTracker ) { siteID = forSiteID - self.discoveryMethod = discoveryMethod self.storageManager = storageManager self.stores = stores state = .idle @@ -167,7 +157,7 @@ final class CardReaderConnectionController { subscriptions.removeAll() } - func searchAndConnect(onCompletion: @escaping (Result) -> Void) { + func searchAndConnect(onCompletion: @escaping (Result) -> Void) { self.onCompletion = onCompletion self.state = .initializing } @@ -322,7 +312,7 @@ private extension CardReaderConnectionController { let action = CardPresentPaymentAction.startCardReaderDiscovery( siteID: siteID, - discoveryMethod: discoveryMethod, + discoveryMethod: .bluetoothScan, onReaderDiscovered: { [weak self] cardReaders in guard let self = self else { return @@ -575,10 +565,10 @@ private extension CardReaderConnectionController { // actually see a success message showing the installation was complete if case .updating(progress: 1) = self.state { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { - self.returnSuccess(result: .connected) + self.returnSuccess(result: .connected(reader)) } } else { - self.returnSuccess(result: .connected) + self.returnSuccess(result: .connected(reader)) } case .failure(let error): ServiceLocator.analytics.track( @@ -738,7 +728,7 @@ private extension CardReaderConnectionController { /// Calls the completion with a success result /// - private func returnSuccess(result: ConnectionResult) { + private func returnSuccess(result: CardReaderConnectionResult) { onCompletion?(.success(result)) alertsPresenter.dismiss() state = .idle diff --git a/WooCommerce/Classes/ViewRelated/CardPresentPayments/LegacyCardReaderConnectionController.swift b/WooCommerce/Classes/ViewRelated/CardPresentPayments/LegacyCardReaderConnectionController.swift index aeb96e690ea..43582191fa9 100644 --- a/WooCommerce/Classes/ViewRelated/CardPresentPayments/LegacyCardReaderConnectionController.swift +++ b/WooCommerce/Classes/ViewRelated/CardPresentPayments/LegacyCardReaderConnectionController.swift @@ -321,13 +321,9 @@ private extension LegacyCardReaderConnectionController { self.state = .searching var didAutoAdvance = false - // TODO: make this a choice for the user, when the switch is enabled - let tapOnIphoneEnabled = ServiceLocator.generalAppSettings.settings.isTapToPayOnIPhoneSwitchEnabled - let discoveryMethod: CardReaderDiscoveryMethod = tapOnIphoneEnabled ? .localMobile : .bluetoothScan - let action = CardPresentPaymentAction.startCardReaderDiscovery( siteID: siteID, - discoveryMethod: discoveryMethod, + discoveryMethod: .bluetoothScan, onReaderDiscovered: { [weak self] cardReaders in guard let self = self else { return