taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

PayTemplateComposable.kt (7795B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2023 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.wallet.payment
     18 
     19 import androidx.compose.foundation.layout.Box
     20 import androidx.compose.foundation.layout.fillMaxSize
     21 import androidx.compose.foundation.layout.padding
     22 import androidx.compose.material3.MaterialTheme
     23 import androidx.compose.material3.Text
     24 import androidx.compose.runtime.Composable
     25 import androidx.compose.ui.Alignment.Companion.Center
     26 import androidx.compose.ui.Modifier
     27 import androidx.compose.ui.res.stringResource
     28 import androidx.compose.ui.text.style.TextAlign
     29 import androidx.compose.ui.tooling.preview.Preview
     30 import androidx.compose.ui.unit.dp
     31 import net.taler.common.Amount
     32 import net.taler.common.ContractTerms
     33 import net.taler.common.CurrencySpecification
     34 import net.taler.common.Merchant
     35 import net.taler.wallet.main.AmountResult
     36 import net.taler.wallet.R
     37 import net.taler.wallet.compose.LoadingScreen
     38 import net.taler.wallet.compose.TalerSurface
     39 import net.taler.wallet.systemBarsPaddingBottom
     40 
     41 @Composable
     42 fun PayTemplateComposable(
     43     currencies: List<String>,
     44     payStatus: PayStatus,
     45     getCurrencySpec: (String) -> CurrencySpecification?,
     46     onCreateAmount: (String, String) -> AmountResult,
     47     onSubmit: (params: TemplateParams) -> Unit,
     48     onError: (msg: String) -> Unit,
     49 ) {
     50     // If wallet is empty, there's no way the user can pay something
     51     if (currencies.isEmpty()) {
     52         PayTemplateError(stringResource(R.string.payment_balance_insufficient))
     53     } else when (val p = payStatus) {
     54         is PayStatus.Checked -> {
     55             val usableCurrencies = currencies
     56                 .intersect(p.supportedCurrencies.toSet())
     57                 .toList()
     58             if (usableCurrencies.isEmpty()) {
     59                 // If user doesn't have any supported currency, they can't pay either
     60                 PayTemplateError(stringResource(R.string.payment_balance_insufficient))
     61             } else {
     62                 PayTemplateOrderComposable(
     63                     usableCurrencies = usableCurrencies,
     64                     templateDetails = p.details,
     65                     onCreateAmount = onCreateAmount,
     66                     onError = onError,
     67                     onSubmit = onSubmit,
     68                     getCurrencySpec = getCurrencySpec,
     69                 )
     70             }
     71         }
     72 
     73         is PayStatus.None, is PayStatus.Loading -> PayTemplateLoading()
     74         is PayStatus.AlreadyPaid -> PayTemplateError(stringResource(R.string.payment_already_paid))
     75         is PayStatus.InsufficientBalance -> {
     76             var errorMsg = stringResource(R.string.payment_balance_insufficient)
     77             p.balanceDetails.causeHint?.stringResId()?.let {
     78                 errorMsg += "\n\n"
     79                 errorMsg += stringResource(it)
     80             }
     81             PayTemplateError(errorMsg)
     82         }
     83         is PayStatus.Pending -> {
     84             val error = p.error
     85             PayTemplateError(if (error != null) {
     86                 stringResource(R.string.payment_error, error.userFacingMsg)
     87             } else {
     88                 stringResource(R.string.payment_template_error)
     89             })
     90         }
     91         is PayStatus.Prepared -> {} // handled in fragment, will redirect
     92         is PayStatus.Success -> {} // handled by other UI flow, no need for content here
     93         is PayStatus.Choices -> {} // only applies to regular payments
     94     }
     95 }
     96 
     97 @Composable
     98 fun PayTemplateError(message: String) {
     99     Box(
    100         modifier = Modifier
    101             .padding(16.dp)
    102             .fillMaxSize()
    103             .systemBarsPaddingBottom(),
    104         contentAlignment = Center,
    105     ) {
    106         Text(
    107             text = message,
    108             style = MaterialTheme.typography.titleLarge,
    109             color = MaterialTheme.colorScheme.error,
    110             textAlign = TextAlign.Center,
    111         )
    112     }
    113 }
    114 
    115 @Composable
    116 fun PayTemplateLoading() {
    117     LoadingScreen()
    118 }
    119 
    120 @Preview
    121 @Composable
    122 fun PayTemplateLoadingPreview() {
    123     TalerSurface {
    124         PayTemplateComposable(
    125             payStatus = PayStatus.Loading,
    126             currencies = listOf("KUDOS", "ARS"),
    127             onCreateAmount = { text, currency ->
    128                 AmountResult.Success(amount = Amount.fromString(currency, text))
    129             },
    130             onSubmit = { _ -> },
    131             onError = { _ -> },
    132             getCurrencySpec = { null },
    133         )
    134     }
    135 }
    136 
    137 @Preview
    138 @Composable
    139 fun PayTemplateInsufficientBalancePreview() {
    140     TalerSurface {
    141         PayTemplateComposable(
    142             payStatus = PayStatus.InsufficientBalance(
    143                 "txn:3409F039F09",
    144                 ContractTerms.V0(
    145                     "test",
    146                     amount = Amount.zero("TESTKUDOS"),
    147                     products = emptyList(),
    148                     orderId = "xxxxx",
    149                     merchantBaseUrl = "https://backend.test.taler.net/",
    150                     merchant = Merchant(name = "Test Backend"),
    151                     maxFee = Amount.zero("TESTKUDOS")
    152                 ),
    153                 Amount.zero("TESTKUDOS"),
    154                 PaymentInsufficientBalanceDetails(
    155                     amountRequested = Amount.fromJSONString("TESTKUDOS:1"),
    156                     causeHint = InsufficientBalanceHint.MerchantDepositInsufficient,
    157                     balanceAvailable = Amount.fromJSONString("TESTKUDOS:1"),
    158                     balanceMaterial = Amount.fromJSONString("TESTKUDOS:1"),
    159                     balanceAgeAcceptable = Amount.fromJSONString("TESTKUDOS:1"),
    160                     balanceReceiverAcceptable = Amount.fromJSONString("TESTKUDOS:0"),
    161                     balanceReceiverDepositable = Amount.fromJSONString("TESTKUDOS:0"),
    162                     balanceExchangeDepositable = Amount.fromJSONString("TESTKUDOS:1"),
    163                     maxEffectiveSpendAmount = Amount.fromJSONString("TESTKUDOS:1"),
    164                     perExchange = emptyMap(),
    165                 )
    166             ),
    167             currencies = listOf("KUDOS", "ARS"),
    168             onCreateAmount = { text, currency ->
    169                 AmountResult.Success(amount = Amount.fromString(currency, text))
    170             },
    171             onSubmit = { _ -> },
    172             onError = { _ -> },
    173             getCurrencySpec = { null },
    174         )
    175     }
    176 }
    177 
    178 @Preview(widthDp = 300)
    179 @Composable
    180 fun PayTemplateAlreadyPaidPreview() {
    181     TalerSurface {
    182         PayTemplateComposable(
    183             payStatus = PayStatus.AlreadyPaid(transactionId = "transactionId"),
    184             currencies = listOf("KUDOS", "ARS"),
    185             onCreateAmount = { text, currency ->
    186                 AmountResult.Success(amount = Amount.fromString(currency, text))
    187             },
    188             onSubmit = { _ -> },
    189             onError = { _ -> },
    190             getCurrencySpec = { null },
    191         )
    192     }
    193 }
    194 
    195 
    196 @Preview
    197 @Composable
    198 fun PayTemplateNoCurrenciesPreview() {
    199     TalerSurface {
    200         PayTemplateComposable(
    201             payStatus = PayStatus.None,
    202             currencies = emptyList(),
    203             onCreateAmount = { text, currency ->
    204                 AmountResult.Success(amount = Amount.fromString(currency, text))
    205             },
    206             onSubmit = { _ -> },
    207             onError = { _ -> },
    208             getCurrencySpec = { null },
    209         )
    210     }
    211 }