Skip to content
Closed
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,40 @@ 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()

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
)
)
}
}

guard siteCIABEligibilityChecker.isFeatureSupported(.cardReader, for: site) else {
return onCompletion(
.failure(
OrderIsEligibleForCardPresentPaymentError.cardReaderPaymentOptionIsNotSupportedForCIABSites
guard let site else {
logFailedDefaultSiteRead(siteID: siteID)

return onCompletion(
.failure(
OrderIsEligibleForCardPresentPaymentError.failedToObtainSite
)
Comment on lines +87 to +90
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can continue with the eligibility check in this case, making the assumption that it's not a CIAB site and skipping those checks. It can be done later if you prefer.

)
)
}

guard siteCIABEligibilityChecker.isFeatureSupported(.cardReader, for: site) else {
return onCompletion(
.failure(
OrderIsEligibleForCardPresentPaymentError.cardReaderPaymentOptionIsNotSupportedForCIABSites
)
)
}
}

guard let order = storage.loadOrder(siteID: siteID, orderID: orderID)?.toReadOnly() else {
Expand All @@ -83,10 +111,42 @@ 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 failedToObtainSite
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps be slightly more descriptive here?

Not essential but may help future debugging/support

Suggested change
case failedToObtainSite
case failedToObtainSiteForCIABFeatureCheck

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@joshheald Addressed in 8ebbd2e. The flow now proceeds to orders check in case if a site wasn't obtained.

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,56 @@ 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)
}
}
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [*] Fix order details presentation when opened from booking details [https://github.com/woocommerce/woocommerce-ios/pull/16331]
- [*] Show POS feedback surveys for eligible merchants [https://github.com/woocommerce/woocommerce-ios/pull/16325]
- [*] Fix product variation selection for order creation [https://github.com/woocommerce/woocommerce-ios/pull/16317]
- [*] Attempt to fix missing card payment options [https://github.com/woocommerce/woocommerce-ios/pull/16411]
- [internal] Hide non-CIAB product types from filters [https://github.com/woocommerce/woocommerce-ios/pull/16354]
- [Internal] Fix warning when displaying offline banner on My Store [https://github.com/woocommerce/woocommerce-ios/pull/16347]
- [Internal] Notify listeners immediately after updating predicates or sort descriptors for results controllers. [https://github.com/woocommerce/woocommerce-ios/pull/16350]
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import Foundation
import XCTest
import Combine
import Fakes
import WooFoundation

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

private typealias Dependencies = PaymentMethodsViewModel.Dependencies

Expand Down Expand Up @@ -807,6 +807,8 @@ final class PaymentMethodsViewModelTests: XCTestCase {
dispatcher: Dispatcher(),
storageManager: storage,
network: MockNetwork(),
crashLogger: MockCrashLogger(),
isCIABEnvironmentSupported: { true },
currentSite: { ciabSite }
)

Expand Down