1- import Combine
21import Foundation
32import UIKit
43import class WooFoundation. CurrencySettings
@@ -17,15 +16,12 @@ import class Yosemite.PluginsService
1716
1817/// Represents the reasons why a site may be ineligible for POS.
1918enum POSIneligibleReason : Equatable {
20- case notTablet
2119 case unsupportedIOSVersion
2220 case unsupportedWooCommerceVersion( minimumVersion: String )
2321 case siteSettingsNotAvailable
2422 case wooCommercePluginNotFound
25- case featureFlagDisabled
2623 case featureSwitchDisabled
2724 case featureSwitchSyncFailure
28- case unsupportedCountry( supportedCountries: [ CountryCode ] )
2925 case unsupportedCurrency( supportedCurrencies: [ CurrencyCode ] )
3026 case selfDeallocated
3127}
@@ -46,9 +42,6 @@ protocol POSEntryPointEligibilityCheckerProtocol {
4642}
4743
4844final class POSTabEligibilityChecker : POSEntryPointEligibilityCheckerProtocol {
49- private var siteSettingsEligibility : POSEligibilityState ?
50- private var featureFlagEligibility : POSEligibilityState ?
51-
5245 private let siteID : Int64
5346 private let userInterfaceIdiom : UIUserInterfaceIdiom
5447 private let siteSettings : SelectedSiteSettingsProtocol
@@ -80,34 +73,20 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
8073
8174 /// Determines whether the POS entry point can be shown based on the selected store and feature gates.
8275 func checkEligibility( ) async -> POSEligibilityState {
83- switch checkDeviceEligibility ( ) {
84- case . eligible:
85- break
86- case . ineligible( let reason) :
87- return . ineligible( reason: reason)
76+ guard #available( iOS 17 . 0 , * ) else {
77+ return . ineligible( reason: . unsupportedIOSVersion)
8878 }
8979
9080 async let siteSettingsEligibility = checkSiteSettingsEligibility ( )
91- async let featureFlagEligibility = checkRemoteFeatureEligibility ( )
9281 async let pluginEligibility = checkPluginEligibility ( )
9382
94- // Checks site settings first since it's likely to complete fastest.
9583 switch await siteSettingsEligibility {
9684 case . eligible:
9785 break
9886 case . ineligible ( let reason) :
9987 return . ineligible( reason: reason)
10088 }
10189
102- // Then checks feature flag.
103- switch await featureFlagEligibility {
104- case . eligible:
105- break
106- case . ineligible( let reason) :
107- return . ineligible( reason: reason)
108- }
109-
110- // Finally checks plugin eligibility.
11190 switch await pluginEligibility {
11291 case . eligible:
11392 return . eligible
@@ -118,55 +97,21 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
11897
11998 /// Checks the final visibility of the POS tab.
12099 func checkVisibility( ) async -> Bool {
121- if featureFlagService. isFeatureFlagEnabled ( . pointOfSaleAsATabi2) {
122- return await checkVisibilityBasedOnCountryAndRemoteFeatureFlag ( )
123- } else {
124- let eligibility = await checkEligibility ( )
125- return eligibility == . eligible
126- }
127- }
128- }
129-
130- private extension POSTabEligibilityChecker {
131- func checkDeviceEligibility( ) -> POSEligibilityState {
132- guard #available( iOS 17 . 0 , * ) else {
133- return . ineligible( reason: . unsupportedIOSVersion)
134- }
135-
136100 guard userInterfaceIdiom == . pad else {
137- return . ineligible( reason: . notTablet)
138- }
139-
140- return . eligible
141- }
142-
143- func checkVisibilityBasedOnCountryAndRemoteFeatureFlag( ) async -> Bool {
144- guard checkDeviceEligibility ( ) == . eligible else {
145101 return false
146102 }
147103
148- async let siteSettingsEligibility = checkSiteSettingsEligibility ( )
104+ async let siteSettingsEligibility = waitAndCheckSiteSettingsEligibility ( )
149105 async let featureFlagEligibility = checkRemoteFeatureEligibility ( )
150106
151- self . siteSettingsEligibility = await siteSettingsEligibility
152107 switch await siteSettingsEligibility {
153- case . eligible:
108+ case . ineligible ( . unsupportedCountry) :
109+ return false
110+ default:
154111 break
155- case let . ineligible( reason) :
156- if case . unsupportedCurrency = reason {
157- break
158- } else {
159- return false
160- }
161112 }
162113
163- self. featureFlagEligibility = await featureFlagEligibility
164- switch await featureFlagEligibility {
165- case . eligible:
166- return true
167- case . ineligible:
168- return false
169- }
114+ return await featureFlagEligibility == . eligible
170115 }
171116}
172117
@@ -220,11 +165,36 @@ private extension POSTabEligibilityChecker {
220165// MARK: - Site Settings Related Eligibility Check
221166
222167private extension POSTabEligibilityChecker {
168+ enum SiteSettingsEligibilityState {
169+ case eligible
170+ case ineligible( reason: SiteSettingsIneligibleReason )
171+ }
172+
173+ enum SiteSettingsIneligibleReason {
174+ case siteSettingsNotAvailable
175+ case unsupportedCountry( supportedCountries: [ CountryCode ] )
176+ case unsupportedCurrency( supportedCurrencies: [ CurrencyCode ] )
177+ }
178+
223179 func checkSiteSettingsEligibility( ) async -> POSEligibilityState {
224- if let siteSettingsEligibility {
225- return siteSettingsEligibility
180+ let siteSettingsEligibility = await waitAndCheckSiteSettingsEligibility ( )
181+ switch siteSettingsEligibility {
182+ case . eligible:
183+ return . eligible
184+ case . ineligible( reason: let reason) :
185+ switch reason {
186+ case . siteSettingsNotAvailable, . unsupportedCountry:
187+ // This is an edge case where the store country is expected to be eligible from the visilibity check, but site settings might have
188+ // changed to an unsupported country during the session. In this case, we return an ineligible reason that prompts the merchant to
189+ // relaunch the app.
190+ return . ineligible( reason: . siteSettingsNotAvailable)
191+ case let . unsupportedCurrency( supportedCurrencies: supportedCurrencies) :
192+ return . ineligible( reason: . unsupportedCurrency( supportedCurrencies: supportedCurrencies) )
193+ }
226194 }
195+ }
227196
197+ func waitAndCheckSiteSettingsEligibility( ) async -> SiteSettingsEligibilityState {
228198 // Waits for the first site settings that matches the given site ID.
229199 let siteSettings = await waitForSiteSettingsRefresh ( )
230200 guard siteSettings. isNotEmpty else {
@@ -249,7 +219,7 @@ private extension POSTabEligibilityChecker {
249219 return [ ]
250220 }
251221
252- func isEligibleFromCountryAndCurrencyCode( countryCode: CountryCode , currencyCode: CurrencyCode ) -> POSEligibilityState {
222+ func isEligibleFromCountryAndCurrencyCode( countryCode: CountryCode , currencyCode: CurrencyCode ) -> SiteSettingsEligibilityState {
253223 let supportedCountries : [ CountryCode ] = [ . US, . GB]
254224 let supportedCurrencies : [ CountryCode : [ CurrencyCode ] ] = [ . US: [ . USD] ,
255225 . GB: [ . GBP] ]
@@ -270,15 +240,21 @@ private extension POSTabEligibilityChecker {
270240// MARK: - Remote Feature Flag Eligibility Check
271241
272242private extension POSTabEligibilityChecker {
273- @MainActor
274- func checkRemoteFeatureEligibility( ) async -> POSEligibilityState {
275- if let featureFlagEligibility {
276- return featureFlagEligibility
277- }
243+ enum RemoteFeatureFlagEligibilityState : Equatable {
244+ case eligible
245+ case ineligible( reason: RemoteFeatureFlagIneligibleReason )
246+ }
247+
248+ enum RemoteFeatureFlagIneligibleReason : Equatable {
249+ case selfDeallocated
250+ case featureFlagDisabled
251+ }
278252
253+ @MainActor
254+ func checkRemoteFeatureEligibility( ) async -> RemoteFeatureFlagEligibilityState {
279255 // Only whitelisted accounts in WPCOM have the Point of Sale remote feature flag enabled. These can be found at D159901-code
280256 // If the account is whitelisted, then the remote value takes preference over the local feature flag configuration
281- return await withCheckedContinuation { [ weak self] continuation in
257+ await withCheckedContinuation { [ weak self] continuation in
282258 guard let self else {
283259 return continuation. resume ( returning: . ineligible( reason: . selfDeallocated) )
284260 }
0 commit comments