Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f6c670d
Update eligibility based on order status
iamgabrielma Apr 28, 2025
c0b5a70
Handle receipt eligibility when failed order
iamgabrielma Apr 28, 2025
7711057
move funcs to private extension
iamgabrielma Apr 28, 2025
c26e0a8
typo: remove internal comment
iamgabrielma Apr 28, 2025
e8a3690
clean up dev version
iamgabrielma Apr 28, 2025
f353f87
Rename and lint
iamgabrielma Apr 28, 2025
1459a25
Merge branch 'trunk' into issue/woomob-104-allow-failed-orders-to-ope…
iamgabrielma Apr 29, 2025
c3dc2dd
dev and beta version checks no longer needed
iamgabrielma Apr 29, 2025
701421c
Move gateway check to use case
iamgabrielma Apr 29, 2025
26ca6d3
refactor isEligibleForFailedPaymentEmailReceipts to closure-based
iamgabrielma Apr 29, 2025
c055fe5
update test
iamgabrielma Apr 29, 2025
0aa4a1c
Merge branch 'trunk' into issue/woomob-104-allow-failed-orders-to-ope…
iamgabrielma May 6, 2025
66b212a
restore original check for payment eligibility
iamgabrielma May 6, 2025
8f20e0f
lint
iamgabrielma May 6, 2025
c231384
encapsulate meetsOrderStatusRequirement in receipt eligibility use case
iamgabrielma May 6, 2025
af4010d
update protocol
iamgabrielma May 6, 2025
843a4fa
Merge branch 'trunk' into issue/woomob-104-allow-failed-orders-to-ope…
iamgabrielma May 9, 2025
bff46d4
check for order status requirements on eligibility
iamgabrielma May 9, 2025
a1bbfac
restore previous implementation
iamgabrielma May 9, 2025
b7e4771
restore enum name. unnecessary change
iamgabrielma May 9, 2025
56ad1d0
clean up
iamgabrielma May 9, 2025
bb54851
release notes
iamgabrielma May 9, 2025
d2fda6c
remove unused function
iamgabrielma May 14, 2025
9eb0773
rename for consistency
iamgabrielma May 14, 2025
fcbbcb0
structure tests
iamgabrielma May 14, 2025
a04d1d7
more explicit on test name
iamgabrielma May 14, 2025
2b185a6
remove unnecessary check
iamgabrielma May 14, 2025
3862c54
Merge branch 'trunk' into issue/woomob-104-allow-failed-orders-to-ope…
iamgabrielma May 14, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -1517,11 +1517,27 @@ extension OrderDetailsDataSource {
}

private func isEligibleForBackendReceipt(completion: @escaping (Bool) -> Void) {
guard !isEligibleForPayment else {
guard isOrderStatusEligibleForReceipt else {
return completion(false)
}
ReceiptEligibilityUseCase().isEligibleForBackendReceipts { isEligibleForReceipt in
completion(isEligibleForReceipt)
let receiptEligibility = ReceiptEligibilityUseCase()

switch order.status {
case .completed, .processing, .refunded:
receiptEligibility.isEligibleForBackendReceipts { isEligibleForReceipt in
completion(isEligibleForReceipt)
}
case .failed:
receiptEligibility.selectedPaymentGatewayID { gatewayID in
guard let gatewayID = gatewayID else {
return completion(false)
}
receiptEligibility.isEligibleForFailedPaymentEmailReceipts(paymentGatewayID: gatewayID) { isEligibleForReceipt in
completion(isEligibleForReceipt)
}
}
default:
completion(false)
}
}

Expand Down Expand Up @@ -2033,3 +2049,13 @@ extension OrderDetailsDataSource {
static let cellDefaultMargin: CGFloat = 16
}
}

// MARK: - Receipts helpers
private extension OrderDetailsDataSource {
var isOrderStatusEligibleForReceipt: Bool {
order.status == .completed ||
order.status == .processing ||
order.status == .refunded ||
order.status == .failed
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,11 @@ final class ReceiptEligibilityUseCase: ReceiptEligibilityUseCaseProtocol {
guard let wcPlugin = wcPlugin, wcPlugin.active else {
return onCompletion(false)
}
// 2. If WooCommerce version is any of the specific API development branches, mark as eligible
if Constants.BackendReceipt.wcPluginDevVersion.contains(wcPlugin.version) {
onCompletion(true)
} else {
// 3. Else, if WooCommerce version is higher than minimum required version, mark as eligible
let isSupported = VersionHelpers.isVersionSupported(version: wcPlugin.version,
minimumRequired: Constants.BackendReceipt.wcPluginMinimumVersion)
onCompletion(isSupported)
}

// 2. If WooCommerce version is higher than minimum required version, mark as eligible
let isSupported = VersionHelpers.isVersionSupported(version: wcPlugin.version,
minimumRequired: Constants.BackendReceipt.wcPluginMinimumVersion)
onCompletion(isSupported)
}
stores.dispatch(action)
}
Expand Down Expand Up @@ -69,30 +65,52 @@ final class ReceiptEligibilityUseCase: ReceiptEligibilityUseCaseProtocol {
/// WooCommerce Stripe Gateway 9.1.0 aligns the app with the web and automatically sets the order as failed when the payment processing fails.
///
func isEligibleForFailedPaymentEmailReceipts(paymentGatewayID: String, onCompletion: @escaping (Bool) -> Void) {
Task { @MainActor in
async let wooCommerceSupported = isPluginSupported(Constants.wcPluginName,
minimumVersion: Constants.ReceiptAfterPayment.wcPluginMinimumVersion)

async let gatewaySupported: Bool = {
switch paymentGatewayID {
case CardPresentPaymentsPlugin.wcPay.gatewayID:
return await isPluginSupported(CardPresentPaymentsPlugin.wcPay.pluginName,
minimumVersion: Constants.ReceiptAfterPayment.wcPayPluginMinimumVersion)
case CardPresentPaymentsPlugin.stripe.gatewayID:
return await isPluginSupported(CardPresentPaymentsPlugin.stripe.pluginName,
minimumVersion: Constants.ReceiptAfterPayment.stripePluginMinimumVersion)
default:
return false
}
}()
isPluginSupported(Constants.wcPluginName, minimumVersion: Constants.FailedReceiptAfterPayment.wcPluginMinimumVersion) { isWooCommerceSupported in
guard isWooCommerceSupported else {
return onCompletion(false)
}

switch paymentGatewayID {
case CardPresentPaymentsPlugin.wcPay.gatewayID:
self.isPluginSupported(CardPresentPaymentsPlugin.wcPay.pluginName,
minimumVersion: Constants.FailedReceiptAfterPayment.wcPayPluginMinimumVersion,
onCompletion: onCompletion)
case CardPresentPaymentsPlugin.stripe.gatewayID:
self.isPluginSupported(CardPresentPaymentsPlugin.stripe.pluginName,
minimumVersion: Constants.FailedReceiptAfterPayment.stripePluginMinimumVersion,
onCompletion: onCompletion)
default:
onCompletion(false)
}
}
}

let (isWooCommerceSupported, isGatewaySupported) = await (wooCommerceSupported, gatewaySupported)
onCompletion(isWooCommerceSupported && isGatewaySupported)
// Returns the current payment gateway ID, needed when checking if a transaction is eligible for receipts
// based on which plugin and versions are active
func selectedPaymentGatewayID(onCompletion: @escaping (String?) -> Void) {
let action = CardPresentPaymentAction.selectedPaymentGatewayAccount { paymentGatewayAccount in
onCompletion(paymentGatewayAccount?.gatewayID)
}
stores.dispatch(action)
}
}

private extension ReceiptEligibilityUseCase {
func isPluginSupported(_ pluginName: String, minimumVersion: String, onCompletion: @escaping (Bool) -> Void) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: is this still used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is not! d2fda6c

let action = SystemStatusAction.fetchSystemPlugin(siteID: siteID, systemPluginName: pluginName) { plugin in
// Plugin must be installed and active
guard let plugin, plugin.active else {
return onCompletion(false)
}

// If plugin version is higher than minimum required version, mark as eligible
let isSupported = VersionHelpers.isVersionSupported(version: plugin.version,
minimumRequired: minimumVersion)
onCompletion(isSupported)
}
stores.dispatch(action)
}

@MainActor
func isPluginSupported(_ pluginName: String, minimumVersion: String) async -> Bool {
await withCheckedContinuation { continuation in
Expand All @@ -102,11 +120,6 @@ private extension ReceiptEligibilityUseCase {
return continuation.resume(returning: false)
}

// Checking for concrete versions to cover dev and beta versions
if plugin.version.contains(minimumVersion) {
return continuation.resume(returning: true)
}

Comment on lines -105 to -109
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ What are the reasons for removing the check that supports dev and beta versions?
Saw this mentioned in the PR description:

Removed the logic to handle dev woocommerce versions

When testing with Woo Beta Tester plugin or for core development, I thought it's common to have the WC version as the dev/beta versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this was very specific to this project: The internals of VersionHelpers used in the same function already checks for beta and dev version that might come with Woo Beta Tester, so it will pass the check as expected. This additional check was put in place just for testing, due to how the changes were provided by core folks with specific development branches to be installed in the test site, which sometimes were behind the latest versions.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm actually having a similar plugin version check in a separate PR and removing this check blocks -beta/-dev/-alpha/-rc. From the test cases for the VersionHelpers, this blocking behavior also seems intentional:

VersionTestCase(foundVersion: "1.0.0-dev", requiredMinimumVersion: "1.0.0", meetsMinimum: false),
VersionTestCase(foundVersion: "1.0.0-alpha", requiredMinimumVersion: "1.0.0", meetsMinimum: false),
VersionTestCase(foundVersion: "1.0.0-a", requiredMinimumVersion: "1.0.0", meetsMinimum: false),
VersionTestCase(foundVersion: "1.0.0-beta", requiredMinimumVersion: "1.0.0", meetsMinimum: false),
VersionTestCase(foundVersion: "1.0.0-b", requiredMinimumVersion: "1.0.0", meetsMinimum: false),
VersionTestCase(foundVersion: "1.0.0-RC1", requiredMinimumVersion: "1.0.0", meetsMinimum: false),
VersionTestCase(foundVersion: "1.0.0-rc1", requiredMinimumVersion: "1.0.0", meetsMinimum: false),

If we want to support alpha/beta/dev/rc builds of the same version, I think we do need to keep this check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we want to support alpha/beta/dev/rc builds of the same version

Interesting, I see what you mean: Anything tagged as -dev or other for the same version we're checking, is always below the non-tagged version:

po VersionHelpers.isVersionSupported(version: "9.0-dev", minimumRequired: "9.0.0")
false

po VersionHelpers.isVersionSupported(version: "9.0.1-dev", minimumRequired: "9.0.0")
true

// If plugin version is higher than minimum required version, mark as eligible
let isSupported = VersionHelpers.isVersionSupported(version: plugin.version,
minimumRequired: minimumVersion)
Expand All @@ -123,10 +136,9 @@ private extension ReceiptEligibilityUseCase {

enum BackendReceipt {
static let wcPluginMinimumVersion = "8.7.0"
static let wcPluginDevVersion: [String] = ["8.7.0-dev", "8.6.0-dev"]
}

enum ReceiptAfterPayment {
enum FailedReceiptAfterPayment {
static let wcPluginMinimumVersion = "9.5.0"
static let wcPayPluginMinimumVersion = "8.6.0"
static let stripePluginMinimumVersion = "9.1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,6 @@ import Yosemite
@testable import WooCommerce

final class ReceiptEligibilityUseCaseTests: XCTestCase {

func test_when_WooCommerce_version_is_incorrect_dev_version_then_returns_false() {
// Given
let stores = MockStoresManager(sessionManager: .makeForTesting())
let plugin = SystemPlugin.fake().copy(name: "WooCommerce",
version: "8.6.0-dev-wrong-version",
active: true)

stores.whenReceivingAction(ofType: SystemStatusAction.self) { action in
switch action {
case let .fetchSystemPlugin(_, _, onCompletion):
onCompletion(plugin)
default:
XCTFail("Unexpected action")
}
}
let sut = ReceiptEligibilityUseCase(stores: stores)

// When
let isEligible: Bool = waitFor { promise in
sut.isEligibleForBackendReceipts(onCompletion: { result in
promise(result)
})
}

// Then
XCTAssertFalse(isEligible)
}

func test_when_WooCommerce_version_is_correct_dev_version_then_returns_true() {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we do decide not to support WC dev version, it might be worth keeping the test and flipping the assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above, the dev/beta versions that follow the actual core deployment process are already covered by the version helper class. This was a very specific case on how the feature was delivered by core (eg: feature released publicly on 9.7 last minute, but we had to work with 9.6 internally) so we added the tests as well. Now are no longer necessary as it's part of the correct core version.

// Given
let stores = MockStoresManager(sessionManager: .makeForTesting())
let plugin = SystemPlugin.fake().copy(name: "WooCommerce",
version: "8.6.0-dev",
active: true)

stores.whenReceivingAction(ofType: SystemStatusAction.self) { action in
switch action {
case let .fetchSystemPlugin(_, _, onCompletion):
onCompletion(plugin)
default:
XCTFail("Unexpected action")
}
}
let sut = ReceiptEligibilityUseCase(stores: stores)

// When
let isEligible: Bool = waitFor { promise in
sut.isEligibleForBackendReceipts(onCompletion: { result in
promise(result)
})
}

// Then
XCTAssertTrue(isEligible)
}

func test_when_WooCommerce_version_is_below_minimum_then_returns_false() {
// Given
let stores = MockStoresManager(sessionManager: .makeForTesting())
Expand Down Expand Up @@ -182,38 +125,6 @@ final class ReceiptEligibilityUseCaseTests: XCTestCase {
XCTAssertTrue(isEligible)
}

func test_isEligibleForFailedPaymentEmailReceipts_when_plugins_are_supported_dev_then_returns_true() {
// Given
let stores = MockStoresManager(sessionManager: .makeForTesting())
let wooCommercePlugin = SystemPlugin.fake().copy(name: "WooCommerce", version: "9.6.0-dev-1181231238", active: true)
let wooPaymentsPlugin = SystemPlugin.fake().copy(name: CardPresentPaymentsPlugin.wcPay.pluginName, version: "8.6.0-test-1", active: true)

stores.whenReceivingAction(ofType: SystemStatusAction.self) { action in
switch action {
case let .fetchSystemPlugin(_, systemPluginName, onCompletion):
if systemPluginName == "WooCommerce" {
onCompletion(wooCommercePlugin)
} else if systemPluginName == CardPresentPaymentsPlugin.wcPay.pluginName {
onCompletion(wooPaymentsPlugin)
}
default:
XCTFail("Unexpected action")
}
}

let sut = ReceiptEligibilityUseCase(stores: stores)

// When
let isEligible: Bool = waitFor { promise in
sut.isEligibleForFailedPaymentEmailReceipts(paymentGatewayID: CardPresentPaymentsPlugin.wcPay.gatewayID, onCompletion: { result in
promise(result)
})
}

// Then
XCTAssertTrue(isEligible)
}

func test_isEligibleForFailedPaymentEmailReceipts_when_woopayments_version_is_incorrect_then_returns_false() {
// Given
let stores = MockStoresManager(sessionManager: .makeForTesting())
Expand Down