Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TTPReq] Integrate Education steps after Terms of Service are accepted #14580

Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7562b48
Create BuiltInCardReaderOnboardingState to represent TTP onboarding s…
staskus Dec 2, 2024
97d80fd
Emit didAcceptTermsOfService event from StripeCardReaderService
staskus Dec 2, 2024
b97d807
Add observeBuiltInCardReaderOnboardingState to CardPresentPaymentStore
staskus Dec 2, 2024
6547391
Create TapToPayEducationViewViewHostingController to present from UIKit
staskus Dec 2, 2024
29503e2
Create presentMerchantEducation to CardPresentPaymentAlertsPresenting
staskus Dec 2, 2024
f9ad911
Integrate merchant education to TTP connection flow
staskus Dec 2, 2024
7615657
Hide merchant education behind the feature flag
staskus Dec 2, 2024
3156e83
Update TapToPayEducationView.swift
staskus Dec 2, 2024
08699fb
Update MockCardReaderService.swift
staskus Dec 2, 2024
7b1c311
Complete merchant education for flows that don't support it
staskus Dec 2, 2024
1f840cf
Present TTP education on top of modal controller
staskus Dec 3, 2024
8d1bef8
Remove BuiltInCardReaderOnboardingState and use a Void publisher to e…
staskus Dec 3, 2024
dab2e9d
Move presentMerchantEducation to BuiltInCardReaderMerchantEducationPr…
staskus Dec 4, 2024
875e2b5
Merge branch 'trunk' into fix/14327-ttpreq-integrate-education-steps-…
staskus Dec 4, 2024
37e6088
TapToPayEducationViewViewHostingController renamed to TapToPayEducati…
staskus Dec 5, 2024
eca7510
Replace NavigationView to NavigationStack in education view
staskus Dec 5, 2024
ce08d74
Remove unnecessary self in TapToPayEducationView
staskus Dec 5, 2024
68b9145
Remove unnecessary WordPressUI import in BuiltInCardReaderMerchantEdu…
staskus Dec 5, 2024
f425046
Merge branch 'trunk' into fix/14327-ttpreq-integrate-education-steps-…
staskus Dec 9, 2024
76a6538
Move isMerchantEducationInProgress to controller state associated type
staskus Dec 10, 2024
39a579f
Use ViewControllerPresenting instead of UIViewController
staskus Dec 10, 2024
8c527ad
Use native dismiss mechanism and onDisappear completion handler for T…
staskus Dec 10, 2024
eac9875
Update BuiltInCardReaderConnectionController.swift
staskus Dec 10, 2024
826d1ed
Merge branch 'trunk' into fix/14327-ttpreq-integrate-education-steps-…
staskus Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Hardware/Hardware.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
012CB49C2CFE04E300093169 /* BuiltInCardReaderOnboardingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012CB49B2CFE04E300093169 /* BuiltInCardReaderOnboardingState.swift */; };
028C39E028255CFE0007BA25 /* Models+Copiable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028C39DF28255CFE0007BA25 /* Models+Copiable.generated.swift */; };
02B5147A28254ED300750B71 /* Codegen in Frameworks */ = {isa = PBXBuildFile; productRef = 02B5147928254ED300750B71 /* Codegen */; };
02FDAB102CEEF11F00B6C8AA /* PaymentChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FDAB0F2CEEF11D00B6C8AA /* PaymentChannel.swift */; };
Expand Down Expand Up @@ -159,6 +160,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
012CB49B2CFE04E300093169 /* BuiltInCardReaderOnboardingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltInCardReaderOnboardingState.swift; sourceTree = "<group>"; };
02351FF56149ADCD11338B19 /* Pods-SampleReceiptPrinter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleReceiptPrinter.release.xcconfig"; path = "Target Support Files/Pods-SampleReceiptPrinter/Pods-SampleReceiptPrinter.release.xcconfig"; sourceTree = "<group>"; };
028C39DF28255CFE0007BA25 /* Models+Copiable.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Models+Copiable.generated.swift"; sourceTree = "<group>"; };
02FDAB0F2CEEF11D00B6C8AA /* PaymentChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentChannel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -485,6 +487,7 @@
D80B4659260E1E160092EDC0 /* StatementDescriptor.swift */,
D88ECCE2262095A10094398A /* UpdateTimeEstimate.swift */,
E1E125AB26EB582B0068A9B0 /* CardReaderSoftwareUpdateState.swift */,
012CB49B2CFE04E300093169 /* BuiltInCardReaderOnboardingState.swift */,
D845BDC1262D98C400A3E40F /* CardBrand.swift */,
DAE622402B8E120F00FAE1D8 /* Wallet.swift */,
D845BDD9262DAADB00A3E40F /* PaymentMethod.swift */,
Expand Down Expand Up @@ -864,6 +867,7 @@
03B440AA2754DFC400759429 /* UnderlyingError.swift in Sources */,
E1E125AC26EB582B0068A9B0 /* CardReaderSoftwareUpdateState.swift in Sources */,
D88FDB3125DD21B000CB0DBD /* CardReader.swift in Sources */,
012CB49C2CFE04E300093169 /* BuiltInCardReaderOnboardingState.swift in Sources */,
039D948B2760C0660044EF38 /* NoOpCardReaderService.swift in Sources */,
D845BDDA262DAADB00A3E40F /* PaymentMethod.swift in Sources */,
D81AE85A25E6A62800D9CFD3 /* StripeCardReaderDiscoveryCache.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A type that represents the possible states of the built-in (Tap to Pay) card reader onboarding flow.
public enum BuiltInCardReaderOnboardingState {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking question: do we plan to add more cases to the onboarding state? If not, I wonder if emitting Void from the publisher (like var builtInCardReaderAcceptToSEvents: AnyPublisher<Void, Never> { get }) on ToS acceptance event is sufficient.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't plan to add any more cases. I created it following the logic of other similar states. However, builtInCardReaderAcceptToSEvents is a good idea and would avoid creating this additional enum.

/// User has accepted the terms of service
case didAcceptTermsOfService
}
3 changes: 3 additions & 0 deletions Hardware/Hardware/CardReader/CardReaderService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public protocol CardReaderService {
/// The Publisher that emits software update state changes
var softwareUpdateEvents: AnyPublisher<CardReaderSoftwareUpdateState, Never> { get }

/// The Publisher that emits the state of the built-in card reader onboarding
var builtInCardReaderOnboardingEvents: AnyPublisher<BuiltInCardReaderOnboardingState, Never> { get }

// MARK: - Commands

/// Checks for support of a given reader type and discovery method combination. Does not start discovery.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public struct NoOpCardReaderService: CardReaderService {
public var softwareUpdateEvents: AnyPublisher<CardReaderSoftwareUpdateState, Never>
= CurrentValueSubject<CardReaderSoftwareUpdateState, Never>(.none).eraseToAnyPublisher()

/// The Publisher that emits the current state of the built-in card reader onboarding
public var builtInCardReaderOnboardingEvents: AnyPublisher<BuiltInCardReaderOnboardingState, Never>
= PassthroughSubject<BuiltInCardReaderOnboardingState, Never>().eraseToAnyPublisher()

public init() {}
// MARK: - Commands

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public final class StripeCardReaderService: NSObject {
private let discoveryStatusSubject = CurrentValueSubject<CardReaderServiceDiscoveryStatus, Never>(.idle)
private let readerEventsSubject = PassthroughSubject<CardReaderEvent, Never>()
private let softwareUpdateSubject = CurrentValueSubject<CardReaderSoftwareUpdateState, Never>(.none)
private let builtInReaderOnboardingState = PassthroughSubject<BuiltInCardReaderOnboardingState, Never>()

private var connectionAttemptInvalidated: Bool = false

/// Volatile, in-memory cache of discovered readers. It has to be cleared after we connect to a reader
Expand Down Expand Up @@ -62,6 +64,10 @@ extension StripeCardReaderService: CardReaderService {
softwareUpdateSubject.eraseToAnyPublisher()
}

public var builtInCardReaderOnboardingEvents: AnyPublisher<BuiltInCardReaderOnboardingState, Never> {
builtInReaderOnboardingState.eraseToAnyPublisher()
}

// MARK: - CardReaderService conformance. Commands

public func checkSupport(for cardReaderType: CardReaderType,
Expand Down Expand Up @@ -989,6 +995,10 @@ extension StripeCardReaderService: LocalMobileReaderDelegate {
softwareUpdateSubject.send(.none)
}
}

public func localMobileReaderDidAcceptTermsOfService(_ reader: Reader) {
builtInReaderOnboardingState.send(.didAcceptTermsOfService)
}
}

// MARK: - Terminal delegate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,8 @@ final class CardPresentPaymentsAlertPresenterAdaptor: CardPresentPaymentAlertsPr
func reset() {
latestReaderConnectionHandler = nil
}

func presentMerchantEducation(completion: @escaping () -> Void) {
completion()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it's not presented, maybe because TTP isn't supported in POS? Maybe it's worth a clarifying comment that this will be updated when TTP is supported.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it wouldn't be presented for POS, unless we start supporting iPhones or collecting payments through TTP. That comes back to your later question about putting presentMerchantEducation in a presenter or choosing a different approach.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import UIKit
import Storage
import SwiftUI
import Yosemite
import Experiments

/// Facilitates connecting to a card reader
///
Expand Down Expand Up @@ -84,6 +85,8 @@ where AlertProvider.AlertDetails == AlertPresenter.AlertDetails {

private let alertsProvider: AlertProvider

private let featureFlagService: FeatureFlagService

/// The reader we want the user to consider connecting to
///
private var candidateReader: CardReader?
Expand Down Expand Up @@ -112,6 +115,8 @@ where AlertProvider.AlertDetails == AlertPresenter.AlertDetails {

private var allowTermsOfServiceAcceptance: Bool

private var isMerchantEducationInProgress: CurrentValueSubject<Bool, Never> = .init(false)

init(
forSiteID: Int64,
storageManager: StorageManagerType = ServiceLocator.storageManager,
Expand All @@ -120,6 +125,7 @@ where AlertProvider.AlertDetails == AlertPresenter.AlertDetails {
alertsProvider: AlertProvider,
configuration: CardPresentPaymentsConfiguration,
analyticsTracker: CardReaderConnectionAnalyticsTracker,
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService,
allowTermsOfServiceAcceptance: Bool = true
) {
siteID = forSiteID
Expand All @@ -130,6 +136,7 @@ where AlertProvider.AlertDetails == AlertPresenter.AlertDetails {
self.alertsProvider = alertsProvider
self.configuration = configuration
self.analyticsTracker = analyticsTracker
self.featureFlagService = featureFlagService
self.allowTermsOfServiceAcceptance = allowTermsOfServiceAcceptance

configureResultsControllers()
Expand Down Expand Up @@ -366,6 +373,30 @@ private extension BuiltInCardReaderConnectionController {
.store(in: &self.subscriptions)
}
stores.dispatch(softwareUpdateAction)


if featureFlagService.isFeatureFlagEnabled(.tapToPayEducation) {
let onboardingAction = CardPresentPaymentAction.observeBuiltInCardReaderOnboardingState { [weak self] onboardingEvents in
guard let self = self else { return }

onboardingEvents
.subscribe(on: DispatchQueue.main)
.sink { [weak self] event in
guard let self = self else { return }
switch event {
case .didAcceptTermsOfService:
self.isMerchantEducationInProgress.send(true)
self.alertsPresenter.presentMerchantEducation { [weak self] in
self?.isMerchantEducationInProgress.send(false)
}
}
}
.store(in: &self.subscriptions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: can remove a few selves

Suggested change
guard let self = self else { return }
onboardingEvents
.subscribe(on: DispatchQueue.main)
.sink { [weak self] event in
guard let self = self else { return }
switch event {
case .didAcceptTermsOfService:
self.isMerchantEducationInProgress.send(true)
self.alertsPresenter.presentMerchantEducation { [weak self] in
self?.isMerchantEducationInProgress.send(false)
}
}
}
.store(in: &self.subscriptions)
guard let self else { return }
onboardingEvents
.subscribe(on: DispatchQueue.main)
.sink { [weak self] event in
guard let self else { return }
switch event {
case .didAcceptTermsOfService:
isMerchantEducationInProgress.send(true)
alertsPresenter.presentMerchantEducation { [weak self] in
self?.isMerchantEducationInProgress.send(false)
}
}
}
.store(in: &subscriptions)

}
stores.dispatch(onboardingAction)
}


let options = CardReaderConnectionOptions(
builtInOptions: BuiltInCardReaderConnectionOptions(termsOfServiceAcceptancePermitted: allowTermsOfServiceAcceptance))

Expand Down Expand Up @@ -537,8 +568,15 @@ private extension BuiltInCardReaderConnectionController {
/// Calls the completion with a success result
///
private func returnSuccess(result: CardReaderConnectionResult) {
onCompletion?(.success(result))
state = .idle
isMerchantEducationInProgress.eraseToAnyPublisher()
.filter { $0 == false }
.first()
.sink { [weak self] _ in
guard let self else { return }
onCompletion?(.success(result))
state = .idle
}
.store(in: &subscriptions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how this works but it makes the function a little misleading. It's changed to "maybe return success sometime", because this is asynchronous, and combine-based.

}

/// Calls the completion with a failure result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,24 @@ struct TapToPayEducationView: View {
})
}
}

// MARK: - Hosting Controller

final class TapToPayEducationViewViewHostingController: UIHostingController<TapToPayEducationView> {
staskus marked this conversation as resolved.
Show resolved Hide resolved
init(onDismiss: @escaping () -> Void) {
let viewModel = TapToPayEducationViewModel(flow: .onboarding, onDismiss: onDismiss)
super.init(rootView: TapToPayEducationView(viewModel: viewModel))

viewModel.onDismiss = { [weak self] in
guard let self else { return }

self.dismiss(animated: true) {
staskus marked this conversation as resolved.
Show resolved Hide resolved
onDismiss()
}
}
}

@MainActor @preconcurrency required dynamic init?(coder aDecoder: NSCoder) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, what's a dynamic init?! New keywords, I'll have to look that one up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Xcode 16 generated this one for me 😀

fatalError("init(coder:) has not been implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class TapToPayEducationViewModel: ObservableObject {
let cardPresentPaymentsOnboardingUseCase: CardPresentPaymentsOnboardingUseCaseProtocol
let siteID: Int64

private let onDismiss: () -> Void
var onDismiss: () -> Void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onDismiss implies a notification that it is being dismissed, but I think this is actually a dismissAction – calling it dismisses the view, and then the other onDismiss (which was passed in to the hosting controller) is called to notify something that it was dismissed as well.


init(flow: Flow = .onboarding,
steps: [TapToPayEducationStepViewModel]? = nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ final class SilenceablePassthroughCardPresentPaymentAlertsPresenter<AlertPresent
alertsPresenter = nil
alertSubscription?.cancel()
}

func presentMerchantEducation(completion: @escaping () -> Void) {
completion()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ protocol CardPresentPaymentAlertsPresenting<AlertDetails> {
associatedtype AlertDetails
func present(viewModel: AlertDetails)
func presentWCSettingsWebView(adminURL: URL, completion: @escaping () -> Void)
func presentMerchantEducation(completion: @escaping () -> Void)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Just feeling a bit uncertain about this, as this is just for TTP and I'm not sure if it requires a separate function in fear of this protocol growing very long in the future with new views like this 🤔 @joshheald WDYT about having a separate function here (maybe renaming to presentTTPMerchantEducation since this is specific to TTP), or include this merchant education in present(viewModel: AlertDetails)? This education UI has a different presentation style as a fullscreen sheet though, it probably requires more work to allow a view model other than CardPresentPaymentsModalViewModel in the UIKit version CardPresentPaymentAlertsPresenter.

Copy link
Collaborator Author

@staskus staskus Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was going back and forth on how to better approach this.

Integrating within func present(viewModel: AlertDetails) would go against a current setup. We want a different ViewModel and a different View for TTP education.

We could also create a separate presenter whose sole responsibility would be to present TTP education on top of the existing context. (Example)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Integrating within func present(viewModel: AlertDetails) would go against a current setup. We want a different ViewModel and a different View for TTP education.

I was thinking of refactoring the UIKit version CardPresentPaymentAlertsPresenter to use a generic AlertDetails similar to the POS version so that we can decide how to present an alert detail. But it will likely require significant changes to maintain backward compatibility.

We could also create a separate presenter whose sole responsibility would be to present TTP education on top of the existing context. (Example)

Nice, I like this approach - it doesn't add complexity to the alerts presenter and the merchant education UI is not an "alert" anyway. When the time comes to support TTP in POS, we can update BuiltInCardReaderMerchantEducationPresenter to maybe a protocol to support both UIKit and SwiftUI. Lemme know what you think of going with this approach!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will start manual testing after this thread concludes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think the second approach of having a separate presenter for education is reasonable. Non-TTP-related controllers don't have to have any knowledge of it. I will include it in the main PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in dab2e9d

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see why you went back and forth here!

I was thinking of refactoring the UIKit version CardPresentPaymentAlertsPresenter to use a generic AlertDetails similar to the POS version so that we can decide how to present an alert detail. But it will likely require significant changes to maintain backward compatibility.

In some ways it does seem a little strange not to include alerts which (currently) only show during a built in reader connection in CardPresentPaymentBuiltInReaderConnectionAlertsProvider. However... just because they (currently) only happen during that connection doesn't make them a connection alert. The needs we have for education are different than we're showing information about an ongoing connection. It's not even quite part of the connection flow; it sort of floats above it.

It would be perfectly valid to show education outside a connection flow as well, and if we did that, it would seem strange if we needed a CardPresentPaymentEventDetails to do it. In the new design, we'd be fine: just have some new CardPresentPaymentEventDetails case like showTapToPayEducation, which the presenter would then use to create a TapToPayEducationViewModel for presentation. When we wanted to present the education flow outside of the connection flow, we just create the TapToPayEducationViewModel directly and don't care about the payment event details, as it's not happening in a payment context. The event is the user tapping something else in the app, instead.

The new design is more flexible, because CardPresentPaymentEventDetails is not a view model, it's just details about an event, and the presenter can decide what view model to make for each event and how to present it.

Unfortunately under the existing UIKit design, it's not so clean, as we have the same type of concrete view model class produced for every event, and it does need to be used to present a view. So unless we refactor the existing code to use the new approach, we can't reasonably include the education step in this way.

It would probably be ideal to do the refactor. But, it's also not factored in to the project so probably not a reasonable choice here. Since we're not doing it now, let's avoid making it harder to do it in the future. I think that having a separate presenter meets that need. We should try to keep it simple though!

func foundSeveralReaders(readerIDs: [String],
connect: @escaping (String) -> Void,
cancelSearch: @escaping () -> Void)
Expand Down Expand Up @@ -143,4 +144,14 @@ final class CardPresentPaymentAlertsPresenter: CardPresentPaymentAlertsPresentin
dismissCommonAndPresent(animated: true)
dismissSeveralFoundAndPresent(animated: true)
}

func presentMerchantEducation(completion: @escaping () -> Void) {
let viewController = TapToPayEducationViewViewHostingController(onDismiss: completion)

if let modalController {
modalController.present(viewController, animated: true)
} else {
rootViewController?.present(viewController, animated: true)
}
staskus marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ final class MockCardPresentPaymentAlertsPresenter: CardPresentPaymentAlertsPrese
func dismiss() {
// no-op
}

func presentMerchantEducation(completion: @escaping () -> Void) {
completion()
}
}

enum MockCardPresentPaymentAlertsPresenterMode {
Expand Down
3 changes: 3 additions & 0 deletions Yosemite/Yosemite/Actions/CardPresentPaymentAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ public enum CardPresentPaymentAction: Action {
/// Check the state of available software updates.
case observeCardReaderUpdateState(onCompletion: (AnyPublisher<CardReaderSoftwareUpdateState, Never>) -> Void)

/// Check the state of the built-in card reader onboarding
case observeBuiltInCardReaderOnboardingState(onCompletion: (AnyPublisher<BuiltInCardReaderOnboardingState, Never>) -> Void)

/// Update card reader firmware.
case startCardReaderUpdate

Expand Down
1 change: 1 addition & 0 deletions Yosemite/Yosemite/Model/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ public typealias StoredProductSettings = Networking.StoredProductSettings
public typealias CardReader = Hardware.CardReader
public typealias CardReaderConnectionOptions = Hardware.CardReaderConnectionOptions
public typealias BuiltInCardReaderConnectionOptions = Hardware.BuiltInCardReaderConnectionOptions
public typealias BuiltInCardReaderOnboardingState = Hardware.BuiltInCardReaderOnboardingState
public typealias CardReaderDiscoveryMethod = Hardware.CardReaderDiscoveryMethod
public typealias CardReaderEvent = Hardware.CardReaderEvent
public typealias CardReaderInput = Hardware.CardReaderInput
Expand Down
6 changes: 6 additions & 0 deletions Yosemite/Yosemite/Stores/CardPresentPaymentStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public final class CardPresentPaymentStore: Store {
cancelRefund(onCompletion: completion)
case .observeCardReaderUpdateState(onCompletion: let completion):
observeCardReaderUpdateState(onCompletion: completion)
case .observeBuiltInCardReaderOnboardingState(let completion):
observeBuiltInCardReaderOnboardingState(onCompletion: completion)
case .startCardReaderUpdate:
startCardReaderUpdate()
case .reset:
Expand Down Expand Up @@ -400,6 +402,10 @@ private extension CardPresentPaymentStore {
onCompletion(cardReaderService.softwareUpdateEvents)
}

func observeBuiltInCardReaderOnboardingState(onCompletion: @escaping (AnyPublisher<BuiltInCardReaderOnboardingState, Never>) -> Void) {
onCompletion(cardReaderService.builtInCardReaderOnboardingEvents)
}

func startCardReaderUpdate() {
cardReaderService.installUpdate()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ final class MockCardReaderService: CardReaderService {
CurrentValueSubject<CardReaderSoftwareUpdateState, Never>(.none).eraseToAnyPublisher()
}

var builtInCardReaderOnboardingEvents: AnyPublisher<Hardware.BuiltInCardReaderOnboardingState, Never> {
PassthroughSubject<BuiltInCardReaderOnboardingState, Never>().eraseToAnyPublisher()
}

/// Boolean flag Indicates that clients have called the start method
var didHitStart = false

Expand Down
Loading