diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index 289a768498f..cdbe8e468d7 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -1,4 +1,5 @@ import SwiftUI +import class WooFoundation.CurrencyFormatter struct PointOfSaleCollectCashView: View { @Environment(\.colorScheme) var colorScheme @@ -8,6 +9,9 @@ struct PointOfSaleCollectCashView: View { @State private var textFieldAmountInput: String = "" @State private var isLoading: Bool = false @State private var errorMessage: String? + @State private var changeDueMessage: String? + + private let currencyFormatter: CurrencyFormatter = WooFoundation.CurrencyFormatter(currencySettings: ServiceLocator.currencySettings) let orderTotal: String @@ -15,13 +19,6 @@ struct PointOfSaleCollectCashView: View { String.localizedStringWithFormat(Localization.backNavigationSubtitle, orderTotal) } - private func validateAmount() -> Bool { - // TODO: - // Validate amount entered vs order total - // https://github.com/woocommerce/woocommerce-ios/issues/14749 - return true - } - @StateObject private var textFieldViewModel = FormattableAmountTextFieldViewModel(size: .extraLarge, locale: Locale.autoupdatingCurrent, storeCurrencySettings: ServiceLocator.currencySettings, @@ -54,8 +51,15 @@ struct PointOfSaleCollectCashView: View { FormattableAmountTextField(viewModel: textFieldViewModel, style: .pos) .onChange(of: textFieldViewModel.amount) { newValue in textFieldAmountInput = newValue + updateChangeDueMessage() } + if let changeDue = changeDueMessage { + Text(changeDue) + .font(.posBodyRegular) + .foregroundColor(.posTextSuccess) + } + if let errorMessage = errorMessage { Text(errorMessage) .font(POSFontStyle.posBodyRegular) @@ -64,7 +68,7 @@ struct PointOfSaleCollectCashView: View { Button(action: { Task { @MainActor in - guard validateAmount() else { + guard validateAmountOnSubmit() else { return } isLoading = true @@ -102,6 +106,7 @@ struct PointOfSaleCollectCashView: View { .background(backgroundColor) .padding() .animation(.easeInOut, value: errorMessage) + .animation(.easeInOut, value: changeDueMessage) .onChange(of: textFieldAmountInput) { _ in errorMessage = nil } @@ -112,6 +117,46 @@ struct PointOfSaleCollectCashView: View { } } +private extension PointOfSaleCollectCashView { + func parseCurrency(_ amountString: String) -> NSDecimalNumber? { + currencyFormatter.convertToDecimal(amountString, locale: .current) + } + + private func formatAsCurrency(_ amount: NSDecimalNumber) -> String { + currencyFormatter.formatAmount(amount) ?? "$0.00" + } + + private func updateChangeDueMessage() { + guard let orderDecimal = parseCurrency(orderTotal), + let inputDecimal = parseCurrency(textFieldAmountInput) else { + changeDueMessage = nil + return + } + + if inputDecimal.compare(orderDecimal) == .orderedDescending { + let changeDue = inputDecimal.subtracting(orderDecimal) + changeDueMessage = String.localizedStringWithFormat(Localization.changeDueMessage, formatAsCurrency(changeDue)) + } else { + changeDueMessage = nil + } + } + + private func validateAmountOnSubmit() -> Bool { + guard let orderDecimal = parseCurrency(orderTotal), + let inputDecimal = parseCurrency(textFieldAmountInput) else { + errorMessage = Localization.failedToCollectCashPayment + return false + } + + if inputDecimal.compare(orderDecimal) == .orderedAscending { + errorMessage = Localization.cashPaymentAmountNotEnough + return false + } + errorMessage = nil + return true + } +} + private extension PointOfSaleCollectCashView { enum Constants { static let buttonSpacing: CGFloat = 12 @@ -147,10 +192,21 @@ private extension PointOfSaleCollectCashView { comment: "Button to mark a cash payment as completed" ) static let failedToCollectCashPayment = NSLocalizedString( - "pointOfSale.cashview.failedToCollectCashPayment.draft", + "pointOfSale.cashview.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( + "pointOfSale.cashview.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." + ) + static let changeDueMessage = NSLocalizedString( + "pointOfSale.cashview.changedue", + value: "Change due: %1$@", + comment: "Change due when the cash amount entered exceeds the order total." + + "Reads as 'Change due: $1.23'" + ) } } diff --git a/WooCommerce/Classes/POS/Utils/Color+WooCommercePOS.swift b/WooCommerce/Classes/POS/Utils/Color+WooCommercePOS.swift index 662c3624819..d155c32b5ce 100644 --- a/WooCommerce/Classes/POS/Utils/Color+WooCommercePOS.swift +++ b/WooCommerce/Classes/POS/Utils/Color+WooCommercePOS.swift @@ -87,6 +87,15 @@ extension Color { ) } + static var posTextSuccess: Color { + Color( + UIColor( + light: UIColor(red: 10.0/255.0, green: 148.0/255.0, blue: 0.0/255.0, alpha: 1.0), + dark: UIColor(red: 10.0/255.0, green: 148.0/255.0, blue: 0.0/255.0, alpha: 1.0) + ) + ) + } + // MARK: - Buttons static var posPrimaryButtonBackground: Color {