From 0a895da54f30b67276895e45984c8abf82cfefb5 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 7 Nov 2025 11:34:47 +0700 Subject: [PATCH 1/6] schedule POS notification if needed when onCollectPaymentTapped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merchants might follow another path for creating an order, through tapping directly in “Collect Payment” after adding products to an order, rather than “Create”. This would not schedule the POS notification for potential merchants. --- .../Orders/Order Creation/EditableOrderViewModel.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/EditableOrderViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/EditableOrderViewModel.swift index c2c76f187bb..eaed4782e45 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/EditableOrderViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/EditableOrderViewModel.swift @@ -1044,6 +1044,9 @@ final class EditableOrderViewModel: ObservableObject { guard let self else { return } self.collectPayment(for: order) self.trackCreateOrderSuccess(usesGiftCard: usesGiftCard) + Task { + await self.posNotificationScheduler.scheduleLocalNotificationIfEligible(for: .potentialMerchant) + } } onFailure: { [weak self] error, usesGiftCard in guard let self else { return } self.fixedNotice = NoticeFactory.createOrderErrorNotice(error, order: self.orderSynchronizer.order) From e344bcecaa7aeb2fe035bf24884b924725012467 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 7 Nov 2025 11:52:27 +0700 Subject: [PATCH 2/6] set pointOfSaleSurveys flag to true --- Modules/Sources/Experiments/DefaultFeatureFlagService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift index 35d93100b77..7e8a9168bb6 100644 --- a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift +++ b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift @@ -99,7 +99,7 @@ public struct DefaultFeatureFlagService: FeatureFlagService { case .ciabBookings: return buildConfig == .localDeveloper || buildConfig == .alpha case .pointOfSaleSurveys: - return buildConfig == .localDeveloper || buildConfig == .alpha + return true case .pointOfSaleCatalogAPI: return false default: From 3eaabf4ababb2bc32bf1d4e312f857cd8c6d61b9 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 7 Nov 2025 11:52:42 +0700 Subject: [PATCH 3/6] remove periphery flags --- WooCommerce/Classes/POS/POSNotificationScheduler.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/WooCommerce/Classes/POS/POSNotificationScheduler.swift b/WooCommerce/Classes/POS/POSNotificationScheduler.swift index 2557d3311a3..22b61322aa5 100644 --- a/WooCommerce/Classes/POS/POSNotificationScheduler.swift +++ b/WooCommerce/Classes/POS/POSNotificationScheduler.swift @@ -3,12 +3,10 @@ import UserNotifications import Yosemite import Experiments -// periphery: ignore - work in progress protocol POSNotificationScheduling { func scheduleLocalNotificationIfEligible(for merchantType: POSNotificationScheduler.MerchantType) async } -// periphery: ignore - work in progress final class POSNotificationScheduler: POSNotificationScheduling { enum MerchantType { case potentialMerchant From 31ba09c99cced7b2006ac78a3b9d081a2f4ca772 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 7 Nov 2025 12:05:16 +0700 Subject: [PATCH 4/6] add new check so current merchants are not eligible for potential merchant notification --- .../POS/POSNotificationScheduler.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/POSNotificationScheduler.swift b/WooCommerce/Classes/POS/POSNotificationScheduler.swift index 22b61322aa5..21e97d39bdb 100644 --- a/WooCommerce/Classes/POS/POSNotificationScheduler.swift +++ b/WooCommerce/Classes/POS/POSNotificationScheduler.swift @@ -62,7 +62,7 @@ final class POSNotificationScheduler: POSNotificationScheduling { func scheduleLocalNotificationIfEligible(for merchantType: POSNotificationScheduler.MerchantType) async { guard featureFlagService.isFeatureFlagEnabled(.pointOfSaleSurveys) else { return } - let isScheduled = await isNotificationScheduled(for: merchantType) + let isScheduled = await isNotificationAlreadyScheduled(for: merchantType) guard !isScheduled else { return } guard isCountryEligible() else { return } @@ -84,7 +84,23 @@ final class POSNotificationScheduler: POSNotificationScheduling { } } - private func isNotificationScheduled(for merchantType: MerchantType) async -> Bool { + private func isNotificationAlreadyScheduled(for merchantType: MerchantType) async -> Bool { + // Check if the specific notification type is already scheduled + let isCurrentMerchantTypeScheduled = await checkIfScheduled(for: merchantType) + if isCurrentMerchantTypeScheduled { + return true + } + + // Don't schedule notification for potential merchant if the user is already marked as current merchant + guard merchantType == .potentialMerchant else { + return false + } + + let isCurrentMerchantScheduled = await checkIfScheduled(for: .currentMerchant) + return isCurrentMerchantScheduled + } + + private func checkIfScheduled(for merchantType: MerchantType) async -> Bool { await withCheckedContinuation { continuation in let action: AppSettingsAction switch merchantType { From fcce2d22a9ba718069571843bcb529485212a8f3 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 7 Nov 2025 12:19:00 +0700 Subject: [PATCH 5/6] update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 608ae7dadc9..72ec7ed3083 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -6,6 +6,7 @@ - [**] We added support for collecting in-person payments (including Tap To Pay) using Stripe Payment Gateway extension in the UK. [https://github.com/woocommerce/woocommerce-ios/pull/16287] - [*] Improve card payments onboarding error handling to show network errors correctly [https://github.com/woocommerce/woocommerce-ios/pull/16304] - [*] Authenticate the admin page automatically for sites with SSO enabled in custom fields, in-person payment setup, and editing tax rates flows. [https://github.com/woocommerce/woocommerce-ios/pull/16318] +- [*] Show POS feedback surveys for eligible merchants [https://github.com/woocommerce/woocommerce-ios/pull/16325] 23.6 ----- From b0d5bed6944b7cd548052f12acc15a4df4df2094 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 7 Nov 2025 12:25:28 +0700 Subject: [PATCH 6/6] additional tests --- .../POS/POSNotificationSchedulerTests.swift | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/WooCommerce/WooCommerceTests/POS/POSNotificationSchedulerTests.swift b/WooCommerce/WooCommerceTests/POS/POSNotificationSchedulerTests.swift index 49dd2280407..e58126c5046 100644 --- a/WooCommerce/WooCommerceTests/POS/POSNotificationSchedulerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/POSNotificationSchedulerTests.swift @@ -260,6 +260,46 @@ struct POSNotificationSchedulerTests { #expect(mockPushNotesManager.requestedLocalNotifications.isEmpty) } + @Test func scheduleLocalNotificationIfEligible_when_currentMerchant_already_scheduled_then_potentialMerchant_cannot_be_scheduled() async throws { + // Given + let siteSettings = sampleSiteSettings(countryCode: "US") + mockFeatureFlagService.isFeatureFlagEnabledReturnValue[.pointOfSaleSurveys] = true + setupMockStores(isCurrentMerchantScheduled: true) + + let scheduler = POSNotificationScheduler( + stores: mockStores, + siteSettings: siteSettings, + featureFlagService: mockFeatureFlagService, + pushNotificationsManager: mockPushNotesManager + ) + + // When + await scheduler.scheduleLocalNotificationIfEligible(for: .potentialMerchant) + + // Then - No notification should be scheduled. Prevents backwards conversion from 'current' to 'potential' merchant. + #expect(mockPushNotesManager.requestedLocalNotifications.isEmpty) + } + + @Test func scheduleLocalNotificationIfEligible_when_potentialMerchant_already_scheduled_then_does_not_duplicate_notification() async throws { + // Given + let siteSettings = sampleSiteSettings(countryCode: "US") + mockFeatureFlagService.isFeatureFlagEnabledReturnValue[.pointOfSaleSurveys] = true + setupMockStores(isPotentialMerchantScheduled: true) + + let scheduler = POSNotificationScheduler( + stores: mockStores, + siteSettings: siteSettings, + featureFlagService: mockFeatureFlagService, + pushNotificationsManager: mockPushNotesManager + ) + + // When + await scheduler.scheduleLocalNotificationIfEligible(for: .potentialMerchant) + + // Then - No duplicate notification should be scheduled + #expect(mockPushNotesManager.requestedLocalNotifications.isEmpty) + } + private func sampleSiteSettings(countryCode: String) -> [SiteSetting] { [ SiteSetting.fake().copy(