-
Notifications
You must be signed in to change notification settings - Fork 121
[Mobile Payments] Badge tap to pay on eligible devices and stores, until Set up Tap to Pay opened #9812
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
[Mobile Payments] Badge tap to pay on eligible devices and stores, until Set up Tap to Pay opened #9812
Changes from all commits
a2e6c45
7f95f3b
88afcf0
1e68232
dd62a19
11b4c6d
2eccabb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,8 @@ final class MainTabViewModel { | |
|
|
||
| private var cancellables = Set<AnyCancellable>() | ||
|
|
||
| let tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker = TapToPayBadgePromotionChecker() | ||
|
|
||
| init(storesManager: StoresManager = ServiceLocator.stores, | ||
| featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) { | ||
| self.storesManager = storesManager | ||
|
|
@@ -93,8 +95,7 @@ final class MainTabViewModel { | |
| listenToReviewsBadgeReloadRequired() | ||
| retrieveShouldShowReviewsBadgeOnHubMenuTabValue() | ||
|
|
||
| listenToNewFeatureBadgeReloadRequired() | ||
| retrieveShouldShowNewFeatureBadgeOnHubMenuTabValue() | ||
| tapToPayBadgePromotionChecker.$shouldShowTapToPayBadges.share().assign(to: &$shouldShowNewFeatureBadgeOnHubMenuTab) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -200,21 +201,6 @@ private extension MainTabViewModel { | |
| object: nil) | ||
| } | ||
|
|
||
|
|
||
| func listenToNewFeatureBadgeReloadRequired() { | ||
| NotificationCenter.default.addObserver(self, | ||
| selector: #selector(setUpTapToPayViewDidAppear), | ||
| name: .setUpTapToPayViewDidAppear, | ||
| object: nil) | ||
|
|
||
| } | ||
|
|
||
| /// Updates the badge after the Set up Tap to Pay flow did appear | ||
| /// | ||
| @objc func setUpTapToPayViewDidAppear() { | ||
| shouldShowNewFeatureBadgeOnHubMenuTab = false | ||
| } | ||
|
|
||
| /// Retrieves whether we should show the reviews on the Menu button and updates `shouldShowReviewsBadge` | ||
| /// | ||
| @objc func retrieveShouldShowReviewsBadgeOnHubMenuTabValue() { | ||
|
|
@@ -229,22 +215,6 @@ private extension MainTabViewModel { | |
| storesManager.dispatch(notificationCountAction) | ||
| } | ||
|
|
||
| /// Retrieves whether we should show the new feature badge on the Menu button | ||
| /// | ||
| func retrieveShouldShowNewFeatureBadgeOnHubMenuTabValue() { | ||
| let action = AppSettingsAction.getFeatureAnnouncementVisibility(campaign: .tapToPayHubMenuBadge) { [weak self] result in | ||
| guard let self = self else { return } | ||
| switch result { | ||
| case .success(let visible): | ||
| self.shouldShowNewFeatureBadgeOnHubMenuTab = visible && self.featureFlagService.isFeatureFlagEnabled(.tapToPayBadge) | ||
| case .failure: | ||
| self.shouldShowNewFeatureBadgeOnHubMenuTab = false | ||
| } | ||
| } | ||
|
|
||
| storesManager.dispatch(action) | ||
| } | ||
|
|
||
| /// Listens for changes on the menu badge display logic and updates it depending on them | ||
| /// | ||
| func synchronizeShouldShowBadgeOnHubMenuTabLogic() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import Foundation | ||
| import Yosemite | ||
| import Experiments | ||
| import Combine | ||
|
|
||
| final class TapToPayBadgePromotionChecker { | ||
| private let featureFlagService: FeatureFlagService | ||
| private let stores: StoresManager | ||
|
|
||
| @Published private(set) var shouldShowTapToPayBadges: Bool = false | ||
|
|
||
| private var cancellables: Set<AnyCancellable> = [] | ||
|
|
||
| init(featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService, | ||
| stores: StoresManager = ServiceLocator.stores) { | ||
| self.featureFlagService = featureFlagService | ||
| self.stores = stores | ||
|
|
||
| listenToTapToPayBadgeReloadRequired() | ||
| Task { | ||
| await checkTapToPayBadgeVisibility() | ||
| } | ||
| } | ||
|
|
||
| func hideTapToPayBadge() { | ||
| guard shouldShowTapToPayBadges else { | ||
| return | ||
| } | ||
| let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .tapToPayHubMenuBadge, remindAfterDays: nil, onCompletion: nil) | ||
| stores.dispatch(action) | ||
| shouldShowTapToPayBadges = false | ||
| } | ||
|
|
||
| @MainActor | ||
| private func checkTapToPayBadgeVisibility() async { | ||
| guard let siteID = stores.sessionManager.defaultStoreID else { | ||
| return shouldShowTapToPayBadges = false | ||
| } | ||
|
|
||
| let supportDeterminer = CardReaderSupportDeterminer(siteID: siteID) | ||
| guard supportDeterminer.siteSupportsLocalMobileReader(), | ||
| await supportDeterminer.deviceSupportsLocalMobileReader(), | ||
| await !supportDeterminer.hasPreviousTapToPayUsage() else { | ||
| return shouldShowTapToPayBadges = false | ||
| } | ||
|
|
||
| do { | ||
| let visible = try await withCheckedThrowingContinuation({ [weak self] continuation in | ||
| let action = AppSettingsAction.getFeatureAnnouncementVisibility(campaign: .tapToPayHubMenuBadge) { result in | ||
| continuation.resume(with: result) | ||
| } | ||
| self?.stores.dispatch(action) | ||
| }) | ||
| shouldShowTapToPayBadges = visible && featureFlagService.isFeatureFlagEnabled(.tapToPayBadge) | ||
| } catch { | ||
| DDLogError("Could not fetch feature announcement visibility \(error)") | ||
| } | ||
| } | ||
|
|
||
| private func listenToTapToPayBadgeReloadRequired() { | ||
| NotificationCenter.default.addObserver(self, | ||
| selector: #selector(setUpTapToPayViewDidAppear), | ||
| name: .setUpTapToPayViewDidAppear, | ||
| object: nil) | ||
| // It's not ideal that we need this, and the notification should be removed when we remove this badge. | ||
| // Changing the store recreates this class, so we check for support again... however, the store country is | ||
| // fetched by the CardPresentPaymentsConfigurationLoader, from the `ServiceLocator.selectedSiteSettings`. | ||
| // The site settings are not updated until slightly later, so we need to refresh the badge logic when they are. | ||
| // Ideally, we would improve the CardPresentConfigurationLoader to accurately get the current country. | ||
| NotificationCenter.default.addObserver(self, | ||
| selector: #selector(refreshBadgeVisibility), | ||
| name: .selectedSiteSettingsRefreshed, | ||
| object: nil) | ||
| } | ||
|
|
||
| @objc private func setUpTapToPayViewDidAppear() { | ||
| hideTapToPayBadge() | ||
| } | ||
|
|
||
| @objc private func refreshBadgeVisibility() { | ||
| Task { | ||
| await checkTapToPayBadgeVisibility() | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,26 +22,33 @@ final class HubMenuCoordinator: Coordinator { | |
|
|
||
| private let willPresentReviewDetailsFromPushNotification: () async -> Void | ||
|
|
||
| private let tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as with |
||
|
|
||
| init(navigationController: UINavigationController, | ||
| pushNotificationsManager: PushNotesManager = ServiceLocator.pushNotesManager, | ||
| storesManager: StoresManager = ServiceLocator.stores, | ||
| noticePresenter: NoticePresenter = ServiceLocator.noticePresenter, | ||
| switchStoreUseCase: SwitchStoreUseCaseProtocol, | ||
| tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker, | ||
| willPresentReviewDetailsFromPushNotification: @escaping () async -> Void) { | ||
|
|
||
| self.pushNotificationsManager = pushNotificationsManager | ||
| self.storesManager = storesManager | ||
| self.noticePresenter = noticePresenter | ||
| self.switchStoreUseCase = switchStoreUseCase | ||
| self.tapToPayBadgePromotionChecker = tapToPayBadgePromotionChecker | ||
| self.willPresentReviewDetailsFromPushNotification = willPresentReviewDetailsFromPushNotification | ||
| self.navigationController = navigationController | ||
| } | ||
|
|
||
| convenience init(navigationController: UINavigationController, willPresentReviewDetailsFromPushNotification: @escaping () async -> Void) { | ||
| convenience init(navigationController: UINavigationController, | ||
| tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker, | ||
| willPresentReviewDetailsFromPushNotification: @escaping () async -> Void) { | ||
| let storesManager = ServiceLocator.stores | ||
| self.init(navigationController: navigationController, | ||
| storesManager: storesManager, | ||
| switchStoreUseCase: SwitchStoreUseCase(stores: storesManager), | ||
| tapToPayBadgePromotionChecker: tapToPayBadgePromotionChecker, | ||
| willPresentReviewDetailsFromPushNotification: willPresentReviewDetailsFromPushNotification) | ||
| } | ||
|
|
||
|
|
@@ -55,7 +62,9 @@ final class HubMenuCoordinator: Coordinator { | |
|
|
||
| /// Replaces `start()` because the menu tab's navigation stack could be updated multiple times when site ID changes. | ||
| func activate(siteID: Int64) { | ||
| hubMenuController = HubMenuViewController(siteID: siteID, navigationController: navigationController) | ||
| hubMenuController = HubMenuViewController(siteID: siteID, | ||
| navigationController: navigationController, | ||
| tapToPayBadgePromotionChecker: tapToPayBadgePromotionChecker) | ||
| if let hubMenuController = hubMenuController { | ||
| navigationController.viewControllers = [hubMenuController] | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps for another PR: Should we abstract this class into some
PromotionCheckerprotocol to keep it detached from the specific TTP promotion checker? It could make sense also inject the protocol in the initializer for testability and to use this common protocol in the future for other feature announcements.