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 6dd302722b5..a6e52b037ea 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 @@ -90,7 +90,7 @@ private fun WooPosTotalsScreen( if (state is WooPosTotalsViewState.ReceiptSending) { WooPosTotalsPaymentReceiptScreen( state, - onEmailAddressChanged = {}, + onEmailAddressChanged = { onUIEvent(WooPosTotalsUIEvent.OnEmailChanged(it)) }, onSendReceiptClicked = { onUIEvent(WooPosTotalsUIEvent.OnSendReceiptClicked) } ) } 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 26143344294..af295147e17 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 @@ -6,4 +6,5 @@ sealed class WooPosTotalsUIEvent { data object RetryOrderCreationClicked : WooPosTotalsUIEvent() data object OnStartReceiptFlowClicked : WooPosTotalsUIEvent() data object OnSendReceiptClicked : WooPosTotalsUIEvent() + data class OnEmailChanged(val email: String) : 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 ee93330f333..b09aa73cc22 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 @@ -13,7 +13,8 @@ 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.items.WooPosItemsViewModel -import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosIsReceiptSendingAvailable +import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosTotalsPaymentReceiptIsSendingAvailable +import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosTotalsPaymentReceiptRepository 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 @@ -36,10 +37,11 @@ class WooPosTotalsViewModel @Inject constructor( private val childrenToParentEventSender: WooPosChildrenToParentEventSender, private val cardReaderFacade: WooPosCardReaderFacade, private val totalsRepository: WooPosTotalsRepository, + private val receiptRepository: WooPosTotalsPaymentReceiptRepository, private val priceFormat: WooPosFormatPrice, private val analyticsTracker: WooPosAnalyticsTracker, private val networkStatus: WooPosNetworkStatus, - private val isReceiptSendingAvailable: WooPosIsReceiptSendingAvailable, + private val isReceiptSendingAvailable: WooPosTotalsPaymentReceiptIsSendingAvailable, savedState: SavedStateHandle, ) : ViewModel() { @@ -81,10 +83,13 @@ class WooPosTotalsViewModel @Inject constructor( is WooPosTotalsUIEvent.RetryOrderCreationClicked -> { createOrderDraft(dataState.value.itemClickedDataList) } - WooPosTotalsUIEvent.OnSendReceiptClicked -> TODO() + WooPosTotalsUIEvent.OnSendReceiptClicked -> sendReceiptByEmail() WooPosTotalsUIEvent.OnStartReceiptFlowClicked -> { uiState.value = WooPosTotalsViewState.ReceiptSending(email = "") } + is WooPosTotalsUIEvent.OnEmailChanged -> { + uiState.value = WooPosTotalsViewState.ReceiptSending(email = event.email) + } } } @@ -100,6 +105,16 @@ class WooPosTotalsViewModel @Inject constructor( } } + private fun sendReceiptByEmail() { + val viewState = uiState.value as WooPosTotalsViewState.ReceiptSending + val email = viewState.email + val orderId = dataState.value.orderId + check(orderId != EMPTY_ORDER_ID) + viewModelScope.launch { + receiptRepository.sendReceiptByEmail(orderId, email) + } + } + private fun listenUpEvents() { viewModelScope.launch { parentToChildrenEventReceiver.events.collect { event -> diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosIsReceiptSendingAvailable.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptIsSendingAvailable.kt similarity index 93% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosIsReceiptSendingAvailable.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptIsSendingAvailable.kt index 6d0c709416c..ac269e81514 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosIsReceiptSendingAvailable.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptIsSendingAvailable.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.inject.Inject -class WooPosIsReceiptSendingAvailable @Inject constructor( +class WooPosTotalsPaymentReceiptIsSendingAvailable @Inject constructor( private val isReceiptsEnabled: WooPosIsReceiptsEnabled, private val getWooCoreVersion: GetWooCorePluginCachedVersion, ) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptRepository.kt new file mode 100644 index 00000000000..2bed30dfc4b --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptRepository.kt @@ -0,0 +1,52 @@ +package com.woocommerce.android.ui.woopos.home.totals.payment.receipt + +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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.wordpress.android.fluxc.store.WCOrderStore +import javax.inject.Inject + +class WooPosTotalsPaymentReceiptRepository @Inject constructor( + private val selectedSite: SelectedSite, + private val orderStore: WCOrderStore, + private val orderCreateEditRepository: OrderCreateEditRepository, + private val orderMapper: OrderMapper, +) { + suspend fun sendReceiptByEmail(orderId: Long, email: String): Result = withContext(Dispatchers.IO) { + val order = getOrderById(orderId) + if (order == null) { + return@withContext Result.failure(Exception("Failed to get order")) + } + + if (updateOrderWithEmail(order, email).isFailure) { + return@withContext Result.failure(Exception("Failed to update order with email")) + } + + return@withContext triggerOrderReceiptSending(orderId) + } + + private suspend fun triggerOrderReceiptSending(orderId: Long): Result { + val sendOrderResult = orderStore.sendOrderReceipt(selectedSite.get(), orderId) + return if (sendOrderResult.isError) { + Result.failure(Exception("Failed to send order receipt")) + } else { + Result.success(Unit) + } + } + + private suspend fun updateOrderWithEmail(order: Order, email: String): Result { + val updatedBillingAddress = order.billingAddress.copy(email = email) + val updatedCustomer = order.customer?.copy(billingAddress = updatedBillingAddress) + return orderCreateEditRepository.createOrUpdateOrder( + order = order.copy(customer = updatedCustomer) + ) + } + + private suspend fun getOrderById(orderId: Long) = + orderStore.getOrderByIdAndSite(orderId, selectedSite.get())?.let { + orderMapper.toAppModel(it) + } +} 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 37ddcd86d60..9d2ae980132 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 @@ -11,7 +11,8 @@ 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.items.WooPosItemsViewModel -import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosIsReceiptSendingAvailable +import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosTotalsPaymentReceiptIsSendingAvailable +import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosTotalsPaymentReceiptRepository import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent @@ -62,9 +63,10 @@ class WooPosTotalsViewModelTest { on { paymentStatus }.thenReturn(MutableStateFlow(WooPosCardReaderPaymentStatus.Unknown)) } private val analyticsTracker: WooPosAnalyticsTracker = mock() - private val isReceiptSendingAvailable: WooPosIsReceiptSendingAvailable = mock { + private val isReceiptSendingAvailable: WooPosTotalsPaymentReceiptIsSendingAvailable = mock { onBlocking { invoke() }.thenReturn(false) } + private val receiptRepository: WooPosTotalsPaymentReceiptRepository = mock() private companion object { private const val EMPTY_ORDER_ID = -1L @@ -706,15 +708,16 @@ class WooPosTotalsViewModelTest { priceFormat: WooPosFormatPrice = mock(), savedState: SavedStateHandle = SavedStateHandle(), ) = WooPosTotalsViewModel( - resourceProvider, - parentToChildrenEventReceiver, - childrenToParentEventSender, - cardReaderFacade, - totalsRepository, - priceFormat, - analyticsTracker, - networkStatus, - isReceiptSendingAvailable, - savedState + resourceProvider = resourceProvider, + parentToChildrenEventReceiver = parentToChildrenEventReceiver, + childrenToParentEventSender = childrenToParentEventSender, + cardReaderFacade = cardReaderFacade, + receiptRepository = receiptRepository, + totalsRepository = totalsRepository, + priceFormat = priceFormat, + analyticsTracker = analyticsTracker, + networkStatus = networkStatus, + isReceiptSendingAvailable = isReceiptSendingAvailable, + savedState = savedState, ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosIsReceiptSendingAvailableTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosIsReceiptSendingAvailableTest.kt index 9679aa4c4cd..269b2b9f357 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosIsReceiptSendingAvailableTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosIsReceiptSendingAvailableTest.kt @@ -12,7 +12,7 @@ class WooPosIsReceiptSendingAvailableTest { private val isReceiptsEnabled: WooPosIsReceiptsEnabled = mock() private val getWooCoreVersion: GetWooCorePluginCachedVersion = mock() - private val receiptSendingAvailable = WooPosIsReceiptSendingAvailable( + private val receiptSendingAvailable = WooPosTotalsPaymentReceiptIsSendingAvailable( isReceiptsEnabled = isReceiptsEnabled, getWooCoreVersion = getWooCoreVersion ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptRepositoryTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptRepositoryTest.kt new file mode 100644 index 00000000000..e05e57942c9 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/receipt/WooPosTotalsPaymentReceiptRepositoryTest.kt @@ -0,0 +1,128 @@ +package com.woocommerce.android.ui.woopos.home.totals.payment.receipt + +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 kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType +import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload +import org.wordpress.android.fluxc.store.WCOrderStore + +class WooPosTotalsPaymentReceiptRepositoryTest { + private val siteModel: SiteModel = mock() + private val selectedSite: SelectedSite = mock { + on { get() }.thenReturn(siteModel) + } + private val orderStore: WCOrderStore = mock() + private val orderCreateEditRepository: OrderCreateEditRepository = mock() + private val orderMapper: OrderMapper = mock() + + private val repository = WooPosTotalsPaymentReceiptRepository( + selectedSite, + orderStore, + orderCreateEditRepository, + orderMapper, + ) + + @Test + fun `given valid order id and email, when sendReceiptByEmail, then return success`() = runTest { + // GIVEN + val orderId = 1L + val email = "test@example.com" + val mockOrder: Order = mock { + on { billingAddress }.thenReturn(mock()) + on { customer }.thenReturn(mock()) + } + whenever(orderStore.getOrderByIdAndSite(orderId, siteModel)).thenReturn(mock()) + whenever(orderMapper.toAppModel(any())).thenReturn(mockOrder) + whenever(orderCreateEditRepository.createOrUpdateOrder(any(), eq(""))).thenReturn(Result.success(mockOrder)) + val sendOrderReceiptResult = WooPayload(Unit) + whenever(orderStore.sendOrderReceipt(siteModel, orderId)).thenReturn(sendOrderReceiptResult) + + // WHEN + val result = repository.sendReceiptByEmail(orderId, email) + + // THEN + assertThat(result.isSuccess).isTrue() + } + + @Test + fun `given invalid order id, when sendReceiptByEmail, then return failure`() = runTest { + // GIVEN + val orderId = 999L + val email = "test@example.com" + whenever(selectedSite.get()).thenReturn(siteModel) + whenever(orderStore.getOrderByIdAndSite(orderId, siteModel)).thenReturn(null) + + // WHEN + val result = repository.sendReceiptByEmail(orderId, email) + + // THEN + assertThat(result.isFailure).isTrue() + } + + @Test + fun `given email update fails, when sendReceiptByEmail, then return failure`() = runTest { + // GIVEN + val email = "test@example.com" + val orderId = 1L + val mockOrder: Order = mock { + on { billingAddress }.thenReturn(mock()) + on { customer }.thenReturn(mock()) + } + + whenever(selectedSite.get()).thenReturn(siteModel) + whenever(orderStore.getOrderByIdAndSite(orderId, siteModel)).thenReturn(mock()) + whenever(orderMapper.toAppModel(any())).thenReturn(mockOrder) + whenever(orderCreateEditRepository.createOrUpdateOrder(anyOrNull(), eq(""))).thenReturn( + Result.failure(Exception("Update failed")) + ) + + // WHEN + val result = repository.sendReceiptByEmail(orderId, email) + + // THEN + assertThat(result.isFailure).isTrue() + } + + @Test + fun `given receipt sending fails, when sendReceiptByEmail, then return failure`() = runTest { + // GIVEN + val orderId = 1L + val email = "test@example.com" + val mockOrder: Order = mock { + on { billingAddress }.thenReturn(mock()) + on { customer }.thenReturn(mock()) + } + whenever(selectedSite.get()).thenReturn(siteModel) + whenever(orderStore.getOrderByIdAndSite(orderId, siteModel)).thenReturn(mock()) + whenever(orderMapper.toAppModel(any())).thenReturn(mockOrder) + whenever(orderCreateEditRepository.createOrUpdateOrder(any(), eq(""))).thenReturn( + Result.success(mockOrder) + ) + val sendOrderReceiptResult = WooPayload( + WooError( + WooErrorType.GENERIC_ERROR, + GenericErrorType.TIMEOUT, + ) + ) + whenever(orderStore.sendOrderReceipt(siteModel, orderId)).thenReturn(sendOrderReceiptResult) + + // WHEN + val result = repository.sendReceiptByEmail(orderId, email) + + // THEN + assertThat(result.isFailure).isTrue() + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 22aaadbf266..9a0f85309f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -86,7 +86,7 @@ stripe-terminal = '3.7.1' tinder-statemachine = '0.2.0' wiremock = '2.26.3' wordpress-aztec = 'v2.1.4' -wordpress-fluxc = 'trunk-3d095f6f0a41e50faab2038a9f195f536b6e4520' +wordpress-fluxc = '3115-79440d50f3b8dd18a4bf98862c3be1861b82a320' wordpress-login = '1.19.0' wordpress-libaddressinput = '0.0.2' wordpress-mediapicker = '0.3.1'