From 363e0b0b6c03cdba7a089e560f977bbe386e0222 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Fri, 29 Aug 2025 15:08:46 +0800 Subject: [PATCH 1/5] Add a new `checkout_cash_payment_tapped` POS event and track it when the merchant taps on cash payment from the checkout screen. Move the existing `cash_payment_tapped` event to be tracked when marking cash payment as completed. --- WooCommerce/Classes/Analytics/TracksProvider.swift | 1 + WooCommerce/Classes/Analytics/WooAnalyticsStat.swift | 1 + WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift | 2 +- .../Classes/POS/Presentation/PointOfSaleCollectCashView.swift | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/Analytics/TracksProvider.swift b/WooCommerce/Classes/Analytics/TracksProvider.swift index 75f69d07b29..30e352a7602 100644 --- a/WooCommerce/Classes/Analytics/TracksProvider.swift +++ b/WooCommerce/Classes/Analytics/TracksProvider.swift @@ -139,6 +139,7 @@ private extension TracksProvider { WooAnalyticsStat.pointOfSaleViewDocsTapped, WooAnalyticsStat.pointOfSaleReaderReadyForCardPayment, WooAnalyticsStat.pointOfSaleCashCollectPaymentSuccess, + WooAnalyticsStat.pointOfSaleCheckoutCashPaymentTapped, WooAnalyticsStat.pointOfSaleCashPaymentTapped, WooAnalyticsStat.pointOfSaleCashPaymentFailed, WooAnalyticsStat.pointOfSaleItemsHeaderTapped, diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index b3d0c9336a5..a47883063c3 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -1257,6 +1257,7 @@ enum WooAnalyticsStat: String { case pointOfSaleItemRemovedFromCart = "item_removed_from_cart" case pointOfSaleCheckoutTapped = "checkout_tapped" case pointOfSaleBackToCartTapped = "back_to_cart_tapped" + case pointOfSaleCheckoutCashPaymentTapped = "checkout_cash_payment_tapped" case pointOfSaleCashPaymentTapped = "cash_payment_tapped" case pointOfSaleCashPaymentFailed = "cash_payment_failed" case pointOfSaleBackToCheckoutFromCashTapped = "back_to_checkout_from_cash" diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index fe5970a002b..4e4a6ec85ec 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -368,7 +368,7 @@ extension PointOfSaleAggregateModel { // Once we get the callback from the card service, we switch to cash collection state @MainActor func startCashPayment() async { - analytics.track(.pointOfSaleCashPaymentTapped) + analytics.track(.pointOfSaleCheckoutCashPaymentTapped) try? await cardPresentPaymentService.cancelPayment() paymentState.cash = .collectingCash } diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index 3a1fdf88c43..4dfa514b474 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -121,6 +121,7 @@ struct PointOfSaleCollectCashView: View { private extension PointOfSaleCollectCashView { private func submitCashAmount() async { + ServiceLocator.analytics.track(.pointOfSaleCashPaymentTapped) guard validateAmountOnSubmit() else { return } From 71192196602988e576f75b208d87332cc0869adf Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Fri, 29 Aug 2025 15:41:12 +0800 Subject: [PATCH 2/5] PointOfSaleCollectCashView: prefill text field with order total, update button enabled state based on input & loading state, and remove validation error. --- .../PointOfSaleCollectCashView.swift | 26 +- .../ViewHelpers/CollectCashViewHelper.swift | 28 +- .../CollectCashViewHelperTests.swift | 352 +++++++++++++----- 3 files changed, 289 insertions(+), 117 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index 4dfa514b474..7225a0de7c1 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -25,6 +25,12 @@ struct PointOfSaleCollectCashView: View { String.localizedStringWithFormat(Localization.backNavigationSubtitle, orderTotal) } + private var isButtonEnabled: Bool { + viewHelper.isPaymentButtonEnabled(orderTotal: orderTotal, + textFieldAmountInput: textFieldAmountInput, + isLoading: isLoading) + } + @StateObject private var textFieldViewModel = FormattableAmountTextFieldViewModel(size: .extraLarge, locale: Locale.autoupdatingCurrent, storeCurrencySettings: ServiceLocator.currencySettings, @@ -89,7 +95,7 @@ struct PointOfSaleCollectCashView: View { .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) .frame(maxWidth: .infinity) .dynamicTypeSize(...DynamicTypeSize.accessibility1) - .disabled(isLoading) + .disabled(!isButtonEnabled) } .padding([.horizontal]) .padding(.bottom, max(keyboardFrame.height - geometry.safeAreaInsets.bottom, @@ -110,6 +116,9 @@ struct PointOfSaleCollectCashView: View { } } .frame(maxWidth: .infinity, maxHeight: .infinity) + .onAppear { + prefillOrderTotal() + } } private func markComplete() async throws { @@ -122,9 +131,6 @@ struct PointOfSaleCollectCashView: View { private extension PointOfSaleCollectCashView { private func submitCashAmount() async { ServiceLocator.analytics.track(.pointOfSaleCashPaymentTapped) - guard validateAmountOnSubmit() else { - return - } isLoading = true do { try await markComplete() @@ -141,14 +147,12 @@ private extension PointOfSaleCollectCashView { textFieldAmountInput: textFieldAmountInput) } - private func validateAmountOnSubmit() -> Bool { - viewHelper.validateAmountOnSubmit( - orderTotal: orderTotal, - textFieldAmountInput: textFieldAmountInput, - onError: { error in - errorMessage = error - }) + private func prefillOrderTotal() { + if let orderDecimal = viewHelper.parseCurrency(orderTotal) { + textFieldViewModel.presetAmount(orderDecimal) } + isTextFieldFocused = true + } } private extension PointOfSaleCollectCashView { diff --git a/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift b/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift index cb159643385..06a56f33e74 100644 --- a/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift +++ b/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift @@ -54,25 +54,17 @@ final class CollectCashViewHelper { } } - func validateAmountOnSubmit(orderTotal: String, + func isPaymentButtonEnabled(orderTotal: String, textFieldAmountInput: String, - onError: (String) -> Void) -> Bool { - let userInput = textFieldAmountInput.isNotEmpty ? textFieldAmountInput : "0" - + isLoading: Bool) -> Bool { guard let orderDecimal = parseCurrency(orderTotal), - let inputDecimal = parseCurrency(userInput) else { - onError(Localization.failedToCollectCashPayment) - return false - } - - if inputDecimal < orderDecimal { - onError(Localization.cashPaymentAmountNotEnough) + let inputDecimal = parseCurrency(textFieldAmountInput.isNotEmpty ? textFieldAmountInput : "0") else { return false } - return true + return inputDecimal >= orderDecimal && !isLoading } - private func parseCurrency(_ amountString: String) -> Decimal? { + func parseCurrency(_ amountString: String) -> Decimal? { // Removes all leading/trailing whitespace, if any let sanitized = amountString.trimmingCharacters(in: .whitespacesAndNewlines) @@ -101,16 +93,6 @@ private extension CollectCashViewHelper { comment: "Change due when the cash amount entered exceeds the order total." + "Reads as 'Change due: $1.23'" ) - static let failedToCollectCashPayment = NSLocalizedString( - "collectcashviewhelper.failedtocollectcashpayment.errormessage", - value: "Error trying to process payment. Try again.", - comment: "Error message when the system fails to collect a cash payment." - ) - static let cashPaymentAmountNotEnough = NSLocalizedString( - "collectcashviewhelper.cashpaymentamountnotenough.errormessage", - value: "Amount must be more or equal to total.", - comment: "Error message when the cash amount entered is less than the order total." - ) } } diff --git a/WooCommerce/WooCommerceTests/POS/ViewHelpers/CollectCashViewHelperTests.swift b/WooCommerce/WooCommerceTests/POS/ViewHelpers/CollectCashViewHelperTests.swift index 954396466bd..a61757a81a9 100644 --- a/WooCommerce/WooCommerceTests/POS/ViewHelpers/CollectCashViewHelperTests.swift +++ b/WooCommerce/WooCommerceTests/POS/ViewHelpers/CollectCashViewHelperTests.swift @@ -1,3 +1,4 @@ +import Foundation import Testing @testable import WooCommerce @@ -89,194 +90,201 @@ struct CollectCashViewHelperTests { #expect(result == nil) } - @Test func validateAmountOnSubmit_when_invalid_orderDecimal_then_returns_false_with_expected_error_message() { + @Test func updateChangeDueMessage_when_input_has_both_dot_and_comma_grouping_separators_then_returns_formatted_change_due_correctly() { + // Given + let orderTotal = "$1,234.00" + let inputAmount = "$1,235.00" + let expectedMessage = "Change due: $1.00" + + // When + let result = sut.updatechangeDueMessage( + orderTotal: orderTotal, + textFieldAmountInput: inputAmount) + + // Then + #expect(result == expectedMessage) + } + + // MARK: - `isPaymentButtonEnabled` Tests + + @Test func isPaymentButtonEnabled_when_invalid_orderDecimal_then_returns_false() { // Given let invalidOrderTotal = "not a value" let validAmountInput = "20.00" - let expectedErrorMessage = "Error trying to process payment. Try again." - var capturedErrorMessage: String? + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: invalidOrderTotal, textFieldAmountInput: validAmountInput, - onError: { error in - capturedErrorMessage = error - }) + isLoading: isLoading) // Then #expect(result == false) - #expect(capturedErrorMessage == expectedErrorMessage) } - @Test func validateAmountOnSubmit_when_invalid_input_then_returns_false_with_expected_error_message() { + @Test func isPaymentButtonEnabled_when_invalid_input_then_returns_false() { // Given let validOrderTotal = "20.00" let invalidAmountInput = "not a value" - let expectedErrorMessage = "Error trying to process payment. Try again." - var capturedErrorMessage: String? + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: validOrderTotal, textFieldAmountInput: invalidAmountInput, - onError: { error in - capturedErrorMessage = error - }) + isLoading: isLoading) // Then #expect(result == false) - #expect(capturedErrorMessage == expectedErrorMessage) } - @Test func validateAmountOnSubmit_when_inputDecimal_is_less_than_orderTotal_then_returns_false_with_expected_error_message() { + @Test func isPaymentButtonEnabled_when_inputDecimal_is_less_than_orderTotal_then_returns_false() { // Given let orderTotal = "20.00" let inputAmount = "10.00" - let expectedErrorMessage = "Amount must be more or equal to total." - var capturedErrorMessage: String? + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, - textFieldAmountInput: inputAmount - ) { error in - capturedErrorMessage = error - } + textFieldAmountInput: inputAmount, + isLoading: isLoading) // Then #expect(result == false) - #expect(capturedErrorMessage == expectedErrorMessage) } - @Test func validateAmountOnSubmit_when_inputDecimal_is_greater_than_or_equal_to_orderTotal_then_returns_true() { + @Test func isPaymentButtonEnabled_when_inputDecimal_is_equal_to_orderTotal_then_returns_true() { // Given let orderTotal = "20.00" let inputAmount = "20.00" - var capturedErrorMessage: String? + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, - textFieldAmountInput: inputAmount - ) { error in - capturedErrorMessage = error - } + textFieldAmountInput: inputAmount, + isLoading: isLoading) // Then #expect(result == true) - #expect(capturedErrorMessage == nil) } - @Test func validateAmountOnSubmit_when_inputDecimal_is_greater_than_or_equal_to_orderTotal_using_grouping_separators_then_returns_true() { + @Test func isPaymentButtonEnabled_when_inputDecimal_is_equal_to_orderTotal_using_grouping_separators_then_returns_true() { // Given let orderTotal = "2,000.00" let inputAmount = "2,000.00" - var capturedErrorMessage: String? + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, - textFieldAmountInput: inputAmount - ) { error in - capturedErrorMessage = error - } + textFieldAmountInput: inputAmount, + isLoading: isLoading) // Then #expect(result == true) - #expect(capturedErrorMessage == nil) } - @Test func updateChangeDueMessage_when_input_has_both_dot_and_comma_grouping_separators_then_returns_formatted_change_due_correctly() { + @Test func isPaymentButtonEnabled_when_input_has_both_dot_and_comma_grouping_separators_returns_false_when_input_amount_is_not_enough() { // Given - let orderTotal = "$1,234.00" + let orderTotal = "$2,000.00" let inputAmount = "$1,235.00" - let expectedMessage = "Change due: $1.00" + let isLoading = false // When - let result = sut.updatechangeDueMessage( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, - textFieldAmountInput: inputAmount) + textFieldAmountInput: inputAmount, + isLoading: isLoading) // Then - #expect(result == expectedMessage) + #expect(result == false) } - @Test func validateAmountOnSubmit_when_input_has_both_dot_and_comma_grouping_separators_returns_error_message_when_input_amount_is_not_enough() { + @Test func isPaymentButtonEnabled_when_input_is_empty_and_orderTotal_is_zero_then_returns_true() { // Given - let orderTotal = "$2,000.00" - let inputAmount = "$1,235.00" - let expectedErrorMessage = "Amount must be more or equal to total." - var capturedErrorMessage: String? + let orderTotal = "$0.00" + let inputAmount = "" + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, - textFieldAmountInput: inputAmount - ) { error in - capturedErrorMessage = error - } + textFieldAmountInput: inputAmount, + isLoading: isLoading) // Then - #expect(result == false) - #expect(capturedErrorMessage == expectedErrorMessage) + #expect(result == true) } - @Test func validateAmountOnSubmit_when_input_is_empty_then_returns_true() { + @Test func isPaymentButtonEnabled_when_orderTotal_is_empty_then_returns_false() { // Given - let orderTotal = "$0.00" + let orderTotal = "" let inputAmount = "" + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, textFieldAmountInput: inputAmount, - onError: { _ in }) + isLoading: isLoading) // Then - #expect(result == true) + #expect(result == false) } - @Test func validateAmountOnSubmit_when_orderTotal_is_empty_then_returns_false_with_expected_error_message() { + @Test func isPaymentButtonEnabled_when_inputDecimal_is_empty_and_less_than_orderTotal_then_returns_false() { // Given - let orderTotal = "" + let orderTotal = "$1.00" let inputAmount = "" - let expectedErrorMessage = "Error trying to process payment. Try again." - var capturedErrorMessage: String? + let isLoading = false // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, textFieldAmountInput: inputAmount, - onError: { error in - capturedErrorMessage = error - }) + isLoading: isLoading) // Then #expect(result == false) - #expect(capturedErrorMessage == expectedErrorMessage) } - @Test func validateAmountOnSubmit_when_inputDecimal_is_empty_and_less_than_orderTotal_then_returns_false_with_expected_error_message() { + @Test func isPaymentButtonEnabled_when_isLoading_is_true_then_returns_false() { // Given - let orderTotal = "$1.00" - let inputAmount = "" - let expectedErrorMessage = "Amount must be more or equal to total." - var capturedErrorMessage: String? + let orderTotal = "$20.00" + let inputAmount = "$20.00" + let isLoading = true // When - let result = sut.validateAmountOnSubmit( + let result = sut.isPaymentButtonEnabled( orderTotal: orderTotal, textFieldAmountInput: inputAmount, - onError: { error in - capturedErrorMessage = error - }) + isLoading: isLoading) // Then #expect(result == false) - #expect(capturedErrorMessage == expectedErrorMessage) } + @Test func isPaymentButtonEnabled_when_input_is_greater_than_orderTotal_and_not_loading_then_returns_true() { + // Given + let orderTotal = "$10.00" + let inputAmount = "$15.00" + let isLoading = false + + // When + let result = sut.isPaymentButtonEnabled( + orderTotal: orderTotal, + textFieldAmountInput: inputAmount, + isLoading: isLoading) + + // Then + #expect(result == true) + } + + // MARK: `formattedChangeDueAmount` Tests + @Test func formattedChangeDueAmount_when_input_equals_total_and_includesZeroChange_is_true_then_returns_zero_amount() { // Given let orderTotal = "20.00" @@ -324,4 +332,182 @@ struct CollectCashViewHelperTests { // Then #expect(result == expectedAmount) } + + // MARK: - `parseCurrency` Tests + + @Test func parseCurrency_when_valid_decimal_string_then_returns_decimal() { + // Given + let amountString = "123.45" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "123.45")) + } + + @Test func parseCurrency_when_string_with_currency_symbol_then_returns_decimal() { + // Given + let amountString = "$123.45" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "123.45")) + } + + @Test func parseCurrency_when_string_with_grouping_separator_then_returns_decimal() { + // Given + let amountString = "1,234.56" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "1234.56")) + } + + @Test func parseCurrency_when_string_with_currency_symbol_and_grouping_separator_then_returns_decimal() { + // Given + let amountString = "$1,234.56" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "1234.56")) + } + + @Test func parseCurrency_when_string_with_leading_and_trailing_whitespace_then_returns_decimal() { + // Given + let amountString = " 123.45 " + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "123.45")) + } + + @Test func parseCurrency_when_zero_amount_then_returns_zero_decimal() { + // Given + let amountString = "0.00" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "0")) + } + + @Test func parseCurrency_when_zero_with_currency_symbol_then_returns_zero_decimal() { + // Given + let amountString = "$0.00" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "0")) + } + + @Test func parseCurrency_when_large_amount_with_multiple_grouping_separators_then_returns_decimal() { + // Given + let amountString = "1,234,567.89" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "1234567.89")) + } + + @Test func parseCurrency_when_invalid_string_then_returns_nil() { + // Given + let amountString = "not a number" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == nil) + } + + @Test func parseCurrency_when_empty_string_then_returns_nil() { + // Given + let amountString = "" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == nil) + } + + @Test func parseCurrency_when_only_currency_symbol_then_returns_nil() { + // Given + let amountString = "$" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == nil) + } + + @Test func parseCurrency_when_only_whitespace_then_returns_nil() { + // Given + let amountString = " " + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == nil) + } + + @Test func parseCurrency_when_string_with_letters_and_numbers_then_returns_nil() { + // Given + let amountString = "abc123.45" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == nil) + } + + @Test func parseCurrency_when_multiple_decimal_points_then_returns_nil() { + // Given + let amountString = "123.45.67" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == nil) + } + + @Test func parseCurrency_when_integer_without_decimal_then_returns_decimal() { + // Given + let amountString = "123" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "123")) + } + + @Test func parseCurrency_when_integer_with_currency_symbol_then_returns_decimal() { + // Given + let amountString = "$123" + + // When + let result = sut.parseCurrency(amountString) + + // Then + #expect(result == Decimal(string: "123")) + } } From 004b6244d0f13882b6e7ef5c978941057eb8d743 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Fri, 29 Aug 2025 16:09:23 +0800 Subject: [PATCH 3/5] Update release notes. --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 08a6610707d..c444df1511a 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,7 +3,7 @@ 23.2 ----- - +- [*] POS: Enhanced cash payment experience with pre-filled amounts. [https://github.com/woocommerce/woocommerce-ios/pull/16058] 23.1 ----- From 0dff14294fc782d65c994d9376bcf7b95bd6c757 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Thu, 4 Sep 2025 10:52:50 +0800 Subject: [PATCH 4/5] Fix build warnings outside of PR scope but in the same file. --- .../Classes/POS/Presentation/PointOfSaleCollectCashView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index 7225a0de7c1..5c49236a8dc 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -62,7 +62,7 @@ struct PointOfSaleCollectCashView: View { await submitCashAmount() } } - .onChange(of: textFieldViewModel.amount) { newValue in + .onChange(of: textFieldViewModel.amount) { _, newValue in textFieldAmountInput = newValue updateChangeDueMessage() } @@ -105,7 +105,7 @@ struct PointOfSaleCollectCashView: View { .frame(minHeight: geometry.size.height) .animation(.easeInOut, value: errorMessage) .animation(.easeInOut, value: changeDueMessage != nil) - .onChange(of: textFieldAmountInput) { _ in + .onChange(of: textFieldAmountInput) { _, _ in errorMessage = nil } .onReceive(Publishers.keyboardFrame) { From 7c576c054f0a73304be7f3e96b1d78234c348faa Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Thu, 4 Sep 2025 10:55:50 +0800 Subject: [PATCH 5/5] Improve readability of `isPaymentButtonEnabled` function. --- .../Classes/POS/ViewHelpers/CollectCashViewHelper.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift b/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift index 06a56f33e74..62bf6a9d0a6 100644 --- a/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift +++ b/WooCommerce/Classes/POS/ViewHelpers/CollectCashViewHelper.swift @@ -57,11 +57,16 @@ final class CollectCashViewHelper { func isPaymentButtonEnabled(orderTotal: String, textFieldAmountInput: String, isLoading: Bool) -> Bool { + guard !isLoading else { + return false + } + + let inputAmount = textFieldAmountInput.isNotEmpty ? textFieldAmountInput : "0" guard let orderDecimal = parseCurrency(orderTotal), - let inputDecimal = parseCurrency(textFieldAmountInput.isNotEmpty ? textFieldAmountInput : "0") else { + let inputDecimal = parseCurrency(inputAmount) else { return false } - return inputDecimal >= orderDecimal && !isLoading + return inputDecimal >= orderDecimal } func parseCurrency(_ amountString: String) -> Decimal? {