Skip to content

Commit 09e8f0e

Browse files
authored
Send Receipt After Payment: Add eligibility checker (#14446)
2 parents 9b91dc3 + 01c94d9 commit 09e8f0e

File tree

3 files changed

+211
-7
lines changed

3 files changed

+211
-7
lines changed

WooCommerce/Classes/ViewModels/Order Details/Receipts/ReceiptEligibilityUseCase.swift

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,28 +32,76 @@ final class ReceiptEligibilityUseCase: ReceiptEligibilityUseCaseProtocol {
3232
return onCompletion(false)
3333
}
3434
// 2. If WooCommerce version is any of the specific API development branches, mark as eligible
35-
if Constants.wcPluginDevVersion.contains(wcPlugin.version) {
35+
if Constants.BackendReceipt.wcPluginDevVersion.contains(wcPlugin.version) {
3636
onCompletion(true)
3737
} else {
3838
// 3. Else, if WooCommerce version is higher than minimum required version, mark as eligible
3939
let isSupported = VersionHelpers.isVersionSupported(version: wcPlugin.version,
40-
minimumRequired: Constants.wcPluginMinimumVersion)
40+
minimumRequired: Constants.BackendReceipt.wcPluginMinimumVersion)
4141
onCompletion(isSupported)
4242
}
4343
}
4444
stores.dispatch(action)
4545
}
4646

4747
func isEligibleSendingReceiptAfterPayment(onCompletion: @escaping (Bool) -> Void) {
48-
// TODO: WooCommerce 9.5.0
49-
onCompletion(featureFlagService.isFeatureFlagEnabled(.sendReceiptAfterPayment))
48+
guard featureFlagService.isFeatureFlagEnabled(.sendReceiptAfterPayment) else {
49+
return onCompletion(false)
50+
}
51+
52+
Task { @MainActor in
53+
async let isWooCommerceSupported = isPluginSupported(Constants.wcPluginName,
54+
minimumVersion: Constants.ReceiptAfterPayment.wcPluginMinimumVersion)
55+
async let isWooPaymentsSupported = isPluginSupported(Constants.wcPayPluginName,
56+
minimumVersion: Constants.ReceiptAfterPayment.wcPayPluginMinimumVersion)
57+
let wooCommerceResult = await isWooCommerceSupported
58+
let wooPaymentsResult = await isWooPaymentsSupported
59+
let isSupported = wooCommerceResult && wooPaymentsResult
60+
61+
onCompletion(isSupported)
62+
}
63+
}
64+
}
65+
66+
private extension ReceiptEligibilityUseCase {
67+
@MainActor
68+
func isPluginSupported(_ pluginName: String, minimumVersion: String) async -> Bool {
69+
await withCheckedContinuation { continuation in
70+
let action = SystemStatusAction.fetchSystemPlugin(siteID: siteID, systemPluginName: pluginName) { plugin in
71+
// Plugin must be installed and active
72+
guard let plugin, plugin.active else {
73+
return continuation.resume(returning: false)
74+
}
75+
76+
// Checking for concrete versions to cover dev and beta versions
77+
if plugin.version.contains(minimumVersion) {
78+
return continuation.resume(returning: true)
79+
}
80+
81+
// If plugin version is higher than minimum required version, mark as eligible
82+
let isSupported = VersionHelpers.isVersionSupported(version: plugin.version,
83+
minimumRequired: minimumVersion)
84+
continuation.resume(returning: isSupported)
85+
}
86+
stores.dispatch(action)
87+
}
5088
}
5189
}
5290

5391
private extension ReceiptEligibilityUseCase {
5492
enum Constants {
5593
static let wcPluginName = "WooCommerce"
56-
static let wcPluginMinimumVersion = "8.7.0"
57-
static let wcPluginDevVersion: [String] = ["8.7.0-dev", "8.6.0-dev"]
94+
static let wcPayPluginName = "WooPayments"
95+
96+
enum BackendReceipt {
97+
static let wcPluginMinimumVersion = "8.7.0"
98+
static let wcPluginDevVersion: [String] = ["8.7.0-dev", "8.6.0-dev"]
99+
}
100+
101+
enum ReceiptAfterPayment {
102+
static let wcPluginMinimumVersion = "9.5.0"
103+
static let wcPayPluginMinimumVersion = "8.6.0"
104+
}
105+
58106
}
59107
}

WooCommerce/WooCommerceTests/Mocks/MockFeatureFlagService.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct MockFeatureFlagService: FeatureFlagService {
2424
private let favoriteProducts: Bool
2525
private let paymentsOnboardingInPointOfSale: Bool
2626
private let isProductGlobalUniqueIdentifierSupported: Bool
27+
private let isSendReceiptAfterPaymentEnabled: Bool
2728

2829
init(isInboxOn: Bool = false,
2930
isShowInboxCTAEnabled: Bool = false,
@@ -46,7 +47,8 @@ struct MockFeatureFlagService: FeatureFlagService {
4647
viewEditCustomFieldsInProductsAndOrders: Bool = false,
4748
favoriteProducts: Bool = false,
4849
paymentsOnboardingInPointOfSale: Bool = false,
49-
isProductGlobalUniqueIdentifierSupported: Bool = false) {
50+
isProductGlobalUniqueIdentifierSupported: Bool = false,
51+
isSendReceiptAfterPaymentEnabled: Bool = false) {
5052
self.isInboxOn = isInboxOn
5153
self.isShowInboxCTAEnabled = isShowInboxCTAEnabled
5254
self.isUpdateOrderOptimisticallyOn = isUpdateOrderOptimisticallyOn
@@ -69,6 +71,7 @@ struct MockFeatureFlagService: FeatureFlagService {
6971
self.favoriteProducts = favoriteProducts
7072
self.paymentsOnboardingInPointOfSale = paymentsOnboardingInPointOfSale
7173
self.isProductGlobalUniqueIdentifierSupported = isProductGlobalUniqueIdentifierSupported
74+
self.isSendReceiptAfterPaymentEnabled = isSendReceiptAfterPaymentEnabled
7275
}
7376

7477
func isFeatureFlagEnabled(_ featureFlag: FeatureFlag) -> Bool {
@@ -117,6 +120,8 @@ struct MockFeatureFlagService: FeatureFlagService {
117120
return paymentsOnboardingInPointOfSale
118121
case .productGlobalUniqueIdentifierSupport:
119122
return isProductGlobalUniqueIdentifierSupported
123+
case .sendReceiptAfterPayment:
124+
return isSendReceiptAfterPaymentEnabled
120125
default:
121126
return false
122127
}

WooCommerce/WooCommerceTests/ViewModels/Receipts/ReceiptEligibilityUseCaseTests.swift

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,155 @@ final class ReceiptEligibilityUseCaseTests: XCTestCase {
136136
// Then
137137
XCTAssertTrue(isEligible)
138138
}
139+
140+
// MARK: - Send Receipt After Payment
141+
142+
func test_isEligibleSendingReceiptAfterPayment_when_feature_flag_is_disabled_then_returns_false() {
143+
// Given
144+
let featureFlag = MockFeatureFlagService(isSendReceiptAfterPaymentEnabled: false)
145+
let stores = MockStoresManager(sessionManager: .makeForTesting())
146+
let sut = ReceiptEligibilityUseCase(stores: stores, featureFlagService: featureFlag)
147+
148+
// When
149+
let isEligible: Bool = waitFor { promise in
150+
sut.isEligibleSendingReceiptAfterPayment(onCompletion: { result in
151+
promise(result)
152+
})
153+
}
154+
155+
// Then
156+
XCTAssertFalse(isEligible)
157+
}
158+
159+
func test_isEligibleSendingReceiptAfterPayment_when_plugins_are_inactive_then_returns_false() {
160+
// Given
161+
let featureFlag = MockFeatureFlagService(isSendReceiptAfterPaymentEnabled: true)
162+
let stores = MockStoresManager(sessionManager: .makeForTesting())
163+
let wooCommercePlugin = SystemPlugin.fake().copy(name: "WooCommerce", version: "9.6.0", active: false)
164+
let wooPaymentsPlugin = SystemPlugin.fake().copy(name: "WooPayments", version: "8.9.0", active: false)
165+
166+
stores.whenReceivingAction(ofType: SystemStatusAction.self) { action in
167+
switch action {
168+
case let .fetchSystemPlugin(_, systemPluginName, onCompletion):
169+
if systemPluginName == "WooCommerce" {
170+
onCompletion(wooCommercePlugin)
171+
} else if systemPluginName == "WooPayments" {
172+
onCompletion(wooPaymentsPlugin)
173+
}
174+
default:
175+
XCTFail("Unexpected action")
176+
}
177+
}
178+
179+
let sut = ReceiptEligibilityUseCase(stores: stores, featureFlagService: featureFlag)
180+
181+
// When
182+
let isEligible: Bool = waitFor { promise in
183+
sut.isEligibleSendingReceiptAfterPayment(onCompletion: { result in
184+
promise(result)
185+
})
186+
}
187+
188+
// Then
189+
XCTAssertFalse(isEligible)
190+
}
191+
192+
func test_isEligibleSendingReceiptAfterPayment_when_plugins_are_supported_then_returns_true() {
193+
// Given
194+
let featureFlag = MockFeatureFlagService(isSendReceiptAfterPaymentEnabled: true)
195+
let stores = MockStoresManager(sessionManager: .makeForTesting())
196+
let wooCommercePlugin = SystemPlugin.fake().copy(name: "WooCommerce", version: "9.5.0", active: true)
197+
let wooPaymentsPlugin = SystemPlugin.fake().copy(name: "WooPayments", version: "8.6.0", active: true)
198+
199+
stores.whenReceivingAction(ofType: SystemStatusAction.self) { action in
200+
switch action {
201+
case let .fetchSystemPlugin(_, systemPluginName, onCompletion):
202+
if systemPluginName == "WooCommerce" {
203+
onCompletion(wooCommercePlugin)
204+
} else if systemPluginName == "WooPayments" {
205+
onCompletion(wooPaymentsPlugin)
206+
}
207+
default:
208+
XCTFail("Unexpected action")
209+
}
210+
}
211+
212+
let sut = ReceiptEligibilityUseCase(stores: stores, featureFlagService: featureFlag)
213+
214+
// When
215+
let isEligible: Bool = waitFor { promise in
216+
sut.isEligibleSendingReceiptAfterPayment(onCompletion: { result in
217+
promise(result)
218+
})
219+
}
220+
221+
// Then
222+
XCTAssertTrue(isEligible)
223+
}
224+
225+
func test_isEligibleSendingReceiptAfterPayment_when_plugins_are_supported_dev_then_returns_true() {
226+
// Given
227+
let featureFlag = MockFeatureFlagService(isSendReceiptAfterPaymentEnabled: true)
228+
let stores = MockStoresManager(sessionManager: .makeForTesting())
229+
let wooCommercePlugin = SystemPlugin.fake().copy(name: "WooCommerce", version: "9.6.0-dev-1181231238", active: true)
230+
let wooPaymentsPlugin = SystemPlugin.fake().copy(name: "WooPayments", version: "8.6.0-test-1", active: true)
231+
232+
stores.whenReceivingAction(ofType: SystemStatusAction.self) { action in
233+
switch action {
234+
case let .fetchSystemPlugin(_, systemPluginName, onCompletion):
235+
if systemPluginName == "WooCommerce" {
236+
onCompletion(wooCommercePlugin)
237+
} else if systemPluginName == "WooPayments" {
238+
onCompletion(wooPaymentsPlugin)
239+
}
240+
default:
241+
XCTFail("Unexpected action")
242+
}
243+
}
244+
245+
let sut = ReceiptEligibilityUseCase(stores: stores, featureFlagService: featureFlag)
246+
247+
// When
248+
let isEligible: Bool = waitFor { promise in
249+
sut.isEligibleSendingReceiptAfterPayment(onCompletion: { result in
250+
promise(result)
251+
})
252+
}
253+
254+
// Then
255+
XCTAssertTrue(isEligible)
256+
}
257+
258+
func test_isEligibleSendingReceiptAfterPayment_when_woopayments_version_is_incorrect_then_returns_false() {
259+
// Given
260+
let featureFlag = MockFeatureFlagService(isSendReceiptAfterPaymentEnabled: true)
261+
let stores = MockStoresManager(sessionManager: .makeForTesting())
262+
let wooCommercePlugin = SystemPlugin.fake().copy(name: "WooCommerce", version: "9.5.0", active: true)
263+
let wooPaymentsPlugin = SystemPlugin.fake().copy(name: "WooPayments", version: "5.0.0-dev", active: true)
264+
265+
stores.whenReceivingAction(ofType: SystemStatusAction.self) { action in
266+
switch action {
267+
case let .fetchSystemPlugin(_, systemPluginName, onCompletion):
268+
if systemPluginName == "WooCommerce" {
269+
onCompletion(wooCommercePlugin)
270+
} else if systemPluginName == "WooPayments" {
271+
onCompletion(wooPaymentsPlugin)
272+
}
273+
default:
274+
XCTFail("Unexpected action")
275+
}
276+
}
277+
278+
let sut = ReceiptEligibilityUseCase(stores: stores, featureFlagService: featureFlag)
279+
280+
// When
281+
let isEligible: Bool = waitFor { promise in
282+
sut.isEligibleSendingReceiptAfterPayment(onCompletion: { result in
283+
promise(result)
284+
})
285+
}
286+
287+
// Then
288+
XCTAssertFalse(isEligible)
289+
}
139290
}

0 commit comments

Comments
 (0)