HistoryFragment.kt
/*
* This file is part of GNU Taler
* (C) 2020 Taler Systems S.A.
*
* GNU Taler is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3, or (at your option) any later version.
*
* GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
package net.taler.merchantpos.history
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import net.taler.common.Amount
import net.taler.common.CurrencySpecification
import net.taler.lib.android.order.OrderDetailStatusBadge
import net.taler.lib.android.toRelativeTime
import net.taler.merchantlib.OrderHistoryEntry
import net.taler.merchantpos.MainActivity
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.PosDestination
import net.taler.merchantpos.R
import net.taler.merchantpos.checkout.Checkout
import net.taler.merchantpos.checkout.CheckoutStage
import net.taler.merchantpos.checkout.PaymentLine
import net.taler.merchantpos.checkout.shown
import net.taler.merchantpos.compose.PosTheme
import net.taler.merchantpos.payment.Payment
import net.taler.merchantpos.paymentoptions.PaymentOption
import net.taler.merchantpos.showPosError
import kotlinx.coroutines.flow.collectLatest
internal interface HistoryActionListener
{
fun onRefundClicked(item: OrderHistoryEntry)
fun onDeleteClicked(item: OrderHistoryEntry)
fun onShowPaymentClicked(item: OrderHistoryEntry)
fun onShowRefundClicked(item: OrderHistoryEntry)
}
class HistoryFragment :
Fragment(),
HistoryActionListener
{
private val model: MainViewModel by activityViewModels()
private val historyManager by lazy { model.historyManager }
private val refundManager by lazy { model.refundManager }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
) = ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
val isLoading by historyManager.isLoading.observeAsState(false)
val isLoadingMore by historyManager.isLoadingMore.observeAsState(false)
val result by historyManager.items.observeAsState()
val pendingRefundOrderId by refundManager.pendingRefundOrderId.observeAsState()
val activePayment by model.paymentManager.payment.observeAsState()
val forceDeleteOrderId by historyManager.forceDeleteOrderId.observeAsState()
val checkouts by model.checkoutManager.checkouts.observeAsState(emptyList())
HistoryScreen(
unfinishedCheckouts =
checkouts
.filter { it.stage != CheckoutStage.Completed }
.sortedByDescending { it.createdAt },
checkoutOptions = model.paymentOptions.options,
currencySpec = model.configManager.currencySpec,
onContinueCheckout = ::onContinueCheckout,
onDismissCheckout = { checkout -> model.checkoutManager.dismiss(checkout.id) },
isLoading = isLoading,
isLoadingMore = isLoadingMore,
result = result,
pendingRefundOrderId = pendingRefundOrderId,
activePayment = activePayment,
onOpenOrder = { item ->
(requireActivity() as MainActivity).navigateTo(PosDestination.OrderDetails.forOrder(item.orderId))
},
onLoadMore = historyManager::loadMoreHistoryIfNeeded,
onRefundClicked = ::onRefundClicked,
onDeleteClicked = ::onDeleteClicked,
onShowPaymentClicked = ::onShowPaymentClicked,
onShowRefundClicked = ::onShowRefundClicked,
)
forceDeleteOrderId?.let { orderId ->
ForceDeleteOrderDialog(
onConfirm = { historyManager.forceDeleteOrder(orderId) },
onDismiss = { historyManager.clearForceDeletePrompt() },
)
}
}
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
)
{
super.onViewCreated(view, savedInstanceState)
historyManager.error.observe(viewLifecycleOwner) { error ->
if (error != null)
{
requireActivity().showPosError(error.mainResId, error.msg)
historyManager.clearError()
}
}
}
override fun onStart()
{
super.onStart()
if (model.configManager.merchantConfig?.baseUrl == null)
{
(requireActivity() as MainActivity).navigateTo(PosDestination.Config)
}
else
{
Toast
.makeText(
requireContext(),
R.string.toast_history_updating,
Toast.LENGTH_SHORT,
).show()
historyManager.fetchHistory {
if (isAdded &&
lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED))
{
Toast
.makeText(
requireContext(),
R.string.toast_history_updated,
Toast.LENGTH_SHORT,
).show()
}
}
}
}
override fun onRefundClicked(item: OrderHistoryEntry)
{
refundManager.startRefund(item)
(requireActivity() as MainActivity).navigateTo(PosDestination.Refund)
}
override fun onDeleteClicked(item: OrderHistoryEntry)
{
historyManager.deleteOrder(item.orderId)
}
private fun onContinueCheckout(checkout: Checkout)
{
if (!model.checkoutManager.resume(checkout.id)) return
val resumed = model.checkoutManager.current ?: return
val destination =
if (resumed.stage == CheckoutStage.AwaitingTaler &&
resumed.backendOrderId != null)
{
model.paymentManager.showCheckoutOrder(resumed)
PosDestination.ProcessPayment
}
else
{
PosDestination.Checkout
}
(requireActivity() as MainActivity).navigateTo(destination)
}
override fun onShowPaymentClicked(item: OrderHistoryEntry)
{
model.paymentManager.resumePayment(item)
(requireActivity() as MainActivity).navigateTo(PosDestination.ProcessPayment)
}
override fun onShowRefundClicked(item: OrderHistoryEntry)
{
if (refundManager.resumeRefund(item))
{
(requireActivity() as MainActivity).navigateTo(PosDestination.RefundUri)
}
else
{
requireActivity().showPosError(R.string.refund_state_missing)
historyManager.fetchHistory()
}
}
}
@Composable
private fun HistoryScreen(
/** Local checkouts merged with backend history by order ID. */
unfinishedCheckouts: List<Checkout> = emptyList(),
checkoutOptions: List<PaymentOption> = emptyList(),
currencySpec: CurrencySpecification? = null,
onContinueCheckout: (Checkout) -> Unit = {},
onDismissCheckout: (Checkout) -> Unit = {},
isLoading: Boolean,
isLoadingMore: Boolean,
result: HistoryResult?,
pendingRefundOrderId: String?,
activePayment: Payment?,
onOpenOrder: (OrderHistoryEntry) -> Unit = {},
onLoadMore: (Int) -> Unit,
onRefundClicked: (OrderHistoryEntry) -> Unit,
onDeleteClicked: (OrderHistoryEntry) -> Unit,
onShowPaymentClicked: (OrderHistoryEntry) -> Unit,
onShowRefundClicked: (OrderHistoryEntry) -> Unit,
)
{
PosTheme {
val listState = rememberLazyListState()
val historyItemKeys = (result as? HistoryResult.Success)?.items?.map { it.orderId }.orEmpty()
val firstHistoryItemKey = historyItemKeys.firstOrNull()
LaunchedEffect(firstHistoryItemKey) {
if (firstHistoryItemKey != null)
{
listState.scrollToItem(0)
}
}
val historyItems = (result as? HistoryResult.Success)?.items.orEmpty()
val historyById = historyItems.associateBy { it.orderId }
val entries = mergeHistoryEntries(unfinishedCheckouts, historyItems, activePayment)
val localCheckouts = entries.checkouts
val backendItems = entries.orders
val headerCount = localCheckouts.size
LaunchedEffect(listState, historyItemKeys, localCheckouts.map { it.id }) {
snapshotFlow {
listState.layoutInfo.visibleItemsInfo
.lastOrNull()
?.index
}.collectLatest { index ->
if (index == null) return@collectLatest
val backendIndex = index - headerCount
val orderId = backendItems.getOrNull(backendIndex)?.orderId
if (orderId != null)
{
onLoadMore(historyItems.indexOfFirst { it.orderId == orderId })
}
else if (index >= headerCount + backendItems.size - 1)
{
onLoadMore(historyItems.lastIndex)
}
}
}
Column(
modifier =
Modifier
.fillMaxSize()
.statusBarsPadding()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
if (isLoading &&
result !is HistoryResult.Success)
{
CircularProgressIndicator()
}
LazyColumn(
state = listState,
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
if (localCheckouts.isNotEmpty())
{
items(localCheckouts, key = { "checkout-${it.id}" }) { checkout ->
UnfinishedCheckoutCard(
checkout = checkout,
status =
checkoutHistoryStatus(
checkout,
historyById[checkout.backendOrderId],
activePayment,
),
onOpenOrder =
historyById[checkout.backendOrderId]?.let { item ->
{ onOpenOrder(item) }
},
options = checkoutOptions,
spec = currencySpec,
onContinue = { onContinueCheckout(checkout) },
onDismiss = { onDismissCheckout(checkout) },
)
}
}
when (result)
{
is HistoryResult.Success ->
{
items(backendItems, key = { it.orderId }) { item ->
HistoryItemCard(
item = item,
spec = currencySpec,
onOpenOrder = { onOpenOrder(item) },
hasPendingRefund = pendingRefundOrderId == item.orderId,
activePayment = activePayment?.takeIf { it.orderId == item.orderId },
onRefundClicked = { onRefundClicked(item) },
onDeleteClicked = { onDeleteClicked(item) },
onShowPaymentClicked = { onShowPaymentClicked(item) },
onShowRefundClicked = { onShowRefundClicked(item) },
)
}
if (isLoadingMore)
{
item(key = "loading-more") {
Box(
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
)
}
}
}
}
null ->
{
Unit
}
}
}
}
}
}
@Composable
private fun HistoryItemCard(
item: OrderHistoryEntry,
spec: CurrencySpecification?,
onOpenOrder: () -> Unit,
hasPendingRefund: Boolean,
activePayment: Payment?,
onRefundClicked: () -> Unit,
onDeleteClicked: () -> Unit,
onShowPaymentClicked: () -> Unit,
onShowRefundClicked: () -> Unit,
)
{
val status = historyStatus(item, hasPendingRefund, activePayment)
Card(
onClick = onOpenOrder,
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
) {
Column(
modifier =
Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = item.summary,
style = MaterialTheme.typography.titleMedium,
)
val remaining =
if (item.paid ||
activePayment?.paid == true)
{
Amount
.zero(item.totalAmount.currency)
}
else
{
item.amount
}
val paid =
if (remaining >=
item.totalAmount)
{
Amount
.zero(item.totalAmount.currency)
}
else
{
item.totalAmount - remaining
}
Text(
stringResource(
R.string.history_total_paid_remaining,
item.totalAmount.shown(spec),
paid.shown(spec),
remaining.shown(spec),
),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
HistoryStatusBadge(status = status)
if (item.hasAnyRefund &&
!item.totalRefundAmount.isZero())
{
HistoryAmountBadge(
amount = item.totalRefundAmount.shown(spec),
status = status,
)
}
}
}
Text(
item.timestamp.ms
.toRelativeTime(LocalContext.current)
.toString(),
style = MaterialTheme.typography.bodyMedium,
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.history_ref_no, item.orderId), modifier = Modifier.weight(1f))
Icon(
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
contentDescription = stringResource(R.string.order_details_title),
)
}
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (item.hasPendingRefund ||
hasPendingRefund)
{
Button(onClick = onShowRefundClicked) {
Text(stringResource(R.string.history_show_refund))
}
}
else if (item.refundable)
{
Button(onClick = onRefundClicked) {
Text(stringResource(R.string.history_refund))
}
}
else if (!item.paid)
{
Button(onClick = onShowPaymentClicked) {
Text(stringResource(R.string.history_show_payment))
}
OutlinedButton(onClick = onDeleteClicked) {
Text(stringResource(R.string.order_delete))
}
}
}
}
}
}
/** An unfinished local checkout, including any payments already recorded. */
@Composable
private fun UnfinishedCheckoutCard(
checkout: Checkout,
status: HistoryStatus,
onOpenOrder: (() -> Unit)?,
options: List<PaymentOption>,
spec: CurrencySpecification?,
onContinue: () -> Unit,
onDismiss: () -> Unit,
)
{
val abandoned = checkout.stage == CheckoutStage.Abandoned
Card(
modifier = if (onOpenOrder != null) Modifier.clickable(onClick = onOpenOrder) else Modifier,
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
) {
Column(
modifier =
Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
text = checkout.contractTerms.summary,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f),
)
HistoryStatusBadge(status)
}
val displayedPaid = if (checkout.isFullyPaidExternally) checkout.total else checkout.paid
val displayedRemaining =
if (checkout.isFullyPaidExternally)
{
Amount
.zero(checkout.total.currency)
}
else
{
checkout.open
}
Text(
stringResource(
R.string.history_checkout_total_paid_remaining,
checkout.total.shown(spec),
displayedPaid.shown(spec),
displayedRemaining.shown(spec),
),
style = MaterialTheme.typography.bodySmall,
)
Text(
checkout.createdAt.toRelativeTime(LocalContext.current).toString(),
style = MaterialTheme.typography.bodyMedium,
)
checkout.backendOrderId?.let { orderId ->
Text(stringResource(R.string.history_ref_no, orderId))
}
if (checkout.payments.isNotEmpty())
{
checkout.payments.forEach { payment -> PaymentLine(payment, spec, options) }
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
if (abandoned)
{
OutlinedButton(onClick = onDismiss) {
Text(stringResource(R.string.checkout_history_dismiss))
}
}
else
{
Button(onClick = onContinue) {
Text(
stringResource(
if (checkout.stage == CheckoutStage.AwaitingTaler &&
checkout.backendOrderId != null)
{
R.string.history_show_payment
}
else
{
R.string.checkout_history_continue
},
),
)
}
}
}
}
}
}
internal data class HistoryEntries(
val checkouts: List<Checkout>,
val orders: List<OrderHistoryEntry>,
)
internal fun mergeHistoryEntries(
checkouts: List<Checkout>,
historyItems: List<OrderHistoryEntry>,
activePayment: Payment?,
): HistoryEntries
{
val paidOrderIds = historyItems.filter { it.paid }.mapTo(mutableSetOf()) { it.orderId }
activePayment?.takeIf { it.paid }?.orderId?.let { paidOrderIds.add(it) }
val unfinished = checkouts.filter { it.stage != CheckoutStage.Completed && it.backendOrderId !in paidOrderIds }
val localOrderIds = unfinished.mapNotNull { it.backendOrderId }.toSet()
return HistoryEntries(unfinished, historyItems.filterNot { it.orderId in localOrderIds })
}
internal fun checkoutHistoryStatus(
checkout: Checkout,
historyItem: OrderHistoryEntry? = null,
activePayment: Payment? = null,
): HistoryStatus =
when
{
historyItem?.let { it.orderId == checkout.backendOrderId && it.claimed } == true ->
{
HistoryStatus.PaymentClaimed
}
activePayment?.let { it.orderId != null && it.orderId == checkout.backendOrderId && it.claimed } == true ->
{
HistoryStatus.PaymentClaimed
}
checkout.isFullyPaidExternally ->
{
HistoryStatus.AwaitingCollection
}
checkout.stage == CheckoutStage.AwaitingTaler ->
{
HistoryStatus.PaymentPending
}
checkout.paid.isZero() && checkout.stage == CheckoutStage.Abandoned ->
{
HistoryStatus.Unpaid
}
checkout.paid.isZero() ->
{
HistoryStatus.PaymentPending
}
else ->
{
HistoryStatus.PartiallyPaid
}
}
internal fun historyStatus(
item: OrderHistoryEntry,
hasPendingRefund: Boolean = false,
activePayment: Payment? = null,
): HistoryStatus =
when
{
item.hasPendingRefund -> HistoryStatus.RefundPending
item.hasAnyRefund -> HistoryStatus.Refunded
hasPendingRefund -> HistoryStatus.RefundPending
activePayment?.let { it.orderId == item.orderId && it.paid } == true -> HistoryStatus.Paid
item.paid -> HistoryStatus.Paid
item.claimed -> HistoryStatus.PaymentClaimed
activePayment?.let { it.orderId == item.orderId && it.claimed } == true -> HistoryStatus.PaymentClaimed
activePayment?.let { it.orderId == item.orderId && it.error != null } == true -> HistoryStatus.Unpaid
activePayment?.orderId == item.orderId -> HistoryStatus.PaymentPending
item.hasExternalPayments && item.amount.isZero() -> HistoryStatus.AwaitingCollection
item.hasExternalPayments -> HistoryStatus.PartiallyPaid
else -> HistoryStatus.Unpaid
}
internal enum class HistoryStatus(
val labelResId: Int,
)
{
Unpaid(R.string.history_status_unpaid),
PartiallyPaid(R.string.history_status_partially_paid),
AwaitingCollection(R.string.history_status_awaiting_collection),
Paid(R.string.history_status_paid),
PaymentPending(R.string.history_status_payment_pending),
PaymentClaimed(R.string.history_status_payment_claimed),
RefundPending(R.string.history_status_refund_pending),
Refunded(R.string.history_status_refunded),
}
@Composable
internal fun HistoryStatusBadge(status: HistoryStatus)
{
val colors =
when (status)
{
HistoryStatus.Unpaid ->
{
MaterialTheme.colorScheme.errorContainer to MaterialTheme.colorScheme.onErrorContainer
}
HistoryStatus.Paid ->
{
MaterialTheme.colorScheme.primaryContainer to MaterialTheme.colorScheme.onPrimaryContainer
}
HistoryStatus.PartiallyPaid, HistoryStatus.AwaitingCollection,
HistoryStatus.PaymentPending,
->
{
MaterialTheme.colorScheme.secondaryContainer to
MaterialTheme.colorScheme.onSecondaryContainer
}
HistoryStatus.PaymentClaimed ->
{
MaterialTheme.colorScheme.tertiaryContainer to
MaterialTheme.colorScheme.onTertiaryContainer
}
HistoryStatus.RefundPending ->
{
MaterialTheme.colorScheme.secondaryContainer to
MaterialTheme.colorScheme.onSecondaryContainer
}
HistoryStatus.Refunded ->
{
MaterialTheme.colorScheme.tertiaryContainer to
MaterialTheme.colorScheme.onTertiaryContainer
}
}
OrderDetailStatusBadge(
label = stringResource(status.labelResId),
containerColor = colors.first,
contentColor = colors.second,
)
}
@Composable
private fun HistoryAmountBadge(
amount: String,
status: HistoryStatus,
)
{
val colors =
when (status)
{
HistoryStatus.RefundPending ->
{
MaterialTheme.colorScheme.secondaryContainer to
MaterialTheme.colorScheme.onSecondaryContainer
}
HistoryStatus.Refunded ->
{
MaterialTheme.colorScheme.tertiaryContainer to
MaterialTheme.colorScheme.onTertiaryContainer
}
else ->
{
MaterialTheme.colorScheme.surfaceVariant to MaterialTheme.colorScheme.onSurfaceVariant
}
}
Surface(
color = colors.first,
contentColor = colors.second,
shape = MaterialTheme.shapes.small,
) {
Text(
text = amount,
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
style = MaterialTheme.typography.labelLarge,
)
}
}
@Composable
private fun ForceDeleteOrderDialog(
onConfirm: () -> Unit,
onDismiss: () -> Unit,
)
{
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.force_delete_dialog_title)) },
text = { Text(stringResource(R.string.force_delete_dialog_message)) },
confirmButton = {
Button(
onClick = onConfirm,
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error,
),
) {
Text(stringResource(R.string.force_delete_dialog_confirm))
}
},
dismissButton = {
OutlinedButton(onClick = onDismiss) {
Text(stringResource(R.string.payment_cancel))
}
},
)
}
@Composable
internal fun HistoryScreenContent(
items: List<OrderHistoryEntry>,
onOpenOrder: (OrderHistoryEntry) -> Unit = {},
)
{
HistoryScreen(
isLoading = false,
isLoadingMore = false,
result = HistoryResult.Success(items),
onOpenOrder = onOpenOrder,
pendingRefundOrderId = null,
activePayment = null,
onLoadMore = {},
onRefundClicked = {},
onDeleteClicked = {},
onShowPaymentClicked = {},
onShowRefundClicked = {},
)
}