Skip to content

Commit 34d3500

Browse files
authored
Merge pull request #8196 from woocommerce/issue/8080-ask-user-for-discovery-method
[Mobile Payments] Ask user to choose a reader type to connect to
2 parents 50202cc + 7ce55ce commit 34d3500

File tree

7 files changed

+176
-32
lines changed

7 files changed

+176
-32
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import UIKit
2+
import Yosemite
3+
4+
final class CardPresentModalSelectSearchType: CardPresentPaymentsModalViewModel {
5+
var textMode: PaymentsModalTextMode
6+
7+
var actionsMode: PaymentsModalActionsMode
8+
9+
var topTitle: String = Localization.title
10+
11+
var topSubtitle: String? = nil
12+
13+
var image: UIImage = .paymentsLoading
14+
15+
var primaryButtonTitle: String?
16+
17+
var secondaryButtonTitle: String?
18+
19+
var auxiliaryButtonTitle: String? = nil
20+
21+
var bottomTitle: String? = Localization.description
22+
23+
var bottomSubtitle: String? = nil
24+
25+
var accessibilityLabel: String? = nil
26+
27+
private var tapOnIphoneAction: (() -> Void)
28+
29+
private var bluetoothProximityAction: (() -> Void)
30+
31+
private var cancelAction: (() -> Void)
32+
33+
func didTapPrimaryButton(in viewController: UIViewController?) {
34+
tapOnIphoneAction()
35+
}
36+
37+
func didTapSecondaryButton(in viewController: UIViewController?) {
38+
bluetoothProximityAction()
39+
}
40+
41+
func didTapAuxiliaryButton(in viewController: UIViewController?) {
42+
cancelAction()
43+
}
44+
45+
init(tapOnIPhoneAction: @escaping () -> Void,
46+
bluetoothAction: @escaping () -> Void,
47+
cancelAction: @escaping () -> Void) {
48+
textMode = .fullInfo
49+
actionsMode = .twoActionAndAuxiliary
50+
primaryButtonTitle = CardReaderDiscoveryMethod.localMobile.name
51+
self.tapOnIphoneAction = tapOnIPhoneAction
52+
secondaryButtonTitle = CardReaderDiscoveryMethod.bluetoothProximity.name
53+
self.bluetoothProximityAction = bluetoothAction
54+
auxiliaryButtonTitle = Localization.cancel
55+
self.cancelAction = cancelAction
56+
}
57+
}
58+
59+
private extension CardPresentModalSelectSearchType {
60+
enum Localization {
61+
static let title = NSLocalizedString(
62+
"Select reader type",
63+
comment: "The title for the alert shown when connecting a card reader, asking the user to choose a " +
64+
"reader type. Only shown when supported on their device.")
65+
66+
static let description = NSLocalizedString(
67+
"Your iPhone can be used as a card reader, or you can connect to an external reader via bluetooth.",
68+
comment: "The description on the alert shown when connecting a card reader, asking the user to choose a " +
69+
"reader type. Only shown when supported on their device.")
70+
71+
static let cancel = NSLocalizedString(
72+
"Cancel",
73+
comment: "Cancel button title")
74+
}
75+
}
76+
77+
private extension CardReaderDiscoveryMethod {
78+
var name: String {
79+
switch self {
80+
case .bluetoothProximity:
81+
return NSLocalizedString(
82+
"Bluetooth Reader",
83+
comment: "The button title on the reader type alert, for the user to choose a bluetooth reader.")
84+
case .localMobile:
85+
return NSLocalizedString(
86+
"Tap to Pay on iPhone",
87+
comment: "The button title on the reader type alert, for the user to choose the built-in reader.")
88+
}
89+
}
90+
}

WooCommerce/Classes/ViewRelated/CardPresentPayments/CardPresentPaymentPreflightController.swift

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import Foundation
22
import Yosemite
33
import Combine
4+
#if !targetEnvironment(simulator)
5+
import ProximityReader
6+
#endif
47

58
enum CardReaderConnectionResult {
69
case connected(CardReader)
@@ -40,6 +43,10 @@ final class CardPresentPaymentPreflightController {
4043
///
4144
private var connectionController: CardReaderConnectionController
4245

46+
/// Controller to connect a card reader.
47+
///
48+
private var builtInConnectionController: CardReaderConnectionController
49+
4350

4451
private(set) var readerConnection = CurrentValueSubject<CardReaderConnectionResult?, Never>(nil)
4552

@@ -62,6 +69,15 @@ final class CardPresentPaymentPreflightController {
6269
// TODO: Replace this with a refactored (New)LegacyCardReaderConnectionController
6370
self.connectionController = CardReaderConnectionController(
6471
forSiteID: siteID,
72+
discoveryMethod: .bluetoothProximity,
73+
knownReaderProvider: CardReaderSettingsKnownReaderStorage(),
74+
alertsPresenter: alertsPresenter,
75+
configuration: configuration,
76+
analyticsTracker: analyticsTracker)
77+
78+
self.builtInConnectionController = CardReaderConnectionController(
79+
forSiteID: siteID,
80+
discoveryMethod: .localMobile,
6581
knownReaderProvider: CardReaderSettingsKnownReaderStorage(),
6682
alertsPresenter: alertsPresenter,
6783
configuration: configuration,
@@ -78,31 +94,63 @@ final class CardPresentPaymentPreflightController {
7894

7995
// TODO: Run onboarding if needed
8096

81-
// TODO: Ask for a Reader type if supported by device
82-
83-
// Attempt to find a reader and connect
84-
connectionController.searchAndConnect { result in
85-
let connectionResult = result.map { connection in
86-
switch connection {
87-
case .connected:
88-
// TODO: pass the reader from the (New)CardReaderConnectionController
89-
guard let connectedReader = self.connectedReader else { return CardReaderConnectionResult.canceled }
90-
return CardReaderConnectionResult.connected(connectedReader)
91-
case .canceled:
92-
return CardReaderConnectionResult.canceled
93-
}
94-
}
97+
// Ask for a Reader type if supported by device/in country
98+
guard localMobileReaderSupported(),
99+
configuration.supportedReaders.contains(.appleBuiltIn)
100+
else {
101+
// Attempt to find a bluetooth reader and connect
102+
connectionController.searchAndConnect(onCompletion: handleConnectionResult)
103+
return
104+
}
95105

96-
switch connectionResult {
97-
case .success(let unwrapped):
98-
self.readerConnection.send(unwrapped)
99-
default:
100-
break
101-
}
106+
alertsPresenter.present(viewModel: CardPresentModalSelectSearchType(
107+
tapOnIPhoneAction: { [weak self] in
108+
guard let self = self else { return }
109+
self.builtInConnectionController.searchAndConnect(
110+
onCompletion: self.handleConnectionResult)
111+
},
112+
bluetoothAction: { [weak self] in
113+
guard let self = self else { return }
114+
self.connectionController.searchAndConnect(
115+
onCompletion: self.handleConnectionResult)
116+
},
117+
cancelAction: { [weak self] in
118+
guard let self = self else { return }
119+
self.alertsPresenter.dismiss()
120+
self.handleConnectionResult(.success(.canceled))
121+
}))
122+
}
123+
124+
private func localMobileReaderSupported() -> Bool {
125+
#if !targetEnvironment(simulator)
126+
if #available(iOS 15.4, *) {
127+
return PaymentCardReader.isSupported
128+
} else {
129+
return false
102130
}
131+
#endif
132+
return true
103133
}
104134

135+
private func handleConnectionResult(_ result: Result<CardReaderConnectionController.ConnectionResult, Error>) {
136+
let connectionResult = result.map { connection in
137+
switch connection {
138+
case .connected:
139+
// TODO: pass the reader from the (New)CardReaderConnectionController
140+
guard let connectedReader = self.connectedReader else { return CardReaderConnectionResult.canceled }
141+
return CardReaderConnectionResult.connected(connectedReader)
142+
case .canceled:
143+
return CardReaderConnectionResult.canceled
144+
}
145+
}
105146

147+
switch connectionResult {
148+
case .success(let unwrapped):
149+
self.readerConnection.send(unwrapped)
150+
default:
151+
break
152+
}
153+
}
106154

107155
/// Configure the CardPresentPaymentStore to use the appropriate backend
108156
///

WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,11 @@ final class CardReaderConnectionController {
134134
}
135135
}
136136

137+
private let discoveryMethod: CardReaderDiscoveryMethod
138+
137139
init(
138140
forSiteID: Int64,
141+
discoveryMethod: CardReaderDiscoveryMethod,
139142
storageManager: StorageManagerType = ServiceLocator.storageManager,
140143
stores: StoresManager = ServiceLocator.stores,
141144
knownReaderProvider: CardReaderSettingsKnownReaderProvider,
@@ -144,6 +147,7 @@ final class CardReaderConnectionController {
144147
analyticsTracker: CardReaderConnectionAnalyticsTracker
145148
) {
146149
siteID = forSiteID
150+
self.discoveryMethod = discoveryMethod
147151
self.storageManager = storageManager
148152
self.stores = stores
149153
state = .idle
@@ -316,10 +320,6 @@ private extension CardReaderConnectionController {
316320
self.state = .searching
317321
var didAutoAdvance = false
318322

319-
// TODO: make this a choice for the user, when the switch is enabled
320-
let tapOnIphoneEnabled = ServiceLocator.generalAppSettings.settings.isTapToPayOnIPhoneSwitchEnabled
321-
let discoveryMethod: CardReaderDiscoveryMethod = tapOnIphoneEnabled ? .localMobile : .bluetoothProximity
322-
323323
let action = CardPresentPaymentAction.startCardReaderDiscovery(
324324
siteID: siteID,
325325
discoveryMethod: discoveryMethod,

WooCommerce/Classes/ViewRelated/Orders/Collect Payments/CollectOrderPaymentUseCase.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
9292
/// Coordinates emailing a receipt after payment success.
9393
private var receiptEmailCoordinator: CardPresentPaymentReceiptEmailCoordinator?
9494

95+
private var preflightController: CardPresentPaymentPreflightController?
96+
9597
private var cancellables: Set<AnyCancellable> = []
9698

9799
init(siteID: Int64,
@@ -137,11 +139,11 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
137139
return handleTotalAmountInvalidError(totalAmountInvalidError(), onCompleted: onCompleted)
138140
}
139141

140-
let preflightController = CardPresentPaymentPreflightController(siteID: siteID,
141-
paymentGatewayAccount: paymentGatewayAccount,
142-
configuration: configuration,
143-
alertsPresenter: alertsPresenter)
144-
preflightController.readerConnection.sink { [weak self] connectionResult in
142+
preflightController = CardPresentPaymentPreflightController(siteID: siteID,
143+
paymentGatewayAccount: paymentGatewayAccount,
144+
configuration: configuration,
145+
alertsPresenter: alertsPresenter)
146+
preflightController?.readerConnection.sink { [weak self] connectionResult in
145147
guard let self = self else { return }
146148
switch connectionResult {
147149
case .connected(let reader):
@@ -171,7 +173,7 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
171173
}
172174
.store(in: &cancellables)
173175

174-
preflightController.start()
176+
preflightController?.start()
175177
}
176178
}
177179

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@
492492
03AA16602719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AA165F2719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift */; };
493493
03AFDE02282C0B82003B67CD /* InPersonPaymentsCompletedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AFDE01282C0B82003B67CD /* InPersonPaymentsCompletedView.swift */; };
494494
03BB9EA5292E2D0C00251E9E /* CardReaderConnectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BB9EA4292E2D0C00251E9E /* CardReaderConnectionController.swift */; };
495+
03BB9EA7292E50B600251E9E /* CardPresentModalSelectSearchType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BB9EA6292E50B600251E9E /* CardPresentModalSelectSearchType.swift */; };
495496
03CF78D127C3DBC000523706 /* WCPayCardBrand+IconsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CF78D027C3DBC000523706 /* WCPayCardBrand+IconsTests.swift */; };
496497
03EF24FA28BF5D21006A033E /* InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF24F928BF5D21006A033E /* InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift */; };
497498
03EF24FC28BF996F006A033E /* InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF24FB28BF996F006A033E /* InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift */; };
@@ -2487,6 +2488,7 @@
24872488
03AA165F2719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptActionCoordinatorTests.swift; sourceTree = "<group>"; };
24882489
03AFDE01282C0B82003B67CD /* InPersonPaymentsCompletedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsCompletedView.swift; sourceTree = "<group>"; };
24892490
03BB9EA4292E2D0C00251E9E /* CardReaderConnectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardReaderConnectionController.swift; sourceTree = "<group>"; };
2491+
03BB9EA6292E50B600251E9E /* CardPresentModalSelectSearchType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentModalSelectSearchType.swift; sourceTree = "<group>"; };
24902492
03CF78D027C3DBC000523706 /* WCPayCardBrand+IconsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WCPayCardBrand+IconsTests.swift"; sourceTree = "<group>"; };
24912493
03EF24F928BF5D21006A033E /* InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsCashOnDeliveryToggleRowViewModel.swift; sourceTree = "<group>"; };
24922494
03EF24FB28BF996F006A033E /* InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift; sourceTree = "<group>"; };
@@ -8525,6 +8527,7 @@
85258527
318477E427A33C650058C7E9 /* CardPresentModalConnectingFailedChargeReader.swift */,
85268528
031B10E2274FE2AE007390BA /* CardPresentModalConnectionFailedUpdateAddress.swift */,
85278529
036CA6B8291E8D4B00E4DF4F /* CardPresentModalPreparingReader.swift */,
8530+
03BB9EA6292E50B600251E9E /* CardPresentModalSelectSearchType.swift */,
85288531
D8EE9697264D3CCB0033B2F9 /* ReceiptViewModel.swift */,
85298532
D8752EF6265E60F4008ACC80 /* PaymentCaptureCelebration.swift */,
85308533
03AA165D2719B7EF005CCB7B /* ReceiptActionCoordinator.swift */,
@@ -10153,6 +10156,7 @@
1015310156
DEC6C51A2747758D006832D3 /* JetpackInstallView.swift in Sources */,
1015410157
DE37517C28DC5FC6000163CB /* Authenticator.swift in Sources */,
1015510158
AED089F227C794BC0020AE10 /* View+CurrencySymbol.swift in Sources */,
10159+
03BB9EA7292E50B600251E9E /* CardPresentModalSelectSearchType.swift in Sources */,
1015610160
D85806292642BA5400A8AB6C /* PaymentCaptureOrchestrator.swift in Sources */,
1015710161
E1E636BB26FB467A00C9D0D7 /* Comparable+Woo.swift in Sources */,
1015810162
450C2CB024CF006A00D570DD /* ProductTagsDataSource.swift in Sources */,

Yosemite/Yosemite/Model/Payments/CardPresentPaymentsConfiguration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public struct CardPresentPaymentsConfiguration {
3838
paymentMethods: [.cardPresent],
3939
currencies: [.USD],
4040
paymentGateways: [WCPayAccount.gatewayID, StripeAccount.gatewayID],
41-
supportedReaders: [.chipper, .stripeM2],
41+
supportedReaders: [.chipper, .stripeM2, .appleBuiltIn],
4242
supportedPluginVersions: [
4343
.init(plugin: .wcPay, minimumVersion: "3.2.1"),
4444
.init(plugin: .stripe, minimumVersion: "6.2.0")

Yosemite/YosemiteTests/Mocks/CardPresentPayments/MockCardReaderService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class MockCardReaderService: CardReaderService {
3535
/// Boolean flag Indicates that clients have provided a CardReaderConfigProvider
3636
var didReceiveAConfigurationProvider = false
3737

38-
/// DiscoveryMethod recieved on starting a payment
38+
/// DiscoveryMethod received on starting a payment
3939
var spyStartDiscoveryMethod: CardReaderDiscoveryMethod? = nil
4040

4141
/// Boolean flag Indicates that clients have called the cancel payment method

0 commit comments

Comments
 (0)