Skip to content

Commit 8206e6e

Browse files
authored
Merge pull request #10650 from woocommerce/10648-backed-receipt-download-and-share-a-file-not-a-link
[Backed Receipt] Download and share a file not a link
2 parents db4b393 + bd89dfb commit 8206e6e

17 files changed

+368
-98
lines changed

RELEASE-NOTES.txt

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
*** PLEASE FOLLOW THIS FORMAT: [<priority indicator, more stars = higher priority>] <description> [<PR URL>]
2+
17.2
3+
-----
4+
- [**] [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]
5+
26
17.1
37
-----
48
- [*] [Internal] Fixed crash when going to background from the order creation screen [https://github.com/woocommerce/woocommerce-android/pull/10600]

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentDialogFragment.kt

-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import com.woocommerce.android.R
2222
import com.woocommerce.android.analytics.AnalyticsTracker
2323
import com.woocommerce.android.databinding.CardReaderPaymentDialogBinding
2424
import com.woocommerce.android.extensions.navigateBackWithNotice
25-
import com.woocommerce.android.model.UiString
2625
import com.woocommerce.android.support.help.HelpOrigin
2726
import com.woocommerce.android.support.requests.SupportRequestFormActivity
2827
import com.woocommerce.android.ui.base.UIMessageResolver
@@ -32,7 +31,6 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.BuiltInR
3231
import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulReceiptSentAutomaticallyState
3332
import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulState
3433
import com.woocommerce.android.ui.payments.refunds.RefundSummaryFragment.Companion.KEY_INTERAC_SUCCESS
35-
import com.woocommerce.android.util.ActivityUtils
3634
import com.woocommerce.android.util.PrintHtmlHelper
3735
import com.woocommerce.android.util.UiHelpers
3836
import com.woocommerce.android.util.UiHelpers.getTextOfUiString
@@ -86,7 +84,6 @@ class CardReaderPaymentDialogFragment : PaymentsBaseDialogFragment(R.layout.card
8684
event.documentName
8785
)
8886
InteracRefundSuccessful -> navigateBackWithNotice(KEY_INTERAC_SUCCESS)
89-
is SendReceipt -> composeEmail(event.address, event.subject, event.content)
9087
is ShowSnackbar -> uiMessageResolver.showSnack(event.message)
9188
is ShowSnackbarInDialog -> Snackbar.make(
9289
requireView(), event.message, BaseTransientBottomBar.LENGTH_LONG
@@ -186,12 +183,6 @@ class CardReaderPaymentDialogFragment : PaymentsBaseDialogFragment(R.layout.card
186183
mp.start()
187184
}
188185

189-
private fun composeEmail(address: String, subject: UiString, content: UiString) {
190-
ActivityUtils.sendEmail(requireActivity(), address, subject, content) {
191-
viewModel.onEmailActivityNotFound()
192-
}
193-
}
194-
195186
override fun onResume() {
196187
super.onResume()
197188
AnalyticsTracker.trackViewShown(this)

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt

+26-17
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ReFetchi
6868
import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundLoadingDataState
6969
import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.RefundSuccessfulState
7070
import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper
71+
import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare
7172
import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper
7273
import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker
7374
import com.woocommerce.android.util.CoroutineDispatchers
@@ -117,6 +118,7 @@ class CardReaderPaymentViewModel
117118
private val paymentReceiptHelper: PaymentReceiptHelper,
118119
private val cardReaderOnboardingChecker: CardReaderOnboardingChecker,
119120
private val cardReaderConfigProvider: CardReaderCountryConfigProvider,
121+
private val paymentReceiptShare: PaymentReceiptShare,
120122
) : ScopedViewModel(savedState) {
121123
private val arguments: CardReaderPaymentDialogFragmentArgs by savedState.navArgs()
122124

@@ -611,9 +613,7 @@ class CardReaderPaymentViewModel
611613
val onSaveUserClicked = {
612614
onSaveForLaterClicked()
613615
}
614-
val onSendReceiptClicked = {
615-
onSendReceiptClicked(order.billingAddress.email)
616-
}
616+
val onSendReceiptClicked = { onSendReceiptClicked() }
617617

618618
if (order.billingAddress.email.isBlank()) {
619619
viewState.postValue(
@@ -723,27 +723,36 @@ class CardReaderPaymentViewModel
723723
}
724724
}
725725

726-
private fun onSendReceiptClicked(billingEmail: String) {
726+
private fun onSendReceiptClicked() {
727727
launch {
728728
tracker.trackEmailReceiptTapped()
729+
val stateBeforeLoading = viewState.value!!
730+
viewState.postValue(ViewState.SharingReceiptState)
729731
val receiptResult = paymentReceiptHelper.getReceiptUrl(orderId)
732+
730733
if (receiptResult.isSuccess) {
731-
triggerEvent(
732-
SendReceipt(
733-
content = UiStringRes(
734-
R.string.card_reader_payment_receipt_email_content,
735-
listOf(UiStringText(receiptResult.getOrThrow()))
736-
),
737-
subject = UiStringRes(
738-
R.string.card_reader_payment_receipt_email_subject,
739-
listOf(UiStringText(selectedSite.get().name.orEmpty()))
740-
),
741-
address = billingEmail
742-
)
743-
)
734+
when (val sharingResult = paymentReceiptShare(receiptResult.getOrThrow(), orderId)) {
735+
is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> {
736+
tracker.trackPaymentsReceiptSharingFailed(sharingResult)
737+
triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_can_not_be_stored))
738+
}
739+
is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> {
740+
tracker.trackPaymentsReceiptSharingFailed(sharingResult)
741+
triggerEvent(ShowSnackbar(R.string.card_reader_payment_receipt_can_not_be_downloaded))
742+
}
743+
is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> {
744+
tracker.trackPaymentsReceiptSharingFailed(sharingResult)
745+
triggerEvent(ShowSnackbar(R.string.card_reader_payment_email_client_not_found))
746+
}
747+
PaymentReceiptShare.ReceiptShareResult.Success -> {
748+
// no-op
749+
}
750+
}
744751
} else {
745752
triggerEvent(ShowSnackbar(R.string.receipt_fetching_error))
746753
}
754+
755+
viewState.postValue(stateBeforeLoading)
747756
}
748757
}
749758

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModelEvent.kt

-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.woocommerce.android.ui.payments.cardreader.payment
22

33
import androidx.annotation.StringRes
4-
import com.woocommerce.android.model.UiString
54
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event
65

76
class ShowSnackbarInDialog(@StringRes val message: Int) : Event()
@@ -17,5 +16,3 @@ object EnableNfc : Event()
1716
data class PurchaseCardReader(val url: String) : Event()
1817

1918
data class PrintReceipt(val receiptUrl: String, val documentName: String) : Event()
20-
21-
data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : Event()

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewState.kt

+9
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,15 @@ sealed class ViewState(
219219
override val isProgressVisible = true
220220
}
221221

222+
object SharingReceiptState : ViewState(
223+
headerLabel = R.string.card_reader_payment_completed_payment_header,
224+
illustration = null,
225+
primaryActionLabel = null,
226+
secondaryActionLabel = null,
227+
) {
228+
override val isProgressVisible = true
229+
}
230+
222231
object ReFetchingOrderState : ViewState(
223232
headerLabel = R.string.card_reader_payment_fetch_order_loading_header,
224233
hintLabel = R.string.card_reader_payment_fetch_order_loading_hint,

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/PaymentReceiptHelper.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class PaymentReceiptHelper @Inject constructor(
110110
const val WCPAY_RECEIPTS_SENDING_SUPPORT_VERSION = "4.0.0"
111111
const val WC_CAN_GENERATE_RECEIPTS_VERSION = "8.7.0"
112112

113-
const val RECEIPT_EXPIRATION_DAYS = 365
113+
const val RECEIPT_EXPIRATION_DAYS = 2
114114
}
115115

116116
class IsDevSiteSupported @Inject constructor() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.woocommerce.android.ui.payments.receipt
2+
3+
import android.app.Application
4+
import android.content.Intent
5+
import android.os.Environment
6+
import androidx.core.content.FileProvider
7+
import com.woocommerce.android.media.FileUtils
8+
import com.woocommerce.android.util.FileDownloader
9+
import javax.inject.Inject
10+
11+
class PaymentReceiptShare @Inject constructor(
12+
private val fileUtils: FileUtils,
13+
private val fileDownloader: FileDownloader,
14+
private val context: Application,
15+
) {
16+
@Suppress("TooGenericExceptionCaught")
17+
suspend operator fun invoke(receiptUrl: String, orderNumber: Long): ReceiptShareResult {
18+
val receiptFile = fileUtils.createTempTimeStampedFile(
19+
storageDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
20+
?: context.filesDir,
21+
prefix = "receipt_$orderNumber",
22+
fileExtension = "html"
23+
)
24+
return if (receiptFile == null) {
25+
ReceiptShareResult.Error.FileCreation
26+
} else if (!fileDownloader.downloadFile(receiptUrl, receiptFile)) {
27+
ReceiptShareResult.Error.FileDownload
28+
} else {
29+
val uri = FileProvider.getUriForFile(
30+
context,
31+
context.packageName + ".provider",
32+
receiptFile
33+
)
34+
val intent = Intent(Intent.ACTION_SEND).apply {
35+
type = "application/*"
36+
putExtra(Intent.EXTRA_STREAM, uri)
37+
}
38+
try {
39+
context.startActivity(
40+
Intent.createChooser(intent, null).apply {
41+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
42+
}
43+
)
44+
ReceiptShareResult.Success
45+
} catch (e: Exception) {
46+
ReceiptShareResult.Error.Sharing(e)
47+
}
48+
}
49+
}
50+
51+
sealed class ReceiptShareResult {
52+
object Success : ReceiptShareResult()
53+
sealed class Error : ReceiptShareResult() {
54+
data class Sharing(val exception: Exception) : Error()
55+
object FileCreation : Error()
56+
object FileDownload : Error()
57+
}
58+
}
59+
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewFragment.kt

+1-9
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import com.woocommerce.android.analytics.AnalyticsTracker
1414
import com.woocommerce.android.databinding.FragmentReceiptPreviewBinding
1515
import com.woocommerce.android.ui.base.BaseFragment
1616
import com.woocommerce.android.ui.base.UIMessageResolver
17-
import com.woocommerce.android.util.ActivityUtils
1817
import com.woocommerce.android.util.PrintHtmlHelper
1918
import com.woocommerce.android.util.UiHelpers
2019
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar
@@ -52,7 +51,7 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview),
5251
true
5352
}
5453
R.id.menu_send -> {
55-
viewModel.onSendEmailClicked()
54+
viewModel.onShareClicked()
5655
true
5756
}
5857
else -> false
@@ -102,16 +101,9 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview),
102101
when (it) {
103102
is LoadUrl -> binding.receiptPreviewPreviewWebview.loadUrl(it.url)
104103
is PrintReceipt -> printHtmlHelper.printReceipt(requireActivity(), it.receiptUrl, it.documentName)
105-
is SendReceipt -> composeEmail(it)
106104
is ShowSnackbar -> uiMessageResolver.showSnack(it.message)
107105
else -> it.isHandled = false
108106
}
109107
}
110108
}
111-
112-
private fun composeEmail(event: SendReceipt) {
113-
ActivityUtils.sendEmail(requireActivity(), event.address, event.subject, event.content) {
114-
viewModel.onEmailActivityNotFound()
115-
}
116-
}
117109
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/receipt/preview/ReceiptPreviewViewModel.kt

+26-24
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@ import androidx.lifecycle.LiveData
44
import androidx.lifecycle.MutableLiveData
55
import androidx.lifecycle.SavedStateHandle
66
import com.woocommerce.android.R.string
7-
import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_FAILED
87
import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_EMAIL_TAPPED
98
import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_CANCELED
109
import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_FAILED
1110
import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_SUCCESS
1211
import com.woocommerce.android.analytics.AnalyticsEvent.RECEIPT_PRINT_TAPPED
1312
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
14-
import com.woocommerce.android.model.UiString.UiStringRes
15-
import com.woocommerce.android.model.UiString.UiStringText
16-
import com.woocommerce.android.tools.SelectedSite
13+
import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare
1714
import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content
1815
import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading
16+
import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker
1917
import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult
2018
import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED
2119
import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED
@@ -32,7 +30,8 @@ class ReceiptPreviewViewModel
3230
@Inject constructor(
3331
savedState: SavedStateHandle,
3432
private val tracker: AnalyticsTrackerWrapper,
35-
private val selectedSite: SelectedSite,
33+
private val paymentsFlowTracker: PaymentsFlowTracker,
34+
private val paymentReceiptShare: PaymentReceiptShare,
3635
) : ScopedViewModel(savedState) {
3736
private val args: ReceiptPreviewFragmentArgs by savedState.navArgs()
3837

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

55-
fun onSendEmailClicked() {
54+
fun onShareClicked() {
5655
launch {
56+
viewState.value = Loading
57+
5758
tracker.track(RECEIPT_EMAIL_TAPPED)
58-
triggerEvent(
59-
SendReceipt(
60-
content = UiStringRes(
61-
string.card_reader_payment_receipt_email_content,
62-
listOf(UiStringText(args.receiptUrl))
63-
),
64-
subject = UiStringRes(
65-
string.card_reader_payment_receipt_email_subject,
66-
listOf(UiStringText(selectedSite.get().name.orEmpty()))
67-
),
68-
address = args.billingEmail
69-
)
70-
)
71-
}
72-
}
59+
when (val sharingResult = paymentReceiptShare(args.receiptUrl, args.orderId)) {
60+
is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> {
61+
paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult)
62+
triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_stored))
63+
}
64+
is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> {
65+
paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult)
66+
triggerEvent(ShowSnackbar(string.card_reader_payment_receipt_can_not_be_downloaded))
67+
}
68+
is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> {
69+
paymentsFlowTracker.trackPaymentsReceiptSharingFailed(sharingResult)
70+
triggerEvent(ShowSnackbar(string.card_reader_payment_email_client_not_found))
71+
}
72+
PaymentReceiptShare.ReceiptShareResult.Success -> {
73+
// no-op
74+
}
75+
}
7376

74-
fun onEmailActivityNotFound() {
75-
tracker.track(RECEIPT_EMAIL_FAILED)
76-
triggerEvent(ShowSnackbar(string.card_reader_payment_email_client_not_found))
77+
viewState.value = Content
78+
}
7779
}
7880

7981
fun onPrintResult(result: PrintJobResult) {
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.woocommerce.android.ui.payments.receipt.preview
22

3-
import com.woocommerce.android.model.UiString
43
import com.woocommerce.android.viewmodel.MultiLiveEvent
54

65
data class LoadUrl(val url: String) : MultiLiveEvent.Event()
76

87
data class PrintReceipt(val receiptUrl: String, val documentName: String) : MultiLiveEvent.Event()
9-
10-
data class SendReceipt(val content: UiString, val subject: UiString, val address: String) : MultiLiveEvent.Event()

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/tracking/PaymentsFlowTracker.kt

+27
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType
7373
import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.STRIPE_EXTENSION_GATEWAY
7474
import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.WOOCOMMERCE_PAYMENTS
7575
import com.woocommerce.android.ui.payments.hub.PaymentsHubViewModel.CashOnDeliverySource
76+
import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare
7677
import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus.Result.NotAvailable
7778
import javax.inject.Inject
7879

@@ -580,6 +581,32 @@ class PaymentsFlowTracker @Inject constructor(
580581
)
581582
}
582583

584+
fun trackPaymentsReceiptSharingFailed(sharingResult: PaymentReceiptShare.ReceiptShareResult.Error) {
585+
when (sharingResult) {
586+
is PaymentReceiptShare.ReceiptShareResult.Error.FileCreation -> {
587+
track(
588+
RECEIPT_EMAIL_FAILED,
589+
errorType = "file_creation_failed",
590+
errorDescription = "File creation failed"
591+
)
592+
}
593+
is PaymentReceiptShare.ReceiptShareResult.Error.FileDownload -> {
594+
track(
595+
RECEIPT_EMAIL_FAILED,
596+
errorType = "file_download_failed",
597+
errorDescription = "File download failed"
598+
)
599+
}
600+
is PaymentReceiptShare.ReceiptShareResult.Error.Sharing -> {
601+
track(
602+
RECEIPT_EMAIL_FAILED,
603+
errorType = "no_app_found",
604+
errorDescription = sharingResult.exception.message
605+
)
606+
}
607+
}
608+
}
609+
583610
private fun getAndResetFlowsDuration(): MutableMap<String, Any> {
584611
val result = mutableMapOf<String, Any>()
585612
.also { mutableMap ->

0 commit comments

Comments
 (0)