Skip to content

Commit

Permalink
Merge pull request #10650 from woocommerce/10648-backed-receipt-downl…
Browse files Browse the repository at this point in the history
…oad-and-share-a-file-not-a-link

[Backed Receipt] Download and share a file not a link
  • Loading branch information
kidinov authored Feb 2, 2024
2 parents db4b393 + bd89dfb commit 8206e6e
Show file tree
Hide file tree
Showing 17 changed files with 368 additions and 98 deletions.
4 changes: 4 additions & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
*** PLEASE FOLLOW THIS FORMAT: [<priority indicator, more stars = higher priority>] <description> [<PR URL>]
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
-----
- [*] [Internal] Fixed crash when going to background from the order creation screen [https://github.com/woocommerce/woocommerce-android/pull/10600]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -611,9 +613,7 @@ class CardReaderPaymentViewModel
val onSaveUserClicked = {
onSaveForLaterClicked()
}
val onSendReceiptClicked = {
onSendReceiptClicked(order.billingAddress.email)
}
val onSendReceiptClicked = { onSendReceiptClicked() }

if (order.billingAddress.email.isBlank()) {
viewState.postValue(
Expand Down Expand Up @@ -723,27 +723,36 @@ class CardReaderPaymentViewModel
}
}

private fun onSendReceiptClicked(billingEmail: String) {
private fun onSendReceiptClicked() {
launch {
tracker.trackEmailReceiptTapped()
val stateBeforeLoading = viewState.value!!
viewState.postValue(ViewState.SharingReceiptState)
val receiptResult = paymentReceiptHelper.getReceiptUrl(orderId)

if (receiptResult.isSuccess) {
triggerEvent(
SendReceipt(
content = UiStringRes(
R.string.card_reader_payment_receipt_email_content,
listOf(UiStringText(receiptResult.getOrThrow()))
),
subject = UiStringRes(
R.string.card_reader_payment_receipt_email_subject,
listOf(UiStringText(selectedSite.get().name.orEmpty()))
),
address = billingEmail
)
)
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 {
triggerEvent(ShowSnackbar(R.string.receipt_fetching_error))
}

viewState.postValue(stateBeforeLoading)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -219,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class PaymentReceiptHelper @Inject constructor(
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 = 365
const val RECEIPT_EXPIRATION_DAYS = 2
}

class IsDevSiteSupported @Inject constructor() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -52,7 +51,7 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview),
true
}
R.id.menu_send -> {
viewModel.onSendEmailClicked()
viewModel.onShareClicked()
true
}
else -> false
Expand Down Expand Up @@ -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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ 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
Expand All @@ -32,7 +30,8 @@ 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()

Expand All @@ -52,28 +51,31 @@ class ReceiptPreviewViewModel
triggerEvent(PrintReceipt(args.receiptUrl, "receipt-order-${args.orderId}"))
}

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

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

fun onEmailActivityNotFound() {
tracker.track(RECEIPT_EMAIL_FAILED)
triggerEvent(ShowSnackbar(string.card_reader_payment_email_client_not_found))
viewState.value = Content
}
}

fun onPrintResult(result: PrintJobResult) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,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.PaymentReceiptShare
import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus.Result.NotAvailable
import javax.inject.Inject

Expand Down Expand Up @@ -580,6 +581,32 @@ class PaymentsFlowTracker @Inject constructor(
)
}

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"
)
}
is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> {
track(
RECEIPT_EMAIL_FAILED,
errorType = "file_download_failed",
errorDescription = "File download failed"
)
}
is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> {
track(
RECEIPT_EMAIL_FAILED,
errorType = "no_app_found",
errorDescription = sharingResult.exception.message
)
}
}
}

private fun getAndResetFlowsDuration(): MutableMap<String, Any> {
val result = mutableMapOf<String, Any>()
.also { mutableMap ->
Expand Down
Loading

0 comments on commit 8206e6e

Please sign in to comment.