taler-android

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

commit 08fe4a1daecf5b7357f8c396aa5e871f7444c464
parent 596b8fa07cb542695f4e87076faa4670cf55ce10
Author: Iván Ávalos <avalos@disroot.org>
Date:   Mon, 21 Jul 2025 19:01:05 +0200

[wallet] render postal code and town in transfer details

bug 0010182

Diffstat:
Mwallet/src/main/java/net/taler/wallet/accounts/Accounts.kt | 25+++++++++++++++++++------
Mwallet/src/main/java/net/taler/wallet/accounts/AccountsFragment.kt | 2++
Mwallet/src/main/java/net/taler/wallet/accounts/AddAccountComposable.kt | 10++++++++--
Mwallet/src/main/java/net/taler/wallet/accounts/AddAccountIBAN.kt | 48+++++++++++++++++++++++++++++++++++++++++++++---
Mwallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt | 4+++-
Mwallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt | 9++++++++-
Mwallet/src/main/java/net/taler/wallet/transactions/Transactions.kt | 2++
Mwallet/src/main/java/net/taler/wallet/transfer/ScreenTransfer.kt | 2++
Mwallet/src/main/java/net/taler/wallet/transfer/TransferIBAN.kt | 8++++++++
Mwallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt | 36++++--------------------------------
Mwallet/src/main/res/values/strings.xml | 4++++
11 files changed, 105 insertions(+), 45 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/accounts/Accounts.kt b/wallet/src/main/java/net/taler/wallet/accounts/Accounts.kt @@ -23,6 +23,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonClassDiscriminator import net.taler.common.Bech32 import net.taler.wallet.backend.TalerErrorInfo +import androidx.core.net.toUri @Serializable data class KnownBankAccountInfo( @@ -80,7 +81,7 @@ sealed class PaytoUri( companion object { fun parse(paytoUri: String): PaytoUri? { - val uri = Uri.parse(paytoUri) + val uri = paytoUri.toUri() if (uri.scheme != "payto") return null if (uri.pathSegments.isEmpty()) return null return when (uri.authority?.lowercase()) { @@ -101,6 +102,8 @@ data class PaytoUriIban( override val targetPath: String, override val params: Map<String, String>, override val receiverName: String?, + val receiverPostalCode: String?, + val receiverTown: String?, ) : PaytoUri( isKnown = true, targetType = "iban", @@ -111,12 +114,16 @@ data class PaytoUriIban( .authority(targetType) .apply { if (bic != null) appendPath(bic) } .appendPath(iban) + .appendQueryParameter("receiver-name", receiverName) + .appendQueryParameter("receiver-postal-code", receiverPostalCode) + .appendQueryParameter("receiver-town", receiverTown) .apply { params.forEach { (key, value) -> - appendQueryParameter(key, value) + if (value.isNotEmpty() && build().getQueryParameter(key) == null) { + appendQueryParameter(key, value) + } } - } - .build().toString() + }.build().toString() companion object { fun fromString(uri: Uri): PaytoUriIban? { @@ -127,6 +134,8 @@ data class PaytoUriIban( } else null, params = uri.queryParametersMap, receiverName = uri.getQueryParameter("receiver-name"), + receiverPostalCode = uri.getQueryParameter("receiver-postal-code"), + receiverTown = uri.getQueryParameter("receiver-town"), targetPath = "", ) } @@ -153,7 +162,9 @@ data class PaytoUriTalerBank( .appendPath(account) .apply { params.forEach { (key, value) -> - appendQueryParameter(key, value) + if (value.isNotEmpty()) { + appendQueryParameter(key, value) + } } } .build().toString() @@ -194,7 +205,9 @@ data class PaytoUriBitcoin( } .apply { params.forEach { (key, value) -> - appendQueryParameter(key, value) + if (value.isNotEmpty()) { + appendQueryParameter(key, value) + } } } .build().toString() diff --git a/wallet/src/main/java/net/taler/wallet/accounts/AccountsFragment.kt b/wallet/src/main/java/net/taler/wallet/accounts/AccountsFragment.kt @@ -290,6 +290,8 @@ val previewKnownAccounts = listOf( targetPath = "", params = emptyMap(), receiverName = "John Doe", + receiverPostalCode = "1234", + receiverTown = "Texas", ).paytoUri, kycCompleted = true, currencies = listOf("KUDOS"), diff --git a/wallet/src/main/java/net/taler/wallet/accounts/AddAccountComposable.kt b/wallet/src/main/java/net/taler/wallet/accounts/AddAccountComposable.kt @@ -101,6 +101,8 @@ fun AddAccountComposable( var ibanError by rememberSaveable(presetPaytoUri) { mutableStateOf(presetPaytoUri == null) } var formAlias by rememberSaveable(presetAccount) { mutableStateOf(presetAccount?.label ?: "") } var ibanName by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.receiverName ?: "") } + var ibanTown by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.receiverTown) } + var ibanZip by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.receiverPostalCode) } var ibanIban by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriIban)?.iban ?: "") } var talerName by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriTalerBank)?.receiverName ?: "") } var talerHost by rememberSaveable(presetPaytoUri) { mutableStateOf((presetPaytoUri as? PaytoUriTalerBank)?.host ?: talerBankHostnames.firstOrNull() ?: "") } @@ -108,7 +110,7 @@ fun AddAccountComposable( var bitcoinAddress by rememberSaveable(presetPaytoUri) { mutableStateOf("") } // TODO: fill-in bitcoin address val paytoUri = when(selectedWireType) { - WireType.IBAN -> getIbanPayto(ibanName, ibanIban) + WireType.IBAN -> getIbanPayto(ibanName, ibanZip, ibanTown, ibanIban) WireType.TalerBank -> getTalerPayto(talerName, talerHost, talerAccount) WireType.Bitcoin -> getBitcoinPayto(bitcoinAddress) else -> null @@ -169,10 +171,14 @@ fun AddAccountComposable( WireType.IBAN -> item { AddAccountIBAN( name = ibanName, + town = ibanTown, + zip = ibanZip, iban = ibanIban, ibanError = ibanError, - onFormEdited = { name, iban -> + onFormEdited = { name, town, zip, iban -> ibanName = name + ibanTown = town + ibanZip = zip ibanIban = iban } ) diff --git a/wallet/src/main/java/net/taler/wallet/accounts/AddAccountIBAN.kt b/wallet/src/main/java/net/taler/wallet/accounts/AddAccountIBAN.kt @@ -36,9 +36,11 @@ import net.taler.wallet.R @Composable fun AddAccountIBAN( name: String, + town: String?, + zip: String?, iban: String, ibanError: Boolean, - onFormEdited: (name: String, iban: String) -> Unit + onFormEdited: (name: String, town: String?, zip: String?, iban: String) -> Unit ) { val focusManager = LocalFocusManager.current OutlinedTextField( @@ -50,7 +52,7 @@ fun AddAccountIBAN( ).fillMaxWidth(), value = name, onValueChange = { input -> - onFormEdited(input, iban) + onFormEdited(input, town, zip, iban) }, singleLine = true, isError = name.isBlank(), @@ -73,7 +75,7 @@ fun AddAccountIBAN( value = iban, singleLine = true, onValueChange = { input -> - onFormEdited(name, input + onFormEdited(name, town, zip, input .uppercase() .replace(" ", "") .replace("\n", "") @@ -101,4 +103,44 @@ fun AddAccountIBAN( keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }), ) + + OutlinedTextField( + modifier = Modifier + .padding( + bottom = 16.dp, + start = 16.dp, + end = 16.dp, + ).fillMaxWidth(), + value = zip ?: "", + singleLine = true, + onValueChange = { input -> + onFormEdited(name, town, input.trim(), iban) + }, + isError = ibanError, + label = { + Text(stringResource(R.string.send_deposit_postal_code)) + }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }), + ) + + OutlinedTextField( + modifier = Modifier + .padding( + bottom = 16.dp, + start = 16.dp, + end = 16.dp, + ).fillMaxWidth(), + value = town ?: "", + singleLine = true, + onValueChange = { input -> + onFormEdited(name, input.trim(), zip, iban) + }, + isError = ibanError, + label = { + Text(stringResource(R.string.send_deposit_town)) + }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }), + ) } \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositFragment.kt @@ -54,10 +54,12 @@ class DepositFragment : Fragment() { ): View { val presetAmount = arguments?.getString("amount")?.let { Amount.fromJSONString(it) } val receiverName = arguments?.getString("receiverName") + val receiverPostalCode = arguments?.getString("receiverPostalCode") + val receiverTown = arguments?.getString("receiverTown") val iban = arguments?.getString("IBAN") if (presetAmount != null && receiverName != null && iban != null) { - val paytoUri = getIbanPayto(receiverName, iban) + val paytoUri = getIbanPayto(receiverName, receiverPostalCode, receiverTown, iban) depositManager.makeDeposit(presetAmount, paytoUri) } diff --git a/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt b/wallet/src/main/java/net/taler/wallet/deposit/DepositManager.kt @@ -185,12 +185,19 @@ class DepositManager( } } -fun getIbanPayto(receiverName: String, iban: String) = PaytoUriIban( +fun getIbanPayto( + receiverName: String, + receiverPostalCode: String?, + receiverTown: String?, + iban: String, +) = PaytoUriIban( iban = iban, bic = null, targetPath = "", params = mapOf("receiver-name" to receiverName), receiverName = receiverName, + receiverPostalCode = receiverPostalCode, + receiverTown = receiverTown, ).paytoUri fun getTalerPayto(receiverName: String, host: String, account: String) = PaytoUriTalerBank( diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt @@ -311,6 +311,8 @@ data class WithdrawalExchangeAccountDetails ( TransferData.IBAN( iban = uri.lastPathSegment!!, receiverName = uri.getQueryParameter("receiver-name"), + receiverTown = uri.getQueryParameter("receiver-town"), + receiverPostalCode = uri.getQueryParameter("receiver-postal-code"), subject = uri.getQueryParameter("message") ?: "Error: No message in URI", amountRaw = amountRaw, amountEffective = amountEffective, diff --git a/wallet/src/main/java/net/taler/wallet/transfer/ScreenTransfer.kt b/wallet/src/main/java/net/taler/wallet/transfer/ScreenTransfer.kt @@ -362,6 +362,8 @@ fun ScreenTransferPreview( amountRaw = Amount("KUDOS", 10, 0), amountEffective = Amount("KUDOS", 9, 5), transferAmount = Amount("KUDOS", 10, 0), + receiverTown = "Biel/Bienne", + receiverPostalCode = "2500", withdrawalAccount = WithdrawalExchangeAccountDetails( paytoUri = "https://taler.net/kudos", transferAmount = Amount("KUDOS", 10, 0), diff --git a/wallet/src/main/java/net/taler/wallet/transfer/TransferIBAN.kt b/wallet/src/main/java/net/taler/wallet/transfer/TransferIBAN.kt @@ -106,6 +106,14 @@ fun TransferIBAN( DetailRow(stringResource(R.string.withdraw_manual_ready_receiver), it) } + transfer.receiverPostalCode?.let { + DetailRow(stringResource(R.string.withdraw_manual_ready_postal_code), it) + } + + transfer.receiverTown?.let { + DetailRow(stringResource(R.string.withdraw_manual_ready_town), it) + } + DetailRow(stringResource(R.string.withdraw_manual_ready_iban), transfer.iban) WithdrawalAmountTransfer( diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt @@ -116,6 +116,8 @@ sealed class TransferData { override val transferAmount: Amount, override val withdrawalAccount: WithdrawalExchangeAccountDetails, val receiverName: String? = null, + val receiverPostalCode: String? = null, + val receiverTown: String? = null, val iban: String, ): TransferData() @@ -514,38 +516,6 @@ class WithdrawManager( } } - /** - * A hack to be able to view bank details for manual withdrawal with the same logic. - * Don't call this from ongoing withdrawal processes as it destroys state. - */ - fun viewManualWithdrawal( - transactionId: String, - exchangeBaseUrl: String? = null, - amountRaw: Amount, - amountEffective: Amount, - withdrawalAccountList: List<WithdrawalExchangeAccountDetails>, - scopeInfo: ScopeInfo, - ) { - _withdrawStatus.value = createManualTransfer( - status = WithdrawStatus( - transactionId = transactionId, - exchangeBaseUrl = exchangeBaseUrl, - amountInfo = WithdrawalDetailsForAmount( - amountRaw = amountRaw, - amountEffective = amountEffective, - withdrawalAccountsList = withdrawalAccountList, - scopeInfo = scopeInfo, - tosAccepted = true, - ) - ), - response = AcceptManualWithdrawalResponse( - transactionId = transactionId, - reservePub = "", - withdrawalAccountsList = withdrawalAccountList, - ) - ) - } - private fun createManualTransfer( status: WithdrawStatus, response: AcceptManualWithdrawalResponse, @@ -590,6 +560,8 @@ class WithdrawManager( TransferData.IBAN( iban = uri.lastPathSegment!!, receiverName = uri.getQueryParameter("receiver-name"), + receiverTown = uri.getQueryParameter("receiver-town"), + receiverPostalCode = uri.getQueryParameter("receiver-postal-code"), subject = uri.getQueryParameter("message") ?: "Error: No message in URI", amountRaw = details.amountRaw, amountEffective = details.amountEffective, diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml @@ -268,9 +268,11 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card <string name="send_deposit_kyc_auth_warning_subject">This is mandatory, otherwise the verification will fail.</string> <string name="send_deposit_name">Account holder</string> <string name="send_deposit_no_methods_error">No supported wire methods</string> + <string name="send_deposit_postal_code">Postal code (optional)</string> <string name="send_deposit_select_account_title">Select bank account</string> <string name="send_deposit_select_amount_title">Select amount to deposit</string> <string name="send_deposit_taler">x-taler-bank</string> + <string name="send_deposit_town">Town (optional)</string> <string name="send_deposit_title">Deposit to bank account</string> <string name="send_deposit_no_alias">No alias</string> <string name="send_peer_create_button">Send funds now</string> @@ -312,8 +314,10 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card <string name="withdraw_manual_ready_details_qr">Wire transfer QR codes</string> <string name="withdraw_manual_ready_iban">IBAN</string> <string name="withdraw_manual_ready_intro">You need to transfer %1$s from your regular bank account to the payment service to receive %2$s as electronic cash in this wallet.</string> + <string name="withdraw_manual_ready_postal_code">Postal code</string> <string name="withdraw_manual_ready_receiver">Recipient</string> <string name="withdraw_manual_ready_subject">Subject</string> + <string name="withdraw_manual_ready_town">Town</string> <string name="withdraw_manual_ready_warning">This is mandatory, otherwise your money will not arrive in this wallet.</string> <string name="withdraw_manual_step">&lt;b&gt;Step %1$s:&lt;/b&gt; %2$s</string> <string name="withdraw_manual_step_finish">Finish the wire transfer of %1$s in your banking app or website, then this withdrawal will proceed automatically. Depending on your bank the transfer can take from minutes to two working days, please be patient.</string>