From a2ee75ac4bf7932e1a4b46f6e8290ff9d8a90725 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 18:03:15 +0100 Subject: [PATCH 01/44] Show full screen "Payment processing" state --- .../ui/woopos/common/composeui/WooPosTheme.kt | 7 ++- .../WooPosHomeChildToParentCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 ++- .../woopos/home/totals/WooPosTotalsScreen.kt | 6 +++ .../home/totals/WooPosTotalsViewModel.kt | 44 ++++++++++++++++--- .../home/totals/WooPosTotalsViewState.kt | 12 +++++ .../WooPosTotalsPaymentProcessingScreen.kt | 33 ++++++++++++++ WooCommerce/src/main/res/values/strings.xml | 4 +- 8 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 226de53dd03..31e2718ae0d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -21,6 +21,7 @@ data class CustomColors( val totalsBackground: Color, val totalsErrorBackground: Color, val paymentSuccessBackground: Color, + val paymentProcessingBackground: Color, val paymentSuccessText: Color, val paymentSuccessIcon: Color, val dialogSubtitleHighlightBackground: Color = Color(0x14747480), @@ -195,7 +196,8 @@ private val DarkCustomColors = CustomColors( paymentSuccessBackground = WooPosColors.darkCustomColorsHomeBackground, paymentSuccessText = WooPosColors.oldGrayLight, paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground, - homeBackground = WooPosColors.darkCustomColorsHomeBackground + homeBackground = WooPosColors.darkCustomColorsHomeBackground, + paymentProcessingBackground = Color(0xFF533582), ) private val LightCustomColors = CustomColors( @@ -208,7 +210,8 @@ private val LightCustomColors = CustomColors( paymentSuccessBackground = WooPosColors.White, paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, - homeBackground = WooPosColors.Gray0 + homeBackground = WooPosColors.Gray0, + paymentProcessingBackground = Color(0xFF533582), ) private val LocalCustomColors = staticCompositionLocalOf { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index c0b8ac6c640..bb34378866e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -23,6 +23,7 @@ sealed class ChildToParentEvent { data object BackFromCheckoutToCartClicked : ChildToParentEvent() data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() + data object PaymentProcessing : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index b7ebdffaac7..6739d63c1dc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -110,7 +110,11 @@ class WooPosHomeViewModel @Inject constructor( ) sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } - + is ChildToParentEvent.PaymentProcessing -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid + ) + } is ChildToParentEvent.OrderSuccessfullyPaid -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 965ebb6130a..b2dea7bbe4f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -45,6 +45,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding +import com.woocommerce.android.ui.woopos.home.totals.payment.processing.WooPosPaymentProcessingScreen import com.woocommerce.android.ui.woopos.home.totals.payment.success.WooPosPaymentSuccessScreen @Composable @@ -87,6 +88,11 @@ private fun WooPosTotalsScreen( ) } } + StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentProcessing) { + if (state is WooPosTotalsViewState.PaymentProcessing) { + WooPosPaymentProcessingScreen(state) + } + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c2daa167249..0ffb4190948 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -20,6 +20,7 @@ import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.* import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker @@ -199,12 +200,45 @@ class WooPosTotalsViewModel @Inject constructor( paymentStateText = paymentState.javaClass.simpleName ) } - if (paymentState is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful) { - uiState.value = - WooPosTotalsViewState.PaymentSuccess( - orderTotalText = paymentState.amountWithCurrencyLabel + + when (paymentState) { + is CardReaderPaymentOrRefundState.CardReaderPaymentState.LoadingData -> { + // TODO: show loading state within the totals pane + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.CollectingPayment -> { + // TODO: show "tap or swipe" state within the totals pane + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.ProcessingPayment, + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentCapturing, + CardReaderPaymentOrRefundState.CardReaderPaymentState.ReFetchingOrder -> { + uiState.value = PaymentProcessing( + title = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title), + subtitle = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle) ) - childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful -> { + uiState.value = + PaymentSuccess( + orderTotalText = paymentState.amountWithCurrencyLabel + ) + childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { + // TODO: show full screen payment failed screen + } + is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { + throw IllegalStateException("Interac refund is not supported in POS") + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment->{ + throw IllegalStateException("Built-in reader is not supported in POS") + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PrintingReceipt -> { + throw IllegalStateException("PrintingReceipt is not supported in POS") + } + CardReaderPaymentOrRefundState.CardReaderPaymentState.SharingReceipt -> { + throw IllegalStateException("SharingReceipt is not supported in POS") + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index ca5cb98029b..fde8e3fec32 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -23,6 +23,18 @@ sealed class WooPosTotalsViewState : Parcelable { ) : Parcelable } + data class PaymentProcessing( + val title: String, + val subtitle: String, + ) : WooPosTotalsViewState() + + data class PaymentFailed( + val title: String, + val subtitle: String, + val actionButtonLabel: String, + val onAction: () -> Unit, + ) : WooPosTotalsViewState() + data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() data class Error(val message: String) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt new file mode 100644 index 00000000000..96ca350ad41 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -0,0 +1,33 @@ +package com.woocommerce.android.ui.woopos.home.totals.payment.processing + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState + +@Composable +fun WooPosPaymentProcessingScreen( + state: WooPosTotalsViewState.PaymentProcessing, +) { + Box( + modifier = Modifier + .background(color = WooPosTheme.colors.paymentProcessingBackground) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally, + verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center, + ) { + Text(text = state.title) + Text(text = state.subtitle) + } + } +} \ No newline at end of file diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 3214c9088b5..3ce55befd22 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4301,6 +4301,9 @@ To process this payment, please connect your reader. Connect to reader + Processing payment + Please wait… + Dimmed background. Tap to close the menu. Card reader connected Card reader not connected. Double tap to connect @@ -4317,7 +4320,6 @@ "Failed to load more items. Please try again." Error loading variations - Customer Orders Registration From 6aecb6ff5bd38c544e5edebf178a196d609fed56 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:25:00 +0100 Subject: [PATCH 02/44] Clean up code --- .../home/totals/WooPosTotalsViewModel.kt | 29 +++++++++++-------- .../WooPosTotalsPaymentProcessingScreen.kt | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 0ffb4190948..2aff9a33a6d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -15,6 +15,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowP import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent @@ -202,41 +203,45 @@ class WooPosTotalsViewModel @Inject constructor( } when (paymentState) { - is CardReaderPaymentOrRefundState.CardReaderPaymentState.LoadingData -> { + is CardReaderPaymentState.LoadingData -> { // TODO: show loading state within the totals pane } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.CollectingPayment -> { + is CardReaderPaymentState.CollectingPayment -> { // TODO: show "tap or swipe" state within the totals pane } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.ProcessingPayment, - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentCapturing, - CardReaderPaymentOrRefundState.CardReaderPaymentState.ReFetchingOrder -> { + is CardReaderPaymentState.ProcessingPayment, + is CardReaderPaymentState.PaymentCapturing, + CardReaderPaymentState.ReFetchingOrder -> { uiState.value = PaymentProcessing( - title = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title), - subtitle = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle) + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ), + subtitle = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) ) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful -> { + is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = PaymentSuccess( orderTotalText = paymentState.amountWithCurrencyLabel ) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { + is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { // TODO: show full screen payment failed screen } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { throw IllegalStateException("Interac refund is not supported in POS") } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment->{ + is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment -> { throw IllegalStateException("Built-in reader is not supported in POS") } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PrintingReceipt -> { + is CardReaderPaymentState.PrintingReceipt -> { throw IllegalStateException("PrintingReceipt is not supported in POS") } - CardReaderPaymentOrRefundState.CardReaderPaymentState.SharingReceipt -> { + CardReaderPaymentState.SharingReceipt -> { throw IllegalStateException("SharingReceipt is not supported in POS") } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 96ca350ad41..dd6c322a57c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -30,4 +30,4 @@ fun WooPosPaymentProcessingScreen( Text(text = state.subtitle) } } -} \ No newline at end of file +} From 4192a2a07c36e45593a9faaef137de1247ac32b9 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:32:26 +0100 Subject: [PATCH 03/44] Add preview --- .../WooPosTotalsPaymentProcessingScreen.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index dd6c322a57c..fe223e5365d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState @@ -31,3 +32,14 @@ fun WooPosPaymentProcessingScreen( } } } + +@WooPosPreview +@Composable +fun WooPosPaymentProcessingScreenPreview() { + WooPosPaymentProcessingScreen( + state = WooPosTotalsViewState.PaymentProcessing( + title = "Processing payment", + subtitle = "Please wait...", + ) + ) +} \ No newline at end of file From 5c325b62bbb0ac279094d244df1088375e4801e1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:34:54 +0100 Subject: [PATCH 04/44] Clean up code --- .../processing/WooPosTotalsPaymentProcessingScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index fe223e5365d..dd54013616c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.processing import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -24,8 +25,8 @@ fun WooPosPaymentProcessingScreen( ) { Column( modifier = Modifier.fillMaxSize(), - horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally, - verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { Text(text = state.title) Text(text = state.subtitle) From e9b3f20c6f169484d2ce8096aefb39632074fc76 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:37:03 +0100 Subject: [PATCH 05/44] Improve preview --- .../WooPosTotalsPaymentProcessingScreen.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index dd54013616c..82c87842904 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -37,10 +37,12 @@ fun WooPosPaymentProcessingScreen( @WooPosPreview @Composable fun WooPosPaymentProcessingScreenPreview() { - WooPosPaymentProcessingScreen( - state = WooPosTotalsViewState.PaymentProcessing( - title = "Processing payment", - subtitle = "Please wait...", + WooPosTheme { + WooPosPaymentProcessingScreen( + state = WooPosTotalsViewState.PaymentProcessing( + title = "Processing payment", + subtitle = "Please wait...", + ) ) - ) + } } \ No newline at end of file From aa1a3219a75e3dd545ecbb8b5fa492ef453509c8 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:37:27 +0100 Subject: [PATCH 06/44] Create basic payment failed screen --- .../failed/WooPosTotalsPaymentFailedScreen.kt | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt new file mode 100644 index 00000000000..266ad9f5553 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -0,0 +1,58 @@ +package com.woocommerce.android.ui.woopos.home.totals.payment.failed + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview +import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsUIEvent +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState + +@Composable +fun WooPosPaymentFailedScreen( + state: WooPosTotalsViewState.PaymentFailed, + onUIEvent: (WooPosTotalsUIEvent) -> Unit +) { + Box( + modifier = Modifier + .background(color = WooPosTheme.colors.paymentProcessingBackground) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(text = state.title) + Text(text = state.subtitle) + WooPosButton(text = "Try another payment method") { + onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + } + WooPosButton(text = "Exit order") { + onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + } + } + } +} + +@WooPosPreview +@Composable +fun WooPosPaymentFailedScreenPreview() { + WooPosTheme { + WooPosPaymentFailedScreen( + state = WooPosTotalsViewState.PaymentFailed( + title = "Payment failed", + subtitle = "Please try again", + ), + onUIEvent = {} + ) + } +} \ No newline at end of file From cde0db78821b5d0e8eaa4c7e7982c4317b41f73c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:51:00 +0100 Subject: [PATCH 07/44] Show payment failed screen --- .../woopos/home/totals/WooPosTotalsScreen.kt | 11 ++++++ .../woopos/home/totals/WooPosTotalsUIEvent.kt | 2 ++ .../home/totals/WooPosTotalsViewModel.kt | 34 +++++++++++++------ .../home/totals/WooPosTotalsViewState.kt | 2 -- WooCommerce/src/main/res/values/strings.xml | 2 ++ 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index b2dea7bbe4f..44e4d3b2ad0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -45,6 +45,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding +import com.woocommerce.android.ui.woopos.home.totals.payment.failed.WooPosPaymentFailedScreen import com.woocommerce.android.ui.woopos.home.totals.payment.processing.WooPosPaymentProcessingScreen import com.woocommerce.android.ui.woopos.home.totals.payment.success.WooPosPaymentSuccessScreen @@ -88,11 +89,21 @@ private fun WooPosTotalsScreen( ) } } + StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentProcessing) { if (state is WooPosTotalsViewState.PaymentProcessing) { WooPosPaymentProcessingScreen(state) } } + + StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentFailed) { + if (state is WooPosTotalsViewState.PaymentFailed) { + WooPosPaymentFailedScreen( + state = state, + onUIEvent = onUIEvent, + ) + } + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt index e0841c61cc9..5540b3b69ac 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt @@ -2,5 +2,7 @@ package com.woocommerce.android.ui.woopos.home.totals sealed class WooPosTotalsUIEvent { data object OnNewTransactionClicked : WooPosTotalsUIEvent() + data object RetryFailedTransactionClicked : WooPosTotalsUIEvent() + data object ExitOrderAfterFailedTransactionClicked : WooPosTotalsUIEvent() data object RetryOrderCreationClicked : WooPosTotalsUIEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 2aff9a33a6d..3ea64566ff2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -147,6 +147,9 @@ class WooPosTotalsViewModel @Inject constructor( is WooPosTotalsUIEvent.RetryOrderCreationClicked -> { createOrderDraft(dataState.value.productIds) } + + WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> TODO() + WooPosTotalsUIEvent.RetryFailedTransactionClicked -> TODO() } } @@ -212,15 +215,8 @@ class WooPosTotalsViewModel @Inject constructor( is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, CardReaderPaymentState.ReFetchingOrder -> { - uiState.value = PaymentProcessing( - title = resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_title - ), - subtitle = resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_subtitle - ) - ) - childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + uiState.value = buildPaymentProcessingState() + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentProcessing) } is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = @@ -230,7 +226,7 @@ class WooPosTotalsViewModel @Inject constructor( childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { - // TODO: show full screen payment failed screen + uiState.value = buildPaymentFailedState() } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { throw IllegalStateException("Interac refund is not supported in POS") @@ -249,6 +245,24 @@ class WooPosTotalsViewModel @Inject constructor( } } + private fun buildPaymentFailedState(): PaymentFailed = PaymentFailed( + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_failed_title + ), + subtitle = resourceProvider.getString( + R.string.woopos_success_totals_payment_failed_subtitle + ) + ) + + private fun buildPaymentProcessingState(): PaymentProcessing = PaymentProcessing( + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ), + subtitle = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) + ) + private fun createOrderDraft(productIds: List) { viewModelScope.launch { uiState.value = WooPosTotalsViewState.Loading diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index fde8e3fec32..9ee0747f8fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -31,8 +31,6 @@ sealed class WooPosTotalsViewState : Parcelable { data class PaymentFailed( val title: String, val subtitle: String, - val actionButtonLabel: String, - val onAction: () -> Unit, ) : WooPosTotalsViewState() data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 3ce55befd22..4b725ac4ddd 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4303,6 +4303,8 @@ Processing payment Please wait… + Payment failed + Unfortunately, this payment has been declined. Dimmed background. Tap to close the menu. Card reader connected From 01b8bab1c1808cfa008cfbe00cbe69db8b6f0f41 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 09:06:50 +0100 Subject: [PATCH 08/44] Update tests --- .../ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 89f2e96a15c..55bcce17634 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -283,7 +283,12 @@ class WooPosTotalsViewModelTest { val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(mock()) } + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) + .thenReturn("Unfortunately, this payment has been declined.") val savedState = createMockSavedStateHandle() + val viewModel = createViewModel( savedState = savedState, parentToChildrenEventReceiver = parentToChildrenEventReceiver, From 1c2ee968c7344c1c5721ee546402e7c6bb284f12 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 09:11:42 +0100 Subject: [PATCH 09/44] Satisfy detekt's complaints --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 4 +++- .../totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt | 2 +- .../payment/processing/WooPosTotalsPaymentProcessingScreen.kt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 3ea64566ff2..bff49a576bb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -21,7 +21,9 @@ import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver -import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.* +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentFailed +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentProcessing +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentSuccess import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 266ad9f5553..2d2cb5999fc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -55,4 +55,4 @@ fun WooPosPaymentFailedScreenPreview() { onUIEvent = {} ) } -} \ No newline at end of file +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 82c87842904..72d7af39682 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -45,4 +45,4 @@ fun WooPosPaymentProcessingScreenPreview() { ) ) } -} \ No newline at end of file +} From 502cb4e9977b5ffb654fcab0265ea3046981469a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 09:53:24 +0100 Subject: [PATCH 10/44] Tune up payment failed composable --- .../composeui/component/WooPosButtons.kt | 33 +++++--- .../failed/WooPosTotalsPaymentFailedScreen.kt | 76 ++++++++++++++----- .../main/res/drawable/woo_pos_ic_error_x.xml | 12 +++ WooCommerce/src/main/res/values/strings.xml | 2 + 4 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt index 2544188965f..61db50bac5b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.common.composeui.component import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -91,6 +92,26 @@ fun WooPosOutlinedButton( text: String, shape: RoundedCornerShape = RoundedCornerShape(4.dp), onClick: () -> Unit, +) = WooPosOutlinedButton( + modifier = modifier, + shape = shape, + content = { + Text( + text = text, + color = MaterialTheme.colors.primary, + style = MaterialTheme.typography.body2, + fontWeight = FontWeight.SemiBold, + ) + }, + onClick = onClick, +) + +@Composable +fun WooPosOutlinedButton( + modifier: Modifier = Modifier, + shape: RoundedCornerShape = RoundedCornerShape(4.dp), + content: @Composable RowScope.() -> Unit, + onClick: () -> Unit, ) { Button( modifier = modifier, @@ -107,15 +128,9 @@ fun WooPosOutlinedButton( disabledElevation = 0.dp, hoveredElevation = 0.dp, focusedElevation = 0.dp - ) - ) { - Text( - text = text, - color = MaterialTheme.colors.primary, - style = MaterialTheme.typography.body2, - fontWeight = FontWeight.SemiBold, - ) - } + ), + content = content + ) } @Composable diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 2d2cb5999fc..4cddfe27d6b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -4,14 +4,29 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosOutlinedButton +import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsUIEvent import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState @@ -20,26 +35,51 @@ fun WooPosPaymentFailedScreen( state: WooPosTotalsViewState.PaymentFailed, onUIEvent: (WooPosTotalsUIEvent) -> Unit ) { - Box( + Column( modifier = Modifier - .background(color = WooPosTheme.colors.paymentProcessingBackground) - .fillMaxSize(), - contentAlignment = Alignment.Center + .background(color = WooPosTheme.colors.homeBackground) + .fillMaxSize() + .padding(vertical = 96.dp.toAdaptivePadding(), horizontal = 295.dp.toAdaptivePadding()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = state.title) - Text(text = state.subtitle) - WooPosButton(text = "Try another payment method") { - onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) - } - WooPosButton(text = "Exit order") { - onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + Spacer(modifier = Modifier.height(56.dp.toAdaptivePadding())) + Icon( + modifier = Modifier.size(64.dp), + painter = painterResource(id = R.drawable.woo_pos_ic_error_x), + contentDescription = stringResource(id = R.string.woopos_error_icon_content_description), + tint = Color.Unspecified, + ) + Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) + Text( + text = state.title, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + Text( + text = state.subtitle, + style = MaterialTheme.typography.h6 + ) + Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) + WooPosButton( + text = stringResource(R.string.woo_pos_payment_failed_try_another_payment_method), + modifier = Modifier.height(80.dp) + ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } + Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) + WooPosOutlinedButton( + modifier = Modifier + .fillMaxWidth() + .height(80.dp), + content = { + Text( + color = MaterialTheme.colors.primary, + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.Bold, + text = stringResource(R.string.woo_pos_payment_failed_exit_order) + ) } - } + ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } } } @@ -50,7 +90,7 @@ fun WooPosPaymentFailedScreenPreview() { WooPosPaymentFailedScreen( state = WooPosTotalsViewState.PaymentFailed( title = "Payment failed", - subtitle = "Please try again", + subtitle = "Unfortunately, this payment has been declined.", ), onUIEvent = {} ) diff --git a/WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml b/WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml new file mode 100644 index 00000000000..a0a0665e11e --- /dev/null +++ b/WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml @@ -0,0 +1,12 @@ + + + + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 4b725ac4ddd..b48e3a59659 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4390,4 +4390,6 @@ Box Envelope Hmm, we can\'t find a WordPress.com account connected to this email address. + Try another payment method + Exit order From 60c7434553585b5e493934d67ade28d0388292fd Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:49:45 +0100 Subject: [PATCH 11/44] Tune up payment failed composable --- .../failed/WooPosTotalsPaymentFailedScreen.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 4cddfe27d6b..6855b82e819 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -39,13 +40,12 @@ fun WooPosPaymentFailedScreen( modifier = Modifier .background(color = WooPosTheme.colors.homeBackground) .fillMaxSize() - .padding(vertical = 96.dp.toAdaptivePadding(), horizontal = 295.dp.toAdaptivePadding()), + .padding(vertical = 96.dp.toAdaptivePadding()), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, ) { - Spacer(modifier = Modifier.height(56.dp.toAdaptivePadding())) + Spacer(modifier = Modifier.height(96.dp.toAdaptivePadding())) Icon( - modifier = Modifier.size(64.dp), + modifier = Modifier.size(84.dp), painter = painterResource(id = R.drawable.woo_pos_ic_error_x), contentDescription = stringResource(id = R.string.woopos_error_icon_content_description), tint = Color.Unspecified, @@ -64,13 +64,15 @@ fun WooPosPaymentFailedScreen( Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) WooPosButton( text = stringResource(R.string.woo_pos_payment_failed_try_another_payment_method), - modifier = Modifier.height(80.dp) + modifier = Modifier + .height(80.dp) + .width(604.dp) ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) WooPosOutlinedButton( modifier = Modifier - .fillMaxWidth() - .height(80.dp), + .height(80.dp) + .width(604.dp), content = { Text( color = MaterialTheme.colors.primary, @@ -80,6 +82,7 @@ fun WooPosPaymentFailedScreen( ) } ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } + Spacer(modifier = Modifier.height(80.dp.toAdaptivePadding())) } } From ebd6543b8dd48cf1de69d6235458338c6842f0d7 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:50:16 +0100 Subject: [PATCH 12/44] Tune up payment failed composable --- .../totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 6855b82e819..e1d77eb2599 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -1,12 +1,9 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.failed import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size From e73501b75c88cc43d201c6afc18fdad5e54812dd Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:53:44 +0100 Subject: [PATCH 13/44] Rename states --- .../android/ui/woopos/home/WooPosHomeScreen.kt | 12 ++++++------ .../android/ui/woopos/home/WooPosHomeState.kt | 4 ++-- .../android/ui/woopos/home/WooPosHomeViewModel.kt | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt index 11972135e4c..c9a18901319 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt @@ -89,18 +89,18 @@ private fun WooPosHomeScreen( WooPosHomeState.ScreenPositionState.Cart.Hidden -> screenWidthDp is WooPosHomeState.ScreenPositionState.Cart.Visible, - WooPosHomeState.ScreenPositionState.Checkout.NotPaid -> productsWidthDp + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals -> productsWidthDp - WooPosHomeState.ScreenPositionState.Checkout.Paid -> productsWidthDp - cartWidthDp + WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> productsWidthDp - cartWidthDp }, label = "productsWidthAnimatedDp" ) val totalsWidthAnimatedDp by animateDpAsState( when (state.screenPositionState) { - is WooPosHomeState.ScreenPositionState.Checkout.Paid -> screenWidthDp + is WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> screenWidthDp is WooPosHomeState.ScreenPositionState.Cart, - WooPosHomeState.ScreenPositionState.Checkout.NotPaid -> totalsWidthDp + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals -> totalsWidthDp }, label = "totalsWidthAnimatedDp" ) @@ -261,7 +261,7 @@ fun WooPosHomeCheckoutScreenPreview() { WooPosTheme { WooPosHomeScreen( state = WooPosHomeState( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.NotPaid, + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals, productsInfoDialog = ProductsInfoDialog(isVisible = false), exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = false), ), @@ -277,7 +277,7 @@ fun WooPosHomeCheckoutPaidScreenPreview() { WooPosTheme { WooPosHomeScreen( state = WooPosHomeState( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid, + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals, productsInfoDialog = ProductsInfoDialog(isVisible = false), exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = false), ), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt index 177e58b10e4..10f9f309c78 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt @@ -26,10 +26,10 @@ data class WooPosHomeState( @Parcelize sealed class Checkout : ScreenPositionState() { @Parcelize - data object NotPaid : Checkout() + data object CartWithTotals : Checkout() @Parcelize - data object Paid : Checkout() + data object FullScreenTotals : Checkout() } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 6739d63c1dc..16d120a1938 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -45,14 +45,14 @@ class WooPosHomeViewModel @Inject constructor( return when (event) { WooPosHomeUIEvent.SystemBackClicked -> { when (_state.value.screenPositionState) { - WooPosHomeState.ScreenPositionState.Checkout.NotPaid -> { + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) sendEventToChildren(ParentToChildrenEvent.BackFromCheckoutToCartClicked) } - WooPosHomeState.ScreenPositionState.Checkout.Paid -> { + WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) @@ -87,7 +87,7 @@ class WooPosHomeViewModel @Inject constructor( when (event) { is ChildToParentEvent.CheckoutClicked -> { _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.NotPaid + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals ) sendEventToChildren(ParentToChildrenEvent.CheckoutClicked(event.productIds)) } @@ -112,12 +112,12 @@ class WooPosHomeViewModel @Inject constructor( } is ChildToParentEvent.PaymentProcessing -> { _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } is ChildToParentEvent.OrderSuccessfullyPaid -> { _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } @@ -160,8 +160,8 @@ class WooPosHomeViewModel @Inject constructor( WooPosHomeState.ScreenPositionState.Cart.Visible WooPosHomeState.ScreenPositionState.Cart.Visible, - WooPosHomeState.ScreenPositionState.Checkout.NotPaid, - WooPosHomeState.ScreenPositionState.Checkout.Paid -> screenPosition + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals, + WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> screenPosition } } } From 56651e2a684a356224a0d0bf3f3a823c5ae9bfb1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:57:21 +0100 Subject: [PATCH 14/44] Ensure failed payment state is always full screen --- .../woopos/home/WooPosHomeChildToParentCommunication.kt | 1 + .../android/ui/woopos/home/WooPosHomeViewModel.kt | 9 +++------ .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index bb34378866e..01890044915 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -24,6 +24,7 @@ sealed class ChildToParentEvent { data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() data object PaymentProcessing : ChildToParentEvent() + data object PaymentFailed : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 16d120a1938..4e940c4e9ea 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -110,12 +110,9 @@ class WooPosHomeViewModel @Inject constructor( ) sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } - is ChildToParentEvent.PaymentProcessing -> { - _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals - ) - } - is ChildToParentEvent.OrderSuccessfullyPaid -> { + is ChildToParentEvent.PaymentProcessing, + is ChildToParentEvent.OrderSuccessfullyPaid, + is ChildToParentEvent.PaymentFailed -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index bff49a576bb..305edc17db4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -229,6 +229,7 @@ class WooPosTotalsViewModel @Inject constructor( } is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { uiState.value = buildPaymentFailedState() + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { throw IllegalStateException("Interac refund is not supported in POS") From e05fafd29c8c6643b8bde0fa92487483a6b20ff4 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 14:27:39 +0100 Subject: [PATCH 15/44] Implement failed payment "retry" action --- .../WooPosHomeChildToParentCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 +++++- .../home/totals/WooPosTotalsViewModel.kt | 19 +++++++++++++++++-- .../failed/WooPosTotalsPaymentFailedScreen.kt | 2 +- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 01890044915..6e48c75ac34 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -25,6 +25,7 @@ sealed class ChildToParentEvent { data object NewTransactionClicked : ChildToParentEvent() data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() + data object RetryFailedPaymentClicked : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 4e940c4e9ea..be5a5a76b86 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -117,7 +117,11 @@ class WooPosHomeViewModel @Inject constructor( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } - + is ChildToParentEvent.RetryFailedPaymentClicked -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals + ) + } ChildToParentEvent.ExitPosClicked -> { _state.value = _state.value.copy( exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = true) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 305edc17db4..25d7c0b23ce 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -75,12 +75,14 @@ class WooPosTotalsViewModel @Inject constructor( val state: StateFlow = uiState - private var dataState: MutableStateFlow = savedState.getStateFlow( + private val dataState: MutableStateFlow = savedState.getStateFlow( scope = viewModelScope, initialValue = TotalsDataState(), key = KEY_STATE, ) + private var order: Order? = null + private var isTTPPaymentInProgress: Boolean get() = savedState.get(KEY_TTP_PAYMENT_IN_PROGRESS) == true set(value) { @@ -151,7 +153,19 @@ class WooPosTotalsViewModel @Inject constructor( } WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> TODO() - WooPosTotalsUIEvent.RetryFailedTransactionClicked -> TODO() + WooPosTotalsUIEvent.RetryFailedTransactionClicked -> { + cancelPaymentAction() + viewModelScope.launch { + childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + if (order == null) { + uiState.value = InitialState + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } else { + uiState.value = buildWooPosTotalsViewState(order!!) + collectPayment() + } + } + } } } @@ -275,6 +289,7 @@ class WooPosTotalsViewModel @Inject constructor( onSuccess = { order -> dataState.value = dataState.value.copy(orderId = order.id) uiState.value = buildWooPosTotalsViewState(order) + this@WooPosTotalsViewModel.order = order analyticsTracker.track(WooPosAnalyticsEvent.Event.OrderCreationSuccess) collectPayment() }, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index e1d77eb2599..80f32ae1de1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -78,7 +78,7 @@ fun WooPosPaymentFailedScreen( text = stringResource(R.string.woo_pos_payment_failed_exit_order) ) } - ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } + ) { onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) } Spacer(modifier = Modifier.height(80.dp.toAdaptivePadding())) } } From d3160ec78fc9fc15893886ab9180768822880158 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 14:45:14 +0100 Subject: [PATCH 16/44] Implement failed payment "exit order" action --- .../WooPosHomeChildToParentCommunication.kt | 1 + .../WooPosHomeParentToChildCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 +++++ .../woopos/home/cart/WooPosCartViewModel.kt | 8 +++++-- .../home/totals/WooPosTotalsViewModel.kt | 24 +++++++++---------- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 6e48c75ac34..e7197367ff4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -26,6 +26,7 @@ sealed class ChildToParentEvent { data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() data object RetryFailedPaymentClicked : ChildToParentEvent() + data object ExitOrderAfterFailedTransactionClicked : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt index ff7093dbf58..98af275c508 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt @@ -25,6 +25,7 @@ sealed class ParentToChildrenEvent { ) : ParentToChildrenEvent() data class CheckoutClicked(val productIds: List) : ParentToChildrenEvent() data object OrderSuccessfullyPaid : ParentToChildrenEvent() + data object OrderCardPaymentAborted : ParentToChildrenEvent() } interface WooPosParentToChildrenEventReceiver { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index be5a5a76b86..313bfa27658 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -104,6 +104,12 @@ class WooPosHomeViewModel @Inject constructor( ) } + is ChildToParentEvent.ExitOrderAfterFailedTransactionClicked -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible + ) + sendEventToChildren(ParentToChildrenEvent.OrderCardPaymentAborted) + } is ChildToParentEvent.NewTransactionClicked -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt index d876a51efa4..6beac3b6b17 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt @@ -117,8 +117,12 @@ class WooPosCartViewModel @Inject constructor( parentToChildrenEventReceiver.events.collect { event -> when (event) { is ParentToChildrenEvent.BackFromCheckoutToCartClicked -> handleBackFromCheckoutToCartClicked() + is ParentToChildrenEvent.ItemClickedInProductSelector -> handleItemClickedInProductSelector(event) - is ParentToChildrenEvent.OrderSuccessfullyPaid -> handleOrderSuccessfullyPaid() + + is ParentToChildrenEvent.OrderSuccessfullyPaid, + is ParentToChildrenEvent.OrderCardPaymentAborted -> clearCart() + is ParentToChildrenEvent.CheckoutClicked -> Unit } } @@ -151,7 +155,7 @@ class WooPosCartViewModel @Inject constructor( } } - private fun handleOrderSuccessfullyPaid() { + private fun clearCart() { _state.value = WooPosCartState() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 25d7c0b23ce..cf33d8369b2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -151,19 +151,18 @@ class WooPosTotalsViewModel @Inject constructor( is WooPosTotalsUIEvent.RetryOrderCreationClicked -> { createOrderDraft(dataState.value.productIds) } - - WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> TODO() - WooPosTotalsUIEvent.RetryFailedTransactionClicked -> { + WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> viewModelScope.launch { + childrenToParentEventSender.sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + } + WooPosTotalsUIEvent.RetryFailedTransactionClicked -> viewModelScope.launch { cancelPaymentAction() - viewModelScope.launch { - childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) - if (order == null) { - uiState.value = InitialState - childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) - } else { - uiState.value = buildWooPosTotalsViewState(order!!) - collectPayment() - } + childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + if (order == null) { + uiState.value = InitialState + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } else { + uiState.value = buildWooPosTotalsViewState(order!!) + collectPayment() } } } @@ -205,6 +204,7 @@ class WooPosTotalsViewModel @Inject constructor( } is ParentToChildrenEvent.ItemClickedInProductSelector, + ParentToChildrenEvent.OrderCardPaymentAborted, ParentToChildrenEvent.OrderSuccessfullyPaid -> Unit } } From 3fc89e6333b62650907f901f05df64f72a6bc076 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 14:50:53 +0100 Subject: [PATCH 17/44] Move debug Totals payment state to the top --- .../woopos/home/totals/WooPosTotalsScreen.kt | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 44e4d3b2ad0..ec47e048a3c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -133,15 +133,22 @@ private fun TotalsLoaded( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - val error = state.error - if (error != null) { - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1.1f) - .background(WooPosTheme.colors.totalsErrorBackground) - ) { - TotalsError(modifier = Modifier, error = error) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .background(WooPosTheme.colors.totalsErrorBackground) + ) { + val error = state.error + when { + error != null -> TotalsError(modifier = Modifier, error = error) + else -> { + Text( + modifier = Modifier.align(Alignment.Center), + text = state.paymentStateText, + style = MaterialTheme.typography.body1, + ) + } } } TotalsGrid(modifier = Modifier.weight(1f), state = state) @@ -230,10 +237,6 @@ private fun TotalsGrid( fontWeightOne = FontWeight.Medium, fontWeightTwo = FontWeight.Bold, ) - Text( - text = state.paymentStateText, - style = MaterialTheme.typography.body1, - ) } } From 84f825faceec5cbd4a403ba6f8cacf6a83a4092e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:07:24 +0100 Subject: [PATCH 18/44] Add home VM test: give home screen is at checkout, when exit order clicked after failed payment, then should show cart --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 002e569a42c..045ccbc9ecc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -273,6 +273,23 @@ class WooPosHomeViewModelTest { assertTrue(viewModel.state.value.screenPositionState is WooPosHomeState.ScreenPositionState.Checkout) } + @Test + fun `give home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + + // WHEN + events.emit(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From 447a9e83604e45e43630ce7ada29563edb9d8375 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:09:25 +0100 Subject: [PATCH 19/44] Add home VM test: give home screen is at checkout, when payment processing started, then should show full screen totals state --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 045ccbc9ecc..fbd3a2e0b3f 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -290,6 +290,23 @@ class WooPosHomeViewModelTest { assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) } + @Test + fun `give home screen is at checkout, when payment processing started, then should show full screen totals state`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + + // WHEN + events.emit(ChildToParentEvent.PaymentProcessing) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From 7ca6439aa8b46d88784acdbfbaf4a790ec7907cc Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:11:19 +0100 Subject: [PATCH 20/44] Add home VM test: give home screen is at checkout, processing payment, when payment fails, then should show full screen totals state --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index fbd3a2e0b3f..2cf513842d6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -307,6 +307,25 @@ class WooPosHomeViewModelTest { assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } + @Test + fun `give home screen is at checkout, processing payment, when payment fails, then should show full screen totals state`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + events.emit(ChildToParentEvent.PaymentProcessing) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + + // WHEN + events.emit(ChildToParentEvent.PaymentFailed) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From b0d9b392188dc11b9beba6a4272f4c489154e6cc Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:12:49 +0100 Subject: [PATCH 21/44] Add home VM test: give home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 2cf513842d6..695a0a2a24d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -326,6 +326,27 @@ class WooPosHomeViewModelTest { assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } + @Test + fun `give home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + events.emit(ChildToParentEvent.PaymentProcessing) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + events.emit(ChildToParentEvent.PaymentFailed) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + + // WHEN + events.emit(ChildToParentEvent.RetryFailedPaymentClicked) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From 9dad874fe05950996e128720a123941763ac222c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:14:49 +0100 Subject: [PATCH 22/44] Tweak heights of totals boxes --- .../android/ui/woopos/home/totals/WooPosTotalsScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index ec47e048a3c..f1687b010cc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -136,7 +136,7 @@ private fun TotalsLoaded( Box( modifier = Modifier .fillMaxWidth() - .weight(1f) + .weight(1.1f) .background(WooPosTheme.colors.totalsErrorBackground) ) { val error = state.error @@ -151,7 +151,7 @@ private fun TotalsLoaded( } } } - TotalsGrid(modifier = Modifier.weight(1f), state = state) + TotalsGrid(modifier = Modifier.weight(.9f), state = state) } } From 810ea0129c4775793c8cb04eb37625f3c596509a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:17:13 +0100 Subject: [PATCH 23/44] Fix typos in test method names --- .../android/ui/woopos/home/WooPosHomeViewModelTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 695a0a2a24d..7db45a0bfee 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -274,7 +274,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { + fun `given home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -291,7 +291,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, when payment processing started, then should show full screen totals state`() = runTest { + fun `given home screen is at checkout, when payment processing started, then should show full screen totals state`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -308,7 +308,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, processing payment, when payment fails, then should show full screen totals state`() = runTest { + fun `given home screen is at checkout, processing payment, when payment fails, then should show full screen totals state`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -327,7 +327,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals`() = runTest { + fun `given home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) From 6abc26e4d1b5f166bdcec4f6793b49e9eacb1c75 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:23:37 +0100 Subject: [PATCH 24/44] Add Cart VM test: given non-empty cart, when card payment is aborted, then should clear the cart --- .../home/cart/WooPosCartViewModelTest.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt index 54784e1f829..d9eb2c2e4b1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt @@ -513,6 +513,37 @@ class WooPosCartViewModelTest { assertThat(finalItem.isAppearanceAnimationPlayed).isTrue } + @Test + fun `given non-empty cart, when card payment is aborted, then should clear the cart`() = runTest { + // GIVEN + val product = ProductTestUtils.generateProduct( + productId = 23L, + productName = "title", + amount = "10.0" + ).copy(firstImageUrl = "url") + + val parentToChildrenEventsMutableFlow = MutableSharedFlow() + whenever(parentToChildrenEventReceiver.events).thenReturn(parentToChildrenEventsMutableFlow) + whenever(getProductById(eq(product.remoteId))).thenReturn(product) + val sut = createSut() + val states = sut.state.captureValues() + + parentToChildrenEventsMutableFlow.emit( + ParentToChildrenEvent.ItemClickedInProductSelector( + WooPosItemsViewModel.ItemClickedData.SimpleProduct(id = product.remoteId) + ) + ) + + // WHEN + parentToChildrenEventsMutableFlow.emit(ParentToChildrenEvent.OrderCardPaymentAborted) + + // THEN + val toolbar = states.last().toolbar + assertThat(toolbar.backIconVisible).isFalse() + assertThat(toolbar.itemsCount).isNull() + assertThat(toolbar.isClearAllButtonVisible).isFalse() + } + private fun createSut(): WooPosCartViewModel { return WooPosCartViewModel( childrenToParentEventSender, From e1eb05c1c7386f7db004445665639c1c64d90904 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:06:00 +0100 Subject: [PATCH 25/44] Add Totals VM test: given order draft created and reader connected, when card tapped, should show payment processing screen --- .../home/totals/WooPosTotalsViewModelTest.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 55bcce17634..2fc7caf2819 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -21,6 +21,8 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentE import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentStateProvider import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderTrackCanceledFlowAction import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper @@ -795,6 +797,49 @@ class WooPosTotalsViewModelTest { assertThat(vm.paymentScope!!.isActive).isFalse } + @Test + fun `given order draft created and reader connected, when card tapped, should show payment processing screen`() = runTest { + // GIVEN + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ) + ).thenReturn("Processing payment") + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) + ).thenReturn("Please wait…") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + + // WHEN + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + + // THEN + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) + } + + @Test + fun `given payment failed, when retry clicked, then should retry`() { + + } + + @Test + fun `given payment failed, when exit order clicked, then should inform home about the situation`() { + + } + private fun createViewModelAndSetupForSuccessfulOrderCreation( controllerFactory: CardReaderPaymentControllerFactory = paymentControllerFactory ): WooPosTotalsViewModel { From 6737f15ec0a8a6d0a2fbdd8c981b94bdd6dbd8b1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:11:00 +0100 Subject: [PATCH 26/44] Add Totals VM test: given payment failed, when retry clicked, then should retry --- .../home/totals/WooPosTotalsViewModelTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 2fc7caf2819..5aa0f837ebb 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -19,6 +19,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracR import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper +import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState @@ -832,7 +833,36 @@ class WooPosTotalsViewModelTest { @Test fun `given payment failed, when retry clicked, then should retry`() { + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) + .thenReturn("Unfortunately, this payment has been declined.") + + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + // WHEN + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( + errorType = PaymentFlowError.NoNetwork, {}) + + // THEN + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) } @Test From 3c3c2196bdb8fb62a6e401965d6f4e0253dd22e1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:40:51 +0100 Subject: [PATCH 27/44] Add Totals VM test: given payment failed, when retry clicked, then should retry --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 5 +---- .../ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 9 ++++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index cf33d8369b2..09e3c65657f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -222,11 +222,8 @@ class WooPosTotalsViewModel @Inject constructor( } when (paymentState) { + is CardReaderPaymentState.CollectingPayment, is CardReaderPaymentState.LoadingData -> { - // TODO: show loading state within the totals pane - } - is CardReaderPaymentState.CollectingPayment -> { - // TODO: show "tap or swipe" state within the totals pane } is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 5aa0f837ebb..deed49f116d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -855,14 +855,17 @@ class WooPosTotalsViewModelTest { ) whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) - - // WHEN paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( errorType = PaymentFlowError.NoNetwork, {}) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + + // WHEN + paymentState.value = CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) // THEN - assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) } @Test From 34d84e50c481023432b007d684fb6acab52fdcf3 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:42:10 +0100 Subject: [PATCH 28/44] Add Totals VM test: given payment failed, when exit order clicked, then should inform home about the situation --- .../home/totals/WooPosTotalsViewModelTest.kt | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index deed49f116d..4cd3512d65f 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -869,8 +869,39 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when exit order clicked, then should inform home about the situation`() { + fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest{ + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) + .thenReturn("Unfortunately, this payment has been declined.") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( + errorType = PaymentFlowError.NoNetwork, {}) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + + // WHEN + vm.onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + + // THEN + verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) } private fun createViewModelAndSetupForSuccessfulOrderCreation( From 24399972f16b6d313dc7082ec39253f50df48a1f Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 17:11:54 +0100 Subject: [PATCH 29/44] Ensure Cart is cleared in case state is restored --- .../woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 313bfa27658..ade9e241f4e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -96,6 +96,7 @@ class WooPosHomeViewModel @Inject constructor( _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) + sendEventToChildren(ParentToChildrenEvent.BackFromCheckoutToCartClicked) } is ChildToParentEvent.ItemClickedInProductSelector -> { From 62684c46023dc920438e575abec83876b2e7bf60 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 17:17:33 +0100 Subject: [PATCH 30/44] Clean up code --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 09e3c65657f..c018cf6e916 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -242,17 +242,11 @@ class WooPosTotalsViewModel @Inject constructor( uiState.value = buildPaymentFailedState() childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } - is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { - throw IllegalStateException("Interac refund is not supported in POS") - } - is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment -> { - throw IllegalStateException("Built-in reader is not supported in POS") - } - is CardReaderPaymentState.PrintingReceipt -> { - throw IllegalStateException("PrintingReceipt is not supported in POS") - } + is CardReaderPaymentOrRefundState.CardReaderInteracRefundState, + is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment, + is CardReaderPaymentState.PrintingReceipt, CardReaderPaymentState.SharingReceipt -> { - throw IllegalStateException("SharingReceipt is not supported in POS") + throw IllegalArgumentException("Payment state: $paymentState not compatible with POS") } } } From 77f74b79f890fc056bf50036d2a504da58bdf77b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 17:36:31 +0100 Subject: [PATCH 31/44] Satisfy detekt's complaints --- .../ui/woopos/home/WooPosHomeViewModel.kt | 1 + .../ui/woopos/home/WooPosHomeViewModelTest.kt | 44 ++++++++++++++----- .../home/totals/WooPosTotalsViewModelTest.kt | 8 ++-- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index ade9e241f4e..d178a545bf0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -81,6 +81,7 @@ class WooPosHomeViewModel @Inject constructor( } } + @Suppress("LongMethod") private fun listenBottomEvents() { viewModelScope.launch { childrenToParentEventReceiver.events.collect { event -> diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 7db45a0bfee..a1d8d7d316b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -281,13 +281,17 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) // WHEN events.emit(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) } @Test @@ -298,13 +302,17 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) // WHEN events.emit(ChildToParentEvent.PaymentProcessing) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } @Test @@ -315,15 +323,21 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) events.emit(ChildToParentEvent.PaymentProcessing) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) // WHEN events.emit(ChildToParentEvent.PaymentFailed) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } @Test @@ -334,17 +348,25 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) events.emit(ChildToParentEvent.PaymentProcessing) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) events.emit(ChildToParentEvent.PaymentFailed) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) // WHEN events.emit(ChildToParentEvent.RetryFailedPaymentClicked) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) } private fun createViewModel() = WooPosHomeViewModel( diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 4cd3512d65f..743c15db356 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -857,7 +857,8 @@ class WooPosTotalsViewModelTest { val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( - errorType = PaymentFlowError.NoNetwork, {}) + errorType = PaymentFlowError.NoNetwork, {} + ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) // WHEN @@ -869,7 +870,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest{ + fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") @@ -894,7 +895,8 @@ class WooPosTotalsViewModelTest { val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( - errorType = PaymentFlowError.NoNetwork, {}) + errorType = PaymentFlowError.NoNetwork, {} + ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) // WHEN From 51eb2b2fd35b382f4086ae00de83e90c7588583d Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 17:55:09 +0100 Subject: [PATCH 32/44] Use WooPosColors for `paymentProcessingBackground` --- .../android/ui/woopos/common/composeui/WooPosTheme.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 31e2718ae0d..5c89e708778 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -197,7 +197,7 @@ private val DarkCustomColors = CustomColors( paymentSuccessText = WooPosColors.oldGrayLight, paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground, homeBackground = WooPosColors.darkCustomColorsHomeBackground, - paymentProcessingBackground = Color(0xFF533582), + paymentProcessingBackground = WooPosColors.WooPurple70, ) private val LightCustomColors = CustomColors( @@ -211,7 +211,7 @@ private val LightCustomColors = CustomColors( paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, homeBackground = WooPosColors.Gray0, - paymentProcessingBackground = Color(0xFF533582), + paymentProcessingBackground = WooPosColors.WooPurple70, ) private val LocalCustomColors = staticCompositionLocalOf { From 100e9882b7d0c9293db7580c07df09ef45a6831b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 18:00:28 +0100 Subject: [PATCH 33/44] Use WooPosColors for `totalsBackground` --- .../android/ui/woopos/common/composeui/WooPosTheme.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 5c89e708778..bd24abaddf6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -206,7 +206,7 @@ private val LightCustomColors = CustomColors( success = WooPosColors.greenNotFromPalette, error = WooPosColors.lightCustomColorsError, totalsErrorBackground = WooPosColors.lightQuaternaryBackground, - totalsBackground = Color(0xFFF6F7F7), + totalsBackground = WooPosColors.Gray0, paymentSuccessBackground = WooPosColors.White, paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, From 8b4f5f7d3a0291e015db03cba68ae6cb5193019a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 19:02:32 +0100 Subject: [PATCH 34/44] Replace hardcoded error message with real one --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 10 +++++----- .../com/woocommerce/android/util/UiStringParser.kt | 12 ++++++++++++ .../woopos/home/totals/WooPosTotalsViewModelTest.kt | 9 +++++---- 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c018cf6e916..7b4f45ce59f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -28,6 +28,7 @@ import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice +import com.woocommerce.android.util.UiStringParser import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.ResourceProvider @@ -57,6 +58,7 @@ class WooPosTotalsViewModel @Inject constructor( private val analyticsTracker: WooPosAnalyticsTracker, private val networkStatus: WooPosNetworkStatus, private val cardReaderPaymentControllerFactory: CardReaderPaymentControllerFactory, + private val uiStringParser: UiStringParser, private val savedState: SavedStateHandle, ) : ViewModel() { @@ -239,7 +241,7 @@ class WooPosTotalsViewModel @Inject constructor( childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { - uiState.value = buildPaymentFailedState() + uiState.value = buildPaymentFailedState(paymentState) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState, @@ -253,13 +255,11 @@ class WooPosTotalsViewModel @Inject constructor( } } - private fun buildPaymentFailedState(): PaymentFailed = PaymentFailed( + private fun buildPaymentFailedState(state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment): PaymentFailed = PaymentFailed( title = resourceProvider.getString( R.string.woopos_success_totals_payment_failed_title ), - subtitle = resourceProvider.getString( - R.string.woopos_success_totals_payment_failed_subtitle - ) + subtitle = uiStringParser.asString(state.errorType.message) ) private fun buildPaymentProcessingState(): PaymentProcessing = PaymentProcessing( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt new file mode 100644 index 00000000000..f9d7c423382 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt @@ -0,0 +1,12 @@ +package com.woocommerce.android.util + +import android.content.Context +import com.woocommerce.android.model.UiString +import com.woocommerce.android.util.UiHelpers.getTextOfUiString +import javax.inject.Inject + +class UiStringParser @Inject constructor( + private val context: Context +) { + fun asString(uiString: UiString): String = getTextOfUiString(context, uiString) +} \ No newline at end of file diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 743c15db356..9f7913cf47e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -41,6 +41,7 @@ import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import com.woocommerce.android.util.CurrencyFormatter +import com.woocommerce.android.util.UiStringParser import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -97,6 +98,7 @@ class WooPosTotalsViewModelTest { private val cardReaderOnboardingChecker: CardReaderOnboardingChecker = mock() private val cardReaderConfigProvider: CardReaderCountryConfigProvider = mock() private val paymentReceiptShare: PaymentReceiptShare = mock() + private val uiStringParser: UiStringParser = mock() private val paymentControllerFactory = CardReaderPaymentControllerFactory( cardReaderManager = cardReaderManager, orderRepository = orderRepository, @@ -840,8 +842,7 @@ class WooPosTotalsViewModelTest { .thenReturn("Please wait…") whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) - .thenReturn("Unfortunately, this payment has been declined.") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) @@ -878,8 +879,7 @@ class WooPosTotalsViewModelTest { .thenReturn("Please wait…") whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) - .thenReturn("Unfortunately, this payment has been declined.") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) @@ -977,6 +977,7 @@ class WooPosTotalsViewModelTest { analyticsTracker = analyticsTracker, networkStatus = networkStatus, cardReaderPaymentControllerFactory = cardReaderPaymentControllerFactory, + uiStringParser = uiStringParser, savedState = savedState, ) } From ba0bcdd9ad60482b5ef9bb61af73b4c873853eab Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 19:17:28 +0100 Subject: [PATCH 35/44] Satisfy detekt's complaints --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 4 +++- .../kotlin/com/woocommerce/android/util/UiStringParser.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 7b4f45ce59f..354978dccec 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -255,7 +255,9 @@ class WooPosTotalsViewModel @Inject constructor( } } - private fun buildPaymentFailedState(state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment): PaymentFailed = PaymentFailed( + private fun buildPaymentFailedState( + state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment + ): PaymentFailed = PaymentFailed( title = resourceProvider.getString( R.string.woopos_success_totals_payment_failed_title ), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt index f9d7c423382..df79f5e7146 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt @@ -9,4 +9,4 @@ class UiStringParser @Inject constructor( private val context: Context ) { fun asString(uiString: UiString): String = getTextOfUiString(context, uiString) -} \ No newline at end of file +} From cf8d030cfaccd8576fc3ab79fe9df91a77e915da Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 09:54:46 +0100 Subject: [PATCH 36/44] Get order from db instead of caching in memory --- .../home/totals/WooPosTotalsRepository.kt | 14 +++++- .../home/totals/WooPosTotalsViewModel.kt | 10 ++--- .../home/totals/WooPosTotalsRepositoryTest.kt | 45 +++++++++---------- .../home/totals/WooPosTotalsViewModelTest.kt | 5 ++- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt index a197ca2aece..d94b80978ab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt @@ -1,6 +1,8 @@ package com.woocommerce.android.ui.woopos.home.totals import com.woocommerce.android.model.Order +import com.woocommerce.android.model.OrderMapper +import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.creation.OrderCreateEditRepository import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.util.DateUtils @@ -8,13 +10,17 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.async import kotlinx.coroutines.withContext +import org.wordpress.android.fluxc.store.WCOrderStore import java.util.Date import javax.inject.Inject class WooPosTotalsRepository @Inject constructor( private val orderCreateEditRepository: OrderCreateEditRepository, private val dateUtils: DateUtils, - private val getProductById: WooPosGetProductById + private val getProductById: WooPosGetProductById, + private val orderStore: WCOrderStore, + private val selectedSite: SelectedSite, + private val orderMapper: OrderMapper, ) { private var orderCreationJob: Deferred>? = null @@ -59,6 +65,12 @@ class WooPosTotalsRepository @Inject constructor( } } + suspend fun getOrderById(orderId: Long) = withContext(IO) { + orderStore.getOrderByIdAndSite(orderId, selectedSite.get())?.let { + orderMapper.toAppModel(it) + } + } + private companion object { /** * This magic value used to indicate that we don't want to send subtotals and totals diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 354978dccec..14767e9141a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -69,7 +69,7 @@ class WooPosTotalsViewModel @Inject constructor( } private val uiState: MutableStateFlow = - savedState.getStateFlow( + savedState.getStateFlow( scope = viewModelScope, initialValue = InitialState, key = "woo_pos_totals_view_state" @@ -83,8 +83,6 @@ class WooPosTotalsViewModel @Inject constructor( key = KEY_STATE, ) - private var order: Order? = null - private var isTTPPaymentInProgress: Boolean get() = savedState.get(KEY_TTP_PAYMENT_IN_PROGRESS) == true set(value) { @@ -159,11 +157,12 @@ class WooPosTotalsViewModel @Inject constructor( WooPosTotalsUIEvent.RetryFailedTransactionClicked -> viewModelScope.launch { cancelPaymentAction() childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + val order = totalsRepository.getOrderById(dataState.value.orderId) if (order == null) { uiState.value = InitialState childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) } else { - uiState.value = buildWooPosTotalsViewState(order!!) + uiState.value = buildWooPosTotalsViewState(order) collectPayment() } } @@ -181,8 +180,6 @@ class WooPosTotalsViewModel @Inject constructor( if (cardReaderFacade.readerStatus.value is Connected) { val state = uiState.value check(state is WooPosTotalsViewState.Totals) - val orderId = dataState.value.orderId - check(orderId != EMPTY_ORDER_ID) check(uiState.value is WooPosTotalsViewState.Totals) createCardReaderPaymentController(dataState.value.orderId) cardReaderPaymentController?.start() @@ -282,7 +279,6 @@ class WooPosTotalsViewModel @Inject constructor( onSuccess = { order -> dataState.value = dataState.value.copy(orderId = order.id) uiState.value = buildWooPosTotalsViewState(order) - this@WooPosTotalsViewModel.order = order analyticsTracker.track(WooPosAnalyticsEvent.Event.OrderCreationSuccess) collectPayment() }, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt index 2d5c07548ea..f65e5c1ed69 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt @@ -1,6 +1,8 @@ package com.woocommerce.android.ui.woopos.home.totals import com.woocommerce.android.model.Order +import com.woocommerce.android.model.OrderMapper +import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.creation.OrderCreateEditRepository import com.woocommerce.android.ui.products.ProductHelper import com.woocommerce.android.ui.products.ProductType @@ -16,12 +18,16 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.store.WCOrderStore class WooPosTotalsRepositoryTest { private val orderCreateEditRepository: OrderCreateEditRepository = mock() private val getProductById: WooPosGetProductById = mock() private val dateUtils: DateUtils = mock() + private val orderStore: WCOrderStore = mock() + private val selectedSite: SelectedSite = mock() + private val orderMapper: OrderMapper = mock() private lateinit var repository: WooPosTotalsRepository @@ -33,11 +39,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given empty product list, when createOrderWithProducts called, then return error`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = emptyList() // WHEN @@ -50,11 +52,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product ids without duplicates, when createOrderWithProducts, then items all quantity one`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L, 2L, 3L) whenever(getProductById(1L)).thenReturn(product1) @@ -79,11 +77,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product id, when createOrderWithProducts, then item name matches original product`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L) whenever(getProductById(1L)).thenReturn(product1) @@ -105,11 +99,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product ids with duplicates, when createOrderWithProducts, then items quantity is correct`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L, 1L, 2L, 3L, 3L, 3L) whenever(getProductById(1L)).thenReturn(product1) @@ -133,11 +123,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product ids, when createOrder with some invalid ids, then return failure`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L, -1L, 3L) val mockOrder: Order = mock() whenever(orderCreateEditRepository.createOrUpdateOrder(any(), eq(""))).thenReturn(Result.success(mockOrder)) @@ -151,4 +137,13 @@ class WooPosTotalsRepositoryTest { assertThat(result.exceptionOrNull()?.message).isEqualTo("Invalid product ID: -1") verify(orderCreateEditRepository, never()).createOrUpdateOrder(any(), eq("")) } + + private fun createRepository() = WooPosTotalsRepository( + orderCreateEditRepository, + dateUtils, + getProductById, + orderStore, + selectedSite, + orderMapper, + ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 9f7913cf47e..ec8dda7c0e9 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -834,7 +834,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when retry clicked, then should retry`() { + fun `given payment failed, when retry clicked, then should retry`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") @@ -906,7 +906,7 @@ class WooPosTotalsViewModelTest { verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) } - private fun createViewModelAndSetupForSuccessfulOrderCreation( + private suspend fun createViewModelAndSetupForSuccessfulOrderCreation( controllerFactory: CardReaderPaymentControllerFactory = paymentControllerFactory ): WooPosTotalsViewModel { whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) @@ -952,6 +952,7 @@ class WooPosTotalsViewModelTest { val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(parentToChildrenEventFlow) } + whenever(totalsRepository.getOrderById(orderId)).thenReturn(order) return createViewModel( totalsRepository = totalsRepository, priceFormat = priceFormat, From 5b12dd873a2726370859fe6032726433f2401ec0 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 14:22:20 +0100 Subject: [PATCH 37/44] Improve failed payment "retry" handling --- .../WooPosHomeChildToParentCommunication.kt | 2 +- .../WooPosHomeParentToChildCommunication.kt | 1 - .../ui/woopos/home/WooPosHomeViewModel.kt | 10 +- .../woopos/home/cart/WooPosCartViewModel.kt | 3 +- .../woopos/home/totals/WooPosTotalsUIEvent.kt | 2 +- .../home/totals/WooPosTotalsViewModel.kt | 71 ++++++++---- .../home/totals/WooPosTotalsViewState.kt | 2 + .../failed/WooPosTotalsPaymentFailedScreen.kt | 34 +++--- WooCommerce/src/main/res/values/strings.xml | 6 +- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 6 +- .../home/cart/WooPosCartViewModelTest.kt | 31 ------ .../home/totals/WooPosTotalsViewModelTest.kt | 104 ++++++++++++++++-- 12 files changed, 179 insertions(+), 93 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index e7197367ff4..39bd1d75405 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -26,7 +26,7 @@ sealed class ChildToParentEvent { data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() data object RetryFailedPaymentClicked : ChildToParentEvent() - data object ExitOrderAfterFailedTransactionClicked : ChildToParentEvent() + data object GoBackToCheckoutAfterFailedPayment : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt index 98af275c508..ff7093dbf58 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt @@ -25,7 +25,6 @@ sealed class ParentToChildrenEvent { ) : ParentToChildrenEvent() data class CheckoutClicked(val productIds: List) : ParentToChildrenEvent() data object OrderSuccessfullyPaid : ParentToChildrenEvent() - data object OrderCardPaymentAborted : ParentToChildrenEvent() } interface WooPosParentToChildrenEventReceiver { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index d178a545bf0..8ef31e407d0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -106,18 +106,13 @@ class WooPosHomeViewModel @Inject constructor( ) } - is ChildToParentEvent.ExitOrderAfterFailedTransactionClicked -> { - _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible - ) - sendEventToChildren(ParentToChildrenEvent.OrderCardPaymentAborted) - } is ChildToParentEvent.NewTransactionClicked -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } + is ChildToParentEvent.PaymentProcessing, is ChildToParentEvent.OrderSuccessfullyPaid, is ChildToParentEvent.PaymentFailed -> { @@ -125,11 +120,14 @@ class WooPosHomeViewModel @Inject constructor( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } + + is ChildToParentEvent.GoBackToCheckoutAfterFailedPayment, is ChildToParentEvent.RetryFailedPaymentClicked -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals ) } + ChildToParentEvent.ExitPosClicked -> { _state.value = _state.value.copy( exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = true) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt index 6beac3b6b17..d62010e71af 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt @@ -120,8 +120,7 @@ class WooPosCartViewModel @Inject constructor( is ParentToChildrenEvent.ItemClickedInProductSelector -> handleItemClickedInProductSelector(event) - is ParentToChildrenEvent.OrderSuccessfullyPaid, - is ParentToChildrenEvent.OrderCardPaymentAborted -> clearCart() + is ParentToChildrenEvent.OrderSuccessfullyPaid -> clearCart() is ParentToChildrenEvent.CheckoutClicked -> Unit } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt index 5540b3b69ac..7d4d7df6ad1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt @@ -3,6 +3,6 @@ package com.woocommerce.android.ui.woopos.home.totals sealed class WooPosTotalsUIEvent { data object OnNewTransactionClicked : WooPosTotalsUIEvent() data object RetryFailedTransactionClicked : WooPosTotalsUIEvent() - data object ExitOrderAfterFailedTransactionClicked : WooPosTotalsUIEvent() + data object GoBackToCheckoutAfterFailedPayment : WooPosTotalsUIEvent() data object RetryOrderCreationClicked : WooPosTotalsUIEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 14767e9141a..2a5fe6fe4bb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -151,24 +151,43 @@ class WooPosTotalsViewModel @Inject constructor( is WooPosTotalsUIEvent.RetryOrderCreationClicked -> { createOrderDraft(dataState.value.productIds) } - WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> viewModelScope.launch { - childrenToParentEventSender.sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + WooPosTotalsUIEvent.GoBackToCheckoutAfterFailedPayment -> viewModelScope.launch { + childrenToParentEventSender.sendToParent(ChildToParentEvent.GoBackToCheckoutAfterFailedPayment) + retryPaymentCollectionFromScratch() } WooPosTotalsUIEvent.RetryFailedTransactionClicked -> viewModelScope.launch { - cancelPaymentAction() - childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) - val order = totalsRepository.getOrderById(dataState.value.orderId) - if (order == null) { - uiState.value = InitialState - childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) - } else { - uiState.value = buildWooPosTotalsViewState(order) - collectPayment() + val paymentState = cardReaderPaymentController?.paymentState?.value + check(paymentState != null) { + "Retry failed transaction clicked but payment controller is null" + } + check(paymentState is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment) { + "Retry failed transaction clicked but payment state is not PaymentFailed" + } + when { + paymentState.onRetry != null -> { + paymentState.onRetry!!() + } + else -> { + childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + retryPaymentCollectionFromScratch() + } } } } } + private suspend fun retryPaymentCollectionFromScratch() { + cancelPaymentAction() + val order = totalsRepository.getOrderById(dataState.value.orderId) + if (order == null) { + uiState.value = InitialState + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } else { + uiState.value = buildWooPosTotalsViewState(order) + collectPayment() + } + } + private fun collectPayment() { if (!networkStatus.isConnected()) { viewModelScope.launch { @@ -203,7 +222,6 @@ class WooPosTotalsViewModel @Inject constructor( } is ParentToChildrenEvent.ItemClickedInProductSelector, - ParentToChildrenEvent.OrderCardPaymentAborted, ParentToChildrenEvent.OrderSuccessfullyPaid -> Unit } } @@ -222,14 +240,15 @@ class WooPosTotalsViewModel @Inject constructor( when (paymentState) { is CardReaderPaymentState.CollectingPayment, - is CardReaderPaymentState.LoadingData -> { - } + is CardReaderPaymentState.LoadingData -> {} + is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, CardReaderPaymentState.ReFetchingOrder -> { uiState.value = buildPaymentProcessingState() childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentProcessing) } + is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = PaymentSuccess( @@ -237,10 +256,12 @@ class WooPosTotalsViewModel @Inject constructor( ) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } + is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { uiState.value = buildPaymentFailedState(paymentState) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } + is CardReaderPaymentOrRefundState.CardReaderInteracRefundState, is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment, is CardReaderPaymentState.PrintingReceipt, @@ -254,12 +275,22 @@ class WooPosTotalsViewModel @Inject constructor( private fun buildPaymentFailedState( state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment - ): PaymentFailed = PaymentFailed( - title = resourceProvider.getString( - R.string.woopos_success_totals_payment_failed_title - ), - subtitle = uiStringParser.asString(state.errorType.message) - ) + ): PaymentFailed { + val isRetryAvailable = state.onRetry != null + val retryButtonLabel = if (isRetryAvailable) { + resourceProvider.getString(R.string.woo_pos_payment_failed_try_again) + } else { + resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method) + } + return PaymentFailed( + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_failed_title + ), + subtitle = uiStringParser.asString(state.errorType.message), + retryPaymentButtonLabel = retryButtonLabel, + isReturnToCheckoutButtonVisible = isRetryAvailable + ) + } private fun buildPaymentProcessingState(): PaymentProcessing = PaymentProcessing( title = resourceProvider.getString( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index 9ee0747f8fa..17feee449ed 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -31,6 +31,8 @@ sealed class WooPosTotalsViewState : Parcelable { data class PaymentFailed( val title: String, val subtitle: String, + val retryPaymentButtonLabel: String, + val isReturnToCheckoutButtonVisible: Boolean = false, ) : WooPosTotalsViewState() data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 80f32ae1de1..49cc6dfea5d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -60,25 +60,27 @@ fun WooPosPaymentFailedScreen( ) Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) WooPosButton( - text = stringResource(R.string.woo_pos_payment_failed_try_another_payment_method), + text = state.retryPaymentButtonLabel, modifier = Modifier .height(80.dp) .width(604.dp) ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } - Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) - WooPosOutlinedButton( - modifier = Modifier - .height(80.dp) - .width(604.dp), - content = { - Text( - color = MaterialTheme.colors.primary, - style = MaterialTheme.typography.h5, - fontWeight = FontWeight.Bold, - text = stringResource(R.string.woo_pos_payment_failed_exit_order) - ) - } - ) { onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) } + if (state.isReturnToCheckoutButtonVisible) { + Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) + WooPosOutlinedButton( + modifier = Modifier + .height(80.dp) + .width(604.dp), + content = { + Text( + color = MaterialTheme.colors.primary, + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.Bold, + text = stringResource(R.string.woo_pos_payment_failed_go_back_to_checkout) + ) + } + ) { onUIEvent(WooPosTotalsUIEvent.GoBackToCheckoutAfterFailedPayment) } + } Spacer(modifier = Modifier.height(80.dp.toAdaptivePadding())) } } @@ -91,6 +93,8 @@ fun WooPosPaymentFailedScreenPreview() { state = WooPosTotalsViewState.PaymentFailed( title = "Payment failed", subtitle = "Unfortunately, this payment has been declined.", + retryPaymentButtonLabel = "Try again", + isReturnToCheckoutButtonVisible = true, ), onUIEvent = {} ) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index b48e3a59659..0a84a88541d 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4304,7 +4304,9 @@ Processing payment Please wait… Payment failed - Unfortunately, this payment has been declined. + Try another payment method + Try payment again + Exit order Dimmed background. Tap to close the menu. Card reader connected @@ -4390,6 +4392,4 @@ Box Envelope Hmm, we can\'t find a WordPress.com account connected to this email address. - Try another payment method - Exit order diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index a1d8d7d316b..e19c12dee6b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -274,7 +274,7 @@ class WooPosHomeViewModelTest { } @Test - fun `given home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { + fun `given home screen is at checkout, when go back to checkout clicked after failed payment, then should show cart with totals`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -286,12 +286,12 @@ class WooPosHomeViewModelTest { ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) // WHEN - events.emit(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + events.emit(ChildToParentEvent.GoBackToCheckoutAfterFailedPayment) // THEN assertThat( viewModel.state.value.screenPositionState - ).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) } @Test diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt index d9eb2c2e4b1..54784e1f829 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt @@ -513,37 +513,6 @@ class WooPosCartViewModelTest { assertThat(finalItem.isAppearanceAnimationPlayed).isTrue } - @Test - fun `given non-empty cart, when card payment is aborted, then should clear the cart`() = runTest { - // GIVEN - val product = ProductTestUtils.generateProduct( - productId = 23L, - productName = "title", - amount = "10.0" - ).copy(firstImageUrl = "url") - - val parentToChildrenEventsMutableFlow = MutableSharedFlow() - whenever(parentToChildrenEventReceiver.events).thenReturn(parentToChildrenEventsMutableFlow) - whenever(getProductById(eq(product.remoteId))).thenReturn(product) - val sut = createSut() - val states = sut.state.captureValues() - - parentToChildrenEventsMutableFlow.emit( - ParentToChildrenEvent.ItemClickedInProductSelector( - WooPosItemsViewModel.ItemClickedData.SimpleProduct(id = product.remoteId) - ) - ) - - // WHEN - parentToChildrenEventsMutableFlow.emit(ParentToChildrenEvent.OrderCardPaymentAborted) - - // THEN - val toolbar = states.last().toolbar - assertThat(toolbar.backIconVisible).isFalse() - assertThat(toolbar.itemsCount).isNull() - assertThat(toolbar.isClearAllButtonVisible).isFalse() - } - private fun createSut(): WooPosCartViewModel { return WooPosCartViewModel( childrenToParentEventSender, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index ec8dda7c0e9..f020cdcd169 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -43,6 +43,7 @@ import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.UiStringParser import com.woocommerce.android.viewmodel.ResourceProvider +import junit.framework.TestCase.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -55,6 +56,7 @@ import org.junit.Before import org.junit.Rule import org.mockito.kotlin.any import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -288,10 +290,7 @@ class WooPosTotalsViewModelTest { val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(mock()) } - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) - .thenReturn("Payment failed") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) - .thenReturn("Unfortunately, this payment has been declined.") + val savedState = createMockSavedStateHandle() val viewModel = createViewModel( @@ -834,7 +833,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when retry clicked, then should retry`() = runTest { + fun `given payment failed with retry action, when retry clicked, then should retry previous payment action`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") @@ -843,6 +842,8 @@ class WooPosTotalsViewModelTest { whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_again)) + .thenReturn("Try payment again") whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) @@ -857,17 +858,98 @@ class WooPosTotalsViewModelTest { whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + val failedPaymentRetryAction: ()->Unit = mock() paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( - errorType = PaymentFlowError.NoNetwork, {} + errorType = PaymentFlowError.NoNetwork, failedPaymentRetryAction + ) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry != null) + + // WHEN + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + + // THEN + verify(failedPaymentRetryAction).invoke() + } + + @Test + fun `given payment failed without retry action, when retry clicked, then should cancel previous payment action and start again`() = + runTest { + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method)) + .thenReturn("Try another payment method") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( + errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" + ) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) + + // WHEN + clearInvocations(mockCardReaderPaymentController) + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + + // THEN + verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).start() + } + + @Test + fun `given payment failed without retry action, when retry clicked, then should go back to checkout`() = runTest { + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method)) + .thenReturn("Try another payment method") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( + errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) // WHEN - paymentState.value = CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) // THEN - assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) + verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) } @Test @@ -879,6 +961,8 @@ class WooPosTotalsViewModelTest { .thenReturn("Please wait…") whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_again)) + .thenReturn("Try payment again") whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(networkStatus.isConnected()).thenReturn(true) @@ -900,10 +984,10 @@ class WooPosTotalsViewModelTest { assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) // WHEN - vm.onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + vm.onUIEvent(WooPosTotalsUIEvent.GoBackToCheckoutAfterFailedPayment) // THEN - verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.GoBackToCheckoutAfterFailedPayment) } private suspend fun createViewModelAndSetupForSuccessfulOrderCreation( From 6c350506e876fb9c64dbcb43b1a777e8e87d2f06 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 15:46:38 +0100 Subject: [PATCH 38/44] Satisfy detekt's complaints --- .../home/totals/WooPosTotalsViewModel.kt | 4 +- .../home/totals/WooPosTotalsViewModelTest.kt | 76 ++++++++++--------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 2a5fe6fe4bb..51f52f8edbb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -278,9 +278,9 @@ class WooPosTotalsViewModel @Inject constructor( ): PaymentFailed { val isRetryAvailable = state.onRetry != null val retryButtonLabel = if (isRetryAvailable) { - resourceProvider.getString(R.string.woo_pos_payment_failed_try_again) + resourceProvider.getString(R.string.woo_pos_payment_failed_try_again) } else { - resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method) + resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method) } return PaymentFailed( title = resourceProvider.getString( diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index f020cdcd169..576eda44b61 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -858,12 +858,14 @@ class WooPosTotalsViewModelTest { whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} - val failedPaymentRetryAction: ()->Unit = mock() + val failedPaymentRetryAction: () -> Unit = mock() paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( errorType = PaymentFlowError.NoNetwork, failedPaymentRetryAction ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) - assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry != null) + assertTrue( + (paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry != null + ) // WHEN vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) @@ -875,43 +877,45 @@ class WooPosTotalsViewModelTest { @Test fun `given payment failed without retry action, when retry clicked, then should cancel previous payment action and start again`() = runTest { - // GIVEN - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) - .thenReturn("Processing payment") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) - .thenReturn("Please wait…") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) - .thenReturn("Payment failed") - whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method)) .thenReturn("Try another payment method") - whenever(networkStatus.isConnected()).thenReturn(true) - val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) - whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) - val mockCardReaderPaymentController: CardReaderPaymentController = mock() - val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) - val paymentState = - MutableStateFlow( - CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( + errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" + ) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue( + (paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null ) - whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) - val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) - paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} - paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( - errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" - ) - assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) - assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) - // WHEN - clearInvocations(mockCardReaderPaymentController) - vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + // WHEN + clearInvocations(mockCardReaderPaymentController) + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) - // THEN - verify(mockCardReaderPaymentController).onCleared() - verify(mockCardReaderPaymentController).start() - } + // THEN + verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).start() + } @Test fun `given payment failed without retry action, when retry clicked, then should go back to checkout`() = runTest { @@ -943,7 +947,9 @@ class WooPosTotalsViewModelTest { errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) - assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) + assertTrue( + (paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null + ) // WHEN vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) From 6c2a7e7ef4050681cdf6b69a1c04f641f749776e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 15:54:30 +0100 Subject: [PATCH 39/44] Update string value Exit order -> Go back to checkout --- WooCommerce/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 0a84a88541d..47dbd4d9931 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4306,7 +4306,7 @@ Payment failed Try another payment method Try payment again - Exit order + Go back to checkout Dimmed background. Tap to close the menu. Card reader connected From 8040ff94a7058f5ccd15d31f8224d2c6b271891e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 17:13:10 +0100 Subject: [PATCH 40/44] Handle PaymentCollecting payment state during retry --- .../WooPosHomeChildToParentCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 +++ .../home/totals/WooPosTotalsViewModel.kt | 41 +++++++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 39bd1d75405..b43c0f2f298 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -23,6 +23,7 @@ sealed class ChildToParentEvent { data object BackFromCheckoutToCartClicked : ChildToParentEvent() data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() + data object PaymentCollecting : ChildToParentEvent() data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() data object RetryFailedPaymentClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 8ef31e407d0..2b4df204a94 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -113,6 +113,12 @@ class WooPosHomeViewModel @Inject constructor( sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } + is ChildToParentEvent.PaymentCollecting -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals + ) + } + is ChildToParentEvent.PaymentProcessing, is ChildToParentEvent.OrderSuccessfullyPaid, is ChildToParentEvent.PaymentFailed -> { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 51f52f8edbb..8d2b42e5023 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -180,8 +180,7 @@ class WooPosTotalsViewModel @Inject constructor( cancelPaymentAction() val order = totalsRepository.getOrderById(dataState.value.orderId) if (order == null) { - uiState.value = InitialState - childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + returnToCart() } else { uiState.value = buildWooPosTotalsViewState(order) collectPayment() @@ -231,16 +230,9 @@ class WooPosTotalsViewModel @Inject constructor( private fun listenToPaymentState() { viewModelScope.launch { cardReaderPaymentController?.paymentState?.collect { paymentState -> - val totalsState = uiState.value - if (totalsState is WooPosTotalsViewState.Totals) { - uiState.value = totalsState.copy( - paymentStateText = paymentState.javaClass.simpleName - ) - } - when (paymentState) { is CardReaderPaymentState.CollectingPayment, - is CardReaderPaymentState.LoadingData -> {} + is CardReaderPaymentState.LoadingData -> handlePaymentState(paymentState) is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, @@ -273,6 +265,28 @@ class WooPosTotalsViewModel @Inject constructor( } } + private suspend fun handlePaymentState(paymentState: CardReaderPaymentOrRefundState) { + val totalsState = uiState.value + if (totalsState is WooPosTotalsViewState.Totals) { + uiState.value = totalsState.copy( + paymentStateText = paymentState.javaClass.simpleName + ) + } else { + val order = totalsRepository.getOrderById(dataState.value.orderId) + if (order == null) { + returnToCart() + } else { + uiState.value = + buildWooPosTotalsViewState(order, paymentState as CardReaderPaymentState) + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) + } + } + } + + private suspend fun returnToCart() { + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } + private fun buildPaymentFailedState( state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment ): PaymentFailed { @@ -330,7 +344,10 @@ class WooPosTotalsViewModel @Inject constructor( } } - private suspend fun buildWooPosTotalsViewState(order: Order): WooPosTotalsViewState.Totals { + private suspend fun buildWooPosTotalsViewState( + order: Order, + paymentState: CardReaderPaymentState? = null + ): WooPosTotalsViewState.Totals { val subtotalAmount = order.productsTotal val taxAmount = order.totalTax val totalAmount = order.total @@ -343,7 +360,7 @@ class WooPosTotalsViewModel @Inject constructor( orderSubtotalText = priceFormat(subtotalAmount), orderTaxText = priceFormat(taxAmount), orderTotalText = priceFormat(totalAmount), - paymentStateText = "", + paymentStateText = paymentState?.javaClass?.simpleName ?: "", error = error ) } From 55925b29cfc82e790771cb6ac8f732a7886f4a79 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 17:25:00 +0100 Subject: [PATCH 41/44] Handle `PaymentCollecting` payment state received during retry --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 8d2b42e5023..c586e619b95 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -232,7 +232,8 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderPaymentController?.paymentState?.collect { paymentState -> when (paymentState) { is CardReaderPaymentState.CollectingPayment, - is CardReaderPaymentState.LoadingData -> handlePaymentState(paymentState) + is CardReaderPaymentState.LoadingData -> + handlePaymentState(paymentState as CardReaderPaymentState) is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, @@ -265,7 +266,7 @@ class WooPosTotalsViewModel @Inject constructor( } } - private suspend fun handlePaymentState(paymentState: CardReaderPaymentOrRefundState) { + private suspend fun handlePaymentState(paymentState: CardReaderPaymentState) { val totalsState = uiState.value if (totalsState is WooPosTotalsViewState.Totals) { uiState.value = totalsState.copy( From 2703d6fde04395b6072e482ea43f250919a76035 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 17:32:36 +0100 Subject: [PATCH 42/44] Clean up code --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 2 +- .../android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c586e619b95..aaf8fe9427e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -278,7 +278,7 @@ class WooPosTotalsViewModel @Inject constructor( returnToCart() } else { uiState.value = - buildWooPosTotalsViewState(order, paymentState as CardReaderPaymentState) + buildWooPosTotalsViewState(order, paymentState) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 576eda44b61..5719e004d94 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -959,7 +959,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest { + fun `given payment failed, when go back to checkout clicked, then should inform home about the situation`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") From 1c2ddee4cb604c964aba821b918dd7f43631aee8 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 18:09:16 +0100 Subject: [PATCH 43/44] Clean up tests after merge --- .../home/totals/WooPosTotalsViewModelTest.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 5719e004d94..01980e4a979 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -769,7 +769,7 @@ class WooPosTotalsViewModelTest { val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) readerStatus.value = CardReaderStatus.Connected(mock()) @@ -787,16 +787,15 @@ class WooPosTotalsViewModelTest { val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) // WHEN readerStatus.value = CardReaderStatus.NotConnected() // THEN - verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).stop() verify(mockCardReaderPaymentController).onBackPressed() - assertThat(vm.paymentScope!!.isActive).isFalse } @Test @@ -817,7 +816,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -850,7 +849,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -892,7 +891,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -913,7 +912,7 @@ class WooPosTotalsViewModelTest { vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) // THEN - verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).stop() verify(mockCardReaderPaymentController).start() } @@ -935,7 +934,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -976,7 +975,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} From 8b218982ef31417149dd423666937e676fdca00e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 18:09:24 +0100 Subject: [PATCH 44/44] Crash in case order is null illegally --- .../home/totals/WooPosTotalsViewModel.kt | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 354220fdf09..ba351005d69 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -168,12 +168,9 @@ class WooPosTotalsViewModel @Inject constructor( private suspend fun retryPaymentCollectionFromScratch() { cancelPaymentAction() val order = totalsRepository.getOrderById(dataState.value.orderId) - if (order == null) { - returnToCart() - } else { - uiState.value = buildWooPosTotalsViewState(order) - collectPayment() - } + checkNotNull(order) + uiState.value = buildWooPosTotalsViewState(order) + collectPayment() } private fun collectPayment() { @@ -222,7 +219,7 @@ class WooPosTotalsViewModel @Inject constructor( when (paymentState) { is CardReaderPaymentState.CollectingPayment, is CardReaderPaymentState.LoadingData -> - handlePaymentState(paymentState as CardReaderPaymentState) + handlePaymentState(paymentState) is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, @@ -233,9 +230,7 @@ class WooPosTotalsViewModel @Inject constructor( is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = - PaymentSuccess( - orderTotalText = paymentState.amountWithCurrencyLabel - ) + PaymentSuccess(orderTotalText = paymentState.amountWithCurrencyLabel) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } @@ -263,13 +258,9 @@ class WooPosTotalsViewModel @Inject constructor( ) } else { val order = totalsRepository.getOrderById(dataState.value.orderId) - if (order == null) { - returnToCart() - } else { - uiState.value = - buildWooPosTotalsViewState(order, paymentState) - childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) - } + checkNotNull(order) + uiState.value = buildWooPosTotalsViewState(order, paymentState) + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) } }