Skip to content

Commit 669dc77

Browse files
authored
Merge pull request #8746 from woocommerce/issue/8353-ttp-connection-requirements-not-met
[Mobile Payments] Show error reason when TTPoI connection requirements aren't met
2 parents 9b98e3b + 2812cc3 commit 669dc77

16 files changed

+249
-33
lines changed

Hardware/Hardware/CardReader/UnderlyingError.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -411,25 +411,25 @@ extension UnderlyingError: LocalizedError {
411411

412412
// MARK: - Built-in reader errors
413413
case .passcodeNotEnabled:
414-
return NSLocalizedString("Your device needs a lock screen passcode set to use the built-in card reader",
414+
return NSLocalizedString("You need to set a lock screen passcode to use Tap to Pay on iPhone",
415415
comment: "Error message shown when the built-in reader cannot be used because " +
416416
"the device does not have a passcode set.")
417417
case .appleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn:
418-
return NSLocalizedString("Please sign in to iCloud on this device, so you can use the built-in card reader",
418+
return NSLocalizedString("Please sign in to iCloud on this device to use Tap to Pay on iPhone",
419419
comment: "Error message shown when the built-in reader cannot be used because " +
420420
"the device is not signed in to iCloud.")
421421
case .nfcDisabled:
422-
return NSLocalizedString("The app could not enable the card reader, because the NFC chip is disabled. " +
422+
return NSLocalizedString("The app could not enable Tap to Pay on iPhone, because the NFC chip is disabled. " +
423423
"Please contact support for more details.",
424424
comment: "Error message shown when the built-in reader cannot be used because " +
425425
"the device's NFC chipset has been disabled by a device management policy.")
426426
case .appleBuiltInReaderFailedToPrepare, .readerNotAccessibleInBackground:
427-
return NSLocalizedString("There was an issue preparing the built in reader for payment – please try again.",
427+
return NSLocalizedString("There was an issue preparing to use Tap to Pay on iPhone – please try again.",
428428
comment: "Error message shown when the built-in reader cannot be used because " +
429429
"there was some issue with the connection. Retryable.")
430430
case .appleBuiltInReaderTOSAcceptanceCanceled, .appleBuiltInReaderTOSNotYetAccepted:
431-
return NSLocalizedString("Please try again, and accept Apple's Terms of Service, so you can use the " +
432-
"built-in card reader",
431+
return NSLocalizedString("Please try again, and accept Apple's Terms of Service, so you can use Tap to " +
432+
"Pay on iPhone",
433433
comment: "Error message shown when the built-in reader cannot be used because " +
434434
"the merchant cancelled or did not complete the Terms of Service acceptance flow")
435435
case .appleBuiltInReaderTOSAcceptanceFailed:
@@ -439,7 +439,7 @@ extension UnderlyingError: LocalizedError {
439439
"the Terms of Service acceptance flow failed, possibly due to issues with " +
440440
"the Apple ID")
441441
case .appleBuiltInReaderMerchantBlocked, .appleBuiltInReaderInvalidMerchant, .appleBuiltInReaderDeviceBanned:
442-
return NSLocalizedString("Please contact support – there was an issue connecting to the built-in reader",
442+
return NSLocalizedString("Please contact support – there was an issue starting Tap to Pay on iPhone",
443443
comment: "Error message shown when the built-in reader cannot be used because " +
444444
"there is an issue with the merchant account or device")
445445
case .unsupportedMobileDeviceConfiguration:
@@ -449,17 +449,17 @@ extension UnderlyingError: LocalizedError {
449449
comment: "Error message shown when the built-in reader cannot be used because " +
450450
"the device does not meet minimum requirements.")
451451
case .commandNotAllowedDuringCall:
452-
return NSLocalizedString("The built-in reader cannot be used during a phone call. Please try again after " +
452+
return NSLocalizedString("Tap to Pay on iPhone cannot be used during a phone call. Please try again after " +
453453
"you finish your call.",
454454
comment: "Error message shown when the built-in reader cannot be used because " +
455455
"there is a call in progress")
456456
case .invalidAmount:
457-
return NSLocalizedString("The amount is not supported by the built in reader – please try a hardware " +
457+
return NSLocalizedString("The amount is not supported for Tap to Pay on iPhone – please try a hardware " +
458458
"reader or another payment method.",
459459
comment: "Error message shown when the built-in reader cannot be used because " +
460460
"the amount for payment is not supported by the built in reader.")
461461
case .invalidCurrency:
462-
return NSLocalizedString("The currency is not supported by the built in reader – please try a hardware " +
462+
return NSLocalizedString("The currency is not supported for Tap to Pay on iPhone – please try a hardware " +
463463
"reader or another payment method.",
464464
comment: "Error message shown when the built-in reader cannot be used because " +
465465
"the currency for payment is not supported by the built in reader.")
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import UIKit
2+
import Yosemite
3+
4+
/// Modal presented when an error occurs while connecting to a reader
5+
///
6+
final class CardPresentModalBuiltInConnectingFailed: 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 = .builtInReaderError
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+
var accessibilityLabel: String? {
30+
return topTitle
31+
}
32+
33+
init(error: Error,
34+
continueSearch: @escaping () -> Void,
35+
cancelSearch: @escaping () -> Void) {
36+
self.continueSearchAction = continueSearch
37+
self.cancelSearchAction = cancelSearch
38+
39+
switch error {
40+
case CardReaderServiceError.connection(let underlyingError):
41+
bottomTitle = underlyingError.localizedDescription
42+
default:
43+
break
44+
}
45+
}
46+
47+
func didTapPrimaryButton(in viewController: UIViewController?) {
48+
continueSearchAction()
49+
}
50+
51+
func didTapSecondaryButton(in viewController: UIViewController?) {
52+
cancelSearchAction()
53+
}
54+
55+
func didTapAuxiliaryButton(in viewController: UIViewController?) { }
56+
}
57+
58+
private extension CardPresentModalBuiltInConnectingFailed {
59+
enum Localization {
60+
static let title = NSLocalizedString(
61+
"Setup failed",
62+
comment: "Title of the alert presented when the user tries to start Tap to Pay on iPhone and it fails"
63+
)
64+
65+
static let tryAgain = NSLocalizedString(
66+
"Try again",
67+
comment: "Button to dismiss the alert presented when starting Tap to Pay on iPhone fails. This allows the search to continue."
68+
)
69+
70+
static let cancel = NSLocalizedString(
71+
"Cancel",
72+
comment: "Button to dismiss the alert presented when starting Tap to Pay on iPhone fails. This also cancels searching."
73+
)
74+
}
75+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import UIKit
2+
import Yosemite
3+
4+
/// Modal presented when an error occurs while connecting to a reader
5+
///
6+
final class CardPresentModalBuiltInConnectingFailedNonRetryable: CardPresentPaymentsModalViewModel {
7+
private let closeAction: () -> Void
8+
9+
let textMode: PaymentsModalTextMode = .reducedTopInfo
10+
let actionsMode: PaymentsModalActionsMode = .oneAction
11+
12+
let topTitle: String = Localization.title
13+
14+
var topSubtitle: String? = nil
15+
16+
let image: UIImage = .builtInReaderError
17+
18+
let primaryButtonTitle: String? = Localization.close
19+
20+
let secondaryButtonTitle: String? = nil
21+
22+
let auxiliaryButtonTitle: String? = nil
23+
24+
var bottomTitle: String? = nil
25+
26+
let bottomSubtitle: String? = nil
27+
28+
var accessibilityLabel: String? {
29+
return topTitle
30+
}
31+
32+
init(error: Error,
33+
close: @escaping () -> Void) {
34+
self.closeAction = close
35+
36+
switch error {
37+
case CardReaderServiceError.connection(let underlyingError):
38+
bottomTitle = underlyingError.localizedDescription
39+
default:
40+
break
41+
}
42+
}
43+
44+
func didTapPrimaryButton(in viewController: UIViewController?) {
45+
closeAction()
46+
}
47+
48+
func didTapSecondaryButton(in viewController: UIViewController?) { }
49+
50+
func didTapAuxiliaryButton(in viewController: UIViewController?) { }
51+
}
52+
53+
private extension CardPresentModalBuiltInConnectingFailedNonRetryable {
54+
enum Localization {
55+
static let title = NSLocalizedString(
56+
"Setup failed",
57+
comment: "Title of the alert presented when the user tries to start Tap to Pay on iPhone and it fails"
58+
)
59+
60+
static let close = NSLocalizedString(
61+
"Close",
62+
comment: "Button to dismiss the alert presented when starting Tap to Pay on iPhone fails. This also cancels searching."
63+
)
64+
}
65+
}

WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalConnectingFailed.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class CardPresentModalConnectingFailed: CardPresentPaymentsModalViewModel
1414

1515
var topSubtitle: String? = nil
1616

17-
let image: UIImage
17+
let image: UIImage = .paymentErrorImage
1818

1919
let primaryButtonTitle: String? = Localization.tryAgain
2020

@@ -30,12 +30,18 @@ final class CardPresentModalConnectingFailed: CardPresentPaymentsModalViewModel
3030
return topTitle
3131
}
3232

33-
init(image: UIImage = .paymentErrorImage,
33+
init(error: Error,
3434
continueSearch: @escaping () -> Void,
3535
cancelSearch: @escaping () -> Void) {
36-
self.image = image
3736
self.continueSearchAction = continueSearch
3837
self.cancelSearchAction = cancelSearch
38+
39+
switch error {
40+
case CardReaderServiceError.connection(let underlyingError):
41+
bottomTitle = underlyingError.localizedDescription
42+
default:
43+
break
44+
}
3945
}
4046

4147
func didTapPrimaryButton(in viewController: UIViewController?) {

WooCommerce/Classes/ViewRelated/CardPresentPayments/BuiltInCardReaderConnectionController.swift

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,9 @@ private extension BuiltInCardReaderConnectionController {
452452

453453
guard case CardReaderServiceError.connection(let underlyingError) = error else {
454454
return alertsPresenter.present(
455-
viewModel: alertsProvider.connectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch))
455+
viewModel: alertsProvider.connectingFailed(error: error,
456+
continueSearch: continueSearch,
457+
cancelSearch: cancelSearch))
456458
}
457459

458460
switch underlyingError {
@@ -469,10 +471,17 @@ private extension BuiltInCardReaderConnectionController {
469471
retrySearch: retrySearch,
470472
cancelSearch: cancelSearch))
471473
default:
472-
alertsPresenter.present(
473-
viewModel: alertsProvider.connectingFailed(
474-
continueSearch: continueSearch,
475-
cancelSearch: cancelSearch))
474+
if underlyingError.canBeResolvedByRetrying {
475+
alertsPresenter.present(
476+
viewModel: alertsProvider.connectingFailed(
477+
error: error,
478+
continueSearch: continueSearch,
479+
cancelSearch: cancelSearch))
480+
} else {
481+
alertsPresenter.present(
482+
viewModel: alertsProvider.connectingFailedNonRetryable(error: error,
483+
close: cancelSearch))
484+
}
476485
}
477486
}
478487

@@ -563,3 +572,19 @@ private extension BuiltInCardReaderConnectionController {
563572
)
564573
}
565574
}
575+
576+
private extension CardReaderServiceUnderlyingError {
577+
var canBeResolvedByRetrying: Bool {
578+
switch self {
579+
case .appleBuiltInReaderTOSAcceptanceRequiresiCloudSignIn,
580+
.passcodeNotEnabled,
581+
.appleBuiltInReaderDeviceBanned,
582+
.appleBuiltInReaderMerchantBlocked,
583+
.nfcDisabled,
584+
.unsupportedMobileDeviceConfiguration:
585+
return false
586+
default:
587+
return true
588+
}
589+
}
590+
}

WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,9 @@ private extension CardReaderConnectionController {
644644

645645
guard case CardReaderServiceError.connection(let underlyingError) = error else {
646646
return alertsPresenter.present(
647-
viewModel: alertsProvider.connectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch))
647+
viewModel: alertsProvider.connectingFailed(error: error,
648+
continueSearch: continueSearch,
649+
cancelSearch: cancelSearch))
648650
}
649651

650652
switch underlyingError {
@@ -668,6 +670,7 @@ private extension CardReaderConnectionController {
668670
default:
669671
alertsPresenter.present(
670672
viewModel: alertsProvider.connectingFailed(
673+
error: error,
671674
continueSearch: continueSearch,
672675
cancelSearch: cancelSearch))
673676
}

WooCommerce/Classes/ViewRelated/CardPresentPayments/LegacyCardReaderConnectionController.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ private extension LegacyCardReaderConnectionController {
679679
}
680680

681681
guard case CardReaderServiceError.connection(let underlyingError) = error else {
682-
return alerts.connectingFailed(from: from, continueSearch: continueSearch, cancelSearch: cancelSearch)
682+
return alerts.connectingFailed(from: from, error: error, continueSearch: continueSearch, cancelSearch: cancelSearch)
683683
}
684684

685685
switch underlyingError {
@@ -695,7 +695,7 @@ private extension LegacyCardReaderConnectionController {
695695
case .bluetoothConnectionFailedBatteryCriticallyLow:
696696
alerts.connectingFailedCriticallyLowBattery(from: from, retrySearch: retrySearch, cancelSearch: cancelSearch)
697697
default:
698-
alerts.connectingFailed(from: from, continueSearch: continueSearch, cancelSearch: cancelSearch)
698+
alerts.connectingFailed(from: from, error: error, continueSearch: continueSearch, cancelSearch: cancelSearch)
699699
}
700700
}
701701

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ struct BluetoothReaderConnectionAlertsProvider: BluetoothReaderConnnectionAlerts
1515
CardPresentModalConnectingToReader()
1616
}
1717

18-
func connectingFailed(continueSearch: @escaping () -> Void,
18+
func connectingFailed(error: Error,
19+
continueSearch: @escaping () -> Void,
1920
cancelSearch: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
20-
CardPresentModalConnectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch)
21+
CardPresentModalConnectingFailed(error: error, continueSearch: continueSearch, cancelSearch: cancelSearch)
22+
}
23+
24+
func connectingFailedNonRetryable(error: Error, close: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
25+
CardPresentModalNonRetryableError(amount: "", error: error, onDismiss: close)
2126
}
2227

2328
func connectingFailedIncompleteAddress(openWCSettings: ((UIViewController) -> Void)?,

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ struct BuiltInReaderConnectionAlertsProvider: CardReaderConnectionAlertsProvidin
1515
CardPresentModalBuiltInConnectingToReader()
1616
}
1717

18-
func connectingFailed(continueSearch: @escaping () -> Void,
18+
func connectingFailed(error: Error,
19+
continueSearch: @escaping () -> Void,
1920
cancelSearch: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
20-
CardPresentModalConnectingFailed(image: .builtInReaderError, continueSearch: continueSearch, cancelSearch: cancelSearch)
21+
CardPresentModalBuiltInConnectingFailed(error: error,
22+
continueSearch: continueSearch,
23+
cancelSearch: cancelSearch)
2124
}
2225

26+
func connectingFailedNonRetryable(error: Error, close: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
27+
CardPresentModalBuiltInConnectingFailedNonRetryable(error: error,
28+
close: close)
29+
}
30+
31+
2332
func connectingFailedIncompleteAddress(openWCSettings: ((UIViewController) -> Void)?,
2433
retrySearch: @escaping () -> Void,
2534
cancelSearch: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,16 @@ protocol CardReaderConnectionAlertsProviding {
2222
/// Defines an alert indicating connecting failed. The user may continue the search
2323
/// or cancel
2424
///
25-
func connectingFailed(continueSearch: @escaping () -> Void,
25+
func connectingFailed(error: Error,
26+
continueSearch: @escaping () -> Void,
2627
cancelSearch: @escaping () -> Void) -> CardPresentPaymentsModalViewModel
2728

29+
/// Defines an alert indicating connecting failed, in a way which must be resolved outside
30+
/// the connection flow. The user can close the alert.
31+
///
32+
func connectingFailedNonRetryable(error: Error,
33+
close: @escaping () -> Void) -> CardPresentPaymentsModalViewModel
34+
2835
/// Defines an alert indicating connecting failed because their address needs updating.
2936
/// The user may try again or cancel
3037
///

0 commit comments

Comments
 (0)