diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index e04b2b4ea8f..52bbdb01e52 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,9 @@ 17.3 ----- - [*] [Internal] Enhanced user experience in shipping label creation with automatic scrolling to the first invalid field upon form submission failure [https://github.com/woocommerce/woocommerce-android/pull/10657] +17.2 +----- +- [**] [Available for users with WooCommerce version of 8.7+, which is not released yet] Every order have a receipt now. The receipts can be shared via many apps installed on the phone [https://github.com/woocommerce/woocommerce-android/pull/10650] 17.1 ----- diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt index ef6fc5fa382..2527475680e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt @@ -456,6 +456,7 @@ enum class AnalyticsEvent(val siteless: Boolean = false) { RECEIPT_PRINT_CANCELED, RECEIPT_PRINT_SUCCESS, RECEIPT_VIEW_TAPPED, + RECEIPT_URL_FETCHING_FAILS, // -- Top-level navigation MAIN_MENU_SETTINGS_TAPPED, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt index f652d0c4dd5..9590be95f3b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailFragment.kt @@ -254,7 +254,7 @@ class OrderDetailFragment : private fun setupObservers(viewModel: OrderDetailViewModel) { viewModel.viewStateData.observe(viewLifecycleOwner) { old, new -> new.orderInfo?.takeIfNotEqualTo(old?.orderInfo) { - showOrderDetail(it.order!!, it.isPaymentCollectableWithCardReader, it.isReceiptButtonsVisible) + showOrderDetail(it.order!!, it.isPaymentCollectableWithCardReader, it.receiptButtonStatus) requireActivity().invalidateOptionsMenu() } new.orderStatus?.takeIfNotEqualTo(old?.orderStatus) { showOrderStatus(it) } @@ -437,7 +437,7 @@ class OrderDetailFragment : private fun showOrderDetail( order: Order, isPaymentCollectableWithCardReader: Boolean, - isReceiptButtonsVisible: Boolean + receiptButtonStatus: OrderDetailViewState.ReceiptButtonStatus ) { binding.orderDetailOrderStatus.updateOrder(order) binding.orderDetailShippingMethodNotice.isVisible = order.hasMultipleShippingLines @@ -449,7 +449,7 @@ class OrderDetailFragment : binding.orderDetailPaymentInfo.updatePaymentInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectableWithCardReader, - isReceiptAvailable = isReceiptButtonsVisible, + receiptButtonStatus = receiptButtonStatus, formatCurrencyForDisplay = currencyFormatter.buildBigDecimalFormatter(order.currency), onIssueRefundClickListener = { viewModel.onIssueOrderRefundClicked() }, onSeeReceiptClickListener = { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt index b86ef2b5a92..195b3b88586 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailTracker.kt @@ -4,11 +4,13 @@ import com.woocommerce.android.analytics.AnalyticsEvent import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.model.Order +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import org.wordpress.android.fluxc.store.WCOrderStore import javax.inject.Inject class OrderDetailTracker @Inject constructor( - private val trackerWrapper: AnalyticsTrackerWrapper + private val trackerWrapper: AnalyticsTrackerWrapper, + private val paymentsFlowTracker: PaymentsFlowTracker, ) { fun trackCustomFieldsTapped() { trackerWrapper.track(AnalyticsEvent.ORDER_VIEW_CUSTOM_FIELDS_TAPPED) @@ -24,9 +26,8 @@ class OrderDetailTracker @Inject constructor( ) } - fun trackReceiptViewTapped(orderId: Long, orderStatus: Order.Status) { - trackerWrapper.track( - AnalyticsEvent.RECEIPT_VIEW_TAPPED, + suspend fun trackReceiptViewTapped(orderId: Long, orderStatus: Order.Status) { + paymentsFlowTracker.trackReceiptViewTapped( mapOf( AnalyticsTracker.KEY_ORDER_ID to orderId, AnalyticsTracker.KEY_STATUS to orderStatus diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt index 38375776ee7..412095ef048 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewModel.kt @@ -49,6 +49,7 @@ import com.woocommerce.android.ui.orders.OrderStatusUpdateSource import com.woocommerce.android.ui.orders.details.customfields.CustomOrderFieldsHelper import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.ui.products.addons.AddonRepository @@ -91,7 +92,8 @@ class OrderDetailViewModel @Inject constructor( private val getOrderSubscriptions: GetOrderSubscriptions, private val giftCardRepository: GiftCardRepository, private val orderProductMapper: OrderProductMapper, - private val productDetailRepository: ProductDetailRepository + private val productDetailRepository: ProductDetailRepository, + private val paymentReceiptHelper: PaymentReceiptHelper, ) : ScopedViewModel(savedState), OnProductFetchedListener { private val navArgs: OrderDetailFragmentArgs by savedState.navArgs() @@ -101,9 +103,13 @@ class OrderDetailViewModel @Inject constructor( get() = requireNotNull(viewState.orderInfo?.order) set(value) { viewState = viewState.copy( - orderInfo = OrderDetailViewState.OrderInfo( + orderInfo = viewState.orderInfo?.copy( + order = value, + isPaymentCollectableWithCardReader = viewState.orderInfo?.isPaymentCollectableWithCardReader + ?: false + ) ?: OrderDetailViewState.OrderInfo( value, - viewState.orderInfo?.isPaymentCollectableWithCardReader ?: false + isPaymentCollectableWithCardReader = false ) ) } @@ -343,10 +349,32 @@ class OrderDetailViewModel @Inject constructor( } fun onSeeReceiptClicked() { - tracker.trackReceiptViewTapped(order.id, order.status) - loadReceiptUrl()?.let { - triggerEvent(PreviewReceipt(order.billingAddress.email, it, order.id)) - } ?: WooLog.e(T.ORDERS, "ReceiptUrl is null, but SeeReceipt button is visible") + launch { + tracker.trackReceiptViewTapped(order.id, order.status) + + viewState = viewState.copy( + orderInfo = viewState.orderInfo?.copy( + receiptButtonStatus = OrderDetailViewState.ReceiptButtonStatus.Loading + ) + ) + + val receiptResult = paymentReceiptHelper.getReceiptUrl(order.id) + + viewState = viewState.copy( + orderInfo = viewState.orderInfo?.copy( + receiptButtonStatus = OrderDetailViewState.ReceiptButtonStatus.Visible + ) + ) + + if (receiptResult.isSuccess) { + triggerEvent(PreviewReceipt(order.billingAddress.email, receiptResult.getOrThrow(), order.id)) + } else { + paymentsFlowTracker.trackReceiptUrlFetchingFails( + errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error", + ) + triggerEvent(ShowSnackbar(string.receipt_fetching_error)) + } + } } fun onPrintingInstructionsClicked() { @@ -372,12 +400,6 @@ class OrderDetailViewModel @Inject constructor( triggerEvent(ShowSnackbar(message)) } - private fun loadReceiptUrl(): String? { - return selectedSite.getIfExists()?.let { - appPrefs.getReceiptUrl(it.id, it.siteId, it.selfHostedSiteId, order.id) - } - } - fun onViewRefundedProductsClicked() { triggerEvent(ViewRefundedProducts(orderId = order.id)) } @@ -580,7 +602,11 @@ class OrderDetailViewModel @Inject constructor( orderInfo = OrderDetailViewState.OrderInfo( order = order, isPaymentCollectableWithCardReader = isPaymentCollectable, - isReceiptButtonsVisible = !loadReceiptUrl().isNullOrEmpty() + receiptButtonStatus = if (paymentReceiptHelper.isReceiptAvailable(order.id) && order.isOrderPaid) { + OrderDetailViewState.ReceiptButtonStatus.Visible + } else { + OrderDetailViewState.ReceiptButtonStatus.Hidden + } ), orderStatus = orderStatus, toolbarTitle = resourceProvider.getString( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt index 77a0558726a..78d2d9736fb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/OrderDetailViewState.kt @@ -33,6 +33,10 @@ data class OrderDetailViewState( data class OrderInfo( val order: Order? = null, val isPaymentCollectableWithCardReader: Boolean = false, - val isReceiptButtonsVisible: Boolean = false + val receiptButtonStatus: ReceiptButtonStatus = ReceiptButtonStatus.Hidden, ) : Parcelable + + enum class ReceiptButtonStatus { + Loading, Hidden, Visible + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt index bc5384b9fc2..1a2bf575cb5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/details/views/OrderDetailPaymentInfoView.kt @@ -17,6 +17,7 @@ import com.woocommerce.android.extensions.show import com.woocommerce.android.model.GiftCardSummary import com.woocommerce.android.model.Order import com.woocommerce.android.model.Refund +import com.woocommerce.android.ui.orders.details.OrderDetailViewState import com.woocommerce.android.ui.orders.details.adapter.OrderDetailRefundsAdapter import com.woocommerce.android.ui.orders.details.adapter.OrderDetailRefundsLineBuilder import dagger.hilt.android.AndroidEntryPoint @@ -36,7 +37,7 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( @Suppress("LongParameterList") fun updatePaymentInfo( order: Order, - isReceiptAvailable: Boolean, + receiptButtonStatus: OrderDetailViewState.ReceiptButtonStatus, isPaymentCollectableWithCardReader: Boolean, formatCurrencyForDisplay: (BigDecimal) -> String, onSeeReceiptClickListener: (view: View) -> Unit, @@ -88,7 +89,7 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( updateFeesSection(order, formatCurrencyForDisplay) updateRefundSection(order, formatCurrencyForDisplay, onIssueRefundClickListener) updateCollectPaymentSection(order, onCollectPaymentClickListener) - updateSeeReceiptSection(isReceiptAvailable, onSeeReceiptClickListener) + updateSeeReceiptSection(receiptButtonStatus, onSeeReceiptClickListener) updatePrintingInstructionSection(isPaymentCollectableWithCardReader, onPrintingInstructionsClickListener) } @@ -192,16 +193,26 @@ class OrderDetailPaymentInfoView @JvmOverloads constructor( } private fun updateSeeReceiptSection( - isReceiptAvailable: Boolean, + receiptButtonStatus: OrderDetailViewState.ReceiptButtonStatus, onSeeReceiptClickListener: (view: View) -> Unit ) { - if (isReceiptAvailable) { - binding.paymentInfoSeeReceiptButton.visibility = VISIBLE - binding.paymentInfoSeeReceiptButton.setOnClickListener( - onSeeReceiptClickListener - ) - } else { - binding.paymentInfoSeeReceiptButton.visibility = GONE + when (receiptButtonStatus) { + OrderDetailViewState.ReceiptButtonStatus.Loading -> { + binding.paymentInfoSeeReceiptButton.visibility = VISIBLE + binding.paymentInfoSeeReceiptButton.isEnabled = false + binding.paymentInfoSeeReceiptButtonProgressBar.visibility = VISIBLE + } + OrderDetailViewState.ReceiptButtonStatus.Hidden -> { + binding.paymentInfoSeeReceiptButton.visibility = GONE + binding.paymentInfoSeeReceiptButtonProgressBar.visibility = GONE + } + OrderDetailViewState.ReceiptButtonStatus.Visible -> { + binding.paymentInfoSeeReceiptButtonProgressBar.visibility = GONE + binding.paymentInfoSeeReceiptButton.visibility = VISIBLE + binding.paymentInfoSeeReceiptButton.setOnClickListener( + onSeeReceiptClickListener + ) + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt index c52fc728e83..9f0bceea198 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingChecker.kt @@ -54,8 +54,6 @@ import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPayment import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject -const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" - /** * This class is used to check if the selected store is ready to accept In Person Payments. The app should check store's * eligibility every time it attempts to connect to a card reader. diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt index a317c226d28..ae5b3ce8354 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt @@ -22,7 +22,6 @@ import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.CardReaderPaymentDialogBinding import com.woocommerce.android.extensions.navigateBackWithNotice -import com.woocommerce.android.model.UiString import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.support.requests.SupportRequestFormActivity import com.woocommerce.android.ui.base.UIMessageResolver @@ -32,7 +31,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInR import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulReceiptSentAutomaticallyState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulState import com.woocommerce.android.ui.payments.refunds.RefundSummaryFragment.Companion.KEY_INTERAC_SUCCESS -import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.util.UiHelpers.getTextOfUiString @@ -86,7 +84,6 @@ class CardReaderPaymentDialogFragment : PaymentsBaseDialogFragment(R.layout.card event.documentName ) InteracRefundSuccessful -> navigateBackWithNotice(KEY_INTERAC_SUCCESS) - is SendReceipt -> composeEmail(event.address, event.subject, event.content) is ShowSnackbar -> uiMessageResolver.showSnack(event.message) is ShowSnackbarInDialog -> Snackbar.make( requireView(), event.message, BaseTransientBottomBar.LENGTH_LONG @@ -186,12 +183,6 @@ class CardReaderPaymentDialogFragment : PaymentsBaseDialogFragment(R.layout.card mp.start() } - private fun composeEmail(address: String, subject: UiString, content: UiString) { - ActivityUtils.sendEmail(requireActivity(), address, subject, content) { - viewModel.onEmailActivityNotFound() - } - } - override fun onResume() { super.onResume() AnalyticsTracker.trackViewShown(this) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelper.kt index bb362048f7c..b4a1b32800d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentOrderHelper.kt @@ -23,5 +23,5 @@ class CardReaderPaymentOrderHelper @Inject constructor( fun getAmountLabel(order: Order): String = currencyFormatter .formatAmountWithCurrency(order.total.toDouble(), order.currency) - fun getReceiptDocumentName(order: Order) = "receipt-order-${order.id}" + fun getReceiptDocumentName(orderId: Long) = "receipt-order-$orderId" } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 87e76828d0f..72b5e18b46a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -68,6 +68,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchi import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CoroutineDispatchers @@ -117,6 +118,7 @@ class CardReaderPaymentViewModel private val paymentReceiptHelper: PaymentReceiptHelper, private val cardReaderOnboardingChecker: CardReaderOnboardingChecker, private val cardReaderConfigProvider: CardReaderCountryConfigProvider, + private val paymentReceiptShare: PaymentReceiptShare, ) : ScopedViewModel(savedState) { private val arguments: CardReaderPaymentDialogFragmentArgs by savedState.navArgs() @@ -605,20 +607,13 @@ class CardReaderPaymentViewModel launch { val order = requireNotNull(orderRepository.getOrderById(orderId)) { "Order URL not available." } val amountLabel = cardReaderPaymentOrderHelper.getAmountLabel(order) - val receiptUrl = paymentReceiptHelper.getReceiptUrl(order.id) val onPrintReceiptClicked = { - onPrintReceiptClicked( - amountLabel, - receiptUrl, - cardReaderPaymentOrderHelper.getReceiptDocumentName(order) - ) + onPrintReceiptClicked(amountLabel) } val onSaveUserClicked = { onSaveForLaterClicked() } - val onSendReceiptClicked = { - onSendReceiptClicked(receiptUrl, order.billingAddress.email) - } + val onSendReceiptClicked = { onSendReceiptClicked() } if (order.billingAddress.email.isBlank()) { viewState.postValue( @@ -698,9 +693,9 @@ class CardReaderPaymentViewModel onCancelPaymentFlow() } - private fun onPrintReceiptClicked(amountWithCurrencyLabel: String, receiptUrl: String, documentName: String) { + private fun onPrintReceiptClicked(amountWithCurrencyLabel: String) { launch { - viewState.value = PrintingReceiptState(amountWithCurrencyLabel, receiptUrl, documentName) + viewState.value = PrintingReceiptState(amountWithCurrencyLabel) tracker.trackPrintReceiptTapped() startPrintingFlow() } @@ -714,48 +709,68 @@ class CardReaderPaymentViewModel private fun startPrintingFlow() { launch { - val order = orderRepository.getOrderById(orderId) - ?: throw IllegalStateException("Order URL not available.") - triggerEvent( - PrintReceipt( - paymentReceiptHelper.getReceiptUrl(order.id), - cardReaderPaymentOrderHelper.getReceiptDocumentName(order) + val receiptResult = paymentReceiptHelper.getReceiptUrl(orderId) + if (receiptResult.isSuccess) { + triggerEvent( + PrintReceipt( + receiptResult.getOrThrow(), + cardReaderPaymentOrderHelper.getReceiptDocumentName(orderId) + ) ) - ) + } else { + tracker.trackReceiptUrlFetchingFails( + errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error", + ) + triggerEvent(ShowSnackbar(R.string.receipt_fetching_error)) + } } } - private fun onSendReceiptClicked(receiptUrl: String, billingEmail: String) { + private fun onSendReceiptClicked() { launch { tracker.trackEmailReceiptTapped() - triggerEvent( - SendReceipt( - content = UiStringRes( - R.string.card_reader_payment_receipt_email_content, - listOf(UiStringText(receiptUrl)) - ), - subject = UiStringRes( - R.string.card_reader_payment_receipt_email_subject, - listOf(UiStringText(selectedSite.get().name.orEmpty())) - ), - address = billingEmail + val stateBeforeLoading = viewState.value!! + viewState.postValue(ViewState.SharingReceiptState) + val receiptResult = paymentReceiptHelper.getReceiptUrl(orderId) + + if (receiptResult.isSuccess) { + when (val sharingResult = paymentReceiptShare(receiptResult.getOrThrow(), orderId)) { + is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { + tracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_can_not_be_stored)) + } + is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { + tracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_can_not_be_downloaded)) + } + is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { + tracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(R.string.card_reader_payment_email_client_not_found)) + } + PaymentReceiptShare.ReceiptShareResult.Success -> { + // no-op + } + } + } else { + tracker.trackReceiptUrlFetchingFails( + errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error", ) - ) - } - } + triggerEvent(ShowSnackbar(R.string.receipt_fetching_error)) + } - fun onEmailActivityNotFound() { - tracker.trackEmailReceiptFailed() - triggerEvent(ShowSnackbarInDialog(R.string.card_reader_payment_email_client_not_found)) + viewState.postValue(stateBeforeLoading) + } } fun onPrintResult(result: PrintJobResult) { showPaymentSuccessfulState() - when (result) { - CANCELLED -> tracker.trackPrintReceiptCancelled() - FAILED -> tracker.trackPrintReceiptFailed() - STARTED -> tracker.trackPrintReceiptSucceeded() + launch { + when (result) { + CANCELLED -> tracker.trackPrintReceiptCancelled() + FAILED -> tracker.trackPrintReceiptFailed() + STARTED -> tracker.trackPrintReceiptSucceeded() + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt index 5a44a75ebb3..6ac68063777 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.payments.cardreader.payment import androidx.annotation.StringRes -import com.woocommerce.android.model.UiString import com.woocommerce.android.viewmodel.MultiLiveEvent.Event class ShowSnackbarInDialog(@StringRes val message: Int) : Event() @@ -17,5 +16,3 @@ object EnableNfc : Event() data class PurchaseCardReader(val url: String) : Event() data class PrintReceipt(val receiptUrl: String, val documentName: String) : Event() - -data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt index 396b9b2711a..1f73c0126df 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt @@ -210,8 +210,6 @@ sealed class ViewState( data class PrintingReceiptState( override val amountWithCurrencyLabel: String, - val receiptUrl: String, - val documentName: String ) : ViewState( headerLabel = R.string.card_reader_payment_completed_payment_header, illustration = null, @@ -221,6 +219,15 @@ sealed class ViewState( override val isProgressVisible = true } + object SharingReceiptState : ViewState( + headerLabel = R.string.card_reader_payment_completed_payment_header, + illustration = null, + primaryActionLabel = null, + secondaryActionLabel = null, + ) { + override val isProgressVisible = true + } + object ReFetchingOrderState : ViewState( headerLabel = R.string.card_reader_payment_fetch_order_loading_header, hintLabel = R.string.card_reader_payment_fetch_order_loading_hint, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt index 4ce9c100406..daf1bbffaee 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt @@ -1,16 +1,21 @@ package com.woocommerce.android.ui.payments.receipt import com.woocommerce.android.AppPrefsWrapper +import com.woocommerce.android.BuildConfig import com.woocommerce.android.extensions.semverCompareTo import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType -import com.woocommerce.android.ui.payments.cardreader.onboarding.WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.WCOrderStore +import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject class PaymentReceiptHelper @Inject constructor( private val selectedSite: SelectedSite, - private val appPrefsWrapper: AppPrefsWrapper + private val wooCommerceStore: WooCommerceStore, + private val appPrefsWrapper: AppPrefsWrapper, + private val orderStore: WCOrderStore, + private val isDevSiteSupported: IsDevSiteSupported, ) { fun storeReceiptUrl(orderId: Long, receiptUrl: String) { selectedSite.get().let { @@ -18,7 +23,37 @@ class PaymentReceiptHelper @Inject constructor( } } - fun getReceiptUrl(orderId: Long) = selectedSite.get().let { + suspend fun getReceiptUrl(orderId: Long): Result = + if (isWCCanGenerateReceipts()) { + val fetchingResult = orderStore.fetchOrdersReceipt( + site = selectedSite.get(), + orderId = orderId, + expirationDays = RECEIPT_EXPIRATION_DAYS, + ) + + val result = fetchingResult.result + if (fetchingResult.isError || result == null) { + Result.failure(Exception(fetchingResult.error.message)) + } else { + Result.success(result.receiptUrl) + } + } else { + val urlFromAppPrefs = getReceiptUrlFromAppPrefs(orderId) + if (urlFromAppPrefs.isEmpty()) { + Result.failure(Exception("Receipt url not found")) + } else { + Result.success(urlFromAppPrefs) + } + } + + suspend fun isReceiptAvailable(orderId: Long) = + when { + isWCCanGenerateReceipts() -> true + getReceiptUrlFromAppPrefs(orderId).isNotEmpty() -> true + else -> false + } + + private fun getReceiptUrlFromAppPrefs(orderId: Long) = selectedSite.get().let { appPrefsWrapper.getReceiptUrl(it.id, it.siteId, it.selfHostedSiteId, orderId) } @@ -41,4 +76,44 @@ class PaymentReceiptHelper @Inject constructor( pluginVersion.semverCompareTo(WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION) >= 0 } } + + suspend fun isWCCanGenerateReceipts(): Boolean { + val currentWooCoreVersion = getWoocommerceCorePluginVersion() + + return currentWooCoreVersion.semverCompareTo(WC_CAN_GENERATE_RECEIPTS_VERSION) >= 0 + } + + private suspend fun getWoocommerceCorePluginVersion(): String { + val sitePlugin = wooCommerceStore.getSitePlugin( + selectedSite.get(), + WooCommerceStore.WooPlugin.WOO_CORE + ) + return if (sitePlugin == null) { + if (isDevSiteSupported()) { + val devPlugin = wooCommerceStore.getSitePlugins(selectedSite.get()) + .firstOrNull { it.name == "woocommerce-dev/woocommerce" } + if (devPlugin != null) { + // return this version so we use the backend to generate receipts for testing + WC_CAN_GENERATE_RECEIPTS_VERSION + } else { + "" + } + } else { + "" + } + } else { + sitePlugin.version + } ?: "" + } + + private companion object { + const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0" + const val WC_CAN_GENERATE_RECEIPTS_VERSION = "8.7.0" + + const val RECEIPT_EXPIRATION_DAYS = 2 + } + + class IsDevSiteSupported @Inject constructor() { + operator fun invoke() = BuildConfig.DEBUG + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt new file mode 100644 index 00000000000..d2a1c502470 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptShare.kt @@ -0,0 +1,59 @@ +package com.woocommerce.android.ui.payments.receipt + +import android.app.Application +import android.content.Intent +import android.os.Environment +import androidx.core.content.FileProvider +import com.woocommerce.android.media.FileUtils +import com.woocommerce.android.util.FileDownloader +import javax.inject.Inject + +class PaymentReceiptShare @Inject constructor( + private val fileUtils: FileUtils, + private val fileDownloader: FileDownloader, + private val context: Application, +) { + @Suppress("TooGenericExceptionCaught") + suspend operator fun invoke(receiptUrl: String, orderNumber: Long): ReceiptShareResult { + val receiptFile = fileUtils.createTempTimeStampedFile( + storageDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + ?: context.filesDir, + prefix = "receipt_$orderNumber", + fileExtension = "html" + ) + return if (receiptFile == null) { + ReceiptShareResult.Error.FileCreation + } else if (!fileDownloader.downloadFile(receiptUrl, receiptFile)) { + ReceiptShareResult.Error.FileDownload + } else { + val uri = FileProvider.getUriForFile( + context, + context.packageName + ".provider", + receiptFile + ) + val intent = Intent(Intent.ACTION_SEND).apply { + type = "application/*" + putExtra(Intent.EXTRA_STREAM, uri) + } + try { + context.startActivity( + Intent.createChooser(intent, null).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + ReceiptShareResult.Success + } catch (e: Exception) { + ReceiptShareResult.Error.Sharing(e) + } + } + } + + sealed class ReceiptShareResult { + object Success : ReceiptShareResult() + sealed class Error : ReceiptShareResult() { + data class Sharing(val exception: Exception) : Error() + object FileCreation : Error() + object FileDownload : Error() + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt index 9523b67ec65..30753e17d69 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt @@ -14,7 +14,6 @@ import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentReceiptPreviewBinding import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver -import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.PrintHtmlHelper import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar @@ -52,7 +51,7 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview), true } R.id.menu_send -> { - viewModel.onSendEmailClicked() + viewModel.onShareClicked() true } else -> false @@ -102,16 +101,9 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview), when (it) { is LoadUrl -> binding.receiptPreviewPreviewWebview.loadUrl(it.url) is PrintReceipt -> printHtmlHelper.printReceipt(requireActivity(), it.receiptUrl, it.documentName) - is SendReceipt -> composeEmail(it) is ShowSnackbar -> uiMessageResolver.showSnack(it.message) else -> it.isHandled = false } } } - - private fun composeEmail(event: SendReceipt) { - ActivityUtils.sendEmail(requireActivity(), event.address, event.subject, event.content) { - viewModel.onEmailActivityNotFound() - } - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt index e609731afa8..54cf312c698 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt @@ -4,18 +4,10 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.R.string -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_FAILED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS -import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED -import com.woocommerce.android.analytics.AnalyticsTrackerWrapper -import com.woocommerce.android.model.UiString.UiStringRes -import com.woocommerce.android.model.UiString.UiStringText -import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED @@ -31,8 +23,8 @@ import javax.inject.Inject class ReceiptPreviewViewModel @Inject constructor( savedState: SavedStateHandle, - private val tracker: AnalyticsTrackerWrapper, - private val selectedSite: SelectedSite, + private val paymentsFlowTracker: PaymentsFlowTracker, + private val paymentReceiptShare: PaymentReceiptShare, ) : ScopedViewModel(savedState) { private val args: ReceiptPreviewFragmentArgs by savedState.navArgs() @@ -48,42 +40,47 @@ class ReceiptPreviewViewModel } fun onPrintClicked() { - tracker.track(RECEIPT_PRINT_TAPPED) - triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}")) - } - - fun onSendEmailClicked() { launch { - tracker.track(RECEIPT_EMAIL_TAPPED) - triggerEvent( - SendReceipt( - content = UiStringRes( - string.card_reader_payment_receipt_email_content, - listOf(UiStringText(args.receiptUrl)) - ), - subject = UiStringRes( - string.card_reader_payment_receipt_email_subject, - listOf(UiStringText(selectedSite.get().name.orEmpty())) - ), - address = args.billingEmail - ) - ) + paymentsFlowTracker.trackPrintReceiptTapped() + triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}")) } } - fun onEmailActivityNotFound() { - tracker.track(RECEIPT_EMAIL_FAILED) - triggerEvent(ShowSnackbar(string.card_reader_payment_email_client_not_found)) + fun onShareClicked() { + launch { + viewState.value = Loading + + paymentsFlowTracker.trackEmailReceiptTapped() + when (val sharingResult = paymentReceiptShare(args.receiptUrl, args.orderId)) { + is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { + paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_stored)) + } + is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { + paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_downloaded)) + } + is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { + paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult) + triggerEvent(ShowSnackbar(string.card_reader_payment_email_client_not_found)) + } + PaymentReceiptShare.ReceiptShareResult.Success -> { + // no-op + } + } + + viewState.value = Content + } } fun onPrintResult(result: PrintJobResult) { - tracker.track( + launch { when (result) { - CANCELLED -> RECEIPT_PRINT_CANCELED - FAILED -> RECEIPT_PRINT_FAILED - STARTED -> RECEIPT_PRINT_SUCCESS + CANCELLED -> paymentsFlowTracker.trackPrintReceiptCancelled() + FAILED -> paymentsFlowTracker.trackPrintReceiptFailed() + STARTED -> paymentsFlowTracker.trackPrintReceiptSucceeded() } - ) + } } sealed class ReceiptPreviewViewState( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt index 5edf89f0559..de027028483 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModelEvent.kt @@ -1,10 +1,7 @@ package com.woocommerce.android.ui.payments.receipt.preview -import com.woocommerce.android.model.UiString import com.woocommerce.android.viewmodel.MultiLiveEvent data class LoadUrl(val url: String) : MultiLiveEvent.Event() data class PrintReceipt(val receiptUrl: String, val documentName: String) : MultiLiveEvent.Event() - -data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : MultiLiveEvent.Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt index 0e7b2739a57..0ffc92e1cb3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt @@ -53,6 +53,8 @@ import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED +import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_URL_FETCHING_FAILS +import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_VIEW_TAPPED import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_CASH_ON_DELIVERY_SOURCE import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_ERROR_DESC @@ -73,6 +75,8 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.STRIPE_EXTENSION_GATEWAY import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.WOOCOMMERCE_PAYMENTS import com.woocommerce.android.ui.payments.hub.PaymentsHubViewModel.CashOnDeliverySource +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus.Result.NotAvailable import javax.inject.Inject @@ -80,7 +84,8 @@ class PaymentsFlowTracker @Inject constructor( private val trackerWrapper: AnalyticsTrackerWrapper, private val appPrefsWrapper: AppPrefsWrapper, private val selectedSite: SelectedSite, - private val cardReaderTrackingInfoProvider: CardReaderTrackingInfoProvider + private val cardReaderTrackingInfoProvider: CardReaderTrackingInfoProvider, + private val paymentReceiptHelper: PaymentReceiptHelper, ) { @VisibleForTesting fun track( @@ -155,6 +160,13 @@ class PaymentsFlowTracker @Inject constructor( } } + private suspend fun getReceiptSource(): Pair = + if (paymentReceiptHelper.isWCCanGenerateReceipts()) { + AnalyticsTracker.KEY_SOURCE to "backend" + } else { + AnalyticsTracker.KEY_SOURCE to "local" + } + @Suppress("ComplexMethod") private fun getOnboardingNotCompletedReason(state: CardReaderOnboardingState): String? = when (state) { @@ -411,28 +423,58 @@ class PaymentsFlowTracker @Inject constructor( ) } - fun trackPrintReceiptTapped() { - track(RECEIPT_PRINT_TAPPED) + suspend fun trackPrintReceiptTapped() { + track( + RECEIPT_PRINT_TAPPED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackEmailReceiptTapped() { - track(RECEIPT_EMAIL_TAPPED) + suspend fun trackEmailReceiptTapped() { + track( + RECEIPT_EMAIL_TAPPED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackEmailReceiptFailed() { - track(RECEIPT_EMAIL_FAILED) + suspend fun trackPrintReceiptCancelled() { + track( + RECEIPT_PRINT_CANCELED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackPrintReceiptCancelled() { - track(RECEIPT_PRINT_CANCELED) + suspend fun trackPrintReceiptFailed() { + track( + RECEIPT_PRINT_FAILED, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackPrintReceiptFailed() { - track(RECEIPT_PRINT_FAILED) + suspend fun trackPrintReceiptSucceeded() { + track( + RECEIPT_PRINT_SUCCESS, + properties = mutableMapOf(getReceiptSource()) + ) } - fun trackPrintReceiptSucceeded() { - track(RECEIPT_PRINT_SUCCESS) + suspend fun trackReceiptViewTapped(properties: Map) { + track( + RECEIPT_VIEW_TAPPED, + properties = properties.toMutableMap().also { + it.putAll( + mapOf(getReceiptSource()) + ) + } + ) + } + + suspend fun trackReceiptUrlFetchingFails(errorDescription: String) { + track( + RECEIPT_URL_FETCHING_FAILS, + properties = mutableMapOf(getReceiptSource()), + errorDescription = errorDescription, + ) } fun trackPaymentCancelled(currentPaymentState: String?) { @@ -580,6 +622,37 @@ class PaymentsFlowTracker @Inject constructor( ) } + suspend fun trackPaymentsReceiptSharingFailed(sharingResult: PaymentReceiptShare.ReceiptShareResult.Error) { + when (sharingResult) { + is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> { + track( + RECEIPT_EMAIL_FAILED, + errorType = "file_creation_failed", + errorDescription = "File creation failed", + properties = mutableMapOf(getReceiptSource()) + ) + } + + is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> { + track( + RECEIPT_EMAIL_FAILED, + errorType = "file_download_failed", + errorDescription = "File download failed", + properties = mutableMapOf(getReceiptSource()) + ) + } + + is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> { + track( + RECEIPT_EMAIL_FAILED, + errorType = "no_app_found", + errorDescription = sharingResult.exception.message, + properties = mutableMapOf(getReceiptSource()) + ) + } + } + } + private fun getAndResetFlowsDuration(): MutableMap { val result = mutableMapOf() .also { mutableMap -> diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index 084ef52d3a6..49866a9841a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -417,6 +417,9 @@ class ProductListFragment : one = R.string.product_selection_count_single ) } + new.isSearchActive?.takeIfNotEqualTo(old?.isSearchActive) { isSearchActive -> + binding.productsRefreshLayout.isEnabled = !isSearchActive + } } viewModel.productList.observe(viewLifecycleOwner) { diff --git a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml index 71df778356f..0566a777164 100644 --- a/WooCommerce/src/main/res/layout/order_detail_payment_info.xml +++ b/WooCommerce/src/main/res/layout/order_detail_payment_info.xml @@ -1,7 +1,7 @@ - @@ -370,65 +370,83 @@ tools:text="$34.00"/> - - - - - - - - - + - - - + android:layout_height="wrap_content"> + + + + + + + + + + + + + + + - - + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index c3319c2b7db..7ef7a5426aa 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -153,6 +153,7 @@ Show password Last update: %s Last update %s (Updates every 30 minutes) + Sorry, we couldn\'t load a receipt for this order