ProcessPaymentFragment.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.payment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
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.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.LiveRegionMode
import androidx.compose.ui.semantics.liveRegion
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels

import net.taler.common.Timestamp
import net.taler.lib.android.AnimatedQrCodeComposable
import net.taler.lib.android.AnimatedQrCodeFrame
import net.taler.lib.android.TalerNfcService.Companion.hasNfc
import net.taler.lib.android.copyToClipBoard
import net.taler.lib.android.shareText
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.CheckoutResult
import net.taler.merchantpos.checkout.RevertResult
import net.taler.merchantpos.compose.PosTheme
import net.taler.merchantpos.devices.TalerBleStatus
import net.taler.merchantpos.devices.TalerUsbStatus
import net.taler.merchantpos.showPosError

import java.text.DateFormat
import java.util.Date

import kotlinx.coroutines.launch

class ProcessPaymentFragment : Fragment()
{
    private val model: MainViewModel by activityViewModels()
    private val paymentManager by lazy { model.paymentManager }
    private var deviceHasNfc = false

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ) = ComposeView(requireContext()).apply {
        setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
        setContent {
            val scope = rememberCoroutineScope()
            val payment by paymentManager.payment.observeAsState()
            val usb by model.talerUsb.state.observeAsState()
            val bluetooth by model.talerBluetooth.state.observeAsState()
            val deleteNeedsForce by paymentManager.deleteNeedsForce.observeAsState()
            payment?.let {
                ProcessPaymentScreen(
                    payment = it,
                    deviceHasNfc =
                        deviceHasNfc || bluetooth?.status == TalerBleStatus.Ready || (
                            usb?.enabled == true &&
                                usb?.devices?.any { it.status == TalerUsbStatus.Ready } == true
                            ),
                    showForceDeleteDialog = deleteNeedsForce == true,
                    onTryHttps = paymentManager::tryHttpsPaymentUri,
                    onContinueAsIs = paymentManager::continueWithInsecurePaymentUri,
                    onCancelInsecurePayment = paymentManager::tryDeleteOrder,
                    onCancel = {
                        if (it.checkoutId != null)
                        {
                            scope.launch {
                                when (val result = model.checkoutManager.revertTalerStep())
                                {
                                    RevertResult.Reverted ->
                                    {
                                        paymentManager.cancelPayment()
                                        (requireActivity() as MainActivity).returnToCheckout()
                                    }

                                    RevertResult.AlreadyPaid ->
                                    {
                                        (requireActivity() as MainActivity).navigateTo(PosDestination.PaymentSuccess)
                                    }

                                    is RevertResult.Failed ->
                                    {
                                        requireActivity().showPosError(
                                            getString(R.string.checkout_revert_failed),
                                            result.message,
                                        )
                                    }
                                }
                            }
                        }
                        else if (it.claimed)
                        {
                            paymentManager.tryDeleteOrder()
                        }
                        else
                        {
                            onPaymentCancel()
                        }
                    },
                    onForceDelete = ::onForceDeleteOrder,
                    onDismissForceDelete = {
                        paymentManager.clearDeleteNeedsForce()
                    },
                    onShare = { uri -> requireContext().shareText(uri) },
                    onCopy = { uri ->
                        copyToClipBoard(requireContext(), "Payment URI", uri)
                    },
                    // For a checkout this step is reversible, so it is not a cancellation.
                    cancelLabel =
                        stringResource(
                            if (it.checkoutId != null)
                            {
                                R.string.checkout_back_to_options
                            }
                            else
                            {
                                R.string.payment_cancel
                            },
                        ),
                    onSelfCollect =
                        if (it.selfCollect)
                        {
                            {
                                scope.launch {
                                    when (val result = model.checkoutManager.collectByMerchant())
                                    {
                                        CheckoutResult.Completed ->
                                        {
                                            (requireActivity() as MainActivity).navigateTo(
                                                PosDestination.PaymentSuccess,
                                            )
                                        }

                                        is CheckoutResult.Failed ->
                                        {
                                            requireActivity().showPosError(
                                                getString(R.string.error_payment),
                                                result.message,
                                            )
                                        }

                                        is CheckoutResult.AwaitingWallet ->
                                        {
                                            Unit
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            null
                        },
                )
            }

            // Navigate back on successful (non-force) delete
            LaunchedEffect(deleteNeedsForce) {
                if (deleteNeedsForce == false)
                {
                    paymentManager.clearDeleteNeedsForce()
                    (requireActivity() as MainActivity).navigateBack()
                }
            }
        }
    }

    override fun onViewCreated(
        view: View,
        savedInstanceState: Bundle?,
    )
    {
        deviceHasNfc = hasNfc(requireContext())
        paymentManager.payment.observe(viewLifecycleOwner) { payment ->
            onPaymentStateChanged(payment)
        }
    }

    override fun onStart()
    {
        super.onStart()
        updateUsbUri(paymentManager.payment.value)
    }

    override fun onStop()
    {
        model.talerUsb.dismiss(this)
        model.talerBluetooth.dismiss(this)
        super.onStop()
    }

    private fun updateUsbUri(payment: Payment?)
    {
        val uri =
            payment
                ?.takeIf {
                    !it.claimed && !it.paid && it.error == null
                }?.talerPayUri
        model.talerUsb.present(this, uri)
        model.talerBluetooth.present(this, uri)
    }

    private fun onPaymentStateChanged(payment: Payment?)
    {
        updateUsbUri(payment)
        payment ?: return
        if (payment.error != null)
        {
            val (mainText, detailText) = getPaymentErrorDisplay(payment)
            requireActivity().showPosError(mainText, detailText)
            (requireActivity() as MainActivity).navigateBack()
            return
        }
        if (payment.paid)
        {
            val orderId = payment.orderId
            if (payment.checkoutId != null &&
                orderId != null)
            {
                model.checkoutManager.onBackendOrderPaid(orderId)
            }
            else
            {
                model.orderManager.onOrderPaid(payment.order.id)
            }
            (requireActivity() as MainActivity).navigateTo(PosDestination.PaymentSuccess)
        }
    }

    private fun onPaymentCancel()
    {
        paymentManager.cancelPayment()
        (requireActivity() as MainActivity).navigateBack()
    }

    private fun onForceDeleteOrder()
    {
        paymentManager.forceDeleteOrder()
        (requireActivity() as MainActivity).navigateBack()
    }

    private fun getPaymentErrorDisplay(payment: Payment): Pair<String, String>
    {
        val error = payment.error.orEmpty()
        if (payment.orderId != null)
        {
            return getString(R.string.error_payment) to error
        }
        val normalized = error.lowercase()
        return when
        {
            "inventory" in normalized ||
                "stock" in normalized ||
                "insufficient" in normalized ||
                "sold out" in normalized ||
                "out of stock" in normalized ->
            {
                getString(R.string.error_inventory_unavailable) to error
            }

            else ->
            {
                getString(R.string.error_order_creation) to error
            }
        }
    }
}

@Composable
private fun ProcessPaymentScreen(
    payment: Payment,
    deviceHasNfc: Boolean,
    showForceDeleteDialog: Boolean,
    onTryHttps: () -> Unit,
    onContinueAsIs: () -> Unit,
    onCancelInsecurePayment: () -> Unit,
    onCancel: () -> Unit,
    onForceDelete: () -> Unit,
    onDismissForceDelete: () -> Unit,
    onShare: (String) -> Unit,
    onCopy: (String) -> Unit,
    /** Offered only when the merchant reached this from a fully settled checkout. */
    onSelfCollect: (() -> Unit)?,
    cancelLabel: String,
)
{
    if (showForceDeleteDialog)
    {
        AlertDialog(
            onDismissRequest = onDismissForceDelete,
            title = { Text(stringResource(R.string.force_delete_dialog_title)) },
            text = { Text(stringResource(R.string.force_delete_dialog_message)) },
            confirmButton = {
                Button(
                    onClick = onForceDelete,
                    colors =
                        ButtonDefaults.buttonColors(
                            containerColor = MaterialTheme.colorScheme.error,
                        ),
                ) {
                    Text(stringResource(R.string.force_delete_dialog_confirm))
                }
            },
            dismissButton = {
                OutlinedButton(onClick = onDismissForceDelete) {
                    Text(stringResource(R.string.payment_cancel))
                }
            },
        )
    }
    else if (payment.insecureTalerPayUri != null)
    {
        AlertDialog(
            onDismissRequest = {},
            title = { Text(stringResource(R.string.insecure_payment_dialog_title)) },
            text = { Text(stringResource(R.string.insecure_payment_dialog_message)) },
            confirmButton = {
                Column(
                    modifier = Modifier.fillMaxWidth(),
                    verticalArrangement = Arrangement.spacedBy(8.dp),
                ) {
                    Button(
                        onClick = onTryHttps,
                        modifier = Modifier.fillMaxWidth(),
                    ) {
                        Text(stringResource(R.string.insecure_payment_dialog_try_https))
                    }
                    OutlinedButton(
                        onClick = onContinueAsIs,
                        modifier = Modifier.fillMaxWidth(),
                    ) {
                        Text(stringResource(R.string.insecure_payment_dialog_continue_as_is))
                    }
                    OutlinedButton(
                        onClick = onCancelInsecurePayment,
                        modifier = Modifier.fillMaxWidth(),
                        colors =
                            ButtonDefaults.outlinedButtonColors(
                                contentColor = MaterialTheme.colorScheme.error,
                            ),
                    ) {
                        Text(stringResource(R.string.insecure_payment_dialog_cancel_order))
                    }
                }
            },
        )
    }

    PosTheme {
        val introText =
            when
            {
                payment.claimed -> stringResource(R.string.checkout_complete_in_wallet)
                payment.selfCollect -> stringResource(R.string.checkout_collect_receipt_title)
                deviceHasNfc && payment.talerPayUri != null -> stringResource(R.string.payment_intro_nfc)
                else -> stringResource(R.string.checkout_scan_qr_prompt)
            }
        val isTablet = LocalConfiguration.current.smallestScreenWidthDp >= 600
        Surface(Modifier.fillMaxSize()) {
            if (payment.statusLoading)
            {
                Column(
                    modifier = Modifier.fillMaxSize().padding(24.dp),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically),
                ) {
                    CircularProgressIndicator()
                    Text(stringResource(R.string.order_details_loading), textAlign = TextAlign.Center)
                    OutlinedButton(onClick = onCancel) { Text(cancelLabel) }
                }
                return@Surface
            }
            BoxWithConstraints(Modifier.fillMaxSize()) {
                if (maxWidth > maxHeight)
                {
                    LandscapePaymentScreen(
                        payment,
                        introText,
                        isTablet,
                        onCancel,
                        onShare,
                        onCopy,
                        onSelfCollect,
                        cancelLabel,
                    )
                }
                else
                {
                    PortraitPaymentScreen(
                        payment,
                        introText,
                        isTablet,
                        onCancel,
                        onShare,
                        onCopy,
                        onSelfCollect,
                        cancelLabel,
                    )
                }
            }
        }
    }
}

@Composable
private fun PaymentQrContent(
    payment: Payment,
    modifier: Modifier = Modifier,
)
{
    if (payment.claimed)
    {
        AnimatedQrCodeFrame(modifier) {
            Column(
                modifier =
                    Modifier
                        .padding(24.dp)
                        .verticalScroll(rememberScrollState())
                        .semantics { liveRegion = LiveRegionMode.Polite },
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(20.dp),
            ) {
                // The QR frame stays light in both themes.
                Text(
                    stringResource(R.string.checkout_order_claimed),
                    style = MaterialTheme.typography.titleMedium,
                    color = Color(0xFF1C1B1F),
                    textAlign = TextAlign.Center,
                )
                Text(
                    stringResource(R.string.checkout_waiting_customer_payment),
                    style = MaterialTheme.typography.titleMedium,
                    color = Color(0xFF1C1B1F),
                    textAlign = TextAlign.Center,
                )
            }
        }
    }
    else
    {
        val payUri = payment.talerPayUri
        if (payUri == null)
        {
            Box(modifier, contentAlignment = Alignment.Center) {
                CircularProgressIndicator(Modifier.size(48.dp))
            }
        }
        else
        {
            AnimatedQrCodeComposable(
                link = payUri,
                logoPainter = painterResource(R.drawable.ic_taler_logo_qr),
                modifier = modifier,
            )
        }
    }
}

@Composable
private fun PaymentBrandHeading(compact: Boolean = false)
{
    Row(
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            stringResource(R.string.payment_pay_with),
            style = if (compact) MaterialTheme.typography.titleMedium else MaterialTheme.typography.titleLarge,
            fontWeight = FontWeight.Bold,
        )
        Image(
            painter = painterResource(R.drawable.ic_taler_logo_brand_on_light),
            contentDescription = stringResource(R.string.payment_brand_name),
            modifier =
                Modifier.size(
                    width = if (compact) 72.dp else 80.dp,
                    height = if (compact) 32.dp else 36.dp,
                ),
            colorFilter =
                if (MaterialTheme.colorScheme.surface.luminance() < 0.5f)
                {
                    ColorFilter.tint(Color.White)
                }
                else
                {
                    null
                },
        )
    }
}

@Composable
private fun PaymentDetailRow(
    label: String,
    value: String,
    amount: Boolean = false,
    compact: Boolean = false,
)
{
    if (compact &&
        LocalDensity.current.fontScale > 1.2f)
    {
        Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp)) {
            Text(label, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
            Text(
                value,
                style = if (amount) MaterialTheme.typography.headlineSmall else MaterialTheme.typography.bodyMedium,
                fontWeight = if (amount) FontWeight.Bold else FontWeight.Normal,
            )
        }
        return
    }
    Row(
        Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(12.dp),
        verticalAlignment = Alignment.Top,
    ) {
        Text(
            label,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            modifier = Modifier.width(if (compact) 68.dp else 100.dp),
        )
        Text(
            value,
            style = if (amount) MaterialTheme.typography.headlineSmall else MaterialTheme.typography.bodyMedium,
            fontWeight = if (amount) FontWeight.Bold else FontWeight.Normal,
            modifier = Modifier.weight(1f),
        )
    }
}

@Composable
private fun PaymentDeadline(payment: Payment)
{
    payment.payDeadline?.takeUnless { it == Timestamp.never() }?.let { deadline ->
        val locale = LocalConfiguration.current.locales[0]
        val formatted =
            remember(deadline, locale) {
                DateFormat
                    .getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale)
                    .format(Date(deadline.ms))
            }
        Text(
            stringResource(R.string.checkout_payment_possible_until, formatted),
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            textAlign = TextAlign.Center,
        )
    }
}

@Composable
private fun PaymentActions(
    payment: Payment,
    onCancel: () -> Unit,
    onShare: (String) -> Unit,
    onCopy: (String) -> Unit,
    onSelfCollect: (() -> Unit)?,
    cancelLabel: String,
)
{
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
        payment.talerPayUri?.let { payUri ->
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
                Button(onClick = { onShare(payUri) }, modifier = Modifier.weight(1f).heightIn(min = 48.dp)) {
                    Text(stringResource(R.string.share))
                }
                OutlinedButton(
                    onClick = { onCopy(payUri) },
                    modifier = Modifier.weight(1f).heightIn(min = 48.dp),
                    colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary),
                ) {
                    Text(stringResource(R.string.copy))
                }
            }
        }
        onSelfCollect?.let { collect ->
            Button(onClick = collect, modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp)) {
                Text(stringResource(R.string.checkout_collect_receipt_on_device))
            }
        }
        OutlinedButton(
            onClick = onCancel,
            modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp),
            colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary),
        ) {
            Text(cancelLabel)
        }
    }
}

@Composable
private fun PortraitPaymentScreen(
    payment: Payment,
    introText: String,
    isTablet: Boolean,
    onCancel: () -> Unit,
    onShare: (String) -> Unit,
    onCopy: (String) -> Unit,
    onSelfCollect: (() -> Unit)?,
    cancelLabel: String,
)
{
    BoxWithConstraints(Modifier.fillMaxSize()) {
        val horizontalPadding = if (isTablet) 48.dp else 24.dp
        val qrSize =
            if (isTablet)
            {
                minOf(maxWidth * 0.56f, maxHeight * 0.44f, 420.dp)
            }
            else
            {
                minOf(maxWidth - horizontalPadding * 2, 320.dp)
            }
        Column(
            modifier =
                Modifier
                    .fillMaxSize()
                    .verticalScroll(rememberScrollState())
                    .padding(horizontal = horizontalPadding, vertical = 16.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            if (!payment.selfCollect) PaymentBrandHeading()
            Text(introText, style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center)
            Text(
                text =
                    if (payment.selfCollect)
                    {
                        stringResource(R.string.checkout_receipt_paid_in_full)
                    }
                    else
                    {
                        payment.order.total.toString()
                    },
                style = MaterialTheme.typography.headlineLarge,
                fontWeight = FontWeight.Bold,
                textAlign = TextAlign.Center,
            )
            PaymentQrContent(payment, Modifier.size(qrSize))
            PaymentDeadline(payment)
            if (payment.selfCollect)
            {
                Text(stringResource(R.string.checkout_receipt_scan_hint), textAlign = TextAlign.Center)
            }
            payment.orderId?.let { id ->
                if (isTablet)
                {
                    PaymentDetailRow(stringResource(R.string.checkout_payment_detail_order), "#$id")
                }
                else
                {
                    Text(stringResource(R.string.payment_order_id, id), textAlign = TextAlign.Center)
                }
            }
            if (payment.summary.isNotBlank())
            {
                if (isTablet)
                {
                    PaymentDetailRow(stringResource(R.string.checkout_payment_detail_summary), payment.summary)
                }
                else
                {
                    Text(
                        payment.summary,
                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                        textAlign = TextAlign.Center,
                    )
                }
            }
            PaymentActions(payment, onCancel, onShare, onCopy, onSelfCollect, cancelLabel)
        }
    }
}

@Composable
private fun LandscapePaymentScreen(
    payment: Payment,
    introText: String,
    isTablet: Boolean,
    onCancel: () -> Unit,
    onShare: (String) -> Unit,
    onCopy: (String) -> Unit,
    onSelfCollect: (() -> Unit)?,
    cancelLabel: String,
)
{
    BoxWithConstraints(Modifier.fillMaxSize()) {
        val bodyHeight =
            minOf(
                (maxHeight - 128.dp * LocalDensity.current.fontScale).coerceAtLeast(0.dp),
                460.dp,
            )
        Column(
            modifier =
                Modifier
                    .fillMaxSize()
                    .padding(
                        horizontal = if (isTablet) 48.dp else 24.dp,
                        vertical = 16.dp,
                    ),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(if (isTablet) 24.dp else 12.dp, Alignment.CenterVertically),
        ) {
            if (isTablet)
            {
                Column(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
                ) {
                    if (!payment.selfCollect) PaymentBrandHeading()
                    Text(introText, style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center)
                }
            }
            Row(
                modifier =
                    if (isTablet)
                    {
                        Modifier.fillMaxWidth().height(bodyHeight)
                    }
                    else
                    {
                        Modifier.weight(1f).fillMaxWidth()
                    },
                horizontalArrangement = Arrangement.spacedBy(if (isTablet) 48.dp else 32.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Column(
                    modifier = Modifier.weight(if (isTablet) 1f else 0.85f).fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically),
                ) {
                    BoxWithConstraints(
                        Modifier.weight(1f, fill = false).fillMaxWidth(),
                        contentAlignment = Alignment.Center,
                    ) {
                        val qrSize = minOf(maxWidth, maxHeight, if (isTablet) 420.dp else 360.dp)
                        PaymentQrContent(payment, Modifier.size(qrSize))
                    }
                    PaymentDeadline(payment)
                }
                Column(
                    modifier = Modifier.weight(1f).verticalScroll(rememberScrollState()),
                    horizontalAlignment = Alignment.Start,
                    verticalArrangement = Arrangement.spacedBy(if (isTablet) 20.dp else 12.dp),
                ) {
                    if (!isTablet)
                    {
                        if (!payment.selfCollect) PaymentBrandHeading(compact = true)
                        Text(introText, style = MaterialTheme.typography.bodyMedium)
                    }
                    payment.orderId?.let { id ->
                        PaymentDetailRow(
                            stringResource(R.string.checkout_payment_detail_order),
                            "#$id",
                            compact = !isTablet,
                        )
                    }
                    if (payment.summary.isNotBlank())
                    {
                        PaymentDetailRow(
                            stringResource(R.string.checkout_payment_detail_summary),
                            payment.summary,
                            compact = !isTablet,
                        )
                    }
                    if (payment.selfCollect)
                    {
                        Text(
                            stringResource(R.string.checkout_receipt_paid_in_full),
                            style = MaterialTheme.typography.headlineSmall,
                        )
                        Text(stringResource(R.string.checkout_receipt_scan_hint))
                    }
                    else
                    {
                        PaymentDetailRow(
                            stringResource(R.string.amount_entry_label),
                            payment.order.total.toString(),
                            amount = true,
                            compact = !isTablet,
                        )
                    }
                    if (isTablet)
                    {
                        payment.merchantName?.takeIf { it.isNotBlank() }?.let { name ->
                            PaymentDetailRow(stringResource(R.string.checkout_payment_detail_merchant), name)
                        }
                    }
                    PaymentActions(payment, onCancel, onShare, onCopy, onSelfCollect, cancelLabel)
                }
            }
        }
    }
}

@Composable
internal fun PaymentScreenContent(payment: Payment)
{
    ProcessPaymentScreen(
        payment = payment,
        deviceHasNfc = false,
        showForceDeleteDialog = false,
        onTryHttps = {},
        onContinueAsIs = {},
        onCancelInsecurePayment = {},
        onCancel = {},
        onForceDelete = {},
        onDismissForceDelete = {},
        onShare = {},
        onCopy = {},
        onSelfCollect = if (payment.selfCollect) ({}) else null,
        cancelLabel =
            stringResource(
                if (payment.checkoutId != null) R.string.checkout_back_to_options else R.string.payment_cancel,
            ),
    )
}