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 }