Skip to content

Commit

Permalink
Merge pull request #10654 from woocommerce/10633-backend-receipts-add…
Browse files Browse the repository at this point in the history
…-tracking

[Backend Receipts] Add tracking
  • Loading branch information
kidinov authored Feb 2, 2024
2 parents 8206e6e + 1ea1f66 commit 57f48eb
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ class OrderDetailViewModel @Inject constructor(
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))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ class CardReaderPaymentViewModel
)
)
} else {
tracker.trackReceiptUrlFetchingFails(
errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error",
)
triggerEvent(ShowSnackbar(R.string.receipt_fetching_error))
}
}
Expand Down Expand Up @@ -749,25 +752,25 @@ class CardReaderPaymentViewModel
}
}
} else {
tracker.trackReceiptUrlFetchingFails(
errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error",
)
triggerEvent(ShowSnackbar(R.string.receipt_fetching_error))
}

viewState.postValue(stateBeforeLoading)
}
}

fun onEmailActivityNotFound() {
tracker.trackEmailReceiptFailed()
triggerEvent(ShowSnackbarInDialog(R.string.card_reader_payment_email_client_not_found))
}

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()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class PaymentReceiptHelper @Inject constructor(
}
}

private suspend fun isWCCanGenerateReceipts(): Boolean {
suspend fun isWCCanGenerateReceipts(): Boolean {
val currentWooCoreVersion = getWoocommerceCorePluginVersion()

return currentWooCoreVersion.semverCompareTo(WC_CAN_GENERATE_RECEIPTS_VERSION) >= 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ 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_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.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
Expand All @@ -29,7 +23,6 @@ import javax.inject.Inject
class ReceiptPreviewViewModel
@Inject constructor(
savedState: SavedStateHandle,
private val tracker: AnalyticsTrackerWrapper,
private val paymentsFlowTracker: PaymentsFlowTracker,
private val paymentReceiptShare: PaymentReceiptShare,
) : ScopedViewModel(savedState) {
Expand All @@ -47,15 +40,17 @@ class ReceiptPreviewViewModel
}

fun onPrintClicked() {
tracker.track(RECEIPT_PRINT_TAPPED)
triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}"))
launch {
paymentsFlowTracker.trackPrintReceiptTapped()
triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}"))
}
}

fun onShareClicked() {
launch {
viewState.value = Loading

tracker.track(RECEIPT_EMAIL_TAPPED)
paymentsFlowTracker.trackEmailReceiptTapped()
when (val sharingResult = paymentReceiptShare(args.receiptUrl, args.orderId)) {
is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> {
paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult)
Expand All @@ -79,13 +74,13 @@ class ReceiptPreviewViewModel
}

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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -73,6 +75,7 @@ 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
Expand All @@ -81,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(
Expand Down Expand Up @@ -156,6 +160,13 @@ class PaymentsFlowTracker @Inject constructor(
}
}

private suspend fun getReceiptSource(): Pair<String, String> =
if (paymentReceiptHelper.isWCCanGenerateReceipts()) {
AnalyticsTracker.KEY_SOURCE to "backend"
} else {
AnalyticsTracker.KEY_SOURCE to "local"
}

@Suppress("ComplexMethod")
private fun getOnboardingNotCompletedReason(state: CardReaderOnboardingState): String? =
when (state) {
Expand Down Expand Up @@ -412,28 +423,58 @@ class PaymentsFlowTracker @Inject constructor(
)
}

fun trackPrintReceiptTapped() {
track(RECEIPT_PRINT_TAPPED)
suspend fun trackPrintReceiptTapped() {
track(
RECEIPT_PRINT_TAPPED,
properties = mutableMapOf(getReceiptSource())
)
}

suspend fun trackEmailReceiptTapped() {
track(
RECEIPT_EMAIL_TAPPED,
properties = mutableMapOf(getReceiptSource())
)
}

fun trackEmailReceiptTapped() {
track(RECEIPT_EMAIL_TAPPED)
suspend fun trackPrintReceiptCancelled() {
track(
RECEIPT_PRINT_CANCELED,
properties = mutableMapOf(getReceiptSource())
)
}

fun trackEmailReceiptFailed() {
track(RECEIPT_EMAIL_FAILED)
suspend fun trackPrintReceiptFailed() {
track(
RECEIPT_PRINT_FAILED,
properties = mutableMapOf(getReceiptSource())
)
}

fun trackPrintReceiptCancelled() {
track(RECEIPT_PRINT_CANCELED)
suspend fun trackPrintReceiptSucceeded() {
track(
RECEIPT_PRINT_SUCCESS,
properties = mutableMapOf(getReceiptSource())
)
}

fun trackPrintReceiptFailed() {
track(RECEIPT_PRINT_FAILED)
suspend fun trackReceiptViewTapped(properties: Map<String, Any>) {
track(
RECEIPT_VIEW_TAPPED,
properties = properties.toMutableMap().also {
it.putAll(
mapOf(getReceiptSource())
)
}
)
}

fun trackPrintReceiptSucceeded() {
track(RECEIPT_PRINT_SUCCESS)
suspend fun trackReceiptUrlFetchingFails(errorDescription: String) {
track(
RECEIPT_URL_FETCHING_FAILS,
properties = mutableMapOf(getReceiptSource()),
errorDescription = errorDescription,
)
}

fun trackPaymentCancelled(currentPaymentState: String?) {
Expand Down Expand Up @@ -581,27 +622,32 @@ class PaymentsFlowTracker @Inject constructor(
)
}

fun trackPaymentsReceiptSharingFailed(sharingResult: PaymentReceiptShare.ReceiptShareResult.Error) {
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"
errorDescription = "File creation failed",
properties = mutableMapOf(getReceiptSource())
)
}

is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> {
track(
RECEIPT_EMAIL_FAILED,
errorType = "file_download_failed",
errorDescription = "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
errorDescription = sharingResult.exception.message,
properties = mutableMapOf(getReceiptSource())
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1250,7 +1250,8 @@ class OrderDetailViewModelTest : BaseUnitTest() {
whenever(orderDetailRepository.fetchOrderNotes(any())).thenReturn(false)
whenever(addonsRepository.containsAddonsFrom(any())).thenReturn(false)

whenever(paymentReceiptHelper.getReceiptUrl(order.id)).thenReturn(Result.failure(Exception("")))
val errorMessage = "error"
whenever(paymentReceiptHelper.getReceiptUrl(order.id)).thenReturn(Result.failure(Exception(errorMessage)))

// WHEN
viewModel.start()
Expand All @@ -1259,6 +1260,9 @@ class OrderDetailViewModelTest : BaseUnitTest() {

// THEN
assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo(string.receipt_fetching_error)
verify(paymentsFlowTracker).trackReceiptUrlFetchingFails(
errorDescription = errorMessage
)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2337,21 +2337,21 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() {
}

@Test
fun `when OS accepts the print request, then print success event tracked`() {
fun `when OS accepts the print request, then print success event tracked`() = testBlocking {
viewModel.onPrintResult(STARTED)

verify(tracker).trackPrintReceiptSucceeded()
}

@Test
fun `when OS refuses the print request, then print failed event tracked`() {
fun `when OS refuses the print request, then print failed event tracked`() = testBlocking {
viewModel.onPrintResult(FAILED)

verify(tracker).trackPrintReceiptFailed()
}

@Test
fun `when manually cancels the print request, then print cancelled event tracked`() {
fun `when manually cancels the print request, then print cancelled event tracked`() = testBlocking {
viewModel.onPrintResult(CANCELLED)

verify(tracker).trackPrintReceiptCancelled()
Expand Down Expand Up @@ -2568,14 +2568,6 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() {
assertThat(viewModel.event.value).isInstanceOf(Exit::class.java)
}

@Test
fun `when email activity not found, then event tracked`() =
testBlocking {
viewModel.onEmailActivityNotFound()

verify(tracker).trackEmailReceiptFailed()
}

@Test
fun `given user presses back button, when re-fetching order, then ReFetchingOrderState shown`() =
testBlocking {
Expand Down
Loading

0 comments on commit 57f48eb

Please sign in to comment.