Skip to content
45 changes: 0 additions & 45 deletions WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ struct POSIneligibleView: View {

private var suggestionText: String {
switch reason {
case .notTablet:
return NSLocalizedString("pos.ineligible.suggestion.notTablet",
value: "Please use a tablet to access POS features.",
comment: "Suggestion for not tablet: use iPad")
case .unsupportedIOSVersion:
return NSLocalizedString("pos.ineligible.suggestion.unsupportedIOSVersion",
value: "Point of Sale requires iOS 17 or later. Please update your device to iOS 17+ to use this feature.",
Expand All @@ -105,16 +101,6 @@ struct POSIneligibleView: View {
return NSLocalizedString("pos.ineligible.suggestion.featureSwitchSyncFailure",
value: "Try relaunching the app or check your internet connection and try again.",
comment: "Suggestion for feature switch sync failure: relaunch or check connection")
case let .unsupportedCountry(supportedCountries):
let countryNames = supportedCountries.map { $0.readableCountry }
let formattedCountryList = ListFormatter.localizedString(byJoining: countryNames)
let format = NSLocalizedString(
"pos.ineligible.suggestion.unsupportedCountry",
value: "POS is currently only available in %1$@. Check back later for availability in your region.",
comment: "Suggestion for unsupported country with list of supported countries. " +
"%1$@ is a placeholder for the localized list of supported country names."
)
return String.localizedStringWithFormat(format, formattedCountryList)
case let .unsupportedCurrency(supportedCurrencies):
let currencyList = supportedCurrencies.map { $0.rawValue }
let formattedCurrencyList = ListFormatter.localizedString(byJoining: currencyList)
Expand All @@ -130,10 +116,6 @@ struct POSIneligibleView: View {
return NSLocalizedString("pos.ineligible.suggestion.siteSettingsNotAvailable",
value: "Check your internet connection and try relaunching the app. If the issue persists, please contact support.",
comment: "Suggestion for site settings unavailable: check connection or contact support")
case .featureFlagDisabled:
return NSLocalizedString("pos.ineligible.suggestion.featureFlagDisabled",
value: "POS is currently disabled.",
comment: "Suggestion for disabled feature flag: notify that POS is disabled remotely")
case .selfDeallocated:
return NSLocalizedString("pos.ineligible.suggestion.selfDeallocated",
value: "Try relaunching the app to resolve this issue.",
Expand Down Expand Up @@ -176,24 +158,6 @@ private extension POSIneligibleView {
}
}

#Preview("Unsupported country") {
if #available(iOS 17.0, *) {
POSIneligibleView(
reason: .unsupportedCountry(supportedCountries: [.US, .GB]),
onRefresh: {}
)
}
}

#Preview("Not a tablet") {
if #available(iOS 17.0, *) {
POSIneligibleView(
reason: .notTablet,
onRefresh: {}
)
}
}

#Preview("Unsupported iOS version") {
if #available(iOS 17.0, *) {
POSIneligibleView(
Expand All @@ -212,15 +176,6 @@ private extension POSIneligibleView {
}
}

#Preview("Feature flag disabled") {
if #available(iOS 17.0, *) {
POSIneligibleView(
reason: .featureFlagDisabled,
onRefresh: {}
)
}
}

#Preview("Feature switch disabled") {
if #available(iOS 17.0, *) {
POSIneligibleView(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Combine
import Foundation
import UIKit
import class WooFoundation.CurrencySettings
Expand All @@ -17,15 +16,12 @@ import class Yosemite.PluginsService

/// Represents the reasons why a site may be ineligible for POS.
enum POSIneligibleReason: Equatable {
case notTablet
case unsupportedIOSVersion
case unsupportedWooCommerceVersion(minimumVersion: String)
case siteSettingsNotAvailable
case wooCommercePluginNotFound
case featureFlagDisabled
case featureSwitchDisabled
case featureSwitchSyncFailure
case unsupportedCountry(supportedCountries: [CountryCode])
case unsupportedCurrency(supportedCurrencies: [CurrencyCode])
case selfDeallocated
}
Expand All @@ -46,9 +42,6 @@ protocol POSEntryPointEligibilityCheckerProtocol {
}

final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
private var siteSettingsEligibility: POSEligibilityState?
private var featureFlagEligibility: POSEligibilityState?

private let siteID: Int64
private let userInterfaceIdiom: UIUserInterfaceIdiom
private let siteSettings: SelectedSiteSettingsProtocol
Expand Down Expand Up @@ -80,34 +73,20 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {

/// Determines whether the POS entry point can be shown based on the selected store and feature gates.
func checkEligibility() async -> POSEligibilityState {
switch checkDeviceEligibility() {
case .eligible:
break
case .ineligible(let reason):
return .ineligible(reason: reason)
guard #available(iOS 17.0, *) else {
return .ineligible(reason: .unsupportedIOSVersion)
}

async let siteSettingsEligibility = checkSiteSettingsEligibility()
async let featureFlagEligibility = checkRemoteFeatureEligibility()
async let pluginEligibility = checkPluginEligibility()

// Checks site settings first since it's likely to complete fastest.
switch await siteSettingsEligibility {
case .eligible:
break
case .ineligible(let reason):
return .ineligible(reason: reason)
}

// Then checks feature flag.
switch await featureFlagEligibility {
case .eligible:
break
case .ineligible(let reason):
return .ineligible(reason: reason)
}

// Finally checks plugin eligibility.
switch await pluginEligibility {
case .eligible:
return .eligible
Expand All @@ -118,55 +97,21 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {

/// Checks the final visibility of the POS tab.
func checkVisibility() async -> Bool {
if featureFlagService.isFeatureFlagEnabled(.pointOfSaleAsATabi2) {
return await checkVisibilityBasedOnCountryAndRemoteFeatureFlag()
} else {
let eligibility = await checkEligibility()
return eligibility == .eligible
}
}
}

private extension POSTabEligibilityChecker {
func checkDeviceEligibility() -> POSEligibilityState {
guard #available(iOS 17.0, *) else {
return .ineligible(reason: .unsupportedIOSVersion)
}

guard userInterfaceIdiom == .pad else {
return .ineligible(reason: .notTablet)
}

return .eligible
}

func checkVisibilityBasedOnCountryAndRemoteFeatureFlag() async -> Bool {
guard checkDeviceEligibility() == .eligible else {
return false
}

async let siteSettingsEligibility = checkSiteSettingsEligibility()
async let siteSettingsEligibility = waitAndCheckSiteSettingsEligibility()
async let featureFlagEligibility = checkRemoteFeatureEligibility()

self.siteSettingsEligibility = await siteSettingsEligibility
switch await siteSettingsEligibility {
case .eligible:
case .ineligible(.unsupportedCountry):
return false
default:
break
case let .ineligible(reason):
if case .unsupportedCurrency = reason {
break
} else {
return false
}
}

self.featureFlagEligibility = await featureFlagEligibility
switch await featureFlagEligibility {
case .eligible:
return true
case .ineligible:
return false
}
return await featureFlagEligibility == .eligible
}
}

Expand Down Expand Up @@ -220,11 +165,36 @@ private extension POSTabEligibilityChecker {
// MARK: - Site Settings Related Eligibility Check

private extension POSTabEligibilityChecker {
enum SiteSettingsEligibilityState {
case eligible
case ineligible(reason: SiteSettingsIneligibleReason)
}

enum SiteSettingsIneligibleReason {
case siteSettingsNotAvailable
case unsupportedCountry(supportedCountries: [CountryCode])
case unsupportedCurrency(supportedCurrencies: [CurrencyCode])
}

func checkSiteSettingsEligibility() async -> POSEligibilityState {
if let siteSettingsEligibility {
return siteSettingsEligibility
let siteSettingsEligibility = await waitAndCheckSiteSettingsEligibility()
switch siteSettingsEligibility {
case .eligible:
return .eligible
case .ineligible(reason: let reason):
switch reason {
case .siteSettingsNotAvailable, .unsupportedCountry:
// This is an edge case where the store country is expected to be eligible from the visilibity check, but site settings might have
// changed to an unsupported country during the session. In this case, we return an ineligible reason that prompts the merchant to
// relaunch the app.
return .ineligible(reason: .siteSettingsNotAvailable)
case let .unsupportedCurrency(supportedCurrencies: supportedCurrencies):
return .ineligible(reason: .unsupportedCurrency(supportedCurrencies: supportedCurrencies))
}
}
}

func waitAndCheckSiteSettingsEligibility() async -> SiteSettingsEligibilityState {
// Waits for the first site settings that matches the given site ID.
let siteSettings = await waitForSiteSettingsRefresh()
guard siteSettings.isNotEmpty else {
Expand All @@ -249,7 +219,7 @@ private extension POSTabEligibilityChecker {
return []
}

func isEligibleFromCountryAndCurrencyCode(countryCode: CountryCode, currencyCode: CurrencyCode) -> POSEligibilityState {
func isEligibleFromCountryAndCurrencyCode(countryCode: CountryCode, currencyCode: CurrencyCode) -> SiteSettingsEligibilityState {
let supportedCountries: [CountryCode] = [.US, .GB]
let supportedCurrencies: [CountryCode: [CurrencyCode]] = [.US: [.USD],
.GB: [.GBP]]
Expand All @@ -270,15 +240,21 @@ private extension POSTabEligibilityChecker {
// MARK: - Remote Feature Flag Eligibility Check

private extension POSTabEligibilityChecker {
@MainActor
func checkRemoteFeatureEligibility() async -> POSEligibilityState {
if let featureFlagEligibility {
return featureFlagEligibility
}
enum RemoteFeatureFlagEligibilityState: Equatable {
case eligible
case ineligible(reason: RemoteFeatureFlagIneligibleReason)
}

enum RemoteFeatureFlagIneligibleReason: Equatable {
case selfDeallocated
case featureFlagDisabled
}

@MainActor
func checkRemoteFeatureEligibility() async -> RemoteFeatureFlagEligibilityState {
// Only whitelisted accounts in WPCOM have the Point of Sale remote feature flag enabled. These can be found at D159901-code
// If the account is whitelisted, then the remote value takes preference over the local feature flag configuration
return await withCheckedContinuation { [weak self] continuation in
await withCheckedContinuation { [weak self] continuation in
guard let self else {
return continuation.resume(returning: .ineligible(reason: .selfDeallocated))
}
Expand Down
Loading