Skip to content

Commit 39ab7b5

Browse files
authored
[Woo POS][Settings] i2: Card reader management design updates (#16321)
2 parents b66223a + a335620 commit 39ab7b5

19 files changed

+374
-87
lines changed

Modules/Sources/Experiments/DefaultFeatureFlagService.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,6 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
100100
return buildConfig == .localDeveloper || buildConfig == .alpha
101101
case .pointOfSaleSurveys:
102102
return buildConfig == .localDeveloper || buildConfig == .alpha
103-
case .pointOfSaleSettingsCardReaderFlow:
104-
return buildConfig == .localDeveloper || buildConfig == .alpha
105103
case .pointOfSaleCatalogAPI:
106104
return false
107105
default:

Modules/Sources/Experiments/FeatureFlag.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,6 @@ public enum FeatureFlag: Int {
208208
///
209209
case pointOfSaleSurveys
210210

211-
/// Enables card reader connection flow within POS settings
212-
///
213-
case pointOfSaleSettingsCardReaderFlow
214-
215211
/// Enables using the catalog API endpoint for Point of Sale catalog full sync
216212
///
217213
case pointOfSaleCatalogAPI

Modules/Sources/PointOfSale/Card Present Payments/CardPresentPaymentCardReader.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ public struct CardPresentPaymentCardReader: Equatable {
77
/// This is an unformatted percentage as a float, e.g. 0.0-1.0
88
let batteryLevel: Float?
99

10-
public init(name: String, batteryLevel: Float?) {
10+
/// The reader's software version, if available.
11+
let softwareVersion: String?
12+
13+
public init(name: String, batteryLevel: Float?, softwareVersion: String? = "Unknown") {
1114
self.name = name
1215
self.batteryLevel = batteryLevel
16+
self.softwareVersion = softwareVersion
1317
}
1418
}

Modules/Sources/PointOfSale/Card Present Payments/CardPresentPaymentFacade.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Foundation
22
import enum Yosemite.PaymentChannel
33
import struct Yosemite.Order
4+
import enum Yosemite.CardReaderSoftwareUpdateState
45
import Combine
56

67
public protocol CardPresentPaymentFacade {
@@ -13,6 +14,10 @@ public protocol CardPresentPaymentFacade {
1314
/// This is a long lasting stream, and will not finish during the life of the façade.
1415
var readerConnectionStatusPublisher: AnyPublisher<CardPresentPaymentReaderConnectionStatus, Never> { get }
1516

17+
/// `cardReaderUpdateStatePublisher` provides the latest software update state for the connected card reader.
18+
/// This is a long lasting stream, and will not finish during the life of the façade.
19+
var cardReaderUpdateStatePublisher: AnyPublisher<CardReaderSoftwareUpdateState, Never> { get }
20+
1621
/// Attempts to a card reader of the specified type.
1722
/// If another type of reader is already connected, this will be disconnected automatically.
1823
/// - Parameters:
@@ -26,6 +31,11 @@ public protocol CardPresentPaymentFacade {
2631
/// Also cancels any in-progress payment, if possible.
2732
func disconnectReader() async
2833

34+
/// Starts a software update for the currently connected card reader, if an update is available.
35+
/// - Throws: `CardPresentPaymentError` for any failures.
36+
/// - Output: publishes intermediate events on the `paymentEventPublisher` as required.
37+
func updateCardReaderSoftware() async throws
38+
2939
/// Collects a card present payment for an order.
3040
/// If the appropriate type of reader is not already connected, this should attempt a connection before the payment.
3141
/// If another type of reader is already connected, this will be disconnected automatically.

Modules/Sources/PointOfSale/Card Present Payments/CardPresentPaymentPreviewService.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Foundation
22
import Combine
33
import enum Yosemite.PaymentChannel
44
import struct Yosemite.Order
5+
import enum Yosemite.CardReaderSoftwareUpdateState
56

67
#if DEBUG
78

@@ -14,6 +15,10 @@ final class CardPresentPaymentPreviewService: CardPresentPaymentFacade {
1415
$readerConnectionStatus.eraseToAnyPublisher()
1516
}
1617

18+
var cardReaderUpdateStatePublisher: AnyPublisher<CardReaderSoftwareUpdateState, Never> {
19+
Just(.none).eraseToAnyPublisher()
20+
}
21+
1722
init(connectionStatus: CardPresentPaymentReaderConnectionStatus = .disconnected) {
1823
self.readerConnectionStatus = connectionStatus
1924
}
@@ -35,6 +40,10 @@ final class CardPresentPaymentPreviewService: CardPresentPaymentFacade {
3540
func cancelPayment() {
3641
// no-op
3742
}
43+
44+
func updateCardReaderSoftware() async throws {
45+
// no-op
46+
}
3847
}
3948

4049
#endif

Modules/Sources/PointOfSale/Models/PointOfSaleAggregateModel.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import protocol Yosemite.PointOfSaleBarcodeScanServiceProtocol
1616
import enum Yosemite.PointOfSaleBarcodeScanError
1717
import protocol Yosemite.POSCatalogSyncCoordinatorProtocol
1818
import class Yosemite.POSCatalogSyncCoordinator
19+
import enum Yosemite.CardReaderSoftwareUpdateState
1920

2021
protocol PointOfSaleAggregateModelProtocol {
2122
var cart: Cart { get }
@@ -28,12 +29,20 @@ protocol PointOfSaleAggregateModelProtocol {
2829
private(set) var orderStage: PointOfSaleOrderStage = .building
2930

3031
private(set) var cardReaderConnectionStatus: CardPresentPaymentReaderConnectionStatus = .disconnected
32+
private(set) var cardReaderUpdateState: CardReaderSoftwareUpdateState = .none
3133
private(set) var paymentState: PointOfSalePaymentState
3234
var cardPresentPaymentAlertViewModel: PointOfSaleCardPresentPaymentAlertType?
3335
private(set) var cardPresentPaymentInlineMessage: PointOfSaleCardPresentPaymentMessageType?
3436
var cardPresentPaymentOnboardingViewContainer: CardPresentPaymentOnboardingViewContainer?
3537
private var onOnboardingCancellation: (() -> Void)?
3638

39+
var isCardReaderUpdateAvailable: Bool {
40+
if case .available = cardReaderUpdateState {
41+
return true
42+
}
43+
return false
44+
}
45+
3746
private(set) var cart: Cart = .init()
3847

3948
var orderState: PointOfSaleOrderState { orderController.orderState.externalState }
@@ -127,6 +136,7 @@ protocol PointOfSaleAggregateModelProtocol {
127136
self.isLocalCatalogEligible = isLocalCatalogEligible
128137

129138
publishCardReaderConnectionStatus()
139+
publishCardReaderUpdateState()
130140
publishPaymentMessages()
131141
setupReaderReconnectionObservation()
132142
setupPaymentSuccessObservation()
@@ -303,6 +313,14 @@ extension PointOfSaleAggregateModel {
303313
.store(in: &cancellables)
304314
}
305315

316+
private func publishCardReaderUpdateState() {
317+
cardPresentPaymentService.cardReaderUpdateStatePublisher
318+
.sink(receiveValue: { [weak self] updateState in
319+
self?.cardReaderUpdateState = updateState
320+
})
321+
.store(in: &cancellables)
322+
}
323+
306324
func connectCardReader() {
307325
analytics.track(.pointOfSaleCardReaderConnectionTapped)
308326
Task { @MainActor [weak self] in
@@ -317,6 +335,13 @@ extension PointOfSaleAggregateModel {
317335
}
318336
}
319337

338+
func updateCardReaderSoftware() {
339+
//TODO: analytics.track(.cardReaderUpdateTapped)
340+
Task { @MainActor [weak self] in
341+
try? await self?.cardPresentPaymentService.updateCardReaderSoftware()
342+
}
343+
}
344+
320345
/// Starts a payment immediately if a reader is connected.
321346
/// Otherwise, schedules a payment to start the next time a reader connects.
322347
/// Note that any scheduled payments are cancelled by `cancelReaderPreparation`

Modules/Sources/PointOfSale/Models/PointOfSaleSettingsController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ protocol POSSettingsControllerProtocol {
7575
final class POSSettingsPreviewController: POSSettingsControllerProtocol {
7676
var connectedCardReader: CardPresentPaymentCardReader? = CardPresentPaymentCardReader(
7777
name: "WisePad 3",
78-
batteryLevel: 0.75
78+
batteryLevel: 0.75,
79+
softwareVersion: "2.0.1.23"
7980
)
8081

8182
var storeViewModel: POSSettingsStoreViewModel = POSSettingsStoreViewModel(siteID: 123,

Modules/Sources/PointOfSale/Presentation/Card Present Payments/Connection Alerts/PointOfSaleCardPresentPaymentAlertType.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ enum PointOfSaleCardPresentPaymentAlertType: Hashable, Identifiable {
4444
return viewModel.cancelReaderUpdate
4545
case .optionalReaderUpdateInProgress(let viewModel):
4646
return viewModel.cancelReaderUpdate
47-
case .readerUpdateCompletion:
48-
// We only support in-line updates at the moment, and they automatically move on to connecting the reader.
49-
return nil
47+
case .readerUpdateCompletion(let viewModel):
48+
return viewModel.buttonViewModel.actionHandler
5049
case .updateFailed(let viewModel):
5150
return viewModel.cancelButtonViewModel.actionHandler
5251
case .updateFailedNonRetryable(let viewModel):

Modules/Sources/PointOfSale/Presentation/Card Present Payments/Connection Alerts/PointOfSaleCardPresentPaymentReaderUpdateCompletionAlertViewModel.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,28 @@ import SwiftUI
44
struct PointOfSaleCardPresentPaymentReaderUpdateCompletionAlertViewModel {
55
let title: String = Localization.title
66
let progressTitle: String = .init(format: Localization.percentCompleteFormat, 100.0)
7+
let buttonViewModel: CardPresentPaymentsModalButtonViewModel
8+
9+
init(doneAction: @escaping () -> Void) {
10+
self.buttonViewModel = CardPresentPaymentsModalButtonViewModel(
11+
title: "",
12+
actionHandler: doneAction)
13+
}
714
}
815

916
extension PointOfSaleCardPresentPaymentReaderUpdateCompletionAlertViewModel: Hashable, Equatable {
1017
func hash(into hasher: inout Hasher) {
1118
hasher.combine(title)
1219
hasher.combine(progressTitle)
20+
hasher.combine(buttonViewModel)
1321
}
1422

1523
/// This can be synthesised – implemented manually to ensure it matches the `hash` function.
1624
static func ==(lhs: PointOfSaleCardPresentPaymentReaderUpdateCompletionAlertViewModel,
1725
rhs: PointOfSaleCardPresentPaymentReaderUpdateCompletionAlertViewModel) -> Bool {
1826
return lhs.title == rhs.title &&
19-
lhs.progressTitle == rhs.progressTitle
27+
lhs.progressTitle == rhs.progressTitle &&
28+
lhs.buttonViewModel == rhs.buttonViewModel
2029
}
2130
}
2231

Modules/Sources/PointOfSale/Presentation/Card Present Payments/PointOfSaleCardPresentPaymentEventPresentationStyle.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ enum PointOfSaleCardPresentPaymentEventPresentationStyle {
125125

126126
case .updateProgress(let requiredUpdate, let progress, let cancelUpdate):
127127
if progress == 1.0 {
128-
self = .alert(.readerUpdateCompletion(viewModel: .init()))
128+
self = .alert(.readerUpdateCompletion(
129+
viewModel: .init(doneAction: dependencies.dismissReaderConnectionModal)))
129130
} else {
130131
self = requiredUpdate ?
131132
.alert(.requiredReaderUpdateInProgress(

0 commit comments

Comments
 (0)