Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions Modules/Sources/Experiments/DefaultFeatureFlagService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
return buildConfig == .localDeveloper || buildConfig == .alpha
case .ciabBookings:
return buildConfig == .localDeveloper || buildConfig == .alpha
case .ciab:
return buildConfig == .localDeveloper || buildConfig == .alpha
case .pointOfSaleSurveys:
return true
case .pointOfSaleCatalogAPI:
Expand Down
5 changes: 5 additions & 0 deletions Modules/Sources/Experiments/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ public enum FeatureFlag: Int {
///
case ciabBookings

/// Represents CIAB environment availability overall
/// Has same underlying logic as `ciabBookings` flag.
///
case ciab

/// Enables surveys for potential and current POS merchants
///
case pointOfSaleSurveys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import Foundation
import protocol Storage.StorageManagerType
import protocol NetworkingCore.Network
import protocol WooFoundation.CrashLogger
import enum WooFoundation.SeverityLevel

/// Determines whether an order is eligible for card present payment or not
///
public final class OrderCardPresentPaymentEligibilityStore: Store {
private let currentSite: () -> Site?
private let isCIABEnvironmentSupported: () -> Bool
private let crashLogger: CrashLogger
private lazy var siteCIABEligibilityChecker: CIABEligibilityCheckerProtocol = CIABEligibilityChecker(
currentSite: currentSite
)
Expand All @@ -14,9 +18,13 @@ public final class OrderCardPresentPaymentEligibilityStore: Store {
dispatcher: Dispatcher,
storageManager: StorageManagerType,
network: Network,
crashLogger: CrashLogger,
isCIABEnvironmentSupported: @escaping () -> Bool,
currentSite: @escaping () -> Site?
) {
self.currentSite = currentSite
self.isCIABEnvironmentSupported = isCIABEnvironmentSupported
self.crashLogger = crashLogger
super.init(
dispatcher: dispatcher,
storageManager: storageManager,
Expand Down Expand Up @@ -56,20 +64,38 @@ private extension OrderCardPresentPaymentEligibilityStore {
onCompletion: (Result<Bool, Error>) -> Void) {
let storage = storageManager.viewStorage

guard let site = storage.loadSite(siteID: siteID)?.toReadOnly() else {
return onCompletion(
.failure(
OrderIsEligibleForCardPresentPaymentError.siteNotFoundInStorage
)
)
}
/// The following checks are only relevant if CIAB is rolled out.
if isCIABEnvironmentSupported() {
let storageSite = storage.loadSite(siteID: siteID)?.toReadOnly()

guard siteCIABEligibilityChecker.isFeatureSupported(.cardReader, for: site) else {
return onCompletion(
.failure(
OrderIsEligibleForCardPresentPaymentError.cardReaderPaymentOptionIsNotSupportedForCIABSites
let site: Site?
if let storageSite {
site = storageSite
} else {
/// Non - fatal fallback to `currentSite` when a storage site is missing
site = currentSite()

logFailedStorageSiteRead(
siteID: siteID,
currentSiteFallbackValue: site
)
)
}

if let site {
guard siteCIABEligibilityChecker.isFeatureSupported(.cardReader, for: site) else {
return onCompletion(
.failure(
OrderIsEligibleForCardPresentPaymentError.cardReaderPaymentOptionIsNotSupportedForCIABSites
)
)
}
} else {
/// Don't interrupt the flow if the `site` is not found
/// Making the assumption that it's not a CIAB site and skipping those checks
///
/// Log an error
logFailedDefaultSiteRead(siteID: siteID)
}
}

guard let order = storage.loadOrder(siteID: siteID, orderID: orderID)?.toReadOnly() else {
Expand All @@ -83,10 +109,41 @@ private extension OrderCardPresentPaymentEligibilityStore {
}
}

/// Error logging
private extension OrderCardPresentPaymentEligibilityStore {
func logFailedStorageSiteRead(siteID: Int64, currentSiteFallbackValue: Site?) {
let message = "OrderCardPresentPaymentEligibilityStore: Storage site missing, falling back to currentSite."

DDLogError(message)

crashLogger.logMessage(
message,
properties: [
"siteID": siteID,
"currentSiteID": currentSiteFallbackValue?.siteID ?? "empty",
],
level: .error
)
}

func logFailedDefaultSiteRead(siteID: Int64) {
let message = "OrderCardPresentPaymentEligibilityStore: Current default site missing."

DDLogError(message)

crashLogger.logMessage(
"OrderCardPresentPaymentEligibilityStore: Current default site missing.",
properties: [
"requestedSiteID": siteID
],
level: .error
)
}
}

extension OrderCardPresentPaymentEligibilityStore {
enum OrderIsEligibleForCardPresentPaymentError: Error {
case orderNotFoundInStorage
case siteNotFoundInStorage
case cardReaderPaymentOptionIsNotSupportedForCIABSites
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ final class MockFeatureFlagService: POSFeatureFlagProviding {
var isProductImageOptimizedHandlingEnabled: Bool
var isFeatureFlagEnabledReturnValue: [FeatureFlag: Bool] = [:]
var isCIABBookingsEnabled: Bool
var isCIABEnabled: Bool

init(isInboxOn: Bool = false,
isShowInboxCTAEnabled: Bool = false,
Expand All @@ -47,7 +48,8 @@ final class MockFeatureFlagService: POSFeatureFlagProviding {
notificationSettings: Bool = false,
allowMerchantAIAPIKey: Bool = false,
isProductImageOptimizedHandlingEnabled: Bool = false,
isCIABBookingsEnabled: Bool = false) {
isCIABBookingsEnabled: Bool = false,
isCIABEnabled: Bool = false) {
self.isInboxOn = isInboxOn
self.isShowInboxCTAEnabled = isShowInboxCTAEnabled
self.isUpdateOrderOptimisticallyOn = isUpdateOrderOptimisticallyOn
Expand All @@ -70,6 +72,7 @@ final class MockFeatureFlagService: POSFeatureFlagProviding {
self.allowMerchantAIAPIKey = allowMerchantAIAPIKey
self.isProductImageOptimizedHandlingEnabled = isProductImageOptimizedHandlingEnabled
self.isCIABBookingsEnabled = isCIABBookingsEnabled
self.isCIABEnabled = isCIABEnabled
}

func isFeatureFlagEnabled(_ featureFlag: FeatureFlag) -> Bool {
Expand Down Expand Up @@ -124,6 +127,8 @@ final class MockFeatureFlagService: POSFeatureFlagProviding {
return isProductImageOptimizedHandlingEnabled
case .ciabBookings:
return isCIABBookingsEnabled
case .ciab:
return isCIABEnabled
default:
return false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import XCTest

@testable import Yosemite
@testable import Networking
@testable import WooFoundation

final class OrderCardPresentPaymentEligibilityStoreTests: XCTestCase {

Expand All @@ -28,6 +29,7 @@ final class OrderCardPresentPaymentEligibilityStoreTests: XCTestCase {
private var store: OrderCardPresentPaymentEligibilityStore!

private var currentSite: Site?
private var isCIABSupported = true

override func setUp() {
super.setUp()
Expand All @@ -38,6 +40,10 @@ final class OrderCardPresentPaymentEligibilityStoreTests: XCTestCase {
dispatcher: dispatcher,
storageManager: storageManager,
network: network,
crashLogger: MockCrashLogger(),
isCIABEnvironmentSupported: { [weak self] in
return self?.isCIABSupported ?? false
},
currentSite: { [weak self] in
return self?.currentSite
}
Expand All @@ -46,6 +52,7 @@ final class OrderCardPresentPaymentEligibilityStoreTests: XCTestCase {

override func tearDown() {
currentSite = nil
isCIABSupported = true
super.tearDown()
}

Expand Down Expand Up @@ -98,6 +105,53 @@ final class OrderCardPresentPaymentEligibilityStoreTests: XCTestCase {
XCTAssertTrue(eligibility)
}

func test_orderIsEligibleForCardPresentPayment_returns_true_for_eligible_order_and_none_stored_site() throws {
// Given
let orderItem = OrderItem.fake().copy(itemID: 1234,
name: "Chocolate cake",
productID: 678,
quantity: 1.0)
let cppEligibleOrder = Order.fake().copy(siteID: sampleSiteID,
orderID: 111,
status: .pending,
currency: "USD",
datePaid: nil,
total: "5.00",
paymentMethodID: "woocommerce_payments",
items: [orderItem])
let nonSubscriptionProduct = Product.fake().copy(siteID: sampleSiteID,
productID: 678,
name: "Chocolate cake",
productTypeKey: "simple")

let regularSite = Site.fake().copy(
siteID: sampleSiteID,
isGarden: false,
gardenName: nil
)
self.currentSite = regularSite

storageManager.insertSampleProduct(readOnlyProduct: nonSubscriptionProduct)
storageManager.insertSampleOrder(readOnlyOrder: cppEligibleOrder)

let configuration = CardPresentPaymentsConfiguration(country: .US)

// When
let result = waitFor { promise in
let action = OrderCardPresentPaymentEligibilityAction
.orderIsEligibleForCardPresentPayment(orderID: 111,
siteID: self.sampleSiteID,
cardPresentPaymentsConfiguration: configuration) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let eligibility = try XCTUnwrap(result.get())
XCTAssertTrue(eligibility)
}

func test_orderIsEligibleForCardPresentPayment_returns_failure_for_CIAB_sites() throws {
// Given
let orderItem = OrderItem.fake().copy(itemID: 1234,
Expand Down Expand Up @@ -147,4 +201,96 @@ final class OrderCardPresentPaymentEligibilityStoreTests: XCTestCase {
.cardReaderPaymentOptionIsNotSupportedForCIABSites)
}
}

func test_orderIsEligibleForCardPresentPayment_returns_success_when_site_is_CIAB_and_CIAB_not_supported() throws {
// Given

/// Simulate that the CIAB environment support is not yet rolled out
isCIABSupported = false

let orderItem = OrderItem.fake().copy(itemID: 1234,
name: "Chocolate cake",
productID: 678,
quantity: 1.0)
let cppEligibleOrder = Order.fake().copy(siteID: sampleSiteID,
orderID: 111,
status: .pending,
currency: "USD",
datePaid: nil,
total: "5.00",
paymentMethodID: "woocommerce_payments",
items: [orderItem])
let nonSubscriptionProduct = Product.fake().copy(siteID: sampleSiteID,
productID: 678,
name: "Chocolate cake",
productTypeKey: "simple")

let ciabSite = Site.fake().copy(
siteID: sampleSiteID,
isGarden: true,
gardenName: "commerce"
)
self.currentSite = ciabSite

storageManager.insertSampleSite(readOnlySite: ciabSite)
storageManager.insertSampleProduct(readOnlyProduct: nonSubscriptionProduct)
storageManager.insertSampleOrder(readOnlyOrder: cppEligibleOrder)

let configuration = CardPresentPaymentsConfiguration(country: .US)

// When
let result = waitFor { promise in
let action = OrderCardPresentPaymentEligibilityAction
.orderIsEligibleForCardPresentPayment(orderID: 111,
siteID: self.sampleSiteID,
cardPresentPaymentsConfiguration: configuration) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let eligibility = try XCTUnwrap(result.get())
XCTAssertTrue(eligibility)
}

func test_orderIsEligibleForCardPresentPayment_returns_success_when_site_is_not_obtained_and_CIAB_supported() throws {
// Given
let orderItem = OrderItem.fake().copy(itemID: 1234,
name: "Chocolate cake",
productID: 678,
quantity: 1.0)
let cppEligibleOrder = Order.fake().copy(siteID: sampleSiteID,
orderID: 111,
status: .pending,
currency: "USD",
datePaid: nil,
total: "5.00",
paymentMethodID: "woocommerce_payments",
items: [orderItem])
let nonSubscriptionProduct = Product.fake().copy(siteID: sampleSiteID,
productID: 678,
name: "Chocolate cake",
productTypeKey: "simple")

storageManager.insertSampleProduct(readOnlyProduct: nonSubscriptionProduct)
storageManager.insertSampleOrder(readOnlyOrder: cppEligibleOrder)

let configuration = CardPresentPaymentsConfiguration(country: .US)

// When
let result = waitFor { promise in
let action = OrderCardPresentPaymentEligibilityAction
.orderIsEligibleForCardPresentPayment(orderID: 111,
siteID: self.sampleSiteID,
cardPresentPaymentsConfiguration: configuration) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let eligibility = try XCTUnwrap(result.get())
XCTAssertTrue(eligibility)
}
}
6 changes: 5 additions & 1 deletion WooCommerce/Classes/Yosemite/AuthenticatedState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,12 @@ class AuthenticatedState: StoresManagerState {
dispatcher: dispatcher,
storageManager: storageManager,
network: network,
crashLogger: ServiceLocator.crashLogging,
isCIABEnvironmentSupported: {
ServiceLocator.featureFlagService.isFeatureFlagEnabled(.ciab)
},
currentSite: {
ServiceLocator.stores.sessionManager.defaultSite
sessionManager.defaultSite
}
),
OrderNoteStore(dispatcher: dispatcher, storageManager: storageManager, network: network),
Expand Down
Loading
Loading