Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POS][Custom payment UI] – Failed payment error handling #13057

Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a2ee75a
Show full screen "Payment processing" state
samiuelson Dec 3, 2024
6aecb6f
Clean up code
samiuelson Dec 3, 2024
4192a2a
Add preview
samiuelson Dec 3, 2024
5c325b6
Clean up code
samiuelson Dec 3, 2024
e9b3f20
Improve preview
samiuelson Dec 3, 2024
aa1a321
Create basic payment failed screen
samiuelson Dec 3, 2024
cde0db7
Show payment failed screen
samiuelson Dec 3, 2024
01b8bab
Update tests
samiuelson Dec 4, 2024
1c2ee96
Satisfy detekt's complaints
samiuelson Dec 4, 2024
502cb4e
Tune up payment failed composable
samiuelson Dec 4, 2024
60c7434
Tune up payment failed composable
samiuelson Dec 4, 2024
ebd6543
Tune up payment failed composable
samiuelson Dec 4, 2024
e73501b
Rename states
samiuelson Dec 4, 2024
56651e2
Ensure failed payment state is always full screen
samiuelson Dec 4, 2024
e05fafd
Implement failed payment "retry" action
samiuelson Dec 4, 2024
d3160ec
Implement failed payment "exit order" action
samiuelson Dec 4, 2024
3fc89e6
Move debug Totals payment state to the top
samiuelson Dec 4, 2024
84f825f
Add home VM test:
samiuelson Dec 4, 2024
447a9e8
Add home VM test:
samiuelson Dec 4, 2024
7ca6439
Add home VM test:
samiuelson Dec 4, 2024
b0d9b39
Add home VM test:
samiuelson Dec 4, 2024
9dad874
Tweak heights of totals boxes
samiuelson Dec 4, 2024
810ea01
Fix typos in test method names
samiuelson Dec 4, 2024
6abc26e
Add Cart VM test:
samiuelson Dec 4, 2024
e1eb05c
Add Totals VM test:
samiuelson Dec 4, 2024
6737f15
Add Totals VM test:
samiuelson Dec 4, 2024
3c3c219
Add Totals VM test:
samiuelson Dec 4, 2024
34d84e5
Add Totals VM test:
samiuelson Dec 4, 2024
2439997
Ensure Cart is cleared in case state is restored
samiuelson Dec 4, 2024
62684c4
Clean up code
samiuelson Dec 4, 2024
77f74b7
Satisfy detekt's complaints
samiuelson Dec 4, 2024
51eb2b2
Use WooPosColors for `paymentProcessingBackground`
samiuelson Dec 5, 2024
100e988
Use WooPosColors for `totalsBackground`
samiuelson Dec 5, 2024
8b4f5f7
Replace hardcoded error message with real one
samiuelson Dec 5, 2024
ba0bcdd
Satisfy detekt's complaints
samiuelson Dec 5, 2024
cf8d030
Get order from db instead of caching in memory
samiuelson Dec 6, 2024
5b12dd8
Improve failed payment "retry" handling
samiuelson Dec 6, 2024
6c35050
Satisfy detekt's complaints
samiuelson Dec 6, 2024
6c2a7e7
Update string value
samiuelson Dec 6, 2024
8040ff9
Handle PaymentCollecting payment state during retry
samiuelson Dec 6, 2024
55925b2
Handle `PaymentCollecting` payment state received during retry
samiuelson Dec 6, 2024
2703d6f
Clean up code
samiuelson Dec 6, 2024
5aa3f0b
Merge branch `custom-payment-ui-6-use-payment-controller-in-pos`
samiuelson Dec 11, 2024
1c2ddee
Clean up tests after merge
samiuelson Dec 11, 2024
8b21898
Crash in case order is null illegally
samiuelson Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
samiuelson marked this conversation as resolved.
Show resolved Hide resolved
)

private val LightCustomColors = CustomColors(
Expand All @@ -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),
Copy link
Contributor

Choose a reason for hiding this comment

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

Just validating that dark and light mode has the same color?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Couldn't find anything better in Figma, but as far as I can see it's the same in both modes on iOS as well. I'll check with the designer.

)

private val LocalCustomColors = staticCompositionLocalOf {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ 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 PaymentFailed : ChildToParentEvent()
data object RetryFailedPaymentClicked : ChildToParentEvent()
data object ExitOrderAfterFailedTransactionClicked : ChildToParentEvent()
data object OrderSuccessfullyPaid : ChildToParentEvent()
data object ExitPosClicked : ChildToParentEvent()
data object ProductsDialogInfoIconClicked : ChildToParentEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ sealed class ParentToChildrenEvent {
) : ParentToChildrenEvent()
data class CheckoutClicked(val productIds: List<Long>) : ParentToChildrenEvent()
data object OrderSuccessfullyPaid : ParentToChildrenEvent()
data object OrderCardPaymentAborted : ParentToChildrenEvent()
}

interface WooPosParentToChildrenEventReceiver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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),
),
Expand All @@ -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),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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))
}
Expand All @@ -104,19 +104,30 @@ 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.OrderSuccessfullyPaid -> {
is ChildToParentEvent.PaymentProcessing,
is ChildToParentEvent.OrderSuccessfullyPaid,
is ChildToParentEvent.PaymentFailed -> {
_state.value = _state.value.copy(
screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid
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)
Expand Down Expand Up @@ -156,8 +167,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
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down Expand Up @@ -151,7 +155,7 @@ class WooPosCartViewModel @Inject constructor(
}
}

private fun handleOrderSuccessfullyPaid() {
private fun clearCart() {
_state.value = WooPosCartState()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ 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

@Composable
Expand Down Expand Up @@ -87,6 +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,
)
}
}
}
}

Expand Down Expand Up @@ -116,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(
samiuelson marked this conversation as resolved.
Show resolved Hide resolved
modifier = Modifier.align(Alignment.Center),
text = state.paymentStateText,
style = MaterialTheme.typography.body1,
)
}
}
}
TotalsGrid(modifier = Modifier.weight(1f), state = state)
Expand Down Expand Up @@ -213,10 +237,6 @@ private fun TotalsGrid(
fontWeightOne = FontWeight.Medium,
fontWeightTwo = FontWeight.Bold,
)
Text(
text = state.paymentStateText,
style = MaterialTheme.typography.body1,
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Loading
Loading