Skip to content

Commit a44cbf3

Browse files
committed
Display connection failures
1 parent a22ae35 commit a44cbf3

File tree

7 files changed

+198
-7
lines changed

7 files changed

+198
-7
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import UIKit
2+
import Yosemite
3+
4+
/// Modal presented when an error occurs while connecting to a reader
5+
///
6+
final class CardPresentModalConnectingFailed: CardPresentPaymentsModalViewModel {
7+
private let continueSearchAction: () -> Void
8+
private let cancelSearchAction: () -> Void
9+
10+
let textMode: PaymentsModalTextMode = .reducedTopInfo
11+
let actionsMode: PaymentsModalActionsMode = .twoAction
12+
13+
let topTitle: String = Localization.title
14+
15+
var topSubtitle: String? = nil
16+
17+
let image: UIImage = .paymentErrorImage
18+
19+
let primaryButtonTitle: String? = Localization.tryAgain
20+
21+
let secondaryButtonTitle: String? = Localization.cancel
22+
23+
let auxiliaryButtonTitle: String? = nil
24+
25+
var bottomTitle: String? = nil
26+
27+
let bottomSubtitle: String? = nil
28+
29+
init(continueSearch: @escaping () -> Void, cancelSearch: @escaping () -> Void) {
30+
self.continueSearchAction = continueSearch
31+
self.cancelSearchAction = cancelSearch
32+
}
33+
34+
func didTapPrimaryButton(in viewController: UIViewController?) {
35+
continueSearchAction()
36+
}
37+
38+
func didTapSecondaryButton(in viewController: UIViewController?) {
39+
cancelSearchAction()
40+
}
41+
42+
func didTapAuxiliaryButton(in viewController: UIViewController?) { }
43+
}
44+
45+
private extension CardPresentModalConnectingFailed {
46+
enum Localization {
47+
static let title = NSLocalizedString(
48+
"We couldn't connect your reader",
49+
comment: "Title of the alert presented when the user tries to connect to a specific card reader and it fails"
50+
)
51+
52+
static let tryAgain = NSLocalizedString(
53+
"Try again",
54+
comment: "Button to dismiss the alert presented when connecting to a specific reader fails. This allows the search to continue."
55+
)
56+
57+
static let cancel = NSLocalizedString(
58+
"Cancel",
59+
comment: "Button to dismiss the alert presented when connecting to a specific reader fails. This also cancels searching."
60+
)
61+
}
62+
}

WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ final class CardReaderConnectionController {
3333
///
3434
case connectToReader
3535

36+
/// A failure occurred while connecting. The search may continue or be canceled. At this time we
37+
/// do not present the detailed error from the service.
38+
///
39+
case connectingFailed
40+
3641
/// User cancelled search/connecting to a card reader. The completion passed to `searchAndConnect`
3742
/// will be called with a `success` `Bool` `False` result. The view controller passed to `searchAndConnect` will be
3843
/// dereferenced and the state set to `idle`
@@ -43,7 +48,7 @@ final class CardReaderConnectionController {
4348
/// will be called with a `failure` result. The view controller passed to `searchAndConnect` will be
4449
/// dereferenced and the state set to `idle`
4550
///
46-
case failed(Error)
51+
case discoveryFailed(Error)
4752
}
4853

4954
private var state: ControllerState {
@@ -122,8 +127,10 @@ private extension CardReaderConnectionController {
122127
onCancel()
123128
case .connectToReader:
124129
onConnectToReader()
125-
case .failed(let error):
126-
onFailed(error: error)
130+
case .connectingFailed:
131+
onConnectingFailed()
132+
case .discoveryFailed(let error):
133+
onDiscoveryFailed(error: error)
127134
}
128135
}
129136

@@ -222,7 +229,7 @@ private extension CardReaderConnectionController {
222229
}
223230

224231
ServiceLocator.analytics.track(.cardReaderDiscoveryFailed, withError: error)
225-
self.state = .failed(error)
232+
self.state = .discoveryFailed(error)
226233
})
227234

228235
ServiceLocator.stores.dispatch(action)
@@ -312,18 +319,40 @@ private extension CardReaderConnectionController {
312319
self.returnSuccess(connected: true)
313320
case .failure(let error):
314321
ServiceLocator.analytics.track(.cardReaderConnectionFailed, withError: error)
315-
self.returnFailure(error: error)
322+
self.state = .connectingFailed
316323
}
317324
}
318325
ServiceLocator.stores.dispatch(action)
319326

320327
alerts.connectingToReader(from: from)
321328
}
322329

323-
/// An error has occurred
330+
/// An error occurred while connecting
331+
///
332+
private func onConnectingFailed() {
333+
guard let from = fromController else {
334+
return
335+
}
336+
337+
/// Let's start over again - we don't want to try and connect to something obviously borked
338+
/// right off the bat
339+
///
340+
self.foundReaders = []
341+
342+
alerts.connectingFailed(
343+
from: from,
344+
continueSearch: {
345+
self.state = .searching
346+
}, cancelSearch: {
347+
self.state = .cancel
348+
}
349+
)
350+
}
351+
352+
/// An error occurred during discovery
324353
/// Presents the error in a modal
325354
///
326-
private func onFailed(error: Error) {
355+
private func onDiscoveryFailed(error: Error) {
327356
guard let from = fromController else {
328357
return
329358
}

WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/CardReaderSettingsAlerts.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ final class CardReaderSettingsAlerts: CardReaderSettingsAlertsProvider {
2020
setViewModelAndPresent(from: from, viewModel: connectingToReader())
2121
}
2222

23+
func connectingFailed(from: UIViewController, continueSearch: @escaping () -> Void, cancelSearch: @escaping () -> Void) {
24+
setViewModelAndPresent(from: from, viewModel: connectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch))
25+
}
26+
2327
func foundReader(from: UIViewController,
2428
name: String,
2529
connect: @escaping () -> Void,
@@ -57,6 +61,10 @@ private extension CardReaderSettingsAlerts {
5761
CardPresentModalConnectingToReader()
5862
}
5963

64+
func connectingFailed(continueSearch: @escaping () -> Void, cancelSearch: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
65+
CardPresentModalConnectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch)
66+
}
67+
6068
func foundReader(name: String, connect: @escaping () -> Void, continueSearch: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
6169
CardPresentModalFoundReader(name: name, connect: connect, continueSearch: continueSearch)
6270
}

WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/CardReaderSettingsAlertsProvider.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ protocol CardReaderSettingsAlertsProvider {
2626
///
2727
func connectingToReader(from: UIViewController)
2828

29+
/// Defines an alert indicating connecting failed. The user may continue the search
30+
/// or cancel
31+
///
32+
func connectingFailed(from: UIViewController,
33+
continueSearch: @escaping () -> Void,
34+
cancelSearch: @escaping () -> Void)
35+
2936
/// Dismisses any alert being presented
3037
///
3138
func dismiss()

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,8 @@
500500
3190D61B26D6D82900EF364D /* CardPresentRetryableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3190D61A26D6D82900EF364D /* CardPresentRetryableError.swift */; };
501501
3190D61D26D6E97B00EF364D /* CardPresentModalRetryableErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3190D61C26D6E97B00EF364D /* CardPresentModalRetryableErrorTests.swift */; };
502502
3198A1E82694DC7200597213 /* MockKnownReadersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3198A1E72694DC7200597213 /* MockKnownReadersProvider.swift */; };
503+
31AD0B1126E9575F000B6391 /* CardPresentModalConnectingFailed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD0B1026E9575F000B6391 /* CardPresentModalConnectingFailed.swift */; };
504+
31AD0B1326E95998000B6391 /* CardPresentModalConnectingFailedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD0B1226E95998000B6391 /* CardPresentModalConnectingFailedTests.swift */; };
503505
31B0551E264B3C7A00134D87 /* CardPresentModalFoundReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B0551D264B3C7A00134D87 /* CardPresentModalFoundReader.swift */; };
504506
31B05523264B3C8900134D87 /* CardPresentModalConnectingToReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B05522264B3C8900134D87 /* CardPresentModalConnectingToReader.swift */; };
505507
31B19B67263B5E580099DAA6 /* CardReaderSettingsSearchingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B19B66263B5E580099DAA6 /* CardReaderSettingsSearchingViewModel.swift */; };
@@ -1904,6 +1906,8 @@
19041906
3190D61A26D6D82900EF364D /* CardPresentRetryableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentRetryableError.swift; sourceTree = "<group>"; };
19051907
3190D61C26D6E97B00EF364D /* CardPresentModalRetryableErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalRetryableErrorTests.swift; sourceTree = "<group>"; };
19061908
3198A1E72694DC7200597213 /* MockKnownReadersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKnownReadersProvider.swift; sourceTree = "<group>"; };
1909+
31AD0B1026E9575F000B6391 /* CardPresentModalConnectingFailed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalConnectingFailed.swift; sourceTree = "<group>"; };
1910+
31AD0B1226E95998000B6391 /* CardPresentModalConnectingFailedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalConnectingFailedTests.swift; sourceTree = "<group>"; };
19071911
31B0551D264B3C7A00134D87 /* CardPresentModalFoundReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalFoundReader.swift; sourceTree = "<group>"; };
19081912
31B05522264B3C8900134D87 /* CardPresentModalConnectingToReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalConnectingToReader.swift; sourceTree = "<group>"; };
19091913
31B19B66263B5E580099DAA6 /* CardReaderSettingsSearchingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderSettingsSearchingViewModel.swift; sourceTree = "<group>"; };
@@ -6113,6 +6117,7 @@
61136117
E17E3BFA266917E20009D977 /* CardPresentModalBluetoothRequiredTests.swift */,
61146118
E17E3BF8266917C10009D977 /* CardPresentModalScanningFailedTests.swift */,
61156119
3190D61C26D6E97B00EF364D /* CardPresentModalRetryableErrorTests.swift */,
6120+
31AD0B1226E95998000B6391 /* CardPresentModalConnectingFailedTests.swift */,
61166121
);
61176122
path = CardPresentPayments;
61186123
sourceTree = "<group>";
@@ -6217,6 +6222,7 @@
62176222
3190D61A26D6D82900EF364D /* CardPresentRetryableError.swift */,
62186223
E1F52DC52668E03B00349D75 /* CardPresentModalBluetoothRequired.swift */,
62196224
31B05522264B3C8900134D87 /* CardPresentModalConnectingToReader.swift */,
6225+
31AD0B1026E9575F000B6391 /* CardPresentModalConnectingFailed.swift */,
62206226
D8EE9697264D3CCB0033B2F9 /* ReceiptViewModel.swift */,
62216227
D8752EF6265E60F4008ACC80 /* PaymentCaptureCelebration.swift */,
62226228
);
@@ -7115,6 +7121,7 @@
71157121
77E53EB8250E6A4E003D385F /* ProductDownloadListViewController.swift in Sources */,
71167122
74460D4022289B7600D7316A /* Coordinator.swift in Sources */,
71177123
B57C743D20F5493300EEFC87 /* AccountHeaderView.swift in Sources */,
7124+
31AD0B1126E9575F000B6391 /* CardPresentModalConnectingFailed.swift in Sources */,
71187125
576EA39425264C9B00AFC0B3 /* RefundConfirmationViewModel.swift in Sources */,
71197126
CC4A4E962655273D00B75DCD /* ShippingLabelPaymentMethods.swift in Sources */,
71207127
45DB6D972632CF9300E83C1A /* ActivityIndicator.swift in Sources */,
@@ -7993,6 +8000,7 @@
79938000
DE126D0F26CA71E8007F901D /* ShippingLabelCustomsFormInputViewModelTests.swift in Sources */,
79948001
0277AE9B256CA8A200F45C4A /* AggregatedShippingLabelOrderItemsTests.swift in Sources */,
79958002
0225C42A24768CE900C5B4F0 /* FilterProductListViewModelProductListFilterTests.swift in Sources */,
8003+
31AD0B1326E95998000B6391 /* CardPresentModalConnectingFailedTests.swift in Sources */,
79968004
E16715CD2666543000326230 /* CardPresentModalSuccessWithoutEmailTests.swift in Sources */,
79978005
02691780232600A6002AFC20 /* ProductsTabProductViewModelTests.swift in Sources */,
79988006
024F1452250B65A40003030A /* AddProductCoordinatorTests.swift in Sources */,

WooCommerce/WooCommerceTests/Mocks/MockCardReaderSettingsAlerts.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ final class MockCardReaderSettingsAlerts: CardReaderSettingsAlertsProvider {
4141
// GNDN
4242
}
4343

44+
func connectingFailed(from: UIViewController, continueSearch: @escaping () -> Void, cancelSearch: @escaping () -> Void) {
45+
// GNDN
46+
}
47+
4448
func dismiss() {
4549
// GNDN
4650
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import XCTest
2+
@testable import WooCommerce
3+
4+
final class CardPresentModalConnectingFailedTests: XCTestCase {
5+
private var viewModel: CardPresentModalConnectingFailed!
6+
private var closures: Closures!
7+
8+
override func setUp() {
9+
super.setUp()
10+
closures = Closures()
11+
viewModel = CardPresentModalConnectingFailed(
12+
continueSearch: closures.continueSearch(),
13+
cancelSearch: closures.cancelSearch()
14+
)
15+
}
16+
17+
override func tearDown() {
18+
viewModel = nil
19+
closures = nil
20+
super.tearDown()
21+
}
22+
23+
func test_viewmodel_provides_expected_image() {
24+
XCTAssertEqual(viewModel.image, Expectations.image)
25+
}
26+
27+
func test_topTitle_is_not_nil() {
28+
XCTAssertNotNil(viewModel.topTitle)
29+
}
30+
31+
func test_topSubtitle_is_nil() {
32+
XCTAssertNil(viewModel.topSubtitle)
33+
}
34+
35+
func test_primary_button_title_is_not_nil() {
36+
XCTAssertNotNil(viewModel.primaryButtonTitle)
37+
}
38+
39+
func test_secondary_button_title_is_not_nil() {
40+
XCTAssertNotNil(viewModel.secondaryButtonTitle)
41+
}
42+
43+
func test_bottom_title_is_nil() {
44+
XCTAssertNil(viewModel.bottomTitle)
45+
}
46+
47+
func test_bottom_subTitle_is_nil() {
48+
XCTAssertNil(viewModel.bottomSubtitle)
49+
}
50+
}
51+
52+
private extension CardPresentModalConnectingFailedTests {
53+
enum Expectations {
54+
static let image = UIImage.paymentErrorImage
55+
}
56+
}
57+
58+
private final class Closures {
59+
var didTapContinueSearch = false
60+
var didTapCancelSearch = false
61+
62+
func continueSearch() -> () -> Void {
63+
return {[weak self] in
64+
self?.didTapContinueSearch = true
65+
}
66+
}
67+
68+
func cancelSearch() -> () -> Void {
69+
return {[weak self] in
70+
self?.didTapCancelSearch = true
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)